Skip to content

Manual Test Runbook — K3: Sentinel SOAR Playbooks

Owner: Sagar  |  Time: ~7 min (Parts A + B offline) · +20 min (optional Part C integration apply)  |  Sandbox: snowops-sandbox-01

Promotes K3 (modules/azure/sentinel-soar/) from 🟦 Code Complete → 🟩 Shipped. Parts A + B are offline ($0). Part C applies the curated playbook pack against a sandbox RG (Logic Apps cost a few cents/month; deployed for minutes is ~$0) and destroys.


Prerequisites

  • Sandbox subscription access active (PIM activated if required)
  • az login done; sandbox subscription selected
  • Identity has Contributor on the sandbox sub (Logic App create)
  • SNOWOPS_SANDBOX_SUBSCRIPTION_ID + SNOWOPS_SANDBOX_TENANT_ID exported
  • 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)

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

terraform -chdir=modules/azure/sentinel-soar/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/sentinel-soar/examples/basic validate

Expected: Success! for both.

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

Expected: PASS — exercises the curated pack (Graph + ARM audiences), the per-playbook override path (one flipped to enforce, one disabled), and a custom playbook, offline.

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

  1. bash cd tests/terratest && go test -count=1 -timeout 15m ./...

Expected: the full suite green (the new TestSentinelSoarValidate included).

Part C — integration apply + dry-run proof (sandbox, ~20 min, ~$0)

No build-tagged integration test ships for K3 — disabling a real account or isolating a real VM needs disposable target identities/resources, so the "containment actually runs" criterion belongs here.

  1. Apply the playbook pack (all dry_run by default — safe):
cd tests/terratest/fixtures/sentinel-soar
terraform init -input=false
# NOTE: the fixture flips isolate-compromised-account to enforce. For a SAFE
# Part C, edit main.tf to remove that override (keep everything dry_run)
# unless you have a disposable test account + granted Graph permission.
terraform apply -auto-approve \
  -var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
  -var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID"
  1. Confirm the Logic Apps exist and their definitions match the resolved mode:
RG=snowops-k3-test-rg
az logic workflow list -g "$RG" --query "[].name" -o table
# Inspect a dry_run playbook — its single action must be a Compose, with NO
# Http action anywhere in the definition:
az logic workflow show -g "$RG" -n snowops-k3-test-disable-risky-service-principal \
  --query "definition.actions" -o json

Expected: the deployed playbooks present (quarantine-vm absent — disabled via the fixture override). Every dry_run playbook's action is a Compose; no Http action exists in a dry-run definition (the structural safety proof).

  1. (Optional, dry-run trigger) POST a synthetic incident to a dry_run playbook's callback URL and confirm a containment-intent record (not a real action) in the run history:
CALLBACK=$(az logic workflow show -g "$RG" -n snowops-k3-test-disable-risky-service-principal \
  --query "accessEndpoint" -o tsv)
# Use the trigger callback URL (az rest / portal) to POST:
#   { "incidentId": "test-1", "servicePrincipalId": "00000000-0000-0000-0000-000000000abc" }
# then check the run history shows a Compose output describing the intended
# containment — and that no Graph call was made.
  1. Destroy:
terraform destroy -auto-approve \
  -var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
  -var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID"

Expected: clean destroy — every Logic App + the RG removed.


Pass criteria

  • Part A — module + example validate; TestSentinelSoarValidate passes
  • Part B — full offline suite passes
  • (Part C) playbooks apply; the disabled playbook is absent; every dry_run definition is a Compose with no Http action (structural safety proof); destroys clean
  • (Part C, optional) a posted incident produces a dry-run intent record, no real call
  • All test resources removed

Failure mode

An enforce playbook returns 403 because its system-assigned identity was not granted the matching Graph/Azure permission out-of-band — this is by design (the two-step enforce gate). A quarantine-vm run with a missing nicResourceId / quarantineNsgId no-ops in dry_run (the record shows what would have run). Both documented in the module README.

Cost impact

Logic Apps bill per action execution (Consumption). Playbooks fire only on incidents — a few cents/month at incident volume; ~$0 for the Part C window. No standing compute.

Removal path

terraform destroy (Part C step 7) removes every playbook workflow + the RG if K3 created it. The identities/VMs the playbooks would act on are owned elsewhere and untouched. 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 proof ☐ PASS / ☐ skipped
Tester
Date
Result ☐ PASS