Skip to content

Manual Test Runbook — G0: Client-side scoped Reader SP bootstrap

Owner: Sagar  |  Time: ~20 min  |  Sandbox: SnowOps sandbox subscription (acts as the "client" tenant for this test)

Purpose

Validate that apps/discovery-auditor/bootstrap/bootstrap.sh creates a service principal with only Reader + Security Reader on the target subscription, federates it to a SnowOps GitHub Actions workflow, and is fully revocable via teardown.sh.

Prerequisites

  • Sandbox subscription access via PIM activated as Owner + Privileged Role Administrator
  • Local tooling: az v2.50+, bash, shellcheck (optional)
  • az login --tenant <SANDBOX_TENANT_ID>; az account show returns the sandbox sub
  • You know the SnowOps repo slug being tested (e.g. snowops-automation/snowops-automation)

Steps

1. Lint

shellcheck apps/discovery-auditor/bootstrap/bootstrap.sh \
           apps/discovery-auditor/bootstrap/teardown.sh
  • No warnings

2. Run bootstrap

cd apps/discovery-auditor/bootstrap
./bootstrap.sh \
  --subscription <SANDBOX_SUB_ID> \
  --snowops-org snowops-automation \
  --snowops-repo snowops-automation \
  --workflow-ref refs/heads/main \
  --expires-on $(date -v+30d +%Y-%m-%d 2>/dev/null || date -d '+30 days' +%Y-%m-%d)
  • Script prints ✅ Bootstrap complete
  • Captured output: AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, AZURE_CLIENT_ID, AUDIT_WINDOW_ENDS
  • No client secret was printed

3. Verify read-only contract

SP_OBJECT_ID=$(az ad sp show --id <APP_ID> --query id -o tsv)
az role assignment list --assignee "$SP_OBJECT_ID" --all -o table
  • Exactly two role assignments: Reader + Security Reader
  • Both scoped to /subscriptions/<SANDBOX_SUB_ID>
  • No Contributor / Owner / Key Vault role anywhere

4. Verify federation

az ad app federated-credential list --id <APP_ID> -o json
  • Exactly one FIC
  • subject is repo:snowops-automation/snowops-automation:ref:refs/heads/main
  • issuer is https://token.actions.githubusercontent.com
  • audiences contains api://AzureADTokenExchange
  • description mentions the audit window end date

5. Negative test — write attempt must fail

Run as the SP from a SnowOps workflow (or az login as the SP locally with a one-shot secret you create + delete after) and try:

az group create --name testwrite --location eastus
  • Fails with AuthorizationFailed — SP cannot write

6. Teardown

./teardown.sh <APP_ID>
  • Prints ✅ Teardown complete
  • az ad app show --id <APP_ID> returns ResourceNotFoundError
  • az role assignment list --assignee <SP_OBJECT_ID> returns empty

Pass criteria

  • All of steps 1–6 marked complete
  • No elevated roles assigned at any point
  • No secrets emitted
  • Teardown leaves zero residue in the sub

Failure modes & escalation

Symptom Likely cause Action
Insufficient privileges on app create Caller lacks Privileged Role Admin Activate via PIM
PrincipalNotFound on role assignment AAD replication lag Re-run; the script's 10s sleep usually covers this
Bootstrap aborts on elevated-roles check A previous bootstrap left a stale Owner assignment on the SP Run teardown for the prior APP_ID first

Sign-off

  • Tester: ___  |  Date: _  |  Result: PASS / FAIL / N/A
  • Notes: