#!/usr/bin/env bash # Hermetic regression for mint-seat-credential.sh: mock curl on PATH, sandboxed # brain home, no tea, no network, no real credentials. # # Pins: # M1 the seat slot is written from the mint RESPONSE (token, granted scopes, # principal), each file mode 600, and the identity check passes. # M2 the admin token is read from MOSAIC_ADMIN_SEAT's slot, never hardcoded; # a missing admin token is reported per instance and exits nonzero. # M3 MOSAIC_GITEA_INSTANCES limits which instances are touched, and the URL # override MOSAIC_GITEA_URL_ is honoured. # M4 no admin seat configured is a usage error (rc=3), nothing written. # M5 the admin token value never appears on stdout or stderr. # M6 secrets never touch argv: no Authorization header, no -u user:pass, no # inline --data JSON carrying the password, on any curl invocation; auth # travels in --config files and bodies in --data @files (#1343 class, # rev-security-01 review 259 blocker). # M7 the scopes record discriminates: a requested-but-not-granted scope # (write:issue) must be ABSENT from .scopes — the pin is on the RESPONSE, # and a mutant writing the requested set fails here (both reviewers). # M8 hyphenated instance names resolve their override through the underscore # variable, matching seat-logins.sh (SF3). # M9 MOSAIC_SEAT_EMAIL_DOMAIN is required: unset is a usage error (rc=3), # nothing written (framework-PR firewall answer). set -euo pipefail WORK_ROOT="${AGENT_WORK_ROOT:-${TMPDIR:-/tmp}}" SANDBOX="$WORK_ROOT/mint-seat-credential-test-$$" MOCK_BIN="$SANDBOX/bin"; BRAIN="$SANDBOX/brain"; CALLS="$SANDBOX/calls.log" cleanup() { rm -rf "$SANDBOX"; } trap cleanup EXIT fail() { echo "FAIL: $*"; exit 1; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TARGET="$SCRIPT_DIR/mint-seat-credential.sh" [ -f "$TARGET" ] || fail "mint-seat-credential.sh not found beside this test" mkdir -p "$MOCK_BIN" "$BRAIN/fleet/agents/admin-seat/secrets" || fail "setup: sandbox" : > "$CALLS" ADMIN_TOKEN_VALUE="admin-token-value-sentinel-4491" printf '%s\n' "$ADMIN_TOKEN_VALUE" > "$BRAIN/fleet/agents/admin-seat/secrets/gitea-alpha-admin-seat.token" chmod 600 "$BRAIN/fleet/agents/admin-seat/secrets/gitea-alpha-admin-seat.token" # A PATH with only the mock bin plus the system tools the script needs, and no tea. SYS_BIN="$SANDBOX/sys"; mkdir -p "$SYS_BIN" for t in bash sed cat mktemp openssl tr head python3 sort printf chmod mkdir rm dirname grep stat; do p="$(command -v "$t" 2>/dev/null || true)"; [ -n "$p" ] && ln -s "$p" "$SYS_BIN/$t" done export PATH="$MOCK_BIN:$SYS_BIN" CALLS export MOSAIC_BRAIN_HOME="$BRAIN" export MOSAIC_GITEA_URL_ALPHA="https://alpha.example.test" export MOSAIC_SEAT_EMAIL_DOMAIN="seats.example.test" unset MOSAIC_ADMIN_SEAT MOSAIC_GITEA_INSTANCES # --- mock curl: records method + URL + a REDACTED auth marker, answers minting -- cat > "$MOCK_BIN/curl" <<'EOF' #!/bin/bash method=GET; url=""; out=""; wcode=0; auth=""; body="" while [ $# -gt 0 ]; do case "$1" in -X) method="$2"; shift 2 ;; -o) out="$2"; shift 2 ;; -w) wcode=1; shift 2 ;; --config) if grep -q 'Authorization: token' "$2" 2>/dev/null; then auth="${auth}token,"; fi if grep -q '^user = ' "$2" 2>/dev/null; then auth="${auth}user,"; fi shift 2 ;; --data) case "$2" in @*) body="@file" ;; *) body="inline" ;; esac shift 2 ;; -H|-u) shift 2 ;; http*) url="$1"; shift ;; *) shift ;; esac done printf '%s %s auth=%s body=%s\n' "$method" "$url" "${auth:-NONE}" "$body" >> "$CALLS" emit() { if [ -n "$out" ]; then printf '%s' "$1" > "$out"; else printf '%s' "$1"; fi; } case "$method $url" in "GET "*/api/v1/users/newseat) exit 22 ;; # 404 under -f: account does not exist yet "POST "*/api/v1/admin/users) emit '{}'; exit 0 ;; "POST "*/api/v1/users/newseat/tokens) emit '{"id":9,"name":"mosaic-seat","sha1":"minted-token-7f3a","scopes":["read:user","write:repository"]}' [ "$wcode" = 1 ] && printf '201'; exit 0 ;; "GET "*/api/v1/user) emit '{"login":"newseat"}'; exit 0 ;; *) emit '{}'; exit 0 ;; esac EOF chmod +x "$MOCK_BIN/curl" [ "$(command -v curl)" = "$MOCK_BIN/curl" ] || fail "setup: curl does not resolve to the mock" command -v tea >/dev/null 2>&1 && fail "setup: tea must be absent from the sandbox PATH" run() { bash "$TARGET" "$@" >"$SANDBOX/out" 2>"$SANDBOX/err"; echo $?; } # M4: no admin seat configured. rc=$(run newseat) [ "$rc" = 3 ] || fail "M4: expected rc=3 without an admin seat, got $rc: $(cat "$SANDBOX/err")" grep -q 'MOSAIC_ADMIN_SEAT' "$SANDBOX/err" || fail "M4: error does not name MOSAIC_ADMIN_SEAT" [ ! -e "$BRAIN/fleet/agents/newseat/secrets/gitea-alpha-newseat.token" ] || fail "M4: a token was written without an admin seat" # M9: email domain is required, unset is a usage error, nothing written. rc=$(MOSAIC_ADMIN_SEAT=admin-seat MOSAIC_GITEA_INSTANCES=alpha MOSAIC_SEAT_EMAIL_DOMAIN= run newseat) [ "$rc" = 3 ] || fail "M9: expected rc=3 with no email domain, got $rc: $(cat "$SANDBOX/err")" grep -q 'MOSAIC_SEAT_EMAIL_DOMAIN' "$SANDBOX/err" || fail "M9: error does not name MOSAIC_SEAT_EMAIL_DOMAIN" [ ! -s "$CALLS" ] || fail "M9: API called without an email domain" [ ! -e "$BRAIN/fleet/agents/newseat/secrets/gitea-alpha-newseat.token" ] || fail "M9: token written without an email domain" # M1 + M3 + M5: mint on the single configured instance. : > "$CALLS" rc=$(MOSAIC_ADMIN_SEAT=admin-seat MOSAIC_GITEA_INSTANCES=alpha run newseat) [ "$rc" = 0 ] || fail "M1: expected rc=0, got $rc: $(cat "$SANDBOX/err")" SLOT="$BRAIN/fleet/agents/newseat/secrets" [ "$(cat "$SLOT/gitea-alpha-newseat.token")" = "minted-token-7f3a" ] || fail "M1: token file not written from the mint response" grep -q 'write:repository' "$SLOT/gitea-alpha-newseat.scopes" || fail "M1: scopes file not written from the response" [ "$(cat "$SLOT/gitea-alpha-newseat.principal")" = "newseat" ] || fail "M1: principal file wrong" for suf in token scopes principal; do m=$(stat -c '%a' "$SLOT/gitea-alpha-newseat.$suf"); [ "$m" = 600 ] || fail "M1: $suf is mode $m, expected 600" done grep -q 'alpha: create, minted, GET /user -> newseat' "$SANDBOX/out" || fail "M1: success line missing: $(cat "$SANDBOX/out")" grep -q 'https://alpha.example.test/api/v1/admin/users' "$CALLS" || fail "M3: URL override not honoured: $(cat "$CALLS")" if grep -q 'usc\|mosaicstack' "$CALLS"; then fail "M3: an instance outside MOSAIC_GITEA_INSTANCES was touched: $(cat "$CALLS")"; fi grep -q 'tea not on PATH' "$SANDBOX/err" || fail "tea-absent path should warn, not fail: $(cat "$SANDBOX/err")" if grep -q "$ADMIN_TOKEN_VALUE" "$SANDBOX/out" "$SANDBOX/err" "$CALLS"; then fail "M5: admin token value leaked to output or call log"; fi # M7: scopes pin discriminates — requested-but-not-granted scope is ABSENT. if grep -q 'write:issue' "$SLOT/gitea-alpha-newseat.scopes"; then fail "M7: write:issue appears in .scopes — the record is the REQUESTED set, not the response" fi # M6: no secret ever travels argv — every call authenticates via --config # (token header or user= basic-auth directive) and bodies go as --data @file. while IFS= read -r line; do case "$line" in *auth=NONE*) fail "M6: unauthenticated call: $line" ;; *body=inline*) fail "M6: inline body (secret in argv risk): $line" ;; esac done < "$CALLS" [ "$(grep -c 'auth=token' "$CALLS")" -eq 3 ] || fail "M6: expected exactly 3 token-auth calls (exists-check, admin write, verify), got: $(cat "$CALLS")" grep -q 'auth=user' "$CALLS" || fail "M6: mint call did not use the user= directive: $(cat "$CALLS")" # M8: hyphenated instance name resolves its override via the underscore variable. printf '%s\n' "$ADMIN_TOKEN_VALUE" > "$BRAIN/fleet/agents/admin-seat/secrets/gitea-my-inst-admin-seat.token" chmod 600 "$BRAIN/fleet/agents/admin-seat/secrets/gitea-my-inst-admin-seat.token" export MOSAIC_GITEA_URL_MY_INST="https://myinst.example.test" : > "$CALLS"; rm -rf "$BRAIN/fleet/agents/newseat" rc=$(MOSAIC_ADMIN_SEAT=admin-seat MOSAIC_GITEA_INSTANCES=my-inst run newseat) [ "$rc" = 0 ] || fail "M8: hyphenated instance mint failed rc=$rc: $(cat "$SANDBOX/err")" grep -q 'https://myinst.example.test/api/v1/admin/users' "$CALLS" || fail "M8: hyphen override (MY_INST) not honoured: $(cat "$CALLS")" unset MOSAIC_GITEA_URL_MY_INST # M2: admin token missing for the instance is reported, rc=1, nothing written. rm -rf "$BRAIN/fleet/agents/newseat" : > "$CALLS" rc=$(MOSAIC_ADMIN_SEAT=other-admin MOSAIC_GITEA_INSTANCES=alpha run newseat) [ "$rc" = 1 ] || fail "M2: expected rc=1 with no admin token, got $rc" grep -q "no admin token for seat 'other-admin'" "$SANDBOX/err" || fail "M2: missing-admin-token not reported: $(cat "$SANDBOX/err")" [ ! -s "$CALLS" ] || fail "M2: API was called without an admin token: $(cat "$CALLS")" [ ! -e "$BRAIN/fleet/agents/newseat/secrets/gitea-alpha-newseat.token" ] || fail "M2: token written without an admin token" echo "mint-seat-credential regression harness passed"