Watch
0
0
Fork
You've already forked hyperhive
0

attic-init: replace the stored public key with the one the cache reports

a re-created cache signs with a new key; the fact was only created once, so
every hive then rejected the swarm's closures. the cache's grant gains update
on that one leaf, and a store that refuses it leaves the key as it was.

Closes #6181
This commit is contained in:
atlas 2026-11-07 07:32:28 +01:00 • committed by mara
commit b0f6ccaa64
3 changed files with 159 additions and 75 deletions

View file

@ -1,7 +1,8 @@
# The shell for the in-swarm cache's first start: what its two host units run
# after they log in to the secret store under the cache's own certificate.
# Each secret is created only while the store holds none, so a second run reads
# what the first one stored and mints nothing.
# what the first one stored and mints nothing. The cache's public key is the
# exception: the store is brought to the key the cache reports.
#
# Pure function — NOT a NixOS module. Call it from a module's `let`:
#
@ -21,9 +22,10 @@
jwtPath,
pullPath,
pushPath,
# The cache's public facts, created only while absent: the server's address
# (scheme included), the cache's name, and the key its narinfo are signed
# with. Hives pull with them.
# The cache's public facts: the server's address (scheme included), the
# cache's name, and the key its narinfo are signed with. Hives pull with
# them. The address and the name are created only while absent; the key is
# replaced whenever the cache reports another.
urlPath,
cachePath,
publicKeyPath,
@ -187,6 +189,31 @@ in
}
ensure_secret ${q urlPath} mint_url >/dev/null || exit 1
ensure_secret ${q cachePath} mint_cache >/dev/null || exit 1
ensure_secret ${q publicKeyPath} mint_public_key >/dev/null || exit 1
stored_key="$(ensure_secret ${q publicKeyPath} mint_public_key)" || exit 1
# A cache created again has a new signing key, so the stored key is
# replaced by the one the cache reports. Only the cache's own grant on this
# path allows an update; a store whose policy lacks it refuses, and the
# stored key stays.
live_key="$(mint_public_key)"
if [ -z "$live_key" ]; then
echo "the cache ${cache} reported no public key." >&2
exit 1
fi
if [ "$stored_key" != "$live_key" ]; then
if printf '%s' "$live_key" | bao kv put ${q publicKeyPath} value=- >/dev/null 2>"$err"; then
echo "replaced the stored public key with the one the cache ${cache} reports."
else
case "$(cat "$err")" in
*"permission denied"*)
echo "the store refuses to update ${publicKeyPath}; the stored key is not the cache's." >&2
;;
*)
cat "$err" >&2
exit 1
;;
esac
fi
fi
'';
}

View file

@ -7,8 +7,9 @@
# ./swarm-grafana.nix gives its admin password. One creates the key atticd signs
# tokens with, if the store holds none, and hands it to the container. The other
# creates the cache, the push and pull tokens, and the facts hives pull with
# (its address, name and public key), each only while absent
# (./lib/attic-init.nix). Neither needs a manual step.
# (its address and name, created only while absent, and its public key, which
# follows what the cache reports; ./lib/attic-init.nix). Neither needs a manual
# step.
#
# The container has a network namespace of its own on the hive bridge. atticd
# listens on that namespace's loopback only, because it cannot listen on a unix

View file

@ -1307,11 +1307,11 @@ let
}
];
# The in-swarm cache's six secrets, create and read and nothing more: its
# host units create each one only while it is absent, so none needs `update`,
# and a rewrite of a signing key or a token an operator holds is refused. The
# cache container's journal forwarder reads its client secret under the same
# leaf.
# The in-swarm cache's six secrets: create and read, so a rewrite of a signing
# key or a token an operator holds is refused. The public key also gets
# `update`: the cache's host unit replaces it when a re-created cache signs
# with another. The cache container's journal forwarder reads its client
# secret under the same leaf.
atticWriters = [
{
name = "swarm-attic";
@ -1328,8 +1328,13 @@ let
atticJwtLeaf
atticPushTokenLeaf
]
++ atticFactLeaves
++ lib.remove atticPublicKeyLeaf atticFactLeaves
)
+ ''
path "${credentialMountPath}/data/${atticPublicKeyLeaf}" {
capabilities = ["create", "read", "update"]
}
''
+ readStanza "${credentialMountPath}/data/swarm/services/${hyperhiveCfg.swarm.attic.otel.clientId}/oidc/client";
}
];
@ -2592,7 +2597,8 @@ in
Subject the store's `swarm-attic` cert-auth role accepts: the identity
the in-swarm cache's host units present. Its grant is create and read on
six paths under `swarm/services/attic/`, `jwt-secret`, `push-token`,
`pull-token`, `url`, `cache` and `public-key`, and nothing else.
`pull-token`, `url`, `cache` and `public-key`, plus `update` on
`public-key`, and nothing else.
⚠️ Reserved as a hive name by ./swarm.nix, like its siblings.
'';