Skip to content

Manual Test Runbook — J6: WORM Audit-Log Archive

Owner: Sagar  |  Time: ~10 min (Parts A + B) · +5 min (optional Part C integration) · +20 min (optional Part D WORM drill)  |  Sandbox: snowops-sandbox-01

Promotes J6 (modules/azure/audit-log-archive/) from 🟦 Code Complete → 🟩 Shipped. Parts C/D cost ~$0 (empty StorageV2 account + low-volume Activity Log). The immutability state in this runbook stays Unlocked so teardown works — never Lock a sandbox archive (Locked is permanent).


Prerequisites

  • Sandbox subscription access active (PIM activated if required)
  • az login done; az account show confirms the sandbox subscription is selected
  • Identity has Contributor on the sandbox sub (+ Storage Blob Data Contributor for the Part D blob writes)
  • Local tooling: terraform >= 1.6, go >= 1.22, az CLI >= 2.50, jq
  • SNOWOPS_SANDBOX_SUBSCRIPTION_ID and SNOWOPS_SANDBOX_TENANT_ID env vars set
  • Working directory: repo root

Steps

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

  1. Module + example:
terraform -chdir=modules/azure/audit-log-archive fmt -recursive -check
terraform -chdir=modules/azure/audit-log-archive init -backend=false -input=false
terraform -chdir=modules/azure/audit-log-archive validate

terraform -chdir=modules/azure/audit-log-archive/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/audit-log-archive/examples/basic validate

Expected: Success! The configuration is valid. for both.

  1. Offline Terratest case:
cd tests/terratest
go test -v -timeout 5m ./modules/azure/... -run TestAuditLogArchiveValidate

Expected: PASS.


Part B — full Terratest suite (offline, ~3 min)

  1. Confirm no regression:
cd tests/terratest
go test -count=1 -timeout 12m ./...

Expected: full offline suite green (27 top-level tests across all packages).


Part C — integration test (real Azure apply + destroy, ~5 min, ~$0)

The fixture sets enable_immutability = false so the deferred destroy tears the account down unattended.

  1. Export sandbox env vars:
export SNOWOPS_SANDBOX_SUBSCRIPTION_ID="<sandbox-subscription-guid>"
export SNOWOPS_SANDBOX_TENANT_ID="<sandbox-tenant-guid>"
  1. Run the J6 integration test:
cd tests/terratest
go test -v -tags integration -timeout 30m ./modules/azure/... -run TestAuditLogArchiveModule
  1. Watch for:
  2. Plan: 3 to add, 0 to change, 0 to destroy. — RG + storage account + Activity Log diagnostic setting.
  3. All output assertions PASS (storage_account_id, primary_blob_endpoint, activity_log_diagnostic_id, immutability_enabled = false).
  4. Destroy complete!

Part D — WORM verification drill (optional, ~20 min)

Proves the catalog criterion: "mutation attempt fails." Applies the example with immutability ON (Unlocked), writes an audit blob, then proves a delete/overwrite is refused within the retention window.

  1. Apply the example with immutability on (a short 1-day period keeps the sandbox cheap; Unlocked stays removable):
cd modules/azure/audit-log-archive/examples/basic
terraform init -input=false
terraform apply -auto-approve \
  -var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
  -var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
  -var "archive_name=snowopsj6drill$RANDOM"
SA=$(terraform output -raw storage_account_id | awk -F/ '{print $NF}')
echo "archive=$SA"

The example sets immutability_state = "Unlocked" + a 365-day period. To keep the drill self-contained you can edit the example to immutability_period_in_days = 1 before this apply.

  1. Confirm the account-level immutability policy + protected append writes:
az storage account show -n "$SA" -g "$(terraform output -raw storage_account_id | awk -F/ '{print $5}')" \
  --query "immutableStorageWithVersioning" -o json

Expected: immutability enabled. Also confirm versioning + TLS: az storage account blob-service-properties show ... shows isVersioningEnabled: true.

  1. Write a test blob, then attempt to delete/overwrite it and confirm WORM refuses (use an AAD login; grant yourself Storage Blob Data Contributor on the account first if needed):
CONTAINER=worm-drill
az storage container create --account-name "$SA" --name "$CONTAINER" --auth-mode login
echo "audit-event-001" > /tmp/evt.txt
az storage blob upload --account-name "$SA" -c "$CONTAINER" -n evt.txt -f /tmp/evt.txt --auth-mode login

# Attempt delete within the immutability window — must FAIL.
az storage blob delete --account-name "$SA" -c "$CONTAINER" -n evt.txt --auth-mode login 2>&1 | head -3

Expected: the delete is rejected with an immutability/ImmutabilityPolicy error. The blob remains readable — that's the WORM guarantee.

  1. (Optional) Confirm the Activity Log is landing: after ~10–15 min the platform creates the insights-activity-logs container with append blobs:

    az storage container list --account-name "$SA" --auth-mode login --query "[].name" -o tsv
    
  2. Teardown (Unlocked → removable):

    terraform destroy -auto-approve \
      -var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
      -var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
      -var "archive_name=$SA"
    

Pass criteria

  • Part A — module + example validate; TestAuditLogArchiveValidate passes
  • Part B — full offline Terratest suite passes
  • Part C — TestAuditLogArchiveModule integration test passes end-to-end
  • Archive account is StorageV2, RA-GZRS, TLS 1.2, versioning on, no public blobs
  • Activity Log diagnostic setting present, targeting the archive account
  • (Part D) account-level immutability (WORM) enabled with protected append writes
  • (Part D) blob delete/overwrite is refused within the retention window
  • All Destroy calls complete without error (immutability left Unlocked)
  • No orphaned RGs / storage accounts remain (az group list -o table)
  • All test resources tagged ephemeral = true (X7 cleanup safety net)

Teardown

The integration test runs terraform destroy automatically. For the Part D drill, destroy the example as shown in step 11. If a Locked policy was ever set (it should not be in sandbox), the account cannot be deleted until retention elapses — there is no override.

az group delete --name "<name_prefix>-audit-rg" --yes --no-wait

Sign-off

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