Manual Test Runbook — H7: Break-Glass Accounts + Sign-In Alert
Owner: Sagar | Time: ~5 min (Parts A + B, offline) · +30 min Part C (account ceremony + apply) · +20 min Part D (live sign-in drill) | Sandbox: snowops-sandbox-tenant-01
Promotes H7 (
modules/azure/break-glass/) from 🟦 Code Complete → 🟩 Shipped. Part C creates the 2 break-glass accounts (manual ceremony — H7 does NOT create accounts or any password), wires them into the role-assignable group, grants permanent Global Administrator, and applies the sign-in alert. Part D proves the alert fires on a live break-glass sign-in. Requires Microsoft Entra ID P1 (role-assignable groups are a P1 feature).
Prerequisites
- Sandbox AAD tenant access (Privileged Role Administrator + Global Administrator active)
- Microsoft Entra ID P1 license active on the sandbox tenant
- A Log Analytics workspace (F1 / J1) the tenant sign-in logs flow into
- At least one standing Global Administrator OUTSIDE the break-glass group (so a destroy can't strip the only admin path)
- 2 hardware FIDO2 security keys (for the break-glass account ceremony)
- Local tooling:
terraform >= 1.6,go >= 1.22,az CLI >= 2.50,jq -
SNOWOPS_SANDBOX_TENANT_ID+SNOWOPS_SANDBOX_SUBSCRIPTION_IDenv vars set - Working directory: repo root
Steps
Part A — terraform fmt + validate (offline, ~2 min)
- Module + example formatting + structural validity:
terraform -chdir=modules/azure/break-glass fmt -recursive -check
terraform -chdir=modules/azure/break-glass init -backend=false -input=false
terraform -chdir=modules/azure/break-glass validate
terraform -chdir=modules/azure/break-glass/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/break-glass/examples/basic validate
Expected: Success! for module + example.
- Run the H7 offline Terratest case:
Expected: 1 top-level test passes.
Part B — full Terratest suite (offline, ~3 min)
- Run the whole offline suite:
Expected: 23 top-level tests pass.
Part C — break-glass account ceremony + apply (~30 min)
The accounts are a deliberate manual ceremony. H7 never creates a user or generates a password (Identity > Secrets — no secret in Terraform state). Create them by hand, register hardware FIDO2, store a split-knowledge password in a physical safe, then feed the object IDs to the module.
- Create the 2 break-glass accounts (cloud-only, in the verified custom
domain — never
<tenant>.onmicrosoft.com):
az ad user create --display-name "snowops-break-glass-01" \
--user-principal-name "break-glass-01@<verified-domain>" \
--password "$(openssl rand -base64 30)" --force-change-password-next-sign-in false
az ad user create --display-name "snowops-break-glass-02" \
--user-principal-name "break-glass-02@<verified-domain>" \
--password "$(openssl rand -base64 30)" --force-change-password-next-sign-in false
Then, in the portal, for EACH account: register a hardware FIDO2 key (Security info → Add → Security key) and write the generated password (split knowledge, two halves to two officers) into the physical safe. These steps cannot be automated — that is the point.
- Capture the inputs:
export SNOWOPS_SANDBOX_TENANT_ID="<sandbox-tenant-guid>"
export SNOWOPS_SANDBOX_SUBSCRIPTION_ID="<sandbox-sub-guid>"
export BG_IDS=$(az ad user list \
--filter "startsWith(displayName,'snowops-break-glass')" \
--query '[].id' -o json)
export LAW_ID=$(az monitor log-analytics workspace show \
-g snowops-security-rg -n snowops-law --query id -o tsv)
echo "$BG_IDS" # confirm exactly 2 object IDs
- Ensure tenant sign-in logs reach the workspace (no native TF resource for the Entra-tenant diagnostic setting — this is the one manual wiring step):
az monitor diagnostic-settings create \
--name snowops-entra-signin \
--resource "/providers/Microsoft.aadiam/diagnosticSettings" \
--logs '[{"category":"SignInLogs","enabled":true},{"category":"NonInteractiveUserSignInLogs","enabled":true}]' \
--workspace "$LAW_ID" 2>/dev/null || \
echo "Configure via Entra → Diagnostic settings if the CLI form errors for your tenant."
- Apply against the sandbox tenant:
cd tests/terratest/fixtures/break-glass
terraform init
terraform apply \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "break_glass_member_object_ids=$BG_IDS" \
-var "log_analytics_workspace_id=$LAW_ID"
- Spot-check:
- Entra → Groups →
snowops-break-glass-accounts: role-assignable, 2 members. - Entra → Roles → Global Administrator → Assignments → Active: the group listed (permanent — NOT eligible).
terraform output assigned_directory_role_template_ids→{"Global Administrator" = "62e90394-..."}.-
terraform output alert_enabled→true;sign_in_alert_rule_idnon-null. -
Dry-run the alert KQL before the live drill — paste the query into Log Analytics → Logs (expect 0 rows until the drill):
Part D — live sign-in drill (~20 min)
-
From a clean browser session, sign in to https://portal.azure.com as
break-glass-01@<verified-domain>(FIDO2). Sign out. -
Within the alert window (≤ ~10 min for a PT5M evaluation), confirm:
- Email arrives at the configured receiver (
secops@snowops.examplein the fixture — substitute a real inbox). - Azure Monitor → Alerts shows a fired alert
snowops-break-glass-signin, severity 0, with the break-glass UserPrincipalName in the payload. - Re-running the KQL from Step 9 now returns the sign-in row.
- Email arrives at the configured receiver (
-
(Optional) Repeat for
break-glass-02to confirm both accounts are covered.
Pass criteria
- Part A —
terraform validatepasses for module + example - Part B — full offline Terratest suite passes (23 top-level tests)
- Part C Step 8 — role-assignable group with 2 members; group holds PERMANENT (active) Global Administrator
- Part C Step 8 —
alert_enabled = true, alert rule ID non-null - Part D Step 11 — break-glass sign-in fires the severity-0 alert AND a receiver is notified within the window
- A standing Global Administrator outside the break-glass group still exists
Teardown
cd tests/terratest/fixtures/break-glass
terraform destroy \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "break_glass_member_object_ids=$BG_IDS" \
-var "log_analytics_workspace_id=$LAW_ID"
# Delete the throwaway accounts (NOT created by the module — clean up by hand):
for id in $(echo "$BG_IDS" | jq -r '.[]'); do az ad user delete --id "$id"; done
Break-glass safety check before destroy. Destroy revokes the group's Global Admin (it was granted via the group). Confirm another standing Global Administrator exists BEFORE destroying, or the tenant becomes unmanageable. The Entra diagnostic setting from Step 6 is not reverted by destroy — remove it manually if the workspace is being torn down.
Sign-off
- Tester: _ | Date: _ | Result: PASS / FAIL / N/A
- Notes: