Skip to content

Manual Test Runbook — B5: PIM for Azure Resources (Owner / Contributor / UAA)

Owner: Sagar  |  Time: ~5 min (Parts A + B) · +20 min (optional Part C sandbox apply) · +25 min (optional Part D live activation drill)  |  Sandbox: snowops-sandbox-01

Promotes B5 (modules/azure/pim-azure-resources/) from 🟦 Code Complete → 🟩 Shipped. Parts A + B are 100% offline. Parts C + D require Entra ID P2 on the sandbox tenant (PIM is a P2 feature) and mutate the subscription's real PIM config for Owner / Contributor / UAA — run in sandbox only, reset after.


Prerequisites

  • Sandbox subscription access active (PIM activated if required)
  • az login done; az account show confirms the sandbox subscription is selected
  • Identity has Owner on the sandbox subscription (needed to write eligible role assignments + role-management policies)
  • Entra ID P2 on the sandbox tenant (Parts C + D only — PIM requires P2)
  • Local tooling: terraform >= 1.6, az CLI >= 2.50
  • SNOWOPS_SANDBOX_SUBSCRIPTION_ID and SNOWOPS_SANDBOX_TENANT_ID env vars set
  • (Parts C + D) four real sandbox AAD groups: tier-0 eligible, tier-1 eligible, tier-0 approver, break-glass (the break-glass group must already hold permanent Owner — wire via B3); a test user who is a member of an eligible group for the drill
  • Working directory: repo root

Steps

Part A — terraform fmt + validate (offline, ~2 min)

  1. Module:
terraform -chdir=modules/azure/pim-azure-resources fmt -recursive -check
terraform -chdir=modules/azure/pim-azure-resources init -backend=false -input=false
terraform -chdir=modules/azure/pim-azure-resources validate

Expected: Success! The configuration is valid.

  1. Example:
terraform -chdir=modules/azure/pim-azure-resources/examples/basic fmt -check
terraform -chdir=modules/azure/pim-azure-resources/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/pim-azure-resources/examples/basic validate

Expected: Success! The configuration is valid.

  1. B5-targeted offline Terratest:
cd tests/terratest
go test -v -timeout 5m ./modules/azure/... -run 'TestPIMAzureResourcesValidate'

Expected: --- PASS: TestPIMAzureResourcesValidate.


