Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/script-tests/attic-root-facts.sh

249 lines
7.7 KiB
Shell

# Cases for ../host-modules/lib/attic-root-facts.nix. Each case runs the script
# against a stub `bao` over a store kept in files, and compares what the script
# left in its directory and which leaves it read.
set -uo pipefail
failures=0
count=0
case="setup"
fail() {
echo "FAILED: $case: $*" >&2
failures=$((failures + 1))
}
expect() { # <description> <got> <want>
count=$((count + 1))
if [ "$2" != "$3" ]; then fail "$1: got '$2', wanted '$3'"; fi
}
dir=$(mktemp -d)
mkdir -p "$dir/bin"
sed "s|@DIR@|$dir|g" "$SCRIPT" >"$dir/script.sh"
calls=$dir/calls
export DIR=$dir
# `login` answers from LOGIN_FAIL. `kv get -field=value <path>` prints the file
# kept for the path; a missing one is the store's "no value found" and DENY_READ
# is a policy that grants nothing. Every call is logged.
cat >"$dir/bin/bao" <<STUB
#!$BASH
echo "bao \$*" >>"$calls"
case "\$1 \$2" in
"login -method=cert")
if [ "\${LOGIN_FAIL:-0}" = 1 ]; then echo "certificate refused" >&2; exit 2; fi
echo store-token
;;
"kv get")
[ "\${BAO_TOKEN-}" = store-token ] || { echo "kv without the login's token" >&2; exit 97; }
f="\$DIR/store/\$(printf '%s' "\$4" | tr / _)"
if [ "\${DENY_READ:-0}" = 1 ]; then echo "permission denied" >&2; exit 2; fi
[ -f "\$f" ] || { echo "No value found at \$4" >&2; exit 2; }
cat "\$f"
;;
*) echo "unexpected call: \$*" >&2; exit 98 ;;
esac
STUB
chmod +x "$dir/bin/bao"
# `systemctl` answers for nix-daemon.service: DAEMON_STARTED is when it last
# became active (empty: never) and every restart request is logged.
cat >"$dir/bin/systemctl" <<STUB
#!$BASH
echo "systemctl \$*" >>"$dir/systemctl"
case "\$1" in
show) printf '%s\n' "\${DAEMON_STARTED-}" ;;
is-failed) exit 1 ;;
try-restart) ;;
*) echo "unexpected call: \$*" >&2; exit 98 ;;
esac
STUB
chmod +x "$dir/bin/systemctl"
export PATH="$dir/bin:$PATH"
KEYLEAF=secret_swarm_hives_alpha_attic_public-key
TOKENLEAF=secret_swarm_hives_alpha_attic_pull-token
reset() {
rm -rf "$dir/store" "$dir/attic" "$dir/nix" "$calls" "$dir/systemctl"
mkdir -p "$dir/store"
: >"$calls"
: >"$dir/systemctl"
}
seed() { # <key> <token>
printf '%s' "$1" >"$dir/store/$KEYLEAF"
printf '%s' "$2" >"$dir/store/$TOKENLEAF"
}
# run [VAR=value…]: the script's exit status.
run() {
env "$@" bash "$dir/script.sh" >"$dir/out" 2>"$dir/err"
}
restarts() { grep -c '^systemctl try-restart' "$dir/systemctl"; }
mode() { stat -c %a "$1" 2>/dev/null || echo missing; }
ino() { stat -c %i "$1" 2>/dev/null || echo missing; }
body() { cat "$1" 2>/dev/null || echo missing; }
case="first run writes the key and the netrc, each 0600, in a 0700 directory"
reset
seed "swarm:S0VZ" "tok-1"
run
expect "exit" $? 0
expect "key" "$(body "$dir/attic/public-key")" "swarm:S0VZ"
expect "netrc" "$(body "$dir/attic/netrc")" "machine cache.example password tok-1"
expect "key mode" "$(mode "$dir/attic/public-key")" 600
expect "netrc mode" "$(mode "$dir/attic/netrc")" 600
expect "dir mode" "$(mode "$dir/attic")" 700
expect "fragment" "$(body "$dir/nix/cache.conf")" "extra-trusted-substituters = https://cache.example/swarm
extra-trusted-public-keys = swarm:S0VZ
netrc-file = $dir/attic/netrc"
expect "fragment mode" "$(mode "$dir/nix/cache.conf")" 644
expect "fragment dir mode" "$(mode "$dir/nix")" 755
expect "no secret in the fragment" "$(grep -c tok-1 "$dir/nix/cache.conf")" 0
case="a second run with the same facts rewrites nothing"
k=$(ino "$dir/attic/public-key")
n=$(ino "$dir/attic/netrc")
c=$(ino "$dir/nix/cache.conf")
run
expect "exit" $? 0
expect "key untouched" "$(ino "$dir/attic/public-key")" "$k"
expect "netrc untouched" "$(ino "$dir/attic/netrc")" "$n"
expect "fragment untouched" "$(ino "$dir/nix/cache.conf")" "$c"
case="a rotated token replaces the netrc and leaves the key"
seed "swarm:S0VZ" "tok-2"
run
expect "exit" $? 0
expect "netrc" "$(body "$dir/attic/netrc")" "machine cache.example password tok-2"
expect "netrc mode" "$(mode "$dir/attic/netrc")" 600
expect "key untouched" "$(ino "$dir/attic/public-key")" "$k"
case="a rotated key replaces the key file"
seed "swarm:Tk9XCg" "tok-2"
run
expect "exit" $? 0
expect "key" "$(body "$dir/attic/public-key")" "swarm:Tk9XCg"
case="only the hive's own leaves are read, after a login"
expect "reads" "$(grep -c '^bao kv get' "$calls")" "$(grep -c '^bao kv get -field=value secret/swarm/hives/alpha/attic/' "$calls")"
expect "login first" "$(head -1 "$calls")" "bao login -method=cert -token-only"
expect "something was read" "$([ "$(grep -c '^bao kv get' "$calls")" -ge 2 ] && echo yes || echo no)" yes
case="a store with no facts exits cleanly and creates nothing"
reset
run
expect "exit" $? 0
expect "no key" "$(body "$dir/attic/public-key")" missing
expect "no netrc" "$(body "$dir/attic/netrc")" missing
case="one fact without the other creates nothing"
reset
printf '%s' "swarm:S0VZ" >"$dir/store/$KEYLEAF"
run
expect "exit" $? 0
expect "no key" "$(body "$dir/attic/public-key")" missing
expect "no netrc" "$(body "$dir/attic/netrc")" missing
# The failure cases start from a good state and an extra file in the directory.
good() {
reset
seed "swarm:S0VZ" "tok-1"
run
touch "$dir/attic/other"
}
intact() {
expect "key kept" "$(body "$dir/attic/public-key")" "swarm:S0VZ"
expect "netrc kept" "$(body "$dir/attic/netrc")" "machine cache.example password tok-1"
expect "other file kept" "$(body "$dir/attic/other")" ""
}
case="a store that refuses the login fails the run and removes nothing"
good
seed "swarm:NEW" "tok-new"
run LOGIN_FAIL=1
expect "exit" $? 1
intact
case="a store that refuses the read fails the run and removes nothing"
good
run DENY_READ=1
expect "exit" $? 1
intact
case="a key with whitespace is refused and nothing changes"
good
seed "swarm:S0 VZ" "tok-new"
run
expect "exit" $? 1
intact
case="a token with whitespace is refused and nothing changes"
good
seed "swarm:S0VZ" "tok new"
run
expect "exit" $? 1
intact
case="a key without a name is refused and nothing changes"
good
seed "S0VZ" "tok-new"
run
expect "exit" $? 1
intact
# A restart of nix-daemon is asked for only when the fragment was written after
# the daemon last started; `now` is when the daemon "started", read between runs.
now() { date -u '+%a %Y-%m-%d %H:%M:%S.%6N UTC'; }
case="a new fragment restarts a daemon that started before it"
reset
seed "swarm:S0VZ" "tok-1"
run DAEMON_STARTED="Sat 2000-01-01 00:00:00.000000 UTC"
expect "exit" $? 0
expect "restart asked" "$(grep -c '^systemctl try-restart --no-block nix-daemon.service' "$dir/systemctl")" 1
case="the same facts leave the fragment and the daemon alone"
started=$(now)
: >"$dir/systemctl"
run DAEMON_STARTED="$started"
expect "exit" $? 0
expect "no restart" "$(restarts)" 0
case="a rotated token changes the netrc but not the fragment, so no restart"
started=$(now)
seed "swarm:S0VZ" "tok-2"
run DAEMON_STARTED="$started"
expect "exit" $? 0
expect "netrc" "$(body "$dir/attic/netrc")" "machine cache.example password tok-2"
expect "no restart" "$(restarts)" 0
case="a rotated key rewrites the fragment and restarts the daemon"
started=$(now)
seed "swarm:Tk9XCg" "tok-2"
run DAEMON_STARTED="$started"
expect "exit" $? 0
expect "key in the fragment" "$(grep -c 'extra-trusted-public-keys = swarm:Tk9XCg' "$dir/nix/cache.conf")" 1
expect "restart asked" "$(restarts)" 1
case="a store with no facts writes no fragment and restarts nothing"
reset
run DAEMON_STARTED="Sat 2000-01-01 00:00:00.000000 UTC"
expect "exit" $? 0
expect "no fragment" "$(body "$dir/nix/cache.conf")" missing
expect "no restart" "$(restarts)" 0
case="a key that is refused writes no fragment"
reset
seed "swarm:S0 VZ" "tok-1"
run
expect "exit" $? 1
expect "no fragment" "$(body "$dir/nix/cache.conf")" missing
if [ "$failures" -ne 0 ]; then
echo "$failures of $count checks failed" >&2
exit 1
fi
echo "$count checks passed"