# Shared shell steps for authelia's OIDC client secrets, whose plaintext half # lives in the swarm secret store and never on disk. authelia keeps only a # digest of each, in `$clients/.digest`, and starts from it whether or not # the store answers. # # `mint_client_secret` is the only writer of a new secret. It creates the # store's leaf (`-cas=0`, so an existing value is never replaced) before it # writes the digest, so a digest on disk always has a secret behind it. It mints # only after a read that says the store holds none; a store that cannot be read # never mints, because a second secret would rotate the first. # # `migrate_client_secret` moves the plaintext file an earlier release left in # `$clients/.secret` into the store and removes it once a read-back shows # the store holds the same value. It never overwrites a store value and never # removes a file the store does not match. # # Return statuses of both: 0 done, 1 retry later (the store could not answer), # 3 an operator has to look (logged at `err`; retrying changes nothing). # # Pure function — NOT a NixOS module. Call it after `./store-seeded-secret.nix` # and `./atomic-write-secret.nix`, with `store_init` and `store_login` run and # `$clients` set to the digest directory. Needs `authelia`, `bao`, `coreutils` # and `sed` on the caller's `path`. { }: '' # . Sets CLIENT_SECRET_VALUE. 0: the store holds a value. 1: it holds # none. 2: it cannot tell (not logged in, down, or refusing the read). client_secret_read() { CLIENT_SECRET_VALUE="" [ "$STORE_UP" = 1 ] || return 2 if CLIENT_SECRET_VALUE="$(store_bao kv get -field=value "$1" 2>"$STORE_ERR")" \ && [ -n "$CLIENT_SECRET_VALUE" ]; then return 0 fi CLIENT_SECRET_VALUE="" if grep -q '^No value found at ' "$STORE_ERR"; then return 1 fi cat "$STORE_ERR" >&2 return 2 } # . Sets MINTED when it created a secret. mint_client_secret() { local id="$1" leaf="$2" dig="$clients/$1.digest" legacy="$clients/$1.secret" local state=0 out secret digest if [ -e "$legacy" ]; then if [ -s "$dig" ]; then return 0 fi echo "<3>oidc client $id: $legacy exists but its digest does not; not minting a second secret. Restore the digest, or remove the file and the store's leaf $leaf to mint again." >&2 return 3 fi client_secret_read "$leaf" || state=$? if [ "$state" = 0 ]; then if [ ! -s "$dig" ]; then echo "<3>oidc client $id: the store holds a secret at $leaf but $dig is missing; not minting a second secret. Restore the digest, or remove the leaf to mint again." >&2 return 3 fi chmod 0600 "$dig" return 0 fi if [ "$state" = 2 ]; then if [ -s "$dig" ]; then chmod 0600 "$dig" echo "oidc client $id: the store cannot be read; keeping its digest" >&2 return 0 fi echo "oidc client $id: the store cannot be read and there is no digest; not minting" >&2 return 1 fi echo "oidc client $id: the store holds no secret at $leaf; minting one" out="$(authelia crypto hash generate pbkdf2 --variant sha512 --random)" secret="$(printf '%s' "$out" | sed -n 's/^Random Password: *//p')" digest="$(printf '%s' "$out" | sed -n 's/^Digest: *//p')" if [ -z "$secret" ] || [ -z "$digest" ]; then echo "<3>authelia crypto hash generate produced no secret/digest for $id" >&2 return 1 fi if ! printf '%s' "$secret" | store_bao kv put -cas=0 "$leaf" value=- >/dev/null 2>"$STORE_ERR"; then cat "$STORE_ERR" >&2 echo "could not create $leaf; reading it back in case another run created it" >&2 fi client_secret_read "$leaf" || return 1 if [ "$CLIENT_SECRET_VALUE" != "$secret" ]; then echo "<3>oidc client $id: $leaf holds a different value than the one minted; not writing a digest" >&2 return 3 fi atomic_write_secret 0600 "" "$dig" "$digest" || return 1 MINTED=1 echo "minted client secret for $id into the store" } # migrate_client_secret() { local id="$1" leaf="$2" legacy="$clients/$1.secret" value state=0 [ -e "$legacy" ] || return 0 value="$(< "$legacy")" if [ -z "$value" ]; then echo "<3>oidc client $id: $legacy is empty; keeping it" >&2 return 3 fi client_secret_read "$leaf" || state=$? if [ "$state" = 2 ]; then echo "oidc client $id: the store cannot be read; keeping $legacy" >&2 return 1 fi if [ "$state" = 0 ]; then if [ "$CLIENT_SECRET_VALUE" != "$value" ]; then echo "<3>oidc client $id: the store at $leaf and $legacy hold different secrets; keeping both" >&2 return 3 fi rm -f "$legacy" || return 1 echo "oidc client $id: already in the store, removed the file $legacy" return 0 fi if ! printf '%s' "$value" | store_bao kv put -cas=0 "$leaf" value=- >/dev/null 2>"$STORE_ERR"; then cat "$STORE_ERR" >&2 echo "oidc client $id: could not create $leaf; reading it back in case another run created it" >&2 fi client_secret_read "$leaf" || return 1 if [ "$CLIENT_SECRET_VALUE" != "$value" ]; then echo "<3>oidc client $id: the store at $leaf and $legacy hold different secrets; keeping both" >&2 return 3 fi rm -f "$legacy" || return 1 echo "oidc client $id: stored in the store, removed the file $legacy" } ''