Watch
0
0
Fork
You've already forked hyperhive
0

write secret and key files atomically with their final mode

Each of these wrote its file in place (or set the mode after the
write), so a write cut short by a kill or a full disk left a partial
file behind, and some secrets existed for a moment at umask mode:

- swarm-authelia/swarm-grafana/hive-forge/swarm-bao generators: a
  partial file passed the `-s`/`-e` guard and was kept forever. They now
  capture the value first and land it with `atomic_write_secret`.
- swarm-controller webhook secret: a truncated file failed the 64-hex
  check and the secret was regenerated, out of step with the forge
  hooks. It is now written 0600 to a sibling temp, fsynced, renamed.
- hive-c0re htpasswd: the temp is now fsynced and set 0644 before the
  rename, like the sibling agents.conf writer, instead of relying on
  c0re's umask.
- hivectl wg init: the private key is created 0400 in a temp and
  renamed, instead of written at umask mode and chmod'ed after.

Closes #5068
This commit is contained in:
atlas 2026-10-09 15:53:39 +02:00 • committed by mara
commit 01b8387ab8
7 changed files with 156 additions and 16 deletions

View file

@ -65,6 +65,7 @@ let
baoCfg = config.services.hyperhive.swarm.bao;
storeRetry = import ../lib/store-retry.nix { };
atomicWriteSecret = import ../lib/atomic-write-secret.nix { };
havePublisherIdentity =
deployCfg.bao.forgePublisherClientCertFile != null
&& deployCfg.bao.forgePublisherClientKeyFile != null;
@ -916,6 +917,8 @@ in
script = ''
set -euo pipefail
${atomicWriteSecret}
# Idempotent by query, same reasoning as the SSO unit: assert
# the effect, not the command's exit code. `--admin`: the
# controller creates repos in the agents org on the swarm's
@ -982,8 +985,7 @@ in
echo "no token-shaped word in forgejo's generate-access-token output" >&2
exit 1
fi
umask 0177
printf '%s' "$token" > "$token_path"
atomic_write_secret 0600 "" "$token_path" "$token"
echo "minted forge access token for ${swarmControllerForgeUser}"
fi
'';

View file

@ -91,6 +91,7 @@ let
unitName = lib.removeSuffix ".service" cfg.unit;
instance = lib.removePrefix "authelia-" unitName;
stateDir = "/var/lib/${unitName}";
atomicWriteSecret = import ./lib/atomic-write-secret.nix { };
caTrust = import ./lib/hive-ca-trust.nix {
inherit lib gatewayCfg;
@ -837,6 +838,8 @@ in
script = ''
set -euo pipefail
${atomicWriteSecret}
# Each is generated once and never rotated here: the
# session and storage keys are load-bearing for data
# already written (sessions, the encrypted store), so
@ -844,7 +847,8 @@ in
for f in ${lib.concatStringsSep " " randomKeys}; do
p=${lib.escapeShellArg stateDir}/"$f".key
if [ ! -s "$p" ]; then
head -c 64 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$p"
value=$(head -c 64 /dev/urandom | od -An -tx1 | tr -d ' \n')
atomic_write_secret 0600 "" "$p" "$value"
echo "generated $p"
fi
chmod 0600 "$p"

View file

@ -3526,8 +3526,8 @@ in
# expression, which would render it world-readable into the store.
for p in so-pin user-pin; do
if [ ! -e ${tokenStoreDir}/$p ]; then
( umask 077; openssl rand -hex 16 > ${tokenStoreDir}/$p )
chmod 0400 ${tokenStoreDir}/$p
pin=$(openssl rand -hex 16)
atomic_write_secret 0400 "" ${tokenStoreDir}/$p "$pin"
fi
done

View file

@ -735,10 +735,13 @@ in
];
script = ''
set -euo pipefail
${atomicWriteSecret}
key=${lib.escapeShellArg secretKeyPath}
if [ ! -s "$key" ]; then
umask 077
openssl rand -hex 32 > "$key"
value=$(openssl rand -hex 32)
atomic_write_secret 0600 "" "$key" "$value"
echo "generated Grafana's datasource encryption key"
fi
'';