diff --git a/docs/integrations/matrix.md b/docs/integrations/matrix.md index ccab6ab5..4706c554 100644 --- a/docs/integrations/matrix.md +++ b/docs/integrations/matrix.md @@ -83,11 +83,18 @@ pin the value before the homeserver mints any ids under the default. ## Default-closed firewall `deploy.matrix.openFirewall` defaults to `false`: hive-c0re and agent -containers on every hive reach the homeserver at `chat.` -via the gateway, so the firewall hole only matters for direct access to -`httpPort` from *outside* the host. Flip to `true` when -announcing the homeserver to other hives or when an external matrix -client needs to reach the client-server API directly. +containers reach the homeserver at `chat.` via the gateway, +so the firewall hole only matters for direct access to `httpPort` from +*outside* the host. Flip to `true` when announcing the homeserver to +other hives or when an external matrix client needs to reach the +client-server API directly. + +hive-c0re provisions matrix (its own sender login, the hive Space and +chat room, invites) through `swarm.matrix.apiUrl`, which defaults to +`https://` on every hive. That login carries +the hive's matrix access token, and on a hive that doesn't run the +homeserver it crosses the network, so the default is TLS; hive-c0re +verifies the gateway's certificate against the hive trust bundle. Federation port 8448 is intentionally not opened here — tuwunel serves the federation API on the same `httpPort` as client-server diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index 25f95e89..ad6113f9 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -21,8 +21,8 @@ use reqwest::StatusCode; /// fallback: `localhost:8008` is right only when the homeserver happens to /// share this daemon's netns, and an address baked into the binary is one /// that builds fine and then talks to the wrong machine. The nix module -/// supplies the loopback address when it is itself the thing running -/// tuwunel, where it is not a guess but a fact about what it just started. +/// defaults it to `https://` on the homeserver's gateway vhost, on every +/// hive whichever host runs tuwunel. /// /// Also not an agent-facing address either way. An agent has its own netns; /// agents are handed the gateway vhost via `HIVE_MATRIX_URL`, and get @@ -126,12 +126,9 @@ pub fn hive_chat_room_id_path() -> PathBuf { /// Whether this hive has a homeserver to provision against. /// -/// **A configured API URL, not a local container.** It used to scan -/// `nixos-container list` for `hive-matrix`, which answers a different -/// question — "is the homeserver a container on this host" — and so made a -/// remote homeserver silently no-op no matter how it was addressed. The -/// nix module still supplies the loopback URL whenever it runs tuwunel -/// itself, so a co-located hive behaves exactly as before. +/// **A configured API URL, not a local container**: whether the homeserver +/// runs on this host is a different question, and a remote homeserver is +/// provisioned through the same URL. #[must_use] pub fn is_present() -> bool { matrix_http().is_some() diff --git a/nix/host-modules/hive-c0re/default.nix b/nix/host-modules/hive-c0re/default.nix index a81cabfd..b88f6fa1 100644 --- a/nix/host-modules/hive-c0re/default.nix +++ b/nix/host-modules/hive-c0re/default.nix @@ -126,15 +126,12 @@ in imports = [ ./options.nix ./theme.nix - # Hive-CA trust for this daemon's outbound TLS. Nothing it is given by - # default is https — the forge and matrix URLs are plain http, and the - # queue's TLS is verified against `HIVE_C0RE_OIDC_CA_FILE` - # (../hive-tls.nix), not this bundle — so this - # changes nothing on an all-local hive. It matters for the split-host - # shape the options invite: `swarm.matrix.apiUrl`'s own example is - # `https://matrix.example.com`, and pointing it at another hive's - # gateway means verifying a leaf signed by a CA generated at runtime, - # which no build-time trust store can contain. + # Hive-CA trust for this daemon's outbound TLS. The matrix provisioning + # URL (`swarm.matrix.apiUrl`) defaults to https on the homeserver's + # gateway vhost, which serves a leaf chaining to the store's services + # root or the swarm root — CAs generated at runtime, which no + # build-time trust store can contain. The queue's TLS is verified + # against `HIVE_C0RE_OIDC_CA_FILE` (../hive-tls.nix), not this bundle. # # ⚠️ `SSL_CERT_FILE` REPLACES the trust store, and unlike the other # consumers of this helper, hive-c0re already makes a *public*-TLS call: diff --git a/nix/host-modules/hive-c0re/environment.nix b/nix/host-modules/hive-c0re/environment.nix index 77751537..9e68bea4 100644 --- a/nix/host-modules/hive-c0re/environment.nix +++ b/nix/host-modules/hive-c0re/environment.nix @@ -181,9 +181,9 @@ in # Client-server API base hive-c0re uses to provision matrix (register # agent users, create the hive space + chat room, invite members). # Supplied by `services.hyperhive.swarm.matrix.apiUrl`, which defaults to - # the same gateway vhost as HIVE_MATRIX_URL above but is its own option, - # so an operator can point provisioning elsewhere without changing what - # agents are handed. + # the same gateway vhost as HIVE_MATRIX_URL above, over https because it + # carries the hive's matrix token and may cross the network. Its own + # option, so provisioning can point elsewhere without changing agents. HIVE_MATRIX_API_URL = config.services.hyperhive.swarm.matrix.apiUrl; } // { diff --git a/nix/host-modules/hive-matrix-service.nix b/nix/host-modules/hive-matrix-service.nix index 1014e8ce..73a5baac 100644 --- a/nix/host-modules/hive-matrix-service.nix +++ b/nix/host-modules/hive-matrix-service.nix @@ -64,10 +64,10 @@ in apiUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; - default = if cfg.gatewayHost == null then null else "http://${cfg.gatewayHost}"; + default = if cfg.gatewayHost == null then null else "https://${cfg.gatewayHost}"; defaultText = lib.literalExpression '' if services.hyperhive.swarm.matrix.gatewayHost == null then null - else "http://''${services.hyperhive.swarm.matrix.gatewayHost}" + else "https://''${services.hyperhive.swarm.matrix.gatewayHost}" ''; example = "https://matrix.example.com"; description = '' @@ -77,10 +77,14 @@ in `hyperhive.matrix.url`, which is the gateway vhost handed to each agent's `hive-matrix-daemon`. - Defaults to the homeserver's gateway vhost (`gatewayHost`), the - same name on every hive whichever host runs tuwunel, and `null` - when there is no vhost. Set it explicitly when the homeserver is - reachable only some other way. + Defaults to `https://` on the homeserver's gateway vhost + (`gatewayHost`), the same name on every hive whichever host runs + tuwunel, and `null` when there is no vhost. hive-c0re logs in + with the hive's matrix sender token over this URL, so it crosses + the network whenever hive-c0re is not on the homeserver's host: + an `http://` value sends that token in cleartext. hive-c0re + verifies the gateway's certificate against the hive trust bundle + (system CAs plus the swarm and services roots). `null` means hive-c0re has no homeserver to provision against and matrix provisioning no-ops. There is deliberately no diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index aa7ac4fc..8f8ca896 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -431,10 +431,10 @@ in example = true; description = '' Open `httpPort` in the host firewall. Off by default - (secure-by-default): the host reaches the homeserver on - loopback, and agent containers reach it at `gatewayHost`'s vhost - (`chat.` by default) via the gateway — so the firewall open only matters for - access from outside the host. Flip to `true` when announcing + (secure-by-default): hive-c0re and agent containers reach the + homeserver at `gatewayHost`'s vhost (`chat.` by + default) via the gateway, so the open port only matters for + direct access to `httpPort` from outside the host. Flip to `true` when announcing the homeserver to other hives or when an external matrix client needs to reach the client-server API directly. diff --git a/nix/module-eval/matrix-core.nix b/nix/module-eval/matrix-core.nix index d5ef8f17..317fc008 100644 --- a/nix/module-eval/matrix-core.nix +++ b/nix/module-eval/matrix-core.nix @@ -31,6 +31,10 @@ let # The same hive with the identity taken away, which separates "a homeserver # is deployed" from "this host can authenticate to the store". matrixNoBaoIdentity = hive { deploy.matrix.enable = true; }; + + # A hive host that does not run the homeserver: its hive-c0re reaches the + # homeserver across the network. + matrixElsewhere = hive { }; cases = [ { # Absence arm, and what makes the one above able to fail: with no leaf @@ -74,6 +78,18 @@ let && !(s.hive-c0re.environment ? BAO_ADDR) && !(lib.any (c: lib.hasPrefix "bao-" c) s.hive-c0re.serviceConfig.LoadCredential); } + { + # hive-c0re logs in with the hive's matrix token over this URL, and on a + # host without the homeserver that login crosses the network, so the + # default must be TLS all the way to the daemon's environment. + name = "hive-c0re provisions matrix over https from a hive that does not run it"; + ok = + !matrixElsewhere.services.hyperhive.deploy.matrix.enable + && matrixElsewhere.services.hyperhive.swarm.matrix.apiUrl == "https://chat.t.local" + && + matrixElsewhere.systemd.services.hive-c0re.environment.HIVE_MATRIX_API_URL + == "https://chat.t.local"; + } ]; in runGroup "matrix-core" cases