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

261 lines
9.4 KiB
Shell

# Cases for ../host-modules/lib/attic-init.nix. Each case runs the key script or
# the cache script against stubs that record every call, over a store kept in
# files, and compares the record and what the script left behind.
set -uo pipefail
failures=0
count=0
case="setup"
fail() {
echo "FAILED: $case: $*" >&2
failures=$((failures + 1))
}
dir=$(mktemp -d)
mkdir -p "$dir/bin" "$dir/store" "$dir/secrets"
# The scripts name their files under @DIR@, which is this run's directory.
for s in ENSURE_JWT PROVISION; do
sed "s|@DIR@|$dir|g" "${!s}" >"$dir/$s.sh"
export "$s=$dir/$s.sh"
done
calls=$dir/calls
export DIR=$dir
# The store is a directory with one file per path. `get` exits 2 on a miss, as
# `bao kv get` does. `put -cas=0` refuses an existing path; without it a put
# overwrites. $RACE_PATH makes another run create that path just before our put
# lands.
cat >"$dir/bin/bao" <<STUB
#!$BASH
file() { printf '%s/store/%s' "\$DIR" "\$(printf '%s' "\$1" | tr / _)"; }
case "\$1 \$2" in
"login -method=cert")
echo "bao login" >>"$calls"
if [ "\${LOGIN_FAIL:-0}" = 1 ]; then echo denied >&2; exit 2; fi
echo store-token
;;
"kv get")
path=\${4}
f=\$(file "\$path")
echo "bao get \$path" >>"$calls"
[ -f "\$f" ] || exit 2
cat "\$f"
;;
"kv put")
if [ "\$3" = "-cas=0" ]; then cas=1; path=\$4; else cas=0; path=\$3; fi
f=\$(file "\$path")
echo "bao put cas=\$cas \$path" >>"$calls"
value=\$(cat)
if [ "\${RACE_PATH:-}" = "\$path" ] && [ ! -f "\$f" ]; then
printf '%s' raced >"\$f"
fi
if [ "\$cas" = 1 ] && [ -f "\$f" ]; then echo "check-and-set parameter did not match" >&2; exit 1; fi
printf '%s' "\$value" >"\$f"
;;
esac
STUB
# The key is a fixed word, so the file the script writes is predictable.
cat >"$dir/bin/openssl" <<STUB
#!$BASH
echo openssl >>"$calls"
echo FAKEPEM
STUB
# A token carries the subject and a serial, so a case can tell which one a call
# used. The key comes in the environment, as the real tool takes it.
cat >"$dir/bin/atticadm" <<STUB
#!$BASH
[ -n "\${ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64:-}" ] || { echo "no key in the environment" >&2; exit 1; }
echo "atticadm \$*" >>"$calls"
n=\$(grep -c '^atticadm' "$calls")
sub=\$2
[ "\$1" = "-f" ] && sub=\$5
echo "tok-\$sub-\$n"
STUB
# The cache exists once \`cache create\` ran. Each call records the token the
# client config names.
cat >"$dir/bin/attic" <<STUB
#!$BASH
tokenfile=\$(sed -n 's/^token-file = "\(.*\)"$/\1/p' "\$XDG_CONFIG_HOME/attic/config.toml")
token=\$(cat "\$tokenfile")
echo "attic \$* | token=\$token" >>"$calls"
case "\$1 \$2" in
"cache info")
[ -f "\$DIR/cache-exists" ] && echo " Public Key: grid:FAKEKEY" >&2
;;
"cache create") touch "\$DIR/cache-exists" ;;
*) exit 1 ;;
esac
STUB
chmod +x "$dir/bin/"*
cat >"$dir/bin/curl" <<STUB
#!$BASH
echo curl >>"$calls"
[ "\${CURL_FAIL:-0}" != 1 ]
STUB
printf '#!%s\nexit 0\n' "$BASH" >"$dir/bin/sleep"
# A container that is not running has nothing to refresh.
printf '#!%s\nexit 1\n' "$BASH" >"$dir/bin/systemctl"
chmod +x "$dir/bin/"*
export PATH="$dir/bin:$PATH"
# n <pattern>: how many recorded calls match.
n() { grep -c -- "$1" "$calls" || true; }
# run <script> [VAR=value…]: the script's exit status.
run() {
local script=$1
shift
env "$@" bash "$script" >"$dir/out" 2>"$dir/err"
}
reset() {
rm -rf "$dir/store" "$dir/secrets" "$dir/cache-exists" "$calls"
mkdir -p "$dir/store" "$dir/secrets"
: >"$calls"
}
stored() { cat "$dir/store/$1" 2>/dev/null || true; }
JWT=secret_swarm_services_attic_jwt-secret
PULL=secret_swarm_services_attic_pull-token
PUSH=secret_swarm_services_attic_push-token
URL=secret_swarm_services_attic_url
CACHE=secret_swarm_services_attic_cache
KEY=secret_swarm_services_attic_public-key
ENVFILE=$dir/secrets/server.env
expect() { # <description> <got> <want>
count=$((count + 1))
if [ "$2" != "$3" ]; then fail "$1: got '$2', wanted '$3'"; fi
}
# ── the key ──────────────────────────────────────────────────────────────
case="first start creates the key and hands it over"
reset
run "$ENSURE_JWT"
expect "exit status" $? 0
expect "key minted once" "$(n '^openssl')" 1
expect "stored key" "$(stored $JWT)" "$(printf 'FAKEPEM\n' | base64 -w0)"
expect "env file" "$(cat "$ENVFILE")" "ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=$(stored $JWT)"
expect "env file mode" "$(stat -c %a "$ENVFILE")" 400
case="a second start mints nothing and keeps the key"
first=$(stored $JWT)
run "$ENSURE_JWT"
expect "exit status" $? 0
expect "key minted still once" "$(n '^openssl')" 1
expect "no second create" "$(n '^bao put')" 1
expect "stored key unchanged" "$(stored $JWT)" "$first"
case="a stored key is used, not replaced, when the file is gone"
rm -f "$ENVFILE"
run "$ENSURE_JWT"
expect "exit status" $? 0
expect "key minted still once" "$(n '^openssl')" 1
expect "env file rewritten from the store" "$(cat "$ENVFILE")" "ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=$first"
case="a create lost to another run uses the winner's key"
reset
run "$ENSURE_JWT" RACE_PATH=secret/swarm/services/attic/jwt-secret
expect "exit status" $? 0
expect "stored key is the winner's" "$(stored $JWT)" raced
expect "env file has the winner's" "$(cat "$ENVFILE")" "ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=raced"
case="a refused login stops before anything is minted"
reset
run "$ENSURE_JWT" LOGIN_FAIL=1
expect "exit status" $? 1
expect "nothing minted" "$(n '^openssl')" 0
expect "nothing written" "$(ls "$dir/secrets" | wc -l)" 0
# ── the cache and the tokens ─────────────────────────────────────────────
case="first start creates the cache and both tokens"
reset
run "$ENSURE_JWT"
: >"$calls"
run "$PROVISION"
expect "exit status" $? 0
expect "pull token stored" "$(stored $PULL)" "tok-swarm-hives-pull-1"
expect "push token stored" "$(stored $PUSH)" "tok-swarm-builder-push-2"
expect "cache created once" "$(n '^attic cache create')" 1
expect "the address is stored" "$(stored $URL)" "http://cache.example"
expect "the cache name is stored" "$(stored $CACHE)" "grid"
expect "the cache's public key is stored" "$(stored $KEY)" "grid:FAKEKEY"
expect "cache created by the short-lived token" "$(n '^attic cache create local:grid | token=tok-swarm-attic-init-3')" 1
expect "tokens minted: pull, push, the creator" "$(n '^atticadm')" 3
expect "pull token reads only the cache" "$(n 'make-token --sub swarm-hives-pull --validity 10y --pull grid$')" 1
expect "push token pulls and pushes" "$(n 'make-token --sub swarm-builder-push --validity 10y --pull grid --push grid$')" 1
expect "the creator may create and pull, for minutes" "$(n 'make-token --sub swarm-attic-init --validity 5m --create-cache grid --pull grid$')" 1
case="a second start mints nothing and creates nothing"
pull=$(stored $PULL)
push=$(stored $PUSH)
: >"$calls"
run "$PROVISION"
expect "exit status" $? 0
expect "no token minted" "$(n '^atticadm')" 0
expect "no create call" "$(n '^bao put')" 0
expect "no cache create" "$(n '^attic cache create')" 0
expect "the pull token looked the cache up" "$(n "^attic cache info local:grid | token=$pull")" 1
expect "pull token unchanged" "$(stored $PULL)" "$pull"
expect "push token unchanged" "$(stored $PUSH)" "$push"
expect "address, name and key unchanged" "$(stored $URL)$(stored $CACHE)$(stored $KEY)" "http://cache.examplegridgrid:FAKEKEY"
case="values the operator stored over the minted ones are kept"
for leaf in $PULL $PUSH $URL $CACHE $KEY; do
printf '%s' "operator-$leaf" >"$dir/store/$leaf"
done
: >"$calls"
run "$PROVISION"
expect "exit status" $? 0
expect "no token minted" "$(n '^atticadm')" 0
expect "nothing written" "$(n '^bao put')" 0
expect "the operator's push token is still stored" "$(stored $PUSH)" "operator-$PUSH"
expect "the operator's pull token is still stored" "$(stored $PULL)" "operator-$PULL"
expect "the operator's address, name and key are still stored" \
"$(stored $URL)$(stored $CACHE)$(stored $KEY)" "operator-${URL}operator-${CACHE}operator-${KEY}"
expect "the cache lookup used the operator's pull token" "$(n "^attic cache info local:grid | token=operator-$PULL")" 1
printf '%s' "$pull" >"$dir/store/$PULL"
printf '%s' "$push" >"$dir/store/$PUSH"
printf '%s' http://cache.example >"$dir/store/$URL"
printf '%s' grid >"$dir/store/$CACHE"
printf '%s' grid:FAKEKEY >"$dir/store/$KEY"
case="an empty server gets its cache back with the stored tokens"
rm -f "$dir/cache-exists"
: >"$calls"
run "$PROVISION"
expect "exit status" $? 0
expect "only the creator is minted" "$(n '^atticadm')" 1
expect "no secret rewritten" "$(n '^bao put')" 0
expect "cache created" "$(n '^attic cache create')" 1
expect "pull token unchanged" "$(stored $PULL)" "$pull"
case="no tokens are minted while the server is down"
reset
run "$ENSURE_JWT"
: >"$calls"
run "$PROVISION" CURL_FAIL=1
expect "exit status" $? 1
expect "nothing minted" "$(n '^atticadm')" 0
expect "nothing stored" "$(n '^bao put')" 0
case="the client reads its token from a file, not from an argument"
reset
run "$ENSURE_JWT"
run "$PROVISION"
expect "no token among the client's arguments" "$(grep -c -- '^attic [^|]*tok-' "$calls" || true)" 0
# The control: the client did see tokens, through its config file.
expect "tokens seen through the config file" "$(grep -c -- '| token=tok-' "$calls" || true)" 4
if [ "$failures" -ne 0 ]; then
echo "$failures of $count checks failed" >&2
exit 1
fi
echo "$count checks passed"