swarm-controller reads its forge token from the store, not a host file
The controller's forge token never entered bao: the forge minted it in its container, hive-forge-swarm-controller-token copied it to a host file, and the controller LoadCredential'ed that file. forgeTokenFile defaulted to that path only where the forge ran, so a controller on any other host had no forge access unless an operator copied the file by hand. Now the forge's host publishes it and the controller reads it: - swarm-bao-forge-publisher (replaces hive-forge-swarm-controller-token) waits for the in-container token, logs in with its own forge-publisher leaf and writes it to swarm/controller/swarm-controller/forge/token. It runs on every forge container start, the old unit's trigger. - New principal swarm-forge-publisher: leaf minted by glue-bao-tls.nix, policy create/update on that one path, written by swarm-bao-forge-publisher-policy, CN reserved as a hive name. - The controller policy gains read on that path. swarm-controller reads it at start under its own certificate with the OIDC secret's retry, and runs without forge access if it still cannot. - forgeTokenFile, its LoadCredential and the forgeEnv gate are gone; SWARM_CONTROLLER_FORGE_URL is always set. hostSwarmControllerTokenFile is removed, and a tmpfiles rule deletes the old host copy on the forge host. The in-container file stays: the mint unit's only-once guard reads it, and it is the publisher's source. Refs #4998
This commit is contained in:
parent
c433bdef92
commit
121c4678aa
18 changed files with 580 additions and 277 deletions
|
|
@ -104,8 +104,7 @@ let
|
|||
# ./grafana.nix's `grafanaRefusedFor`.
|
||||
refusedOver = m: needle: lib.any (a: !a.assertion && lib.hasInfix needle a.message) m.assertions;
|
||||
|
||||
tokenFile = m: m.services.hyperhive.deploy.swarm-controller.forgeTokenFile;
|
||||
forgePath = "/var/lib/hyperhive-forge/swarm-controller.token";
|
||||
publisher = m: m.systemd.services.swarm-bao-forge-publisher;
|
||||
|
||||
forgeSettings = forgeHere.containers.hive-forge.config.services.forgejo.settings;
|
||||
|
||||
|
|
@ -122,7 +121,7 @@ let
|
|||
ok =
|
||||
!bare.services.hyperhive.deploy.forgejo.enable
|
||||
&& !(bare.containers ? hive-forge)
|
||||
&& !(bare.systemd.services ? hive-forge-swarm-controller-token)
|
||||
&& !(bare.systemd.services ? swarm-bao-forge-publisher)
|
||||
&& !(lib.elem "forge.t.local" bare.services.hyperhive.gateway.localNames)
|
||||
&& !(refusedOver bare "deploy.forgejo.sso.clientSecretFile");
|
||||
}
|
||||
|
|
@ -280,14 +279,39 @@ let
|
|||
&& refusedOver ciUnderForgeNoIdentity "services.hyperhive.deploy.bao.enable";
|
||||
}
|
||||
{
|
||||
# Nothing writes the delivery path away from the forge, and a
|
||||
# `LoadCredential=` naming a missing path is fatal to the unit.
|
||||
name = "the controller's forge token defaults to the delivery path only where the forge runs";
|
||||
# The forge's host publishes the controller's token under its own leaf,
|
||||
# which the store mints beside it here. `value=@` is what keeps the token
|
||||
# out of the process's arguments.
|
||||
name = "the forge's host publishes the controller's forge token to the store under its own leaf";
|
||||
ok =
|
||||
tokenFile bare == null
|
||||
&& tokenFile servicesForgeElsewhere == null
|
||||
&& tokenFile forgeHere == forgePath
|
||||
&& tokenFile allLocal == forgePath;
|
||||
let
|
||||
u = publisher allLocal;
|
||||
in
|
||||
u.environment.BAO_CLIENT_CERT == "/var/lib/swarm-bao-pki/forge-publisher.pem"
|
||||
&& u.environment.BAO_CLIENT_KEY == "/var/lib/swarm-bao-pki/forge-publisher-key.pem"
|
||||
&& lib.hasInfix "bao kv put secret/swarm/controller/swarm-controller/forge/token value=@\"$src\"" u.script
|
||||
&& lib.hasInfix "/var/lib/nixos-containers/hive-forge/var/lib/forgejo/swarm-controller-token" u.script
|
||||
&& lib.elem "container@hive-forge.service" u.wantedBy
|
||||
&& !(allLocal.systemd.services ? hive-forge-swarm-controller-token);
|
||||
}
|
||||
{
|
||||
# The absence arm: a forge host without the store has no leaf unless one
|
||||
# is placed, and the unit says which options to set.
|
||||
name = "a forge host with no publisher leaf renders a publisher that fails naming the options";
|
||||
ok =
|
||||
let
|
||||
s = (publisher forgeHere).script;
|
||||
in
|
||||
lib.hasInfix "forgePublisherClientCertFile" s
|
||||
&& lib.hasInfix "exit 1" s
|
||||
&& !(lib.hasInfix "bao kv put" s);
|
||||
}
|
||||
{
|
||||
# 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";
|
||||
ok =
|
||||
lib.elem "r /var/lib/hyperhive-forge/swarm-controller.token" forgeHere.systemd.tmpfiles.rules
|
||||
&& !(lib.elem "r /var/lib/hyperhive-forge/swarm-controller.token" bare.systemd.tmpfiles.rules);
|
||||
}
|
||||
{
|
||||
# The only thing that makes a human's forge account: nothing else
|
||||
|
|
|
|||
Loading…
Reference in a new issue