Watch
0
0
Fork
You've already forked hyperhive
0

swarm-authelia: run the secret publisher and a journal forwarder inside the container

swarm-secret-publish and its path unit now run inside authelia's container,
beside the mint, and read each client secret at the path authelia writes it.
The host unit and host path unit, which read the secrets through the
container's root on the host, are gone, and so is
deploy.authelia.hostClientSecretDir, which only they read. The publisher keeps
its store identity, the secret-publisher leaf. The host hands the leaf and the
store's CA into the container as nspawn system credentials, and the unit loads
them; where this host mints the leaf, the container waits for swarm-bao-pki.
Authelia does not wait for the publisher, which retries every 30 seconds while
the store is down.

The container also runs the journal forwarder the other service containers
run, pushing to a receiver of its own on the swarm collector (serviceProducers
swarm-authelia) as a new machine OIDC client (swarm.authelia.otel.clientId)
that authelia registers for itself and the publisher carries to the store. The
fetch unit reads it back under the publisher's leaf, also handed to it as a
system credential, so the swarm-secret-publisher policy gains read on that one
leaf; its stanza keeps create and update, which the exact path would otherwise
take from the services/* prefix. Without the leaf the forwarder is off.

The container drops --link-journal=host, and the host collector stops reading
it. The services' "nothing at <path>" errors name the publisher in authelia's
container.
This commit is contained in:
atlas 2026-10-16 15:05:29 +02:00 • committed by mara
commit b68a8ef290
36 changed files with 753 additions and 363 deletions

View file

@ -25,18 +25,13 @@ let
# No store identity, which the first case below relies on.
natsQueueOnly = shared.natsQueueOnly;
# The IdP and the queue on one host. A reader of authelia's host paths has to
# follow the `deploy` namespace; seven of those reads go through an alias a
# path-shaped grep cannot see.
# The IdP and the queue on one host.
autheliaAndNats = hive {
deploy.authelia.enable = true;
deploy.nats.enable = true;
deploy.authelia.usersFile = "/var/lib/test-authelia/users.yml";
deploy.authelia.package = pkgs.emptyDirectory;
deploy.authelia.bridgePackage = pkgs.emptyDirectory;
# Renders the publisher, which reads the host client-secret dir.
deploy.swarm-secret-publisher.baoClientCertFile = "/etc/pki/publisher.pem";
deploy.swarm-secret-publisher.baoClientKeyFile = "/etc/pki/publisher-key.pem";
};
# The queue with a store identity for its responder, placed by hand: the
@ -671,16 +666,6 @@ let
&& lib.hasInfix "[ -s /var/lib/swarm-bao-pki/nats-auth.pem ] ||" mint
&& lib.hasInfix "/var/lib/swarm-bao-pki nats-auth " mint;
}
{
# Not a rename test. `hostClientSecretDir` is `readOnly`, so the fixture
# cannot define it; what can break is a reader left pointing at
# `swarm.authelia` instead of `deploy.authelia`. Several modules read this
# through an `autheliaCfg` alias, where a path-shaped grep does not see it.
name = "a consumer of authelia's host client-secret dir renders it from the deploy namespace";
ok =
autheliaAndNats.systemd.paths.swarm-secret-publish.pathConfig.PathChanged
== "/var/lib/nixos-containers/swarm-authelia/var/lib/authelia-swarm/oidc-clients";
}
{
# authelia exits at startup on `users: {}`, so an empty database, and a
# file holding exactly that document, must be replaced by the disabled
@ -1017,6 +1002,58 @@ let
&& lib.elem "-/run/swarm-victoriametrics-egress" h.systemd.services.nginx.serviceConfig.ReadWritePaths
&& lib.hasInfix "rm -f ${sock}" h.systemd.services.nginx.preStart;
}
{
# Authelia's forwarder reads its secret from the store under the
# publisher's identity, handed in for both units. The IdP host with no
# identity is the control: the forwarder is off there, and on the
# services host it is on, so the lookup is the right one.
name = "the authelia container forwards its journal to its own route under the publisher's identity";
ok =
let
h = servicesHost;
container = h.containers.swarm-authelia;
c = container.config;
s = c.services.opentelemetry-collector.settings;
otel = h.services.hyperhive.swarm.otel;
clientId = h.services.hyperhive.swarm.authelia.otel.clientId;
fetch = c.systemd.services.swarm-journal-forwarder-oidc;
dir = forwarderFetchDir;
registered = lib.findFirst (
x: x.id == clientId
) null h.services.hyperhive.swarm.authelia.oidc.clients;
reader = lib.head s.service.telemetry.metrics.readers;
taken = [
8888
8889
8890
h.services.hyperhive.swarm.nats.otel.telemetryPort
]
++ map (o: o.port) hostNetnsForwarders;
in
c.services.opentelemetry-collector.enable
&& s.exporters.otlphttp.endpoint == "https://${otel.domain}/swarm-authelia"
&& s.extensions."oauth2client/swarm".client_id == clientId
&& s.extensions."oauth2client/swarm".endpoint_params.audience == clientId
&& otel.serviceProducers.swarm-authelia.clientId == clientId
&&
(lib.findFirst (a: a.key == "service.name") null s.processors.resource.attributes).value
== "authelia"
&& fetch.environment.BAO_CLIENT_CERT == "${dir}/publisher-bao-cert"
&& fetch.environment.BAO_CLIENT_KEY == "${dir}/publisher-bao-key"
&& lib.elem "publisher-bao-cert" fetch.serviceConfig.LoadCredential
&& lib.elem "publisher-bao-key" fetch.serviceConfig.LoadCredential
&& lib.hasInfix "bao kv get -field=value secret/swarm/services/${clientId}/oidc/client" fetch.script
&& registered.kind == "machine"
&& registered.audience == [ clientId ]
&& !(lib.elem "--link-journal=host" container.extraFlags)
&& lib.any (lib.hasPrefix "--load-credential=publisher-bao-cert:") container.extraFlags
&& reader.pull.exporter.prometheus.host == "127.0.0.1"
&& !(lib.elem reader.pull.exporter.prometheus.port taken)
&& !autheliaColocated.containers.swarm-authelia.config.services.opentelemetry-collector.enable
&& !(
autheliaColocated.containers.swarm-authelia.config.systemd.services ? swarm-journal-forwarder-oidc
);
}
]
++ forwarderCases
++ hostNetnsForwarderCases;