read grafana's datasource key from the store into /run, seeding an empty store from the old file
This commit is contained in:
parent
fcc9c8d0ba
commit
204abb1d0e
1 changed files with 236 additions and 69 deletions
|
|
@ -369,8 +369,8 @@ let
|
|||
# Grafana unable to read its stored datasource secrets.
|
||||
secretKeyStorePath = "secret/swarm/services/${cfg.oidc.clientId}/secret-key";
|
||||
secretKeyPath = "${secretDir}/secret-key";
|
||||
# The earlier file as the host sees it. Never removed: it is the seed, and
|
||||
# the key Grafana runs on while the store's policy does not grant the leaf.
|
||||
# The earlier file as the host sees it: the seed for an empty store. It stays
|
||||
# on disk, unused once the store holds the key.
|
||||
legacySecretKeyPath = "/var/lib/nixos-containers/${cfg.machine}/var/lib/grafana-secret/secret_key";
|
||||
|
||||
# The built-in `admin` account's password. Grafana's API takes basic auth
|
||||
|
|
@ -411,6 +411,7 @@ let
|
|||
|
||||
atomicWriteSecret = import ./lib/atomic-write-secret.nix { };
|
||||
refreshConsumer = import ./lib/refresh-consumer.nix { };
|
||||
grafanaSecretKey = import ./lib/grafana-secret-key.nix { };
|
||||
|
||||
# First statement of each delivery script, ahead of the store login: the
|
||||
# directory is the container's bind source, so it has to exist even when the
|
||||
|
|
@ -1035,12 +1036,11 @@ in
|
|||
# first, and a new key only when there is no earlier file either. Same
|
||||
# shape and grant as the admin password above.
|
||||
#
|
||||
# The key never changes. An earlier release generated it in the container
|
||||
# on first boot and kept it, so a host that has run Grafana stores that
|
||||
# same key. While the store's policy does not grant the leaf yet (the
|
||||
# store's host is deployed after this one) or the store is unreachable,
|
||||
# the earlier file is what Grafana runs on, and the unit succeeds; a host
|
||||
# without one has no key to keep, so there the unit fails and retries.
|
||||
# The key never changes. An earlier release generated it in the container on
|
||||
# first boot, so a host that has run Grafana stores that same key
|
||||
# (`grafana_secret_key`, ./lib/grafana-secret-key.nix). The earlier file is
|
||||
# only a seed: with the store down or not granting the leaf the unit fails
|
||||
# and retries, and Grafana does not start without the key in /run.
|
||||
systemd.services.swarm-bao-grafana-secret-key = {
|
||||
description = "create Grafana's datasource encryption key in the swarm secret store if absent and fetch it";
|
||||
# Ordered after the store where it runs here:
|
||||
|
|
@ -1079,70 +1079,17 @@ in
|
|||
|
||||
${ensureSecretDir}
|
||||
|
||||
err="$(mktemp)"
|
||||
trap 'rm -f "$err"' EXIT
|
||||
${grafanaSecretKey}
|
||||
|
||||
# The earlier file's key, or nothing.
|
||||
earlier_key() {
|
||||
if [ -s ${lib.escapeShellArg legacySecretKeyPath} ]; then
|
||||
tr -d '\n' < ${lib.escapeShellArg legacySecretKeyPath}
|
||||
fi
|
||||
}
|
||||
grafana_secret_key ${lib.escapeShellArg secretKeyStorePath} ${lib.escapeShellArg legacySecretKeyPath} openssl rand -hex 32 || exit 1
|
||||
|
||||
# Owner and mode as for the OIDC secret above, for its reasons.
|
||||
# `$__file{}` is read as Grafana starts, so a changed file needs a
|
||||
# restart.
|
||||
land() {
|
||||
if secret_differs ${lib.escapeShellArg secretKeyPath} "$1"; then
|
||||
atomic_write_secret 0400 ${lib.escapeShellArg "${toString config.ids.uids.grafana}:0"} ${lib.escapeShellArg secretKeyPath} "$1"
|
||||
fi
|
||||
refresh_consumer ${lib.escapeShellArg cfg.machine} grafana.service ${lib.escapeShellArg secretKeyPath}
|
||||
}
|
||||
|
||||
# The store cannot answer: run on the earlier file's key, or fail.
|
||||
use_earlier_key() {
|
||||
local key
|
||||
key="$(earlier_key)"
|
||||
if [ -z "$key" ]; then
|
||||
echo "$1, and there is no earlier key at ${legacySecretKeyPath}; Grafana does not start without one." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$1; running Grafana on the key at ${legacySecretKeyPath}." >&2
|
||||
land "$key"
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ! BAO_TOKEN="$(bao login -method=cert -token-only 2>"$err")"; then
|
||||
cat "$err" >&2
|
||||
use_earlier_key "could not log in to swarm-bao with Grafana's certificate"
|
||||
if secret_differs ${lib.escapeShellArg secretKeyPath} "$GRAFANA_KEY"; then
|
||||
atomic_write_secret 0400 ${lib.escapeShellArg "${toString config.ids.uids.grafana}:0"} ${lib.escapeShellArg secretKeyPath} "$GRAFANA_KEY"
|
||||
fi
|
||||
export BAO_TOKEN
|
||||
|
||||
# Read, create on a miss, read again: the admin password's sequence
|
||||
# above, for its reasons, with the earlier key as the first candidate.
|
||||
if ! key="$(bao kv get -field=value ${lib.escapeShellArg secretKeyStorePath} 2>"$err")"; then
|
||||
candidate="$(earlier_key)"
|
||||
if [ -n "$candidate" ]; then
|
||||
echo "storing the key at ${legacySecretKeyPath} in ${secretKeyStorePath}."
|
||||
else
|
||||
candidate="$(openssl rand -hex 32)"
|
||||
echo "no earlier key; minting one in ${secretKeyStorePath}."
|
||||
fi
|
||||
if ! printf '%s' "$candidate" | bao kv put -cas=0 ${lib.escapeShellArg secretKeyStorePath} value=- >/dev/null 2>"$err"; then
|
||||
echo "could not create ${secretKeyStorePath}; reading it back in case another run created it." >&2
|
||||
cat "$err" >&2
|
||||
fi
|
||||
if ! key="$(bao kv get -field=value ${lib.escapeShellArg secretKeyStorePath} 2>"$err")"; then
|
||||
cat "$err" >&2
|
||||
use_earlier_key "swarm-bao did not return ${secretKeyStorePath}"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$key" ]; then
|
||||
echo "swarm-bao returned an empty ${secretKeyStorePath}; leaving the file as it is." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
land "$key"
|
||||
refresh_consumer ${lib.escapeShellArg cfg.machine} grafana.service ${lib.escapeShellArg secretKeyPath}
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue