The mint creates each client's secret in the swarm secret store and writes only the digest to disk. It mints only when the store answers and holds none. A new unit moves the plaintext files an earlier release left into the store and removes each once the store holds the same value. The bridge reads its secret from the store into /run before each start. The secret publisher unit and its path unit are gone.
321 lines
10 KiB
Shell
321 lines
10 KiB
Shell
# Cases for ./oidc-client-secrets.nix. Each case sets up the store's leaf, the
|
|
# digest and the plaintext file an earlier release left, and the stub's
|
|
# behaviour, runs the function under `set -e` as the units' scripts do, and
|
|
# compares its status, what the store holds, what is left on disk and whether
|
|
# `authelia` ran.
|
|
set -uo pipefail
|
|
|
|
failures=0
|
|
count=0
|
|
|
|
fail() {
|
|
echo "FAILED: $case: $*" >&2
|
|
failures=$((failures + 1))
|
|
}
|
|
|
|
id=swarm-grafana
|
|
leaf=secret/swarm/services/swarm-grafana/oidc/client
|
|
|
|
# setup <description>
|
|
setup() {
|
|
case=$1
|
|
count=$((count + 1))
|
|
dir=$(mktemp -d)
|
|
mkdir "$dir/kv" "$dir/clients"
|
|
export FAKE_BAO_KV=$dir/kv FAKE_BAO_LOG=$dir/bao.log FAKE_BAO_LOGIN_RC=0
|
|
export FAKE_AUTHELIA_LOG=$dir/authelia.log
|
|
unset FAKE_BAO_DENY_READ FAKE_BAO_DENY_WRITE FAKE_BAO_RACE FAKE_AUTHELIA_EMPTY
|
|
: >"$FAKE_BAO_LOG"
|
|
: >"$FAKE_AUTHELIA_LOG"
|
|
clients=$dir/clients
|
|
digest=$clients/$id.digest
|
|
legacy=$clients/$id.secret
|
|
stored=$FAKE_BAO_KV/$(printf '%s' "$leaf" | tr / _)
|
|
}
|
|
|
|
# run <function> — the function for $id and $leaf, with the stubs first on
|
|
# PATH, after the store login the units make first. Sets $rc and $minted.
|
|
run() {
|
|
minted=$(
|
|
set -e
|
|
PATH="$FAKE_BIN/bin:$PATH"
|
|
unset BAO_TOKEN
|
|
# shellcheck source=/dev/null
|
|
. "$HELPER"
|
|
clients=$dir/clients
|
|
store_init
|
|
store_login 2>>"$dir/err"
|
|
rc=0
|
|
"$1" "$id" "$leaf" 2>>"$dir/err" >>"$dir/out" || rc=$?
|
|
echo "${MINTED-}"
|
|
exit "$rc"
|
|
)
|
|
rc=$?
|
|
}
|
|
|
|
# expect <status>
|
|
expect() {
|
|
if [ "$rc" != "$1" ]; then fail "exited $rc, wanted $1: $(cat "$dir/err" "$dir/out" 2>/dev/null)"; fi
|
|
}
|
|
|
|
# held <value>, or held "" for a leaf the store does not have.
|
|
held() {
|
|
if [ -z "$1" ]; then
|
|
if [ -e "$stored" ]; then fail "the store holds '$(cat "$stored")', wanted nothing"; fi
|
|
elif [ "$(cat "$stored" 2>/dev/null)" != "$1" ]; then
|
|
fail "the store holds '$(cat "$stored" 2>/dev/null)', wanted '$1'"
|
|
fi
|
|
}
|
|
|
|
# digest_is <value>, or digest_is "" for no digest file.
|
|
digest_is() {
|
|
if [ -z "$1" ]; then
|
|
if [ -e "$digest" ]; then fail "a digest was written: '$(cat "$digest")'"; fi
|
|
elif [ "$(cat "$digest" 2>/dev/null)" != "$1" ]; then
|
|
fail "the digest is '$(cat "$digest" 2>/dev/null)', wanted '$1'"
|
|
fi
|
|
}
|
|
|
|
ran_authelia() {
|
|
[ -s "$FAKE_AUTHELIA_LOG" ]
|
|
}
|
|
|
|
store_called() {
|
|
grep -q '^kv' "$FAKE_BAO_LOG"
|
|
}
|
|
|
|
logged() {
|
|
grep -qF -- "$1" "$dir/err" "$dir/out" 2>/dev/null
|
|
}
|
|
|
|
no_secret_in_argv() {
|
|
if grep -q -e minted-secret -e old-secret "$FAKE_BAO_LOG" "$FAKE_AUTHELIA_LOG"; then
|
|
fail "a secret reached a command line"
|
|
fi
|
|
}
|
|
|
|
# --- mint_client_secret ---------------------------------------------------
|
|
|
|
setup "mint: a fresh client is minted into the store and only its digest is written"
|
|
run mint_client_secret
|
|
expect 0
|
|
held minted-secret
|
|
digest_is '$pbkdf2-sha512$fake'
|
|
if [ "$minted" != 1 ]; then fail "did not report the mint"; fi
|
|
if [ -e "$legacy" ]; then fail "wrote a plaintext file"; fi
|
|
if [ "$(stat -c %a "$digest")" != 600 ]; then fail "digest mode is $(stat -c %a "$digest")"; fi
|
|
if ! logged "minted client secret for $id into the store"; then fail "no mint line"; fi
|
|
no_secret_in_argv
|
|
|
|
setup "mint: a client the store and the disk both hold is left alone"
|
|
printf 'old-secret' >"$stored"
|
|
printf 'old-digest\n' >"$digest"
|
|
run mint_client_secret
|
|
expect 0
|
|
held old-secret
|
|
digest_is old-digest
|
|
if ran_authelia; then fail "minted although the store held a value"; fi
|
|
if grep -q '^kv put' "$FAKE_BAO_LOG"; then fail "wrote to a leaf that held a value"; fi
|
|
if [ "$minted" = 1 ]; then fail "reported a mint"; fi
|
|
|
|
setup "mint: a digest whose secret is gone from the store is minted again"
|
|
printf 'old-digest\n' >"$digest"
|
|
run mint_client_secret
|
|
expect 0
|
|
held minted-secret
|
|
digest_is '$pbkdf2-sha512$fake'
|
|
if [ "$minted" != 1 ]; then fail "did not report the mint"; fi
|
|
|
|
setup "mint: a store that refuses the login leaves the digest, and mints nothing"
|
|
printf 'old-digest\n' >"$digest"
|
|
export FAKE_BAO_LOGIN_RC=1
|
|
run mint_client_secret
|
|
expect 0
|
|
digest_is old-digest
|
|
if ran_authelia; then fail "minted with the store down"; fi
|
|
if store_called; then fail "called kv after a refused login"; fi
|
|
|
|
setup "mint: a store that refuses the read leaves the digest, and mints nothing"
|
|
printf 'old-digest\n' >"$digest"
|
|
export FAKE_BAO_DENY_READ=1
|
|
run mint_client_secret
|
|
expect 0
|
|
digest_is old-digest
|
|
if ran_authelia; then fail "minted although the read was refused"; fi
|
|
if grep -q '^kv put' "$FAKE_BAO_LOG"; then fail "wrote although the read was refused"; fi
|
|
|
|
setup "mint: no digest and a store that is down fails to retry, and mints nothing"
|
|
export FAKE_BAO_LOGIN_RC=1
|
|
run mint_client_secret
|
|
expect 1
|
|
digest_is ""
|
|
if ran_authelia; then fail "minted with the store down"; fi
|
|
|
|
setup "mint: no digest and a store that refuses the read fails to retry, and mints nothing"
|
|
export FAKE_BAO_DENY_READ=1
|
|
run mint_client_secret
|
|
expect 1
|
|
digest_is ""
|
|
if ran_authelia; then fail "minted although the read was refused"; fi
|
|
|
|
setup "mint: no digest but a secret in the store is for an operator, and nothing is minted"
|
|
printf 'old-secret' >"$stored"
|
|
run mint_client_secret
|
|
expect 3
|
|
held old-secret
|
|
digest_is ""
|
|
if ran_authelia; then fail "minted over the store's secret"; fi
|
|
if ! logged "<3>oidc client $id"; then fail "no err-priority line"; fi
|
|
|
|
setup "mint: a plaintext file with its digest is left for the migration"
|
|
printf 'old-secret\n' >"$legacy"
|
|
printf 'old-digest\n' >"$digest"
|
|
run mint_client_secret
|
|
expect 0
|
|
digest_is old-digest
|
|
if ran_authelia; then fail "minted although a file held the secret"; fi
|
|
if store_called; then fail "called the store"; fi
|
|
|
|
setup "mint: a plaintext file with no digest is for an operator, and nothing is minted"
|
|
printf 'old-secret\n' >"$legacy"
|
|
run mint_client_secret
|
|
expect 3
|
|
digest_is ""
|
|
held ""
|
|
if ran_authelia; then fail "minted over the file's secret"; fi
|
|
if ! logged "<3>oidc client $id"; then fail "no err-priority line"; fi
|
|
|
|
setup "mint: an authelia that prints nothing writes nothing"
|
|
export FAKE_AUTHELIA_EMPTY=1
|
|
run mint_client_secret
|
|
expect 1
|
|
held ""
|
|
digest_is ""
|
|
|
|
setup "mint: a store that refuses the write leaves no digest"
|
|
export FAKE_BAO_DENY_WRITE=1
|
|
run mint_client_secret
|
|
expect 1
|
|
held ""
|
|
digest_is ""
|
|
|
|
setup "mint: losing the race to another run writes no digest and keeps the winner's secret"
|
|
export FAKE_BAO_RACE=winner-secret
|
|
run mint_client_secret
|
|
expect 3
|
|
held winner-secret
|
|
digest_is ""
|
|
|
|
if [ "$(id -u)" != 0 ]; then
|
|
setup "mint: a digest that cannot be written is a failure, not a mint"
|
|
chmod 500 "$clients"
|
|
run mint_client_secret
|
|
chmod 700 "$clients"
|
|
expect 1
|
|
if [ "$minted" = 1 ]; then fail "reported a mint without a digest"; fi
|
|
digest_is ""
|
|
fi
|
|
|
|
# --- migrate_client_secret ------------------------------------------------
|
|
|
|
setup "migrate: a file the store lacks is stored, read back and removed"
|
|
printf 'old-secret\n' >"$legacy"
|
|
run migrate_client_secret
|
|
expect 0
|
|
held old-secret
|
|
if [ -e "$legacy" ]; then fail "left the file"; fi
|
|
if ! logged "stored in the store, removed the file $legacy"; then fail "no stored line"; fi
|
|
if ran_authelia; then fail "minted"; fi
|
|
no_secret_in_argv
|
|
|
|
setup "migrate: a file the store already holds is removed, and the store is not written"
|
|
printf 'old-secret\n' >"$legacy"
|
|
printf 'old-secret\n' >"$stored"
|
|
run migrate_client_secret
|
|
expect 0
|
|
held "old-secret"
|
|
if [ -e "$legacy" ]; then fail "left the file"; fi
|
|
if grep -q '^kv put' "$FAKE_BAO_LOG"; then fail "wrote to a leaf that held a value"; fi
|
|
if ! logged "already in the store, removed the file $legacy"; then fail "no removed line"; fi
|
|
|
|
setup "migrate: a store holding a different secret keeps both and reports an error"
|
|
printf 'old-secret\n' >"$legacy"
|
|
printf 'other-secret' >"$stored"
|
|
run migrate_client_secret
|
|
expect 3
|
|
held other-secret
|
|
if [ "$(cat "$legacy")" != old-secret ]; then fail "changed the file"; fi
|
|
if grep -q '^kv put' "$FAKE_BAO_LOG"; then fail "wrote to a leaf that held a value"; fi
|
|
if ! logged "<3>oidc client $id"; then fail "no err-priority line"; fi
|
|
|
|
setup "migrate: a store that refuses the login keeps the file and retries"
|
|
printf 'old-secret\n' >"$legacy"
|
|
export FAKE_BAO_LOGIN_RC=1
|
|
run migrate_client_secret
|
|
expect 1
|
|
if [ ! -e "$legacy" ]; then fail "removed the file"; fi
|
|
if store_called; then fail "called kv after a refused login"; fi
|
|
|
|
setup "migrate: a store that refuses the read keeps the file and retries"
|
|
printf 'old-secret\n' >"$legacy"
|
|
export FAKE_BAO_DENY_READ=1
|
|
run migrate_client_secret
|
|
expect 1
|
|
if [ ! -e "$legacy" ]; then fail "removed the file"; fi
|
|
if grep -q '^kv put' "$FAKE_BAO_LOG"; then fail "wrote although the read was refused"; fi
|
|
|
|
setup "migrate: a store that refuses the write keeps the file and retries"
|
|
printf 'old-secret\n' >"$legacy"
|
|
export FAKE_BAO_DENY_WRITE=1
|
|
run migrate_client_secret
|
|
expect 1
|
|
if [ ! -e "$legacy" ]; then fail "removed the file"; fi
|
|
held ""
|
|
|
|
setup "migrate: losing the race to a different secret keeps the file and reports an error"
|
|
printf 'old-secret\n' >"$legacy"
|
|
export FAKE_BAO_RACE=winner-secret
|
|
run migrate_client_secret
|
|
expect 3
|
|
held winner-secret
|
|
if [ ! -e "$legacy" ]; then fail "removed the file"; fi
|
|
|
|
setup "migrate: an empty file is kept and reported"
|
|
: >"$legacy"
|
|
run migrate_client_secret
|
|
expect 3
|
|
if [ ! -e "$legacy" ]; then fail "removed the file"; fi
|
|
held ""
|
|
|
|
setup "migrate: no file is nothing to do, store or no store"
|
|
export FAKE_BAO_LOGIN_RC=1
|
|
run migrate_client_secret
|
|
expect 0
|
|
if store_called; then fail "called the store"; fi
|
|
|
|
if [ "$(id -u)" != 0 ]; then
|
|
setup "migrate: a file that cannot be removed is a failure, not a move"
|
|
printf 'old-secret\n' >"$legacy"
|
|
chmod 500 "$clients"
|
|
run migrate_client_secret
|
|
chmod 700 "$clients"
|
|
expect 1
|
|
held old-secret
|
|
if ! [ -e "$legacy" ]; then fail "file vanished"; fi
|
|
if logged "removed the file"; then fail "logged a removal that did not happen"; fi
|
|
|
|
setup "migrate: a file the store already holds that cannot be removed is a failure"
|
|
printf 'old-secret\n' >"$legacy"
|
|
printf 'old-secret' >"$stored"
|
|
chmod 500 "$clients"
|
|
run migrate_client_secret
|
|
chmod 700 "$clients"
|
|
expect 1
|
|
if logged "removed the file"; then fail "logged a removal that did not happen"; fi
|
|
fi
|
|
|
|
if [ "$failures" != 0 ]; then
|
|
echo "$failures of $count cases failed" >&2
|
|
exit 1
|
|
fi
|
|
echo "$count cases passed"
|