Watch
0
0
Fork
You've already forked hyperhive
0

forge: fetch the OIDC client secret from bao instead of a host-side copy

The forge's login source read its OIDC client secret from a file that
`hive-forge-oidc-secret` copied out of authelia's container tree. That
unit only existed where authelia ran on the forge's host (`ssoLocal`),
so a forge anywhere else needed the file placed by hand.

The publisher on authelia's host now carries the forge's client secret
to `secret/swarm/services/<forge client id>/oidc/client`, like every
other swarm service's. On the forge's host `swarm-bao-forge-oidc`, a copy
of `swarm-bao-matrix-oidc`, reads it under the forge publisher's leaf
(the forge host's one store identity, whose policy gains read on that
one path) and writes the file `forgejo-sso-source` reads, re-running the
login source when the value changes. It retries on the shared
store-retry shape while the store is down or the secret is not yet
published.

`hive-forge-oidc-secret` is removed, `clientSecretFile` defaults to the
in-container path wherever the forge runs, and the `ssoLocal` gate on
the login source's resolver ordering goes with it: the login source
fetches the issuer's discovery document on every forge host.

Refs #5015
This commit is contained in:
atlas 2026-10-09 23:30:17 +02:00 • committed by mara
commit f6ca5f1433
10 changed files with 332 additions and 159 deletions

View file

@ -40,10 +40,21 @@ let
deploy.forgejo.enable = false;
};
# The forge without authelia: the other half of the split.
# The forge without authelia: the other half of the split, with its secret's
# path and its store identity chosen by hand.
forgeNoAuthelia = hive {
deploy.forgejo.enable = true;
deploy.forgejo.sso.clientSecretFile = "/var/lib/forgejo-oidc/by-hand.secret";
deploy.bao.forgePublisherClientCertFile = "/etc/pki/forge-publisher.pem";
deploy.bao.forgePublisherClientKeyFile = "/etc/pki/forge-publisher-key.pem";
};
# The forge with neither authelia nor the store, its store identity placed by
# hand: the deployment the store fetch exists for.
forgeAlone = hive {
deploy.forgejo.enable = true;
deploy.bao.forgePublisherClientCertFile = "/etc/pki/forge-publisher.pem";
deploy.bao.forgePublisherClientKeyFile = "/etc/pki/forge-publisher-key.pem";
};
# The runner alone: no hive, no forge, no store, its store identity placed
@ -105,6 +116,7 @@ let
refusedOver = m: needle: lib.any (a: !a.assertion && lib.hasInfix needle a.message) m.assertions;
publisher = m: m.systemd.services.swarm-bao-forge-publisher;
oidcFetch = m: m.systemd.services.swarm-bao-forge-oidc;
forgeSettings = forgeHere.containers.hive-forge.config.services.forgejo.settings;
@ -329,6 +341,79 @@ let
&& lib.hasInfix "exit 1" s
&& !(lib.hasInfix "bao kv put" s);
}
{
# The forge's OIDC secret has one route, the store, under the forge
# publisher's leaf, landing where `clientSecretFile` points inside the
# container. No unit copies it out of authelia's tree, even on a host
# that runs authelia, and a forge host without authelia needs no path
# set by hand.
name = "the forge reads its OIDC secret from the store under the forge publisher's leaf";
ok =
let
u = oidcFetch forgeAlone;
d = forgeAlone.services.hyperhive.deploy;
in
!d.authelia.enable
&& !d.bao.enable
&& d.forgejo.sso.clientSecretFile == "/var/lib/forgejo-oidc/forgejo.secret"
&& !(refusedOver forgeAlone "deploy.forgejo.sso.clientSecretFile")
&& u.environment.BAO_CLIENT_CERT == "/etc/pki/forge-publisher.pem"
&& u.environment.BAO_CLIENT_KEY == "/etc/pki/forge-publisher-key.pem"
&& lib.hasInfix "bao kv get -field=value secret/swarm/services/forgejo/oidc/client" u.script
&& lib.hasInfix "/var/lib/nixos-containers/hive-forge/var/lib/forgejo-oidc/forgejo.secret" u.script
&& lib.hasInfix "refresh_consumer hive-forge forgejo-sso-source.service" u.script
# An unpublished or empty secret fails the unit, so `storeRetry` retries it.
&& !(lib.hasInfix "exit 0" u.script)
&& u.serviceConfig.Restart == "on-failure"
&& lib.elem "container@hive-forge.service" u.before
&& lib.elem "multi-user.target" u.wantedBy
&& (oidcFetch allLocal).environment.BAO_CLIENT_CERT == "/var/lib/swarm-bao-pki/forge-publisher.pem"
&& !(forgeAlone.systemd.services ? hive-forge-oidc-secret)
&& !(allLocal.systemd.services ? hive-forge-oidc-secret)
&& !(bare.systemd.services ? swarm-bao-forge-oidc);
}
{
# An operator-chosen path is where the fetched secret lands.
name = "the forge's OIDC secret lands where clientSecretFile points";
ok =
let
s = (oidcFetch forgeNoAuthelia).script;
in
lib.hasInfix "/var/lib/nixos-containers/hive-forge/var/lib/forgejo-oidc/by-hand.secret" s;
}
{
# Beside the store, the fetch waits for the unit writing the forge
# publisher's role, the leaf it logs in with.
name = "the forge's OIDC secret fetch is ordered after the forge publisher's policy unit";
ok =
let
u = oidcFetch allLocal;
in
lib.elem "swarm-bao-forge-publisher-policy.service" u.after
&& lib.elem "swarm-bao-forge-publisher-policy.service" u.wants;
}
{
# The absence arm: no publisher leaf, no secret, and the unit says which
# options to set.
name = "a forge host with no publisher leaf renders an OIDC secret fetch that fails naming the options";
ok =
let
u = oidcFetch forgeHere;
in
!(u.environment ? BAO_CLIENT_CERT)
&& lib.hasInfix "forgePublisherClientCertFile" u.script
&& lib.hasInfix "exit 1" u.script
&& !(lib.hasInfix "bao login" u.script);
}
{
# The login source fetches the issuer's discovery document by name
# wherever the forge runs, so it waits for the resolver on a forge host
# without authelia too.
name = "the login source waits for the container's resolver on a forge host without authelia";
ok = lib.elem "forgejo-sso-source.service" (
forgeAlone.containers.hive-forge.config.systemd.services.swarm-bridge-dns.before
);
}
{
# The host copy is removed where the forge runs: nothing reads it.
name = "the forge's host removes its old copy of the controller's forge token";