Part B — full offline suite + pre-commit (offline, ~3 min)

  1. Full offline suite (confirms B5 hasn't regressed F0–F6 / H1–H3 / B2–B4):
cd tests/terratest
go test -v -timeout 10m ./...

Expected: 22 top-level tests pass (the 21 from v0.28 plus TestPIMAzureResourcesValidate).

  1. pre-commit + pre-push on every new file:
pre-commit run --files \
  modules/azure/pim-azure-resources/versions.tf \
  modules/azure/pim-azure-resources/variables.tf \
  modules/azure/pim-azure-resources/main.tf \
  modules/azure/pim-azure-resources/outputs.tf \
  modules/azure/pim-azure-resources/README.md \
  modules/azure/pim-azure-resources/examples/basic/versions.tf \
  modules/azure/pim-azure-resources/examples/basic/variables.tf \
  modules/azure/pim-azure-resources/examples/basic/main.tf \
  modules/azure/pim-azure-resources/examples/basic/outputs.tf \
  tests/terratest/fixtures/pim-azure-resources/main.tf \
  tests/terratest/fixtures/pim-azure-resources/variables.tf \
  tests/terratest/fixtures/pim-azure-resources/outputs.tf \
  tests/terratest/modules/azure/pim_azure_resources_validate_test.go \
  docs/runbooks/test/B5.md

pre-commit run --hook-stage pre-push --all-files

Expected: every hook PASS; FINAL_EXIT=0.


Part C — sandbox apply (requires Entra ID P2, ~20 min)

Mutates the sandbox subscription's PIM config for Owner / Contributor / UAA. Reset in Teardown.

  1. Confirm env + the four group IDs, then apply the example:
export SNOWOPS_SANDBOX_SUBSCRIPTION_ID="<sandbox-subscription-guid>"
export SNOWOPS_SANDBOX_TENANT_ID="<sandbox-tenant-guid>"

terraform -chdir=modules/azure/pim-azure-resources/examples/basic apply \
  -var subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID \
  -var tenant_id=$SNOWOPS_SANDBOX_TENANT_ID \
  -var tier0_eligible_group_object_id=<tier0-eligible-group> \
  -var tier1_eligible_group_object_id=<tier1-eligible-group> \
  -var tier0_approver_group_object_id=<approver-group> \
  -var break_glass_group_object_id=<break-glass-group>
  1. Watch for:
  2. azurerm_pim_eligible_role_assignment.tier0["Owner/..."] + ["User Access Administrator/..."]: Creation complete.
  3. azurerm_pim_eligible_role_assignment.tier1["Contributor/..."]: Creation complete.
  4. azurerm_role_management_policy.tier0["Owner"] + ["User Access Administrator"]: Creation complete.
  5. azurerm_role_management_policy.tier1["Contributor"]: Creation complete.

  6. Verify the eligibility + activation policy landed in the portal or via CLI:

# Eligible assignments at subscription scope
az role assignment list --scope "/subscriptions/$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
  --include-classic-administrators false -o table   # active assignments
# (PIM eligible assignments show in Entra → PIM → Azure resources → Roles)

Expected (portal): under PIM → Azure resources → → Roles, the eligible group appears as Eligible for Owner + UAA (tier-0) and Contributor (tier-1); the role settings for Owner/UAA show Require approval = Yes, MFA + justification + ticket on activation, 8h max; Contributor shows MFA + justification, 4h, no approval.


Part D — live activation drill (optional, requires P2 + a test user, ~25 min)

  1. Tier-1 (no approval): sign in as a test user in the tier-1 eligible group → PIM → My roles → Azure resources → Activate Contributor. Expect an MFA prompt + a justification field, max 4h, and immediate activation (no approval gate).

  2. Tier-0 (approval): as a test user in the tier-0 eligible group, activate Owner (or UAA). Expect MFA + justification + a ticket field + the request entering Pending approval. As a member of the approver group, approve it under PIM → Approve requests. Confirm the role activates only after approval, for max 8h.

  3. Confirm activation expiry: after the window (or by deactivating manually), the active assignment disappears and the principal reverts to eligible-only.


Pass criteria

  • Part A — terraform validate passes for module + example
  • Part A — TestPIMAzureResourcesValidate passes
  • Part B — full offline suite passes (22 top-level tests)
  • Part B — pre-commit + pre-push all green
  • Part C — eligible assignments created for tier-0 (Owner, UAA) + tier-1 (Contributor)
  • Part C — role-management policies show approval=Yes/8h for tier-0, no-approval/4h for tier-1, MFA+justification on both
  • (Part D) tier-1 activation: MFA prompt, immediate, ≤4h
  • (Part D) tier-0 activation: MFA + ticket, pending approval, activates only after an approver approves, ≤8h
  • Break-glass group retains permanent Owner throughout (never gated by PIM)

Teardown

terraform -chdir=modules/azure/pim-azure-resources/examples/basic destroy \
  -var subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID \
  -var tenant_id=$SNOWOPS_SANDBOX_TENANT_ID \
  -var tier0_eligible_group_object_id=<tier0-eligible-group> \
  -var tier1_eligible_group_object_id=<tier1-eligible-group> \
  -var tier0_approver_group_object_id=<approver-group> \
  -var break_glass_group_object_id=<break-glass-group>

Destroy removes the eligible assignments and resets the role-management policies to Azure defaults (looser than SnowOps). The break-glass group's permanent Owner is NOT touched (owned by B3 / out-of-band), so the subscription never loses its standing administrator.


Sign-off

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