Manual Test Runbook — S3: Auto-Remediation Playbooks¶
Owner: Sagar | Time: ~8 min (Parts A + B offline) · +15 min (optional Part C integration apply) | Sandbox: snowops-sandbox-01
Promotes S3 (
modules/azure/auto-remediation-playbooks/) from 🟦 Code Complete → 🟩 Shipped. Parts A + B are offline ($0). Part C applies the curated pack (indry_run) against a sandbox RG (~$0 — Logic Apps Consumption bills per execution; the apply lasts minutes and POSTs at most a handful of test findings) and destroys.
Prerequisites¶
- Sandbox subscription access active (PIM activated if required)
-
az logindone; sandbox subscription selected - Identity has Contributor on the sandbox sub (RG + Logic App create)
-
SNOWOPS_SANDBOX_SUBSCRIPTION_ID+SNOWOPS_SANDBOX_TENANT_IDexported - Local tooling:
terraform >= 1.6,go >= 1.22,az CLI >= 2.50 - Working directory: repo root
Steps¶
Part A — terraform fmt + validate (offline, ~3 min)¶
- Module + example:
terraform -chdir=modules/azure/auto-remediation-playbooks fmt -recursive -check
terraform -chdir=modules/azure/auto-remediation-playbooks init -backend=false -input=false
terraform -chdir=modules/azure/auto-remediation-playbooks validate
terraform -chdir=modules/azure/auto-remediation-playbooks/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/auto-remediation-playbooks/examples/basic validate
Expected: Success! for both.
- Offline Terratest case:
cd tests/terratest
go test -v -timeout 5m ./modules/azure/... -run TestAutoRemediationPlaybooksValidate
Expected: PASS — exercises the full curated pack (incl. the
workspace-gated enable-resource-diagnostics playbook), a mode override
flipping lock-down-public-storage to enforce, an enabled override
dropping remove-risky-nsg-rule, and a custom playbook with the collision
precondition intact, offline.
Part B — full Terratest suite (offline, ~5 min)¶
bash cd tests/terratest && go test -count=1 -timeout 15m ./...
Expected: the full suite green (the new TestAutoRemediationPlaybooksValidate included).
Part C — integration apply (sandbox, ~15 min, ~$0)¶
No build-tagged integration test ships for S3 — exercising a real ARM write needs a disposable target resource (e.g. a throwaway storage account to PATCH), and the "POST a finding → observe the run-history record / real write" criterion belongs here, not in a teardown-on-completion test.
- Apply the fixture (stays in
dry_runpack-wide — safe to point at a real workspace ARM ID):
cd tests/terratest/fixtures/auto-remediation-playbooks
terraform init -input=false
# Optionally point log_analytics_workspace_id at a REAL sandbox workspace
# ARM ID first (edit main.tf) to also exercise the diagnostic-settings path.
terraform apply -auto-approve \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID"
- Confirm the workflows exist with the expected shape:
RG=snowops-s3-test-rg
az logic workflow list --resource-group "$RG" \
--query "[].{name:name, state:state}" -o table
Expected: 4 workflows — *-enforce-mandatory-tags, *-lock-down-public-storage,
*-enable-resource-diagnostics, *-quarantine-orphaned-disk (the custom
playbook). *-remove-risky-nsg-rule is absent (disabled via override).
- POST a synthetic finding to a
dry_runplaybook's trigger and confirm it composes an intent record with no Azure write attempted:
ENDPOINT=$(az logic workflow show -g "$RG" -n snowops-s3-test-enforce-mandatory-tags \
--query accessEndpoint -o tsv)/triggers/when-a-finding-is-reported/paths/invoke?api-version=2016-06-01
curl -s -X POST "$ENDPOINT" -H 'Content-Type: application/json' -d '{
"findingId": "f-test-1",
"findingKind": "missing-required-tags",
"resourceId": "/subscriptions/'"$SNOWOPS_SANDBOX_SUBSCRIPTION_ID"'/resourceGroups/'"$RG"'/providers/Microsoft.Storage/storageAccounts/doesnotexist",
"detectedAt": "2026-06-07T12:00:00Z",
"proposedTags": { "CostCenter": "shared" }
}'
az logic workflow run list -g "$RG" -n snowops-s3-test-enforce-mandatory-tags -o table
Expected: the run Succeeded; its evaluate-and-remediate action output
shows mode: "dry_run" and the wouldDo summary — no Http action exists
in the definition, so nothing in Azure changed.
- (Optional, enforce-path check) Inspect
*-lock-down-public-storage— confirm its definition contains a realHttpaction authenticated viaManagedServiceIdentity, and that its system-assigned identity (fromprincipal_idsoutput) currently has no write RBAC role — i.e. running it would 403, not silently succeed:
az logic workflow show -g "$RG" -n snowops-s3-test-lock-down-public-storage \
--query "definition.actions['evaluate-and-remediate'].type" -o tsv # expect: Http
PRINCIPAL_ID=$(terraform output -json principal_ids | jq -r '."lock-down-public-storage"')
az role assignment list --assignee "$PRINCIPAL_ID" -o table # expect: empty
- Destroy:
terraform destroy -auto-approve \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID"
Expected: clean destroy — playbooks hold no state of their own; the RG and every workflow are removed. (The J1 workspace, if a real one was wired, is untouched.)
Pass criteria¶
- Part A — module + example validate;
TestAutoRemediationPlaybooksValidatepasses - Part B — full offline suite passes
- (Part C) fixture applies the pack; workflow count + the disabled rule + the override + the custom playbook match; destroys clean
- (Part C) a
dry_runplaybook composes an intent record and performs no Azure write - (Part C, optional) an
enforce-mode playbook's definition carries a realHttpaction but its identity has no granted RBAC (403-not-silent-success) - All test resources removed
Failure mode¶
A playbook in enforce mode whose identity hasn't been granted the matching
write RBAC role yet "does nothing" when triggered — by design (see the README
safety model). The opposite failure — a dry_run playbook somehow mutating
Azure — is structurally impossible: no Http action exists in its definition.
Cost impact¶
Logic Apps (Consumption) bill per action execution — a few cents per 1,000 runs. Cost tracks finding volume; idle playbooks cost nothing. Part C apply + a handful of test triggers is ~$0.
Removal path¶
terraform destroy (Part C step 8) removes every workflow and the RG if S3
created it. Verified clean in Part C.
Sign-Off¶
| Field | Value |
|---|---|
| Part A (validate) | ☐ PASS |
| Part B (offline suite) | ☐ PASS |
| Part C (integration apply) | ☐ PASS / ☐ skipped |
| Part C dry-run check | ☐ PASS / ☐ skipped |
| Tester | |
| Date | |
| Result | ☐ PASS |