store_seeded: report a failed read instead of minting over it
This commit is contained in:
parent
b0a6a28d10
commit
5f9629dfc7
3 changed files with 113 additions and 14 deletions
|
|
@ -5,7 +5,8 @@
|
|||
#
|
||||
# `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.
|
||||
# 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.
|
||||
#
|
||||
# Pure function — NOT a NixOS module. Call it from a module's `let`:
|
||||
#
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
# key="$(store_seeded secret/swarm/services/foo/key /var/lib/foo/key mint_key)" || exit 1
|
||||
# '';
|
||||
#
|
||||
# Needs `bao` and `coreutils` on the caller's `path` and the `BAO_*` client
|
||||
# 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.
|
||||
{ }:
|
||||
''
|
||||
|
|
@ -48,15 +49,31 @@
|
|||
}
|
||||
|
||||
# <leaf> <earlier file> <mint command…>. The value on stdout; status 1 when
|
||||
# neither the store nor the file can give one.
|
||||
# neither the store nor the file can give one. `kv get` exits 2 both for a
|
||||
# leaf that holds nothing ("No value found at <path>") 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
|
||||
local leaf="$1" earlier="$2" value candidate rc=0
|
||||
shift 2
|
||||
if [ "$STORE_UP" = 1 ]; then
|
||||
if value="$(store_bao kv get -field=value "$leaf" 2>"$STORE_ERR")" && [ -n "$value" ]; then
|
||||
value="$(store_bao kv get -field=value "$leaf" 2>"$STORE_ERR")" || 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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
let
|
||||
# `login` answers from `FAKE_BAO_LOGIN_RC`. `kv get` and `kv put -cas=0` act
|
||||
# on a file per leaf under `FAKE_BAO_KV`; `FAKE_BAO_DENY_READ` and
|
||||
# `FAKE_BAO_DENY_WRITE` answer 403 as a policy that grants neither does.
|
||||
# `FAKE_BAO_DENY_WRITE` answer 403 as a policy that grants neither does, and
|
||||
# `FAKE_BAO_READ_TIMEOUT` is `timeout` ending the read: status 124, silent.
|
||||
# `FAKE_BAO_READ_5XX` is a server error on the read. A leaf nobody wrote is
|
||||
# "No value found at <path>", status 2, as `bao kv get` answers it.
|
||||
# `FAKE_BAO_RACE` has another run create the leaf between this one's read and
|
||||
# its write. Every call is logged without the value `put` reads from stdin.
|
||||
fakeBao = pkgs.writeShellScriptBin "bao" ''
|
||||
|
|
@ -24,14 +27,16 @@ let
|
|||
"kv get")
|
||||
[ "''${BAO_TOKEN-}" = s.fake ] || { echo "fake bao: kv without the login's token" >&2; exit 97; }
|
||||
leaf="$FAKE_BAO_KV/$(printf '%s' "$4" | tr / _)"
|
||||
[ -z "''${FAKE_BAO_READ_TIMEOUT-}" ] || exit 124
|
||||
[ -z "''${FAKE_BAO_READ_5XX-}" ] || { echo "Error reading secret/data/x: Error making API request. Code: 503. Errors: * Vault is sealed" >&2; exit 2; }
|
||||
[ -z "''${FAKE_BAO_DENY_READ-}" ] || { echo "fake bao: permission denied" >&2; exit 2; }
|
||||
if [ -n "''${FAKE_BAO_RACE-}" ] && [ ! -e "$FAKE_BAO_KV/.raced" ]; then
|
||||
touch "$FAKE_BAO_KV/.raced"
|
||||
printf '%s' "$FAKE_BAO_RACE" > "$leaf"
|
||||
echo "fake bao: no value found" >&2
|
||||
echo "No value found at secret/data/x" >&2
|
||||
exit 2
|
||||
fi
|
||||
[ -e "$leaf" ] || { echo "fake bao: no value found" >&2; exit 2; }
|
||||
[ -e "$leaf" ] || { echo "No value found at secret/data/x" >&2; exit 2; }
|
||||
cat "$leaf"
|
||||
;;
|
||||
"kv put")
|
||||
|
|
@ -55,7 +60,10 @@ pkgs.runCommand "hyperhive-script-test-store-seeded-secret"
|
|||
import ../host-modules/lib/store-seeded-secret.nix { }
|
||||
);
|
||||
FAKE_BAO_BIN = "${fakeBao}/bin";
|
||||
nativeBuildInputs = [ pkgs.coreutils ];
|
||||
nativeBuildInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.gnugrep
|
||||
];
|
||||
}
|
||||
''
|
||||
${pkgs.bash}/bin/bash ${./store-seeded-secret.sh}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ setup() {
|
|||
dir=$(mktemp -d)
|
||||
mkdir "$dir/kv"
|
||||
export FAKE_BAO_KV=$dir/kv FAKE_BAO_LOG=$dir/bao.log FAKE_BAO_LOGIN_RC=0
|
||||
unset FAKE_BAO_DENY_READ FAKE_BAO_DENY_WRITE FAKE_BAO_RACE
|
||||
unset FAKE_BAO_DENY_READ FAKE_BAO_DENY_WRITE FAKE_BAO_RACE FAKE_BAO_READ_TIMEOUT FAKE_BAO_READ_5XX
|
||||
: >"$FAKE_BAO_LOG"
|
||||
earlier=$dir/earlier
|
||||
minted=$dir/minted
|
||||
|
|
@ -69,6 +69,19 @@ ran_mint() {
|
|||
[ -s "$minted" ]
|
||||
}
|
||||
|
||||
no_write() {
|
||||
if grep -q '^kv put' "$FAKE_BAO_LOG"; then fail "wrote to the store"; fi
|
||||
}
|
||||
|
||||
no_mint() {
|
||||
if ran_mint; then fail "ran the mint command"; fi
|
||||
}
|
||||
|
||||
# err_has <text>: the run's stderr contains it.
|
||||
err_has() {
|
||||
if ! grep -qF -- "$1" "$dir/err"; then fail "stderr lacks '$1': $(cat "$dir/err")"; fi
|
||||
}
|
||||
|
||||
setup "an empty store is seeded from the file, unchanged, and nothing is minted"
|
||||
printf 'old-value\n' >"$earlier"
|
||||
run
|
||||
|
|
@ -111,19 +124,80 @@ run
|
|||
expect 0 winner-value
|
||||
held winner-value
|
||||
|
||||
setup "a store that grants neither read nor write leaves the file's value in use"
|
||||
setup "a refused read uses the file's value, writes nothing, mints nothing and prints the refusal"
|
||||
printf 'old-value\n' >"$earlier"
|
||||
export FAKE_BAO_DENY_READ=1 FAKE_BAO_DENY_WRITE=1
|
||||
run
|
||||
expect 0 old-value
|
||||
held ""
|
||||
if ran_mint; then fail "minted although the file held a value"; fi
|
||||
no_write
|
||||
no_mint
|
||||
err_has "could not read $leaf from the swarm secret store"
|
||||
err_has "permission denied"
|
||||
err_has "using the value of $earlier"
|
||||
|
||||
setup "a store that grants neither, and no file, gives no value and mints nothing into it"
|
||||
setup "a refused read, and no file, fails without writing or minting and prints the refusal"
|
||||
export FAKE_BAO_DENY_READ=1 FAKE_BAO_DENY_WRITE=1
|
||||
run
|
||||
expect 1 ""
|
||||
held ""
|
||||
no_write
|
||||
no_mint
|
||||
err_has "could not read $leaf from the swarm secret store"
|
||||
err_has "permission denied"
|
||||
|
||||
setup "a read that times out uses the file's value without writing, and says why"
|
||||
printf 'old-value\n' >"$earlier"
|
||||
export FAKE_BAO_READ_TIMEOUT=1
|
||||
run
|
||||
expect 0 old-value
|
||||
held ""
|
||||
no_write
|
||||
no_mint
|
||||
err_has "could not read $leaf from the swarm secret store (exit status 124)"
|
||||
|
||||
setup "a read that times out, and no file, fails without writing or minting"
|
||||
export FAKE_BAO_READ_TIMEOUT=1
|
||||
run
|
||||
expect 1 ""
|
||||
held ""
|
||||
no_write
|
||||
no_mint
|
||||
err_has "(exit status 124)"
|
||||
|
||||
setup "a read that times out on a leaf the store holds leaves the leaf as it is"
|
||||
printf 'stored-value' >"$stored"
|
||||
printf 'old-value\n' >"$earlier"
|
||||
export FAKE_BAO_READ_TIMEOUT=1
|
||||
run
|
||||
expect 0 old-value
|
||||
held stored-value
|
||||
no_write
|
||||
no_mint
|
||||
|
||||
setup "a read that gets a server error, and no file, fails without writing and prints the error"
|
||||
export FAKE_BAO_READ_5XX=1
|
||||
run
|
||||
expect 1 ""
|
||||
held ""
|
||||
no_write
|
||||
no_mint
|
||||
err_has "Code: 503"
|
||||
|
||||
setup "a leaf the store does not hold is the only read failure that mints, and no error is printed"
|
||||
run
|
||||
expect 0 minted-value
|
||||
held minted-value
|
||||
if ! ran_mint; then fail "did not mint"; fi
|
||||
if grep -q 'could not read' "$dir/err"; then fail "reported a missing leaf as a failed read"; fi
|
||||
|
||||
setup "a store that takes no write leaves the file's value in use"
|
||||
printf 'old-value\n' >"$earlier"
|
||||
export FAKE_BAO_DENY_WRITE=1
|
||||
run
|
||||
expect 0 old-value
|
||||
held ""
|
||||
if ran_mint; then fail "minted although the file held a value"; fi
|
||||
|
||||
setup "a store that refuses the login leaves the file's value in use and is not called again"
|
||||
printf 'old-value\n' >"$earlier"
|
||||
|
|
|
|||
Loading…
Reference in a new issue