diff --git a/nix/host-modules/lib/hive-ca-trust.nix b/nix/host-modules/lib/hive-ca-trust.nix index 5e46e878..468b3bcb 100644 --- a/nix/host-modules/lib/hive-ca-trust.nix +++ b/nix/host-modules/lib/hive-ca-trust.nix @@ -62,75 +62,4 @@ in after = [ "hive-tls-ca.service" ]; requires = [ "hive-tls-ca.service" ]; }; - - # System CAs + hive CA in one bundle, with `SSL_CERT_FILE` set on each - # consumer — for runtimes whose trust variable *replaces* the store (Go, - # rustls-native-certs). An additive one (Node's `NODE_EXTRA_CA_CERTS`, - # hive-ci) needs no bundle and should not use this. - # - # imports = [ (caTrust.trustBundle { inherit pkgs; name = "swarm-nats"; - # consumers = [ "swarm-nats-auth" ]; }) ]; - # - # Three constraints, each earned: - # - `requires` on the CONSUMER: `before` orders but does not gate, so a - # failed assembly otherwise leaves it running and trusting *nothing*. - # - assemble to a temp path, verify, then move: `cat` of an empty bind - # exits 0, and a partial bundle must never appear under the final name. - # - `consumers` are BARE unit names — they are `systemd.services` keys - # (no suffix) *and* go in `before`/`requires` (suffixed). Reversed, the - # edge names a unit that does not exist and systemd orders nothing. - # - # Returns a module, not bare services: a caller already writing - # `systemd.services.` cannot also write `systemd.services`. - trustBundle = - { - name, - consumers, - pkgs, - }: - let - dir = "/run/${name}-ca"; - bundlePath = "${dir}/trust-bundle.pem"; - unit = "${name}-ca-bundle"; - in - { - _file = "hive-ca-trust.nix#trustBundle:${name}"; - config.systemd.services = lib.optionalAttrs useSelfSigned ( - { - ${unit} = { - description = "assemble ${name} TLS trust bundle (system CAs + hive CA)"; - wantedBy = [ "multi-user.target" ]; - before = map (c: "${c}.service") consumers; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - SyslogIdentifier = unit; - }; - path = [ - pkgs.coreutils - pkgs.gnugrep - ]; - script = '' - set -euo pipefail - install -d -m 0755 ${dir} - tmp=${bundlePath}.tmp - cat /etc/ssl/certs/ca-certificates.crt ${caContainerPath} > "$tmp" - # `cat` of an empty or missing-but-mounted source exits 0, so the - # result has to be inspected rather than the command trusted. - if ! grep -q 'BEGIN CERTIFICATE' "$tmp"; then - echo "${unit}: assembled bundle contains no certificate" >&2 - exit 1 - fi - chmod 0644 "$tmp" - mv "$tmp" ${bundlePath} - ''; - }; - } - // lib.genAttrs consumers (_: { - requires = [ "${unit}.service" ]; - after = [ "${unit}.service" ]; - environment.SSL_CERT_FILE = bundlePath; - }) - ); - }; } diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 753b067f..cd63c2a6 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -64,18 +64,6 @@ let unitName = "authelia-${instance}"; stateDir = "/var/lib/${unitName}"; - tlsCfg = hyperhiveCfg.tls; - caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; }; - # `swarm-authelia-bridge` verifies the gateway when it introspects by name. - # Nothing in this container trusted the swarm CA, which is a runtime file no - # build-time option can name — so an https call out of here could only ever - # fail `UnknownIssuer`. Same defect the queue's responder hit. - caBundleModule = caTrust.trustBundle { - inherit pkgs; - name = cfg.machine; - consumers = [ "swarm-authelia-bridge" ]; - }; - # The SWARM's domain, because that is where the protected apps now live # (`forge.`, `chat.`, `auth.`). It moves in the # same commit as `domain` below and cannot lag it: authelia validates @@ -763,19 +751,12 @@ in }; }; - # Order the container after the host CA generator, so the bind source - # exists before nspawn sets the mount up. - systemd.services."container@${cfg.machine}" = caTrust.containerOrdering; - containers.${cfg.machine} = { autoStart = true; ephemeral = false; # Shared host netns, like the forge and matrix containers: the # gateway reaches authelia at 127.0.0.1:. privateNetwork = false; - # Public trust bundle only, read-only. Empty when the gateway is not - # self-signed, so the whole trust path drops out cleanly. - bindMounts = caTrust.bindMount; config = { ... }: @@ -785,7 +766,6 @@ in inherit (networkCfg) bridgeIp; dnsConsumers = [ "authelia-${instance}.service" ]; }) - caBundleModule ]; system.stateVersion = "26.05"; diff --git a/nix/host-modules/swarm-nats.nix b/nix/host-modules/swarm-nats.nix index a29121e5..bfb83083 100644 --- a/nix/host-modules/swarm-nats.nix +++ b/nix/host-modules/swarm-nats.nix @@ -23,22 +23,6 @@ let clientAccount = "APP"; machine = "swarm-nats"; - - tlsCfg = config.services.hyperhive.tls; - gatewayCfg = config.services.hyperhive.gateway; - caTrust = import ./lib/hive-ca-trust.nix { inherit lib tlsCfg gatewayCfg; }; - # The responder introspects authelia over https BY NAME. Its HTTP client is - # reqwest/rustls, and `rustls-platform-verifier` resolves roots through - # `rustls-native-certs`, which reads `SSL_CERT_FILE` — so the same assembled - # bundle the Go containers use applies here. Without it the handshake fails - # `UnknownIssuer`, introspection fails, and the responder denies *every* - # client: one missing trust anchor surfacing as `authorization violation` at - # every would-be queue user. - caBundleModule = caTrust.trustBundle { - inherit pkgs; - name = machine; - consumers = [ "swarm-nats-auth" ]; - }; # Where the responder's credentials live *inside* the container, and the # host path that resolves to. Two names for one location, because the # host is the only place both filesystems are addressable. @@ -454,9 +438,6 @@ in # unauthenticated interim state would be a hole rather than a # rough edge. privateNetwork = false; - # Binds only the public trust bundle, read-only. Empty when the gateway - # is not self-signed, so the whole trust path drops out cleanly. - bindMounts = caTrust.bindMount; config = { ... }: { @@ -469,7 +450,6 @@ in # file exists. dnsConsumers = [ "swarm-nats-auth.service" ]; }) - caBundleModule ]; system.stateVersion = "26.05"; @@ -595,11 +575,6 @@ in # refuses to start when a bind source is missing, so one absent seed # would take down the **whole container including the queue**, not # merely the responder. A far larger blast radius than the fault. - # Order the container after the host CA generator, so the bind source - # exists before nspawn sets the mount up. Without it a late CA fails the - # container start outright rather than degrading. - systemd.services."container@${machine}" = caTrust.containerOrdering; - systemd.services.swarm-nats-auth-secrets = lib.mkIf responderConfigured { description = "deliver the swarm queue responder's credentials"; before = [ "container@swarm-nats.service" ];