Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/host-modules/glue-bao-readers-policy-order.nix
atlas 141be16c6d hive-forge: the controller-token mint writes straight to the store, and the on-disk token goes
`forgejo-swarm-controller-account`, in the forge's container, now puts
the swarm-controller's forge token into the swarm secret store itself,
under the forge publisher's leaf. The container already receives that
leaf as nspawn system credentials for its journal forwarder; the mint
loads the same three. The token reaches bao on stdin (`value=-`) and
the forge's API through a header file, never as an argument, and never
touches disk.

The mint's guard is a marker holding the published token's name, at
/var/lib/forgejo/swarm-controller-token-name, written last: after the
put, and after every other `swarm-controller-boot-*` token of the
account is revoked through forgejo's admin API, authenticated as the
new token. So a put that failed, or a marker lost while the store kept
an older token, leaves no live token behind once a mint completes. The
mint logs in to the store and checks that the account is a site admin
before it mints, so a store that is down or an account the admin API
would refuse mints nothing on each retry. Failures retry under
store-retry; forgejo does not wait for the mint. No bao read and no
grant change: the forge publisher keeps create/update on the token.

Removed: the host's `swarm-bao-forge-publisher` service and path unit,
which read the token file through the container's root, and their
entries in the store and policy ordering glue. The forge publisher's
policy unit stays; `swarm-bao-forge-oidc` still uses the leaf.

On deploy, an existing forge host has no marker, so the mint runs once:
it mints a new token, publishes it, revokes the old one, and deletes
the old /var/lib/forgejo/swarm-controller-token. The controller's next
forge call gets a 401, re-reads the store and carries on.
2026-10-16 17:48:56 +02:00

108 lines
4.2 KiB
Nix

# Glue: where the store and one of its readers share a host, the reader waits
# for the unit that writes the cert-auth role it logs in with, and for any
# other policy unit whose rules it relies on.
#
# ONE PAIRING PER FILE — the store's policy units ← the readers that rely
# on them, and nothing else. Deleting this leaves every reader as it is on a
# host whose store is remote: it may log in before its role exists, and its own
# retries are what carry it past that.
#
# ⚠️ Gated on BOTH the store and that reader being here. Off the store's host
# there is no local policy unit to order against. On the store's host without
# the reader, setting `systemd.services.<reader>.after` would define a unit
# with no ExecStart, so each gate below restates the one the reader's own
# module puts on it. A reader whose gate changes must change here too.
#
# Ordering, never a requirement: a policy unit that failed still counts as
# done, and the reader's own retries carry it past that. `wants` as well as
# `after`, so a reader started on its own pulls its policy unit into the same
# transaction.
{
lib,
config,
...
}:
let
hyperhiveCfg = config.services.hyperhive;
deployCfg = hyperhiveCfg.deploy;
baoDeploy = deployCfg.bao;
havePair = cert: key: cert != null && key != null;
# Reader unit → the gate its own module defines it under.
readersHere = {
# ./glue-queue-agent-credential.nix
swarm-bao-queue-agent =
deployCfg.hive-controller.enable
&& havePair baoDeploy.queueAgentClientCertFile baoDeploy.queueAgentClientKeyFile;
# ./swarm-grafana.nix
swarm-bao-grafana-oidc = deployCfg.grafana.enable;
# ./swarm-otel.nix
swarm-bao-otel-oidc =
deployCfg.swarm-otel.enable
&& havePair baoDeploy.otelOidcClientCertFile baoDeploy.otelOidcClientKeyFile;
# ./swarm-bao.nix: its block is gated on the store, which `orderAfterPolicies`
# already checks.
swarm-bao-forwarder-oidc = true;
# ./swarm-nats.nix: the queue's TLS leaf, not a secret, but the same wait.
swarm-bao-nats-tls = deployCfg.nats.enable;
# ./swarm-ci-runner.nix
swarm-bao-ci-runner = deployCfg.ci-runner.enable;
};
# Readers that log in under another unit's principal: reader unit → the
# gate its own module defines it under, and the policy units it waits for.
readersOfOtherPolicy = {
# ./swarm-nats.nix, under the queue responder's leaf. Also after the
# publisher's policy, which denies the publisher the callout seeds' paths:
# until it is written the publisher may create them, and this unit keeps
# a seed it finds there.
swarm-bao-nats-callout-xkey = {
here = deployCfg.nats.enable;
policies = [
"swarm-bao-nats-auth-policy"
"swarm-bao-secret-publisher-policy"
];
};
# ./swarm-grafana.nix, under Grafana's leaf, which may create the admin
# password, the metrics bearer and the controller's Grafana token only
# once that policy is written.
swarm-bao-grafana-admin = {
here = deployCfg.grafana.enable;
policies = [ "swarm-bao-grafana-oidc-policy" ];
};
swarm-bao-grafana-metrics-bearer = {
here = deployCfg.grafana.enable;
policies = [ "swarm-bao-grafana-oidc-policy" ];
};
swarm-bao-grafana-controller-token = {
here = deployCfg.grafana.enable;
policies = [ "swarm-bao-grafana-oidc-policy" ];
};
# ./hive-matrix.nix, under matrix-ctl's leaf.
swarm-bao-matrix-oidc = {
here = deployCfg.matrix.enable;
policies = [ "swarm-bao-matrix-ctl-policy" ];
};
# ./hive-forge/default.nix, under the forge publisher's leaf.
swarm-bao-forge-oidc = {
here = deployCfg.forgejo.enable;
policies = [ "swarm-bao-forge-publisher-policy" ];
};
};
orderAfterPolicies =
reader: here: policies:
lib.mkIf (baoDeploy.enable && here) {
systemd.services.${reader} = {
after = map (p: "${p}.service") policies;
wants = map (p: "${p}.service") policies;
};
};
in
{
config = lib.mkMerge (
lib.mapAttrsToList (reader: here: orderAfterPolicies reader here [ "${reader}-policy" ]) readersHere
++ lib.mapAttrsToList (reader: r: orderAfterPolicies reader r.here r.policies) readersOfOtherPolicy
);
}