Watch
0
0
Fork
You've already forked hyperhive
0

report the granter login's real error and retry on a standby store

This commit is contained in:
atlas 2026-11-04 16:49:17 +01:00 • committed by mara
commit 1e2e93da59
3 changed files with 265 additions and 21 deletions

View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# The stub `bao` for ./granter-login.nix. `FAKE_BAO_LOGIN` picks the login's
# answer, in openbao's wording: `ok`, `standby` (500, a node that is not active
# yet), `no-role` (400, no trusted chain), `no-chain-match` (400, a chain that
# no role accepts), `sealed` (503). `FAKE_BAO_STATUS_RC` is `status`'s exit
# status. `FAKE_BAO_LOGIN_FIRST`, when set, answers the first login with it and
# later ones with `FAKE_BAO_LOGIN`; `FAKE_BAO_COUNT` is the file counting them.
echo "$*" >>"$FAKE_BAO_LOG"
case "$1" in
login)
n=$(($(cat "$FAKE_BAO_COUNT" 2>/dev/null || echo 0) + 1))
echo "$n" >"$FAKE_BAO_COUNT"
answer=$FAKE_BAO_LOGIN
if [ "$n" = 1 ] && [ -n "${FAKE_BAO_LOGIN_FIRST-}" ]; then answer=$FAKE_BAO_LOGIN_FIRST; fi
url='URL: PUT https://bao.t.local:8200/v1/auth/cert/login'
case "$answer" in
ok) printf 's.fake' ;;
standby)
printf 'Error authenticating: Error making API request.\n\n%s\nCode: 500. Errors:\n\n* local node not active but active cluster node not found\n' "$url" >&2
exit 2
;;
no-role)
printf 'Error authenticating: Error making API request.\n\n%s\nCode: 400. Errors:\n\n* invalid certificate or no client certificate supplied\n' "$url" >&2
exit 2
;;
no-chain-match)
printf 'Error authenticating: Error making API request.\n\n%s\nCode: 400. Errors:\n\n* no chain matching all constraints could be found for this login certificate\n' "$url" >&2
exit 2
;;
sealed)
printf 'Error authenticating: Error making API request.\n\n%s\nCode: 503. Errors:\n\n* Vault is sealed\n' "$url" >&2
exit 2
;;
esac
;;
status)
[ "${FAKE_BAO_STATUS_RC-0}" = 0 ] || {
echo "fake bao: status: Vault is sealed" >&2
exit 2
}
;;
*)
echo "fake bao: unexpected call: $*" >&2
exit 98
;;
esac

View file

@ -0,0 +1,43 @@
# `checks.script-test-granter-login` — runs ../host-modules/lib/granter-login.nix
# against a stub `bao` whose login and status answers are set per case. The
# cases are in ./granter-login.sh and the stub is ./granter-login-fake-bao.sh.
#
# What runs is the rendered login, with the same `baoStoreUp` text the unit
# passes, rendered once with the bootstrap-token hint and once without.
{
pkgs,
...
}:
let
render =
haveBootstrapToken:
pkgs.writeText "granter-login.sh" (
import ../host-modules/lib/granter-login.nix {
inherit (pkgs) lib;
granterCn = "bao-granter";
granterPolicyName = "bao-granter";
inherit haveBootstrapToken;
baoStoreUp = ''HOME=/var/empty bao status >/dev/null 2>>"$err"'';
setupSteps = [ "bao policy write bao-bootstrap /etc/bootstrap.hcl" ];
}
);
fakeBao = pkgs.runCommand "fake-bao" { } ''
install -Dm755 ${./granter-login-fake-bao.sh} $out/bin/bao
patchShebangs $out/bin
'';
in
pkgs.runCommand "hyperhive-script-test-granter-login"
{
LOGIN = render false;
LOGIN_WITH_TOKEN = render true;
FAKE_BAO_BIN = "${fakeBao}/bin";
nativeBuildInputs = [
pkgs.coreutils
pkgs.gnugrep
];
}
''
${pkgs.bash}/bin/bash ${./granter-login.sh}
touch "$out"
''

View file

@ -0,0 +1,103 @@
# Cases for ./granter-login.nix. Each case sets the stub's answers, runs the
# rendered login under `set -e` as a unit's script does, then compares its exit
# status, what it printed to stderr and the token it left exported to children.
set -uo pipefail
failures=0
count=0
fail() {
echo "FAILED: $case: $*" >&2
failures=$((failures + 1))
}
# setup <description>
setup() {
case=$1
count=$((count + 1))
dir=$(mktemp -d)
export FAKE_BAO_LOG=$dir/bao.log FAKE_BAO_COUNT=$dir/count FAKE_BAO_LOGIN=ok FAKE_BAO_STATUS_RC=0
unset FAKE_BAO_LOGIN_FIRST
: >"$FAKE_BAO_LOG"
login=$LOGIN
}
# run — the login, then the token it exported. Sets $rc, $got, and $dir/err.
run() {
got=$(
(
set -e
PATH="$FAKE_BAO_BIN:$PATH"
unset BAO_TOKEN
# shellcheck source=/dev/null
. "$login"
env | sed -n "s/^BAO_TOKEN=//p"
) 2>"$dir/err"
)
rc=$?
}
# expect <status> <token>
expect() {
if [ "$rc" != "$1" ]; then fail "exited $rc, wanted $1: $(cat "$dir/err")"; fi
if [ "$got" != "$2" ]; then fail "token '$got', wanted '$2'"; fi
}
err_has() {
if ! grep -qF -- "$1" "$dir/err"; then fail "stderr lacks '$1': $(cat "$dir/err")"; fi
}
err_lacks() {
if grep -qF -- "$1" "$dir/err"; then fail "stderr has '$1': $(cat "$dir/err")"; fi
}
setup "a successful login exports the token and prints nothing"
run
expect 0 s.fake
if [ -s "$dir/err" ]; then fail "printed: $(cat "$dir/err")"; fi
setup "a standby store fails the login with its own error, does not blame the role, and the next attempt succeeds"
export FAKE_BAO_LOGIN_FIRST=standby
run
expect 1 ""
err_has "local node not active but active cluster node not found"
err_has "login failed without a refused certificate"
err_lacks "role is not set up"
err_lacks "one-time step"
run
expect 0 s.fake
setup "an unknown role names the role and the setup steps, then bao's error"
export FAKE_BAO_LOGIN=no-role
run
expect 1 ""
err_has "refused the granter's certificate (CN bao-granter): the bao-granter role is not set up."
err_has "one-time step, as root on this host"
err_has " bao policy write bao-bootstrap /etc/bootstrap.hcl"
err_has "invalid certificate or no client certificate supplied"
err_lacks "swarm-bao-granter-role writes that role"
setup "a certificate no role accepts is a missing role too"
export FAKE_BAO_LOGIN=no-chain-match
run
expect 1 ""
err_has "the bao-granter role is not set up."
err_has "no chain matching all constraints"
setup "a unit with a bootstrap token also points at the unit that writes the role"
export FAKE_BAO_LOGIN=no-role
login=$LOGIN_WITH_TOKEN
run
expect 1 ""
err_has "swarm-bao-granter-role writes that role"
setup "a sealed store is reported as sealed or unreachable, with bao's error"
export FAKE_BAO_LOGIN=sealed FAKE_BAO_STATUS_RC=2
run
expect 1 ""
err_has "the store is sealed or unreachable; retrying."
err_has "Vault is sealed"
err_lacks "role is not set up"
echo "granter-login: $count cases, $failures failures"
[ "$failures" = 0 ]