The hive collector's `oauth2client` secret came from a `clientSecretFile` defaulting to authelia's own minted file on the IdP's host, with a `hive-otel-client-secret` oneshot that only waited up to 120s for that file to exist; any other hive had to copy the file across by hand. `swarm-bao-hive-otel-oidc` now logs in with the hive's leaf (`deploy.bao.clientCertFile`), reads `swarm/hives/<hive>/oidc/client` (written by the publisher, granted by the hive leaf's `swarm/hives/<hive>/*` read), writes it root 0400 when it differs, and restarts the collector when the file is newer than its last start. Retries every 30s for 24h (`lib/store-retry.nix`), like the matrix and forge fetches. `clientSecretFile` defaults to that file wherever the hive leaf is set and stays as an override; an overridden file renders no fetch. otelcol-contrib 0.151.0's oauth2client re-reads `client_secret_file` on every token fetch, but the path is `LoadCredential`'s start-time copy, so a changed secret still needs a restart. `lib/refresh-consumer.nix` gains `refresh_unit` for a host unit; `refresh_consumer` now calls it with `--machine`. nats-authelia's "consumer of the host client-secret dir" case pointed at the collector's old default; it now reads the publisher's path unit. Refs #5015
75 lines
3.3 KiB
Nix
75 lines
3.3 KiB
Nix
# Shared shell steps for a host oneshot that fetches a credential into a
|
|
# file a service loads at start (`LoadCredential`, or a config file expanded
|
|
# once while parsing). Such a service never sees a value that lands after it
|
|
# started — late or rotated — until it restarts.
|
|
#
|
|
# The file's mtime is the record of a change, so write the file only when
|
|
# `secret_differs` says so. The restart decision is then re-derived from
|
|
# the file on every run: a run that wrote the file but failed before the
|
|
# restart leaves a retry that still sees the file newer than the service.
|
|
#
|
|
# Pure function — NOT a NixOS module. Call it from a module's `let`:
|
|
#
|
|
# refreshConsumer = import ./lib/refresh-consumer.nix { };
|
|
# script = ''
|
|
# ${refreshConsumer}
|
|
# if secret_differs "$path" "$secret"; then
|
|
# atomic_write_secret 0400 root:root "$path" "$secret"
|
|
# fi
|
|
# refresh_consumer my-machine my.service "$path" # a unit in a container
|
|
# refresh_unit my.service "$path" # a host unit
|
|
# '';
|
|
#
|
|
# Requires `systemd` and `coreutils` on the caller's `path`.
|
|
{ }:
|
|
''
|
|
# True when $1 is missing or holds something other than $2. `$(< path)`
|
|
# is a bash builtin, so the value never becomes an argument in /proc;
|
|
# both sides lose their trailing newlines, which is how
|
|
# `atomic_write_secret` writes and `$(bao …)` reads.
|
|
secret_differs() {
|
|
[ ! -f "$1" ] || [ "$(< "$1")" != "$2" ]
|
|
}
|
|
|
|
# <unit> <path> [systemctl option…]. Nothing when <unit> last entered
|
|
# `active` after <path> was last written. Otherwise a running <unit> is
|
|
# restarted and a failed one — it may have hit its start limit without the
|
|
# credential — is reset and started; one stopped on purpose stays stopped.
|
|
# The options are passed to every `systemctl` call, which is how
|
|
# `refresh_consumer` reaches into a container. `--no-block` because a
|
|
# caller ordered `Before=` its consumer must not wait on a job that waits
|
|
# on the caller (see `swarm-services-cert`'s propagation in ../hive-tls.nix).
|
|
refresh_unit() {
|
|
local unit="$1" path="$2" started started_us=0 written_us
|
|
shift 2
|
|
# Empty for a unit that has never been active, which `date` would
|
|
# otherwise read as today's midnight.
|
|
started="$(systemctl "$@" show --timestamp=us+utc -p ActiveEnterTimestamp --value "$unit")"
|
|
if [ -n "$started" ]; then
|
|
started_us="$(date -u -d "$started" +%s%6N)"
|
|
fi
|
|
written_us="$(stat -c %.6Y "$path" | tr -d .)"
|
|
if [ "$written_us" -le "$started_us" ]; then
|
|
return 0
|
|
fi
|
|
if systemctl "$@" is-failed --quiet "$unit"; then
|
|
echo "$path changed after $unit last started, and $unit had failed — starting it"
|
|
systemctl "$@" reset-failed "$unit"
|
|
systemctl "$@" start --no-block "$unit"
|
|
else
|
|
echo "$path changed after $unit last started — restarting it if it runs"
|
|
systemctl "$@" try-restart --no-block "$unit"
|
|
fi
|
|
}
|
|
|
|
# <machine> <unit> <path>. `refresh_unit` inside `container@<machine>`, and
|
|
# nothing while that container is not active: a container that starts
|
|
# later loads the file as it starts.
|
|
refresh_consumer() {
|
|
local machine="$1" unit="$2" path="$3"
|
|
if ! systemctl is-active --quiet "container@$machine.service"; then
|
|
return 0
|
|
fi
|
|
refresh_unit "$unit" "$path" --machine="$machine"
|
|
}
|
|
''
|