host-modules: atomic_write_secret takes the value as an argument, not stdin
The pipe contract had a gap: if a producer piped into atomic_write_secret exited non-zero after writing partial output, cat still saw a clean EOF and wrote that partial content through to the live target via mv — pipefail only reported the failure afterward, once the bad write was already committed. The helper now takes the value as its 4th argument and writes it itself with printf (a shell builtin, so the value never touches an external process's own argv/environ, same as a function argument never does), so there is no pipe left to fail silently. Callers that compute the value with a command now capture it into a variable first (`value=$(cmd)`), which fails under `set -e` before atomic_write_secret is ever called — swarm-bao.nix's pin.env site is the one that needed this (`pin_env_value="BAO_HSM_PIN=$(cat ...)"`). All seven call sites converted; output is byte-identical (same printf '%s\n' framing, now applied inside the helper instead of by each caller). Refs #4723
This commit is contained in:
parent
7a9fadc21a
commit
770f68c272
6 changed files with 36 additions and 15 deletions
|
|
@ -241,7 +241,7 @@ in
|
|||
exit 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "$token" | atomic_write_secret 0600 "" ${lib.escapeShellArg (toString deployCfg.matrix.appserviceTokenFile)}
|
||||
atomic_write_secret 0600 "" ${lib.escapeShellArg (toString deployCfg.matrix.appserviceTokenFile)} "$token"
|
||||
|
||||
# Re-stamp the registration file from the token just written. The
|
||||
# token is half an agreement — the registration the homeserver loads
|
||||
|
|
|
|||
|
|
@ -283,11 +283,11 @@ in
|
|||
|
||||
install -d -m 0755 ${lib.escapeShellArg credentialDir}
|
||||
|
||||
printf '%s\n' "$secret" | atomic_write_secret 0600 "" ${lib.escapeShellArg secretFile}
|
||||
atomic_write_secret 0600 "" ${lib.escapeShellArg secretFile} "$secret"
|
||||
|
||||
# `0644` on purpose: an OIDC client id is presented to the token
|
||||
# endpoint on every connection and is public by construction.
|
||||
printf '%s\n' "$client_id" | atomic_write_secret 0644 "" ${lib.escapeShellArg clientIdFile}
|
||||
atomic_write_secret 0644 "" ${lib.escapeShellArg clientIdFile} "$client_id"
|
||||
'';
|
||||
};
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,24 +7,40 @@
|
|||
# the `chmod`/`chown`/`mv -f` sequence below ever makes the target mode and
|
||||
# owner visible, and only once the content is already final.
|
||||
#
|
||||
# The value is a function ARGUMENT, not piped in on stdin: a producer that
|
||||
# exits non-zero partway through a pipe still leaves `cat` a clean EOF, so
|
||||
# `atomic_write_secret`'s own writer has no way to tell a truncated read from
|
||||
# an intentionally short one — the partial content would still land at the
|
||||
# live path, `mv` and all, with only `pipefail` reporting the failure
|
||||
# afterwards. A caller that computes the value with a command captures it
|
||||
# into a variable first (`value=$(cmd)`, which fails under `set -e` before
|
||||
# this function is ever called) and passes the finished value in.
|
||||
#
|
||||
# `$4` is never handed to an external program — it's a shell function
|
||||
# argument, not a process's `argv`/environment, so it never appears in that
|
||||
# process's own `/proc/<pid>/cmdline`; the write inside uses `printf`, a
|
||||
# shell builtin, for the same reason.
|
||||
#
|
||||
# Pure function — NOT a NixOS module. Call it from a module's `let`:
|
||||
#
|
||||
# atomicWriteSecret = import ./lib/atomic-write-secret.nix { };
|
||||
# ...
|
||||
# script = ''
|
||||
# ${atomicWriteSecret}
|
||||
# printf '%s\n' "$secret" | atomic_write_secret 0600 "" "$path"
|
||||
# printf '%s\n' "$secret" | atomic_write_secret 0400 "grafana:0" "$path"
|
||||
# atomic_write_secret 0600 "" "$path" "$secret"
|
||||
# atomic_write_secret 0400 "grafana:0" "$path" "$secret"
|
||||
# '';
|
||||
#
|
||||
# Third argument to `atomic_write_secret` is the target path; the second is
|
||||
# an owner for `chown` (`user:group` or a bare uid), or "" to leave the
|
||||
# mktemp-created root:root ownership as it is. Reads its content from
|
||||
# stdin. Requires `coreutils` on the caller's `path`.
|
||||
# Args: mode, an owner for `chown` (`user:group` or a bare uid, or "" to
|
||||
# leave the mktemp-created root:root ownership as it is), the target path,
|
||||
# and the value. The value is written followed by a trailing newline (every
|
||||
# call site's prior `printf '%s\n' "$value"` behaviour) — a caller building
|
||||
# a multi-field value (e.g. `KEY=$value`) assembles the whole string first
|
||||
# and passes that. Requires `coreutils` on the caller's `path`.
|
||||
{ }:
|
||||
''
|
||||
atomic_write_secret() {
|
||||
local mode="$1" owner="$2" target="$3" tmp
|
||||
local mode="$1" owner="$2" target="$3" value="$4" tmp
|
||||
tmp="$(mktemp "$(dirname -- "$target")/.$(basename -- "$target").XXXXXX")"
|
||||
# The write happens in a subshell so its own EXIT trap cleans up `$tmp`
|
||||
# on failure (a `RETURN` trap does not fire when `set -e` aborts the
|
||||
|
|
@ -42,7 +58,7 @@
|
|||
exit "$rc"
|
||||
}
|
||||
trap _atomic_write_secret_cleanup EXIT
|
||||
cat > "$tmp"
|
||||
printf '%s\n' "$value" > "$tmp"
|
||||
chmod "$mode" "$tmp"
|
||||
if [ -n "$owner" ]; then
|
||||
chown "$owner" "$tmp"
|
||||
|
|
|
|||
|
|
@ -1841,7 +1841,7 @@ in
|
|||
# an argument in /proc the way `install <<<"$secret"` or an `echo`
|
||||
# from `path` would.
|
||||
install -d -m 0755 ${lib.escapeShellArg forwarderHostSecretDir}
|
||||
printf '%s\n' "$secret" | atomic_write_secret 0400 root:root ${lib.escapeShellArg forwarderHostSecretPath}
|
||||
atomic_write_secret 0400 root:root ${lib.escapeShellArg forwarderHostSecretPath} "$secret"
|
||||
'';
|
||||
};
|
||||
})
|
||||
|
|
@ -2647,7 +2647,12 @@ in
|
|||
chgrp ${tokenGroup} ${tokenStoreDir}/tpm2_pkcs11.sqlite3
|
||||
chmod 0660 ${tokenStoreDir}/tpm2_pkcs11.sqlite3
|
||||
|
||||
printf 'BAO_HSM_PIN=%s\n' "$(cat ${tokenStoreDir}/user-pin)" | atomic_write_secret 0400 "" ${pinEnvFile}
|
||||
# Captured first, not piped: a `$(cmd)` that fails aborts here
|
||||
# under `set -e`, before the helper — which cannot see a
|
||||
# producer's exit code once its output is inside the pipe —
|
||||
# is ever called.
|
||||
pin_env_value="BAO_HSM_PIN=$(cat ${tokenStoreDir}/user-pin)"
|
||||
atomic_write_secret 0400 "" ${pinEnvFile} "$pin_env_value"
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -693,7 +693,7 @@ in
|
|||
# grants the group nothing; if this mode ever widens, the gid has to be
|
||||
# discovered at runtime rather than assumed.
|
||||
install -d -m 0755 ${lib.escapeShellArg hostSecretDir}
|
||||
printf '%s\n' "$secret" | atomic_write_secret 0400 ${lib.escapeShellArg "${toString config.ids.uids.grafana}:0"} ${lib.escapeShellArg hostSecretPath}
|
||||
atomic_write_secret 0400 ${lib.escapeShellArg "${toString config.ids.uids.grafana}:0"} ${lib.escapeShellArg hostSecretPath} "$secret"
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -866,7 +866,7 @@ in
|
|||
# no uid to give this to — `LoadCredential` reads it as root before
|
||||
# the sandbox exists and re-exposes it to whichever uid the unit got.
|
||||
install -d -m 0755 ${lib.escapeShellArg collectorHostSecretDir}
|
||||
printf '%s\n' "$secret" | atomic_write_secret 0400 root:root ${lib.escapeShellArg collectorHostSecretPath}
|
||||
atomic_write_secret 0400 root:root ${lib.escapeShellArg collectorHostSecretPath} "$secret"
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue