# Shared shell steps for a unit that keeps a secret it once generated on disk # in the swarm secret store instead. The store's value wins; a store holding # none is seeded from the old file, so the secret keeps its value; a new value # is minted only when there is no file either. # # `store_seeded` prints the value and never rotates one: the store is written # with `-cas=0`, so only a leaf that holds nothing takes a value, and a lost # race reads the winner's back. A read of the leaf that fails for any reason # but "No value found" prints the store's error and neither mints nor writes. # # `store_prefetch` reads many leaves concurrently, and the first `store_read` of # a leaf returns that answer, status and error included, instead of asking # again, so what a caller decides from a read is the same. Later reads of the # leaf, such as one that checks a write, ask the store. # # Pure function — NOT a NixOS module. Call it from a module's `let`: # # storeSeededSecret = import ./lib/store-seeded-secret.nix { }; # script = '' # ${storeSeededSecret} # store_init # store_login # key="$(store_seeded secret/swarm/services/foo/key /var/lib/foo/key mint_key)" || exit 1 # ''; # # Needs `bao`, `coreutils` and `gnugrep` on the caller's `path` and the `BAO_*` client # environment set. Takes the EXIT trap, to remove its scratch file. { }: '' # Bounded, so a store that drops packets cannot hold the caller past its # start timeout; one that refuses answers at once. store_bao() { timeout 15 bao "$@" } store_init() { STORE_ERR="$(mktemp)" STORE_UP=0 STORE_PREFETCH="$(mktemp -d)" trap 'rm -rf "$STORE_ERR" "$STORE_PREFETCH"' EXIT } # Sets STORE_UP. A store that cannot be logged in to leaves every secret to # its file. store_login() { local token if token="$(store_bao login -method=cert -token-only 2>"$STORE_ERR")"; then BAO_TOKEN="$token" export BAO_TOKEN STORE_UP=1 else cat "$STORE_ERR" >&2 echo "could not log in to the swarm secret store; secrets keep the value of their files." >&2 fi } # … Starts the first read of each leaf now, at most STORE_PREFETCH_JOBS # at a time, and returns when all have ended. Each read is bounded by # `store_bao`, and its answer is kept for `store_read`. Does nothing without # a login. store_prefetch() { local leaf key [ "$STORE_UP" = 1 ] || return 0 for leaf in "$@"; do while [ "$(jobs -rp | wc -l)" -ge "''${STORE_PREFETCH_JOBS:-8}" ]; do wait -n || true done key="$STORE_PREFETCH/$(printf '%s' "$leaf" | tr / _)" ( rc=0 store_bao kv get -field=value "$leaf" >"$key.out" 2>"$key.err" || rc=$? echo "$rc" >"$key.rc" ) & done wait } # . `store_bao kv get -field=value `, with the error in # $STORE_ERR: the answer `store_prefetch` kept, once, then the store's. store_read() { local key="$STORE_PREFETCH/$(printf '%s' "$1" | tr / _)" rc if [ -e "$key.rc" ]; then rc="$(< "$key.rc")" cat "$key.out" cat "$key.err" >"$STORE_ERR" rm -f "$key.out" "$key.err" "$key.rc" return "$rc" fi store_bao kv get -field=value "$1" 2>"$STORE_ERR" } # . The value on stdout; status 1 when # neither the store nor the file can give one. `kv get` exits 2 both for a # leaf that holds nothing ("No value found at ") and for a refused # read, so only that message means absent. Any other failure of the first # read, the `timeout` (124) included, neither mints nor writes: the file's # value is used when it has one, and the call fails when it has none. store_seeded() { local leaf="$1" earlier="$2" value candidate rc=0 shift 2 if [ "$STORE_UP" = 1 ]; then value="$(store_read "$leaf")" || rc=$? if [ "$rc" = 0 ] && [ -n "$value" ]; then printf '%s' "$value" return 0 fi if [ "$rc" != 0 ] && ! grep -q '^No value found at ' "$STORE_ERR"; then echo "could not read $leaf from the swarm secret store (exit status $rc):" >&2 cat "$STORE_ERR" >&2 if [ -s "$earlier" ]; then echo "using the value of $earlier for $leaf; nothing is written to the store." >&2 printf '%s' "$(< "$earlier")" return 0 fi echo "no value for $leaf in the store or at $earlier." >&2 return 1 fi if [ -s "$earlier" ]; then candidate="$(< "$earlier")" echo "storing the value of $earlier in $leaf." >&2 else candidate="$("$@")" echo "no earlier value; minting $leaf." >&2 fi if ! printf '%s' "$candidate" | store_bao kv put -cas=0 "$leaf" value=- >/dev/null 2>"$STORE_ERR"; then echo "could not create $leaf; reading it back in case another run created it." >&2 cat "$STORE_ERR" >&2 fi if value="$(store_bao kv get -field=value "$leaf" 2>"$STORE_ERR")" && [ -n "$value" ]; then printf '%s' "$value" return 0 fi cat "$STORE_ERR" >&2 echo "the store did not return $leaf." >&2 fi if [ -s "$earlier" ]; then echo "using the value of $earlier for $leaf." >&2 printf '%s' "$(< "$earlier")" return 0 fi echo "no value for $leaf in the store or at $earlier." >&2 return 1 } ''