diff --git a/docs/integrations/matrix.md b/docs/integrations/matrix.md index 61637248..de4601b4 100644 --- a/docs/integrations/matrix.md +++ b/docs/integrations/matrix.md @@ -98,7 +98,7 @@ homeserver it crosses the network, so the default is TLS; hive-c0re verifies the gateway's certificate against the hive trust bundle. Federation peers find the homeserver through `.well-known/matrix/server` -on the hive domain, which names `:` +on the hive domain, which names `:443` ([`gateway.md::Discovery flow`](../networking/gateway.md#discovery-flow-matrix)). Port 8448 isn't used. diff --git a/docs/networking/gateway.md b/docs/networking/gateway.md index 48d30bc7..613a0379 100644 --- a/docs/networking/gateway.md +++ b/docs/networking/gateway.md @@ -75,7 +75,7 @@ nginx obtains and renews certs via the HTTP-01 challenge on `port` (default 80); ### Self-signed TLS (default) -On by default, listening on `httpsPort` (default 443) beside the plain-http `port` (default 80). +On by default, listening on `httpsPort` (default 443) beside the plain-http `port` (default 80). `httpsPort` only sets the listen port: swarm service URLs never carry a port, so clients reach them on 443. The issuer is the **secret store's `pki` mount**, not a bare self-signed leaf. `hive-tls-resign.service` (from `hive-tls.nix`) asks the store for a gateway **leaf** through this hive's own role, presenting the hive's own store identity (`services.hyperhive.deploy.hive-controller.tls.baoHiveIdentityCertFile` / `baoHiveIdentityKeyFile`), and writes it under `…tls.stateDir` (default `/var/lib/hive-tls`). `hive-gateway-self-signed-cert` then imports the leaf into nginx's state dir (`/var/lib/hive-gateway/tls/{cert,key}.pem`). On a host that runs no hive the import unit installs the swarm-services leaf there instead, or a throwaway self-signed pair until that leaf lands. [`swarm/ca.md`](../swarm/ca.md) has the role, the identity and what a hive without one does. @@ -169,7 +169,7 @@ services.hyperhive.deploy.forgejo.openFirewall = false; # default services.hyperhive.swarm.forge.rootUrl = "https://forge.example.com/"; ``` -`rootUrl` (default `null`) overrides the Forgejo `ROOT_URL` that's autoderived as `https:///`, with `:` appended when that port isn't 443. The gateway always terminates TLS, so the forge is always advertised over `https://`. Set `rootUrl` explicitly when `forge.domain` differs from the public URL, or for a bespoke shape such as an external reverse proxy on another host or path. It must end with `/` (an assertion enforces this). +`rootUrl` (default `null`) overrides the Forgejo `ROOT_URL` that's autoderived as `https:///`. The gateway always terminates TLS, so the forge is always advertised over `https://`. Set `rootUrl` explicitly when `forge.domain` differs from the public URL, or for a bespoke shape such as an external reverse proxy on another host or path. It must end with `/` (an assertion enforces this). ## Security headers @@ -210,13 +210,13 @@ When enabled, every vhost adds `Strict-Transport-Security: max-age=…[; include The operator points a client at ``: -1. The client fetches `https:///.well-known/matrix/client` → `{"m.homeserver":{"base_url":"https://chat."}}` (no port suffix when `httpsPort` is 443). +1. The client fetches `https:///.well-known/matrix/client` → `{"m.homeserver":{"base_url":"https://chat."}}`. 2. It connects to `chat./_matrix/client/...`. 3. The gateway proxies `/_matrix/*` to tuwunel over its unix socket, `/run/hive-matrix/tuwunel.sock`. matrix-dart-sdk (FluffyChat and others) always fetches the well-known over `https`, which is why [self-signed TLS](#self-signed-tls-default) is on by default. -Federation peers fetch `.well-known/matrix/server` → `{"m.server":"chat.:"}`. The port is always explicit, even 443: a delegated host without a port means the federation default 8448, not 443. Peers then reach `/_matrix/` on the chat vhost through the gateway, so the gateway must be reachable from them (`gateway.openFirewall`). +Federation peers fetch `.well-known/matrix/server` → `{"m.server":"chat.:443"}`. The port is explicit even though it's the https default: a delegated host without a port means the federation default 8448, not 443. Peers then reach `/_matrix/` on the chat vhost through the gateway, so the gateway must be reachable from them (`gateway.openFirewall`). ## Sub-domain shape (rationale) diff --git a/nix/host-modules/glue-controller-env.nix b/nix/host-modules/glue-controller-env.nix index 1f9d5d1d..53db5986 100644 --- a/nix/host-modules/glue-controller-env.nix +++ b/nix/host-modules/glue-controller-env.nix @@ -24,8 +24,7 @@ # ./glue-swarm-otel-oidc-client.nix), and `deploy.swarm-otel.enable` only says it # runs HERE. The endpoint is its swarm-service name through the gateway, never a # loopback literal, with no trailing slash: the http exporter appends -# `/v1/metrics` and the gateway route strips `/${producerName}`. The port suffix -# is elided on 443, as in ./swarm-bao.nix. +# `/v1/metrics` and the gateway route strips `/${producerName}`. # `SWARM_CONTROLLER_OTEL_AUDIENCE` is the audience ./glue-controller-oidc-client.nix # registers and ./swarm-otel.nix's `oidc/${producerName}` authenticator checks. { @@ -38,9 +37,6 @@ let deployCfg = hyperhiveCfg.deploy; otelSwarmCfg = hyperhiveCfg.swarm.otel; queueClientId = hyperhiveCfg.swarm.controller.queueClientId; - gatewayPortSuffix = lib.optionalString ( - hyperhiveCfg.gateway.httpsPort != 443 - ) ":${toString hyperhiveCfg.gateway.httpsPort}"; in { config = lib.mkIf deployCfg.swarm-controller.enable ( @@ -53,7 +49,7 @@ in }) { systemd.services.swarm-controller.environment = { - OTEL_EXPORTER_OTLP_ENDPOINT = "https://${otelSwarmCfg.domain}${gatewayPortSuffix}/${otelSwarmCfg.producerName}"; + OTEL_EXPORTER_OTLP_ENDPOINT = "https://${otelSwarmCfg.domain}/${otelSwarmCfg.producerName}"; SWARM_CONTROLLER_OTEL_AUDIENCE = queueClientId; }; } diff --git a/nix/host-modules/glue-matrix-hive-discovery.nix b/nix/host-modules/glue-matrix-hive-discovery.nix index 8885e947..12c78ae8 100644 --- a/nix/host-modules/glue-matrix-hive-discovery.nix +++ b/nix/host-modules/glue-matrix-hive-discovery.nix @@ -23,12 +23,7 @@ let gatewayCfg = hyperhiveCfg.gateway; matrixCfg = hyperhiveCfg.swarm.matrix; - publicPort = gatewayCfg.httpsPort; - # The canonical 443 elides the port; see `docs/networking/gateway.md` - # ("Self-signed TLS"). - homeserverUrl = "https://${matrixCfg.gatewayHost}${ - lib.optionalString (publicPort != 443) ":${toString publicPort}" - }"; + homeserverUrl = "https://${matrixCfg.gatewayHost}"; in { config = @@ -73,7 +68,7 @@ in "= /.well-known/matrix/server" = { extraConfig = '' default_type application/json; - return 200 '{"m.server":"${matrixCfg.gatewayHost}:${toString publicPort}"}'; + return 200 '{"m.server":"${matrixCfg.gatewayHost}:443"}'; ''; }; }; diff --git a/nix/host-modules/hive-c0re/vhosts.nix b/nix/host-modules/hive-c0re/vhosts.nix index 336e6822..49e39bba 100644 --- a/nix/host-modules/hive-c0re/vhosts.nix +++ b/nix/host-modules/hive-c0re/vhosts.nix @@ -19,10 +19,6 @@ let # Dashboard SPA dist, static-served by nginx. dashboardDist = "${hyperhiveCfg.c0re.servedFrontend}/dashboard"; - # The canonical 443 elides the port; see `docs/networking/gateway.md` - # ("Self-signed TLS"). - publicPortSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; - # `/agent/` catch-all 404 + the two internal error-page targets it # points at. Per-agent `location /agent//` blocks live in the # runtime-generated `/var/lib/hive-gateway/conf/agents.conf` (included via @@ -205,7 +201,7 @@ in # lands. ${hyperhiveCfg.domain} = { locations."/".extraConfig = '' - return 301 https://${dashboardDomain}${publicPortSuffix}$request_uri; + return 301 https://${dashboardDomain}$request_uri; ''; }; }; diff --git a/nix/host-modules/hive-forge/default.nix b/nix/host-modules/hive-forge/default.nix index d4b9658f..d9caf905 100644 --- a/nix/host-modules/hive-forge/default.nix +++ b/nix/host-modules/hive-forge/default.nix @@ -83,12 +83,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.hive-forge` in # ../swarm-otel-service.nix declares, as a client of its own rather than the - # login source's. The port suffix is elided on 443, as in ../swarm-bao.nix. + # login source's. forwarderClientId = cfg.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${config.services.hyperhive.swarm.otel.domain}${gatewayPortSuffix}/hive-forge"; + forwarderEndpoint = "https://${config.services.hyperhive.swarm.otel.domain}/hive-forge"; forwarderUnit = "swarm-journal-forwarder-oidc"; # In the host netns: distinct from every other collector's there, which the # assertion below checks. @@ -115,15 +112,11 @@ let # gateway vhost server-name, so ROOT_URL just uses it directly. The # gateway always terminates TLS (self-signed is the implicit floor when # neither `tls.certDir` nor ACME is configured), so the forge is always - # advertised over `https` on `httpsPort` — the canonical 443 elides the - # port suffix. Operators can still override via `cfg.rootUrl` for - # bespoke shapes. Both bindings are duplicated in ./service.nix, which - # builds `sso.redirectUri` from them; keep the two equal. - defaultRootUrl = - let - portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; - in - "https://${cfg.domain}${portSuffix}/"; + # advertised as plain `https:///`. Operators can still override via + # `cfg.rootUrl` for bespoke shapes. Both bindings are duplicated in + # ./service.nix, which builds `sso.redirectUri` from them; keep the two + # equal. + defaultRootUrl = "https://${cfg.domain}/"; effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; # `actionsFromSelf` resolves `uses:` against this forge, which then has to diff --git a/nix/host-modules/hive-forge/service.nix b/nix/host-modules/hive-forge/service.nix index 202d4718..f0a1a1f2 100644 --- a/nix/host-modules/hive-forge/service.nix +++ b/nix/host-modules/hive-forge/service.nix @@ -8,7 +8,6 @@ }: let cfg = config.services.hyperhive.swarm.forge; - gatewayCfg = config.services.hyperhive.gateway; swarmDomain = config.services.hyperhive.swarm.domain; # Forgejo's name for the login source. Duplicated in ./default.nix, which @@ -22,11 +21,7 @@ let # Forgejo's `ROOT_URL`, duplicated from ./default.nix, which documents its # shape. - defaultRootUrl = - let - portSuffix = if gatewayCfg.httpsPort == 443 then "" else ":${toString gatewayCfg.httpsPort}"; - in - "https://${cfg.domain}${portSuffix}/"; + defaultRootUrl = "https://${cfg.domain}/"; effectiveRootUrl = if cfg.rootUrl != null then cfg.rootUrl else defaultRootUrl; in { @@ -117,8 +112,8 @@ in (default), `ROOT_URL` is `https://''${cfg.domain}/`. The gateway always terminates TLS (self-signed is the implicit floor when no `gateway.tls.certDir` / ACME is set), so the forge is always - advertised over https. A non-canonical `gateway.httpsPort` is - appended as `:`. + advertised over https, on the default port. A gateway that listens + on another `gateway.httpsPort` does not change this URL. Set this only for a genuinely bespoke shape (e.g. an external reverse proxy on a different host/path). Must end with `/` per @@ -159,12 +154,6 @@ in than a knob. The glue that registers this client wherever authelia runs reads it from here instead of restating the format. - - ⚠️ With {option}`services.hyperhive.swarm.forge.rootUrl` unset, - `ROOT_URL` follows the gateway's `httpsPort`, which is - per-host. An authelia host that is not the forge's host renders - the forge's callback only if the two agree on it; set `rootUrl` - if they do not. ''; }; # The secret half is fetched from the swarm secret store on the host diff --git a/nix/host-modules/hive-gateway/options.nix b/nix/host-modules/hive-gateway/options.nix index fc16b92d..a4a86831 100644 --- a/nix/host-modules/hive-gateway/options.nix +++ b/nix/host-modules/hive-gateway/options.nix @@ -307,10 +307,15 @@ in default = 443; example = 8443; description = '' - TCP port for the TLS-terminated vhosts. Default 443. The gateway - always terminates TLS (self-signed is the implicit floor when no - `tls.certDir` / ACME is configured), so this port is always active - alongside the plain-http `port`. + TCP port nginx listens on for the TLS-terminated vhosts, and the one + `openFirewall` opens. Default 443. The gateway always terminates TLS + (self-signed is the implicit floor when no `tls.certDir` / ACME is + configured), so this port is always active alongside the plain-http + `port`. + + Listen-only: it does not change any URL. Every swarm service URL is + `https://` with no port, so clients reach the services on 443 + whatever this is set to. ''; }; diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index 4360553c..d998967f 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -123,14 +123,10 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.hive-matrix` in # ./swarm-otel-service.nix declares, as a client of its own rather than the - # homeserver login's. The port suffix is elided on 443, as in - # ./swarm-bao.nix. It reads its client secret with matrix-ctl's identity, + # homeserver login's. It reads its client secret with matrix-ctl's identity, # bound in above: one identity for the whole container. forwarderClientId = cfg.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${config.services.hyperhive.swarm.otel.domain}${gatewayPortSuffix}/hive-matrix"; + forwarderEndpoint = "https://${config.services.hyperhive.swarm.otel.domain}/hive-matrix"; # Loopback in the container's own netns, and distinct from the other service # containers' forwarders all the same. forwarderTelemetryPort = 8899; diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 92ad3963..af0392d7 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -201,13 +201,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-authelia` in - # ./swarm-otel-service.nix declares, as a client of its own. The port suffix - # is elided on 443, as in ./swarm-bao.nix. + # ./swarm-otel-service.nix declares, as a client of its own. forwarderClientId = cfg.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}${gatewayPortSuffix}/${cfg.machine}"; + forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}/${cfg.machine}"; # Loopback port of the forwarder's own metrics, inside the container's netns: # no other collector shares it. The module requires a value. forwarderTelemetryPort = 8901; diff --git a/nix/host-modules/swarm-bao.nix b/nix/host-modules/swarm-bao.nix index aaf45572..60280ee7 100644 --- a/nix/host-modules/swarm-bao.nix +++ b/nix/host-modules/swarm-bao.nix @@ -1442,11 +1442,7 @@ let # wrong. The path selects WHICH receiver: this container is a swarm-level # producer and not a hive, so it is the swarm tier's own route, the same one # ./glue-controller-env.nix exports to. `/` on that vhost answers 404. - # - # The port suffix follows ./hive-forge/default.nix: elided on the canonical - # 443 so this renders byte for byte like the tier's other two consumers of - # this name, present when an operator moved the gateway's TLS port. - otelFirstHop = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${swarmOtelCfg.storeProducerName}"; + otelFirstHop = "https://${swarmOtelCfg.domain}/${swarmOtelCfg.storeProducerName}"; # The forwarder's own credential, and the three names it takes on the way # in. The shape is ./swarm-otel.nix's `swarm-bao-otel-oidc` — the unit that @@ -1498,11 +1494,6 @@ let }; swarmOtelCfg = hyperhiveCfg.swarm.otel; - gatewayPortSuffix = - let - p = hyperhiveCfg.gateway.httpsPort; - in - lib.optionalString (p != 443) ":${toString p}"; # Non-zero is what SERVES the endpoint at all — the switch is a duration, not # a boolean, so a zero here is an openbao that answers 404 on a listener diff --git a/nix/host-modules/swarm-builder.nix b/nix/host-modules/swarm-builder.nix index efc6224f..0a87b502 100644 --- a/nix/host-modules/swarm-builder.nix +++ b/nix/host-modules/swarm-builder.nix @@ -43,13 +43,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-builder` in - # ./swarm-otel-service.nix declares, by the collector's gateway name. The - # port suffix is elided on 443, as in ./swarm-bao.nix. + # ./swarm-otel-service.nix declares, by the collector's gateway name. swarmOtelCfg = hyperhiveCfg.swarm.otel; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${machine}"; + forwarderEndpoint = "https://${swarmOtelCfg.domain}/${machine}"; forwarderUnit = "swarm-journal-forwarder-oidc"; # Loopback in the container's own netns, and distinct from the other service # containers' forwarders all the same. diff --git a/nix/host-modules/swarm-ci-metrics.nix b/nix/host-modules/swarm-ci-metrics.nix index 483f491f..49a68793 100644 --- a/nix/host-modules/swarm-ci-metrics.nix +++ b/nix/host-modules/swarm-ci-metrics.nix @@ -31,12 +31,8 @@ let clientId = hyperhiveCfg.swarm.ci-metrics.clientId; # The route `serviceProducers.swarm-ci-metrics` in ./swarm-otel-service.nix - # declares, by the collector's gateway name. The port suffix is elided on - # 443, as in ./swarm-builder.nix. - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - otlpEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${unit}"; + # declares, by the collector's gateway name. + otlpEndpoint = "https://${swarmOtelCfg.domain}/${unit}"; haveClientIdentity = baoDeploy.ciMetricsClientCertFile != null && baoDeploy.ciMetricsClientKeyFile != null; diff --git a/nix/host-modules/swarm-ci-runner.nix b/nix/host-modules/swarm-ci-runner.nix index dd22a615..690b0348 100644 --- a/nix/host-modules/swarm-ci-runner.nix +++ b/nix/host-modules/swarm-ci-runner.nix @@ -63,14 +63,10 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-ci-runner` in - # ./swarm-otel-service.nix declares. The port suffix is elided on 443, as in - # ./swarm-bao.nix. + # ./swarm-otel-service.nix declares. swarmOtelCfg = config.services.hyperhive.swarm.otel; forwarderClientId = config.services.hyperhive.swarm.ci-runner.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${machine}"; + forwarderEndpoint = "https://${swarmOtelCfg.domain}/${machine}"; forwarderTokenUrl = "${toString config.services.hyperhive.swarm.authelia.url}/api/oidc/token"; forwarderUnit = "swarm-journal-forwarder-oidc"; # Loopback in the container's own netns, and distinct from the other service diff --git a/nix/host-modules/swarm-claude-login.nix b/nix/host-modules/swarm-claude-login.nix index 7d9f8bf7..1c75147f 100644 --- a/nix/host-modules/swarm-claude-login.nix +++ b/nix/host-modules/swarm-claude-login.nix @@ -46,13 +46,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-claude-login` in - # ./swarm-otel-service.nix declares, by the collector's gateway name. The - # port suffix is elided on 443, as in ./swarm-bao.nix. + # ./swarm-otel-service.nix declares, by the collector's gateway name. swarmOtelCfg = hyperhiveCfg.swarm.otel; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${machine}"; + forwarderEndpoint = "https://${swarmOtelCfg.domain}/${machine}"; forwarderUnit = "swarm-journal-forwarder-oidc"; # Loopback in the container's own netns, and distinct from the other service # containers' forwarders all the same. diff --git a/nix/host-modules/swarm-grafana.nix b/nix/host-modules/swarm-grafana.nix index d6d77012..b5c7f79c 100644 --- a/nix/host-modules/swarm-grafana.nix +++ b/nix/host-modules/swarm-grafana.nix @@ -306,12 +306,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-grafana` in # ./swarm-otel-service.nix declares, as a client of its own rather than the - # browser login's. The port suffix is elided on 443, as in ./swarm-bao.nix. + # browser login's. forwarderClientId = cfg.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}${gatewayPortSuffix}/${cfg.machine}"; + forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}/${cfg.machine}"; forwarderUnit = "swarm-journal-forwarder-oidc"; # Loopback port of the forwarder's own metrics, inside the container's netns: # no other collector shares it. The module requires a value. diff --git a/nix/host-modules/swarm-nats.nix b/nix/host-modules/swarm-nats.nix index c2d61fdd..f213f7cd 100644 --- a/nix/host-modules/swarm-nats.nix +++ b/nix/host-modules/swarm-nats.nix @@ -27,13 +27,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-nats` in - # ./swarm-otel-service.nix declares, by the collector's gateway name. The - # port suffix is elided on 443, as in ./swarm-bao.nix. + # ./swarm-otel-service.nix declares, by the collector's gateway name. swarmOtelCfg = config.services.hyperhive.swarm.otel; - gatewayPortSuffix = lib.optionalString ( - config.services.hyperhive.gateway.httpsPort != 443 - ) ":${toString config.services.hyperhive.gateway.httpsPort}"; - forwarderEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${machine}"; + forwarderEndpoint = "https://${swarmOtelCfg.domain}/${machine}"; tlsCfg = config.services.hyperhive.deploy.hive-controller.tls; gatewayCfg = config.services.hyperhive.gateway; diff --git a/nix/host-modules/swarm-notify-router.nix b/nix/host-modules/swarm-notify-router.nix index 41006f53..e3ed7431 100644 --- a/nix/host-modules/swarm-notify-router.nix +++ b/nix/host-modules/swarm-notify-router.nix @@ -38,10 +38,7 @@ let # collector, the route `serviceProducers.swarm-notify-router` in # ./swarm-otel-service.nix declares, as ./swarm-claude-login.nix does. swarmOtelCfg = hyperhiveCfg.swarm.otel; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${machine}"; + forwarderEndpoint = "https://${swarmOtelCfg.domain}/${machine}"; forwarderUnit = "swarm-journal-forwarder-oidc"; # Loopback in the container's own netns, and distinct from the other service # containers' forwarders all the same. diff --git a/nix/host-modules/swarm-review-classifier.nix b/nix/host-modules/swarm-review-classifier.nix index 72b1b2f7..3393b82e 100644 --- a/nix/host-modules/swarm-review-classifier.nix +++ b/nix/host-modules/swarm-review-classifier.nix @@ -48,13 +48,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-review-classifier` in - # ./swarm-otel-service.nix declares, by the collector's gateway name. The - # port suffix is elided on 443, as in ./swarm-bao.nix. + # ./swarm-otel-service.nix declares, by the collector's gateway name. swarmOtelCfg = hyperhiveCfg.swarm.otel; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${swarmOtelCfg.domain}${gatewayPortSuffix}/${machine}"; + forwarderEndpoint = "https://${swarmOtelCfg.domain}/${machine}"; forwarderUnit = "swarm-journal-forwarder-oidc"; # Loopback in the container's own netns, and distinct from the other service # containers' forwarders all the same. diff --git a/nix/host-modules/swarm-victorialogs.nix b/nix/host-modules/swarm-victorialogs.nix index a3f09089..559fc1ef 100644 --- a/nix/host-modules/swarm-victorialogs.nix +++ b/nix/host-modules/swarm-victorialogs.nix @@ -83,13 +83,9 @@ let # The container's journal forwarder pushes to its own receiver on the swarm # collector, the route `serviceProducers.swarm-victorialogs` in - # ./swarm-otel-service.nix declares. The port suffix is elided on 443, as in - # ./swarm-bao.nix. + # ./swarm-otel-service.nix declares. forwarderClientId = cfg.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; - forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}${gatewayPortSuffix}/${cfg.machine}"; + forwarderEndpoint = "https://${hyperhiveCfg.swarm.otel.domain}/${cfg.machine}"; forwarderUnit = "swarm-journal-forwarder-oidc"; # In the host netns: distinct from every other collector's there, which the # assertion below checks. diff --git a/nix/host-modules/swarm-victoriametrics.nix b/nix/host-modules/swarm-victoriametrics.nix index a8a2f86b..48730bb1 100644 --- a/nix/host-modules/swarm-victoriametrics.nix +++ b/nix/host-modules/swarm-victoriametrics.nix @@ -149,9 +149,6 @@ let # The client secret is fetched on the host, under the responder's store # identity, and bound in read-only: the container cannot reach the store. forwarderClientId = cfg.otel.clientId; - gatewayPortSuffix = lib.optionalString ( - gatewayCfg.httpsPort != 443 - ) ":${toString gatewayCfg.httpsPort}"; otelDomain = hyperhiveCfg.swarm.otel.domain; autheliaDomain = hyperhiveCfg.swarm.authelia.domain; @@ -177,7 +174,7 @@ let } location ^~ /${cfg.machine}/ { - proxy_pass https://${otelDomain}${gatewayPortSuffix}; + proxy_pass https://${otelDomain}; ${gatewayCfg.lib.verifiedProxyTo otelDomain} } diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index 38d66ad8..ebcc733e 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -64,12 +64,12 @@ let otel.debug = true; }; - # The controller behind a gateway on a non-default TLS port: the only - # fixture whose collector URL carries a port. Only the controller's - # environment is read off it. + # The controller behind a gateway on a non-default TLS port, with the + # firewall opened. Its collector URL and its listener are read off it. controllerAltPort = hive { deploy.swarm-controller.enable = true; gateway.httpsPort = 8443; + gateway.openFirewall = true; }; # On the store's host, which mints the runner's store identity. @@ -167,6 +167,27 @@ let name = "the all-local mode turns the swarm controller on"; ok = allLocal.services.hyperhive.deploy.swarm-controller.enable; } + { + # `gateway.httpsPort` is a listen setting: the nginx TLS listener and the + # opened firewall port follow it, while a service URL built on the same + # host stays `https://`. The listener and firewall arms are the + # control that the port took effect on this fixture, so the URL arm + # cannot pass on a fixture where it was never set. + name = "a non-default gateway TLS port moves the listener and firewall, not the service URLs"; + ok = + let + otel = controllerAltPort.services.hyperhive.swarm.otel; + listen = controllerAltPort.services.nginx.virtualHosts."_".listen; + tcp = controllerAltPort.networking.firewall.allowedTCPPorts; + url = + controllerAltPort.systemd.services.swarm-controller.environment.OTEL_EXPORTER_OTLP_ENDPOINT or null; + in + lib.any (l: l.port == 8443 && (l.ssl or false)) listen + && !(lib.any (l: l.port == 443) listen) + && lib.elem 8443 tcp + && !(lib.elem 443 tcp) + && url == "https://${otel.domain}/${otel.producerName}"; + } { # ../host-modules/glue-controller-env.nix: the controller always exports to # the swarm's collector. `controllerAlone` runs no collector of its own @@ -195,9 +216,6 @@ let (env shared.controllerAlone).OTEL_EXPORTER_OTLP_ENDPOINT or null == "https://${otel.domain}/${otel.producerName}" && (env shared.controllerAlone).SWARM_CONTROLLER_OTEL_AUDIENCE or null == clientId - && - (env controllerAltPort).OTEL_EXPORTER_OTLP_ENDPOINT or null - == "https://${otel.domain}:8443/${otel.producerName}" && !(centralToggleOff.systemd.services ? swarm-controller); } { diff --git a/nix/module-eval/forge-placement.nix b/nix/module-eval/forge-placement.nix index 6c992731..c07f3c16 100644 --- a/nix/module-eval/forge-placement.nix +++ b/nix/module-eval/forge-placement.nix @@ -34,17 +34,21 @@ let # The shared services here, the forge somewhere else. Every derivation in # ../host-modules/swarm-required-services.nix is `mkDefault`, so this stays # expressible — and it is also the authelia-without-forge host the OIDC - # client case needs. + # client case needs. Its gateway listens on a non-default TLS port, which no + # URL it renders may carry. servicesForgeElsewhere = hive { deploy.allSwarmServices = true; deploy.forgejo.enable = false; + gateway.httpsPort = 8443; }; # The forge with neither authelia nor the store, its store identity placed by # hand: the other half of the split, and the deployment the store fetch - # exists for. + # exists for. Its gateway listens on a non-default TLS port, which the + # forge's own URL may not carry. forgeAlone = hive { deploy.forgejo.enable = true; + gateway.httpsPort = 8443; deploy.bao.forgePublisherClientCertFile = "/etc/pki/forge-publisher.pem"; deploy.bao.forgePublisherClientKeyFile = "/etc/pki/forge-publisher-key.pem"; }; @@ -166,6 +170,18 @@ let servicesForgeElsewhere.services.hyperhive.swarm.forge.sso.redirectUri == "https://forge.t.local/user/oauth2/authelia/callback"; } + { + # The gateway's TLS port is a listen setting. The first clause is the + # control that the fixture's gateway is on 8443, so the URL arms cannot + # pass on a fixture where it was never set. + name = "the forge's URLs carry no port when the gateway listens on a non-default one"; + ok = + forgeAlone.services.hyperhive.gateway.httpsPort == 8443 + && + forgeAlone.containers.hive-forge.config.services.forgejo.settings.server.ROOT_URL + == "https://forge.t.local/" + && servicesForgeElsewhere.services.hyperhive.gateway.httpsPort == 8443; + } { # The runner reaches the forge by name and reads its token with its own # certificate, so nothing about the host it runs on is asked. diff --git a/nix/module-eval/matrix-core.nix b/nix/module-eval/matrix-core.nix index 768a2a66..c45f0e90 100644 --- a/nix/module-eval/matrix-core.nix +++ b/nix/module-eval/matrix-core.nix @@ -132,14 +132,17 @@ let # The discovery locations reach the hive domain's vhost from a glue # module, merged into the vhost hive-c0re declares. The matrix-elsewhere # arm reads the same vhost, which still exists there with its redirect, - # so its absence of discovery cannot pass on a misspelled name. + # so its absence of discovery cannot pass on a misspelled name. The + # fixture's gateway is on a non-default TLS port, which none of the URLs + # carry. name = "matrix discovery sits on the hive domain's vhost only where the homeserver runs"; ok = let here = matrixNoBaoIdentity.services.nginx.virtualHosts."h1.t.local".locations; elsewhere = matrixElsewhere.services.nginx.virtualHosts."h1.t.local".locations; in - lib.hasInfix ''{"m.homeserver":{"base_url":"https://chat.t.local"}}'' ( + matrixNoBaoIdentity.services.hyperhive.gateway.httpsPort == 8443 + && lib.hasInfix ''{"m.homeserver":{"base_url":"https://chat.t.local"}}'' ( here."= /.well-known/matrix/client".extraConfig or "" ) && lib.hasInfix ''{"m.server":"chat.t.local:443"}'' ( diff --git a/nix/module-eval/shared.nix b/nix/module-eval/shared.nix index ba54ad8a..885cd8e5 100644 --- a/nix/module-eval/shared.nix +++ b/nix/module-eval/shared.nix @@ -82,7 +82,12 @@ in controllerAlone = hive { deploy.swarm-controller.enable = true; }; - matrixNoBaoIdentity = hiveWithController { deploy.matrix.enable = true; }; + # On a non-default gateway TLS port, so the URLs it renders (discovery, the + # hive-domain redirect) show whether they follow it. + matrixNoBaoIdentity = hiveWithController { + deploy.matrix.enable = true; + gateway.httpsPort = 8443; + }; baoWithMatrix = hiveWithController { deploy.bao.enable = true;