diff --git a/docs/v1-redeploy-changelog.md b/docs/v1-redeploy-changelog.md index c964d63..c59e1d5 100644 --- a/docs/v1-redeploy-changelog.md +++ b/docs/v1-redeploy-changelog.md @@ -832,5 +832,28 @@ (.5.0-.7.254), which 4a lists as "want present". Harmless while the reservation persists across teardown, but a fresh-MAAS rebuild needs it -- phase-00-maas-carve.sh creates it idempotently. +### tests/phase-03-openrc renamed -> tests/phase-03-adminrc; DOCFIX-052 (2026-06-27) +The phase-03 admin-openrc harness did NOT commit with 63f5832: the .gitignore credential glob +`*-openrc` (intended for the admin-openrc secret file) also matched the directory NAME +tests/phase-03-openrc, so GitBucket/GitHub-Desktop never offered it. Per operator decision the +directory is RENAMED to tests/phase-03-adminrc -- sidesteps the glob with zero erosion of the +credential-ignore net (the negation alternative was declined to keep `*-openrc` at full breadth). +Harness re-run green under the new path (extractor 4/4 + integration 4/4 + 0600 assertion). +CONVENTION: do not name non-secret repo paths `*-openrc`; the glob will silently ignore them. +DOCFIX-052 -- .gitignore `*-openrc` matches directory names as well as the credential file; the + only colliding path was tests/phase-03-openrc (now renamed). Glob left as-is (correct for secrets). + +### Phase-06 entry-audit -- interactive-paste hazards found (no D/DOCFIX; block-authoring notes) +Running the read-only entry audit surfaced THREE block-authoring bugs (fixed in the re-issued +checks; recorded so they do not recur in scripted phase-06 blocks): + - `!` inside a double-quoted echo triggers bash history expansion on interactive paste + ("event not found"); broke the provider-ext line. AVOID `!` in pasted blocks (akin to the + DOCFIX-021 heredoc-stdin traps). + - `openstack show | jq ... || echo absent` is wrong for presence: jq on EMPTY stdin exits + 0, so the `||` never fires. Test existence by the show exit code (`show -c id >/dev/null 2>&1`) + BEFORE piping to jq. + - oslo.config writes `key = value` (spaces); a `^key=` grep gives false negatives. Match + `key[[:space:]]*=` (the magnum [trust] count of 0 was this false negative, not a real absence). + ### Next-free numbers -Design decision: D-057. Doc fix: DOCFIX-052. +Design decision: D-057. Doc fix: DOCFIX-053. diff --git a/tests/phase-03-adminrc/fakebin/juju b/tests/phase-03-adminrc/fakebin/juju new file mode 100644 index 0000000..af76c18 --- /dev/null +++ b/tests/phase-03-adminrc/fakebin/juju @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +sub="${1:-}" +case "$sub" in + whoami) echo "User: jessea123" ;; + run) + if printf '%s ' "$@" | grep -q 'get-root-ca'; then + printf '{"vault/0":{"output":"-----BEGIN CERTIFICATE-----\\nMIIBfakepem0123\\n-----END CERTIFICATE-----"}}\n' + elif printf '%s ' "$@" | grep -q 'get-admin-password'; then + if [ "${PASS_EMPTY:-0}" = 1 ]; then printf '{"keystone/0":{"output":{}}}\n' + else printf '{"keystone/0":{"admin-password":"%s"}}\n' "${ADMIN_PASS_FIX:-s3cr3t-pw}"; fi + fi ;; +esac diff --git a/tests/phase-03-adminrc/fakebin/openssl b/tests/phase-03-adminrc/fakebin/openssl new file mode 100644 index 0000000..8162661 --- /dev/null +++ b/tests/phase-03-adminrc/fakebin/openssl @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# fake openssl x509 -- emit a subject/dates banner +echo "subject=CN = Vault Root Certificate Authority (charm-pki-local)" +echo "notBefore=Jun 27 00:00:00 2026 GMT" +echo "notAfter=Jun 24 00:00:00 2036 GMT" diff --git a/tests/phase-03-adminrc/fakebin/openstack b/tests/phase-03-adminrc/fakebin/openstack new file mode 100644 index 0000000..5343de9 --- /dev/null +++ b/tests/phase-03-adminrc/fakebin/openstack @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +case "${1:-} ${2:-}" in + "token issue") [ "${OS_PROJECT_NAME:-}" = "${CORRECT_PROJECT:-admin}" ] && exit 0 || exit 1 ;; + "endpoint list") echo "keystone public https://10.12.4.50:5000/v3"; echo "keystone admin https://10.12.8.50:35357/v3" ;; +esac +exit 0 diff --git a/tests/phase-03-adminrc/run-tests.sh b/tests/phase-03-adminrc/run-tests.sh new file mode 100644 index 0000000..98bd6b7 --- /dev/null +++ b/tests/phase-03-adminrc/run-tests.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# tests/phase-03-openrc/run-tests.sh -- offline regression for phase-03-admin-openrc.sh +# + unit test of extract_admin_password.py. Fake juju/openstack/openssl; real python3/jq. +set -euo pipefail +IFS=$'\n\t' +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPTS="$(cd "$HERE/../../scripts" && pwd)" +TARGET="$SCRIPTS/phase-03-admin-openrc.sh" +EXTRACT="$SCRIPTS/extract_admin_password.py" +BIN="$HERE/fakebin" +command -v jq >/dev/null 2>&1 || { echo "FAIL: jq required" >&2; exit 1; } +[ -f "$TARGET" ] && [ -f "$EXTRACT" ] || { echo "FAIL: target/helper missing" >&2; exit 1; } +chmod +x "$BIN"/* 2>/dev/null || true +WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT +rc_all=0 + +echo "=== unit: extract_admin_password.py ===" +u() { local want="$1" json="$2" label="$3" got + got=$(printf '%s' "$json" | python3 "$EXTRACT") + if [ "$got" = "$want" ]; then printf ' [OK] %-34s -> %s\n' "$label" "${got:-}" + else printf ' [XX] %-34s -> %s (want %s)\n' "$label" "${got:-}" "$want"; rc_all=1; fi +} +u "pw1" '{"keystone/0":{"admin-password":"pw1"}}' "top-level admin-password" +u "pw2" '{"a":{"b":{"password":"pw2"}}}' "nested password" +u "pw3" '[{"x":1},{"Stdout":"pw3"}]' "list + Stdout" +u "" '{"keystone/0":{"output":{}}}' "no password -> empty" + +echo "=== integration: phase-03-admin-openrc.sh ===" +run() { + local want="$1" re="$2" label="$3"; shift 3 + local rc + rm -rf "$WORK/vault-init" "$WORK/admin-openrc" + set +e + PATH="$BIN:$PATH" HOME="$WORK" CA="$WORK/vault-init/ca.pem" RC="$WORK/admin-openrc" \ + env "$@" bash "$TARGET" >"$WORK/out" 2>&1 + rc=$?; set -e + if [ "$rc" -eq "$want" ] && grep -qE "$re" "$WORK/out"; then + printf ' [OK] %-38s exit %s\n' "$label" "$rc" + else + printf ' [XX] %-38s exit %s (want %s; /%s/)\n' "$label" "$rc" "$want" "$re" + sed 's/^/ /' "$WORK/out"; rc_all=1 + fi +} +run 0 'admin project = admin ' "happy: admin scopes" CORRECT_PROJECT=admin +run 0 'admin project = admin_domain' "fallback: admin_domain wins" CORRECT_PROJECT=admin_domain +run 1 'no candidate project scoped' "none scopes -> FATAL" CORRECT_PROJECT=none +run 1 'password extract failed' "password empty -> FATAL" CORRECT_PROJECT=admin PASS_EMPTY=1 + +echo "=== assert: written openrc is 0600 ===" +rm -rf "$WORK/vault-init" "$WORK/admin-openrc" +PATH="$BIN:$PATH" HOME="$WORK" CA="$WORK/vault-init/ca.pem" RC="$WORK/admin-openrc" \ + CORRECT_PROJECT=admin bash "$TARGET" >/dev/null 2>&1 || true +perm=$(stat -c '%a' "$WORK/admin-openrc" 2>/dev/null || echo "missing") +if [ "$perm" = "600" ]; then echo " [OK] admin-openrc mode 600"; else echo " [XX] admin-openrc mode=$perm (want 600)"; rc_all=1; fi + +echo +[ "$rc_all" -eq 0 ] && echo "ALL PASS" || echo "SOME FAILED" +exit "$rc_all"