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:
parent
6bf4792d61
commit
f6ca5f1433
10 changed files with 332 additions and 159 deletions
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Reference in a new issue