Manual Test Runbook — M2: Customer-Managed Keys (HSM-backed, auto-rotation)
Owner: Sagar | Time: ~10 min (Parts A + B) · +10 min (optional Part C integration apply) · +15 min (optional Part D rotation drill) | Sandbox: snowops-sandbox-01
Promotes M2 (
modules/azure/cmk/) from 🟦 Code Complete → 🟩 Shipped. Part C cost ~$1 prorated (a Premium vault + one HSM key). M2 has a build-tagged integration test (Part C); a real key ROTATION is time-delayed, so the rotate-now drill is the manual Part D.
Prerequisites
- Sandbox subscription access active (PIM activated if required)
-
az logindone; sandbox subscription selected - Identity has Owner OR (Contributor + User Access Administrator) on the sandbox sub — UAA is needed because the fixture grants the deployer Crypto Officer on the vault it creates
-
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/cmk fmt -recursive -check
terraform -chdir=modules/azure/cmk init -backend=false -input=false
terraform -chdir=modules/azure/cmk validate
terraform -chdir=modules/azure/cmk/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/cmk/examples/basic validate
Expected: Success! for both.
- Offline Terratest case:
Expected: PASS (exercises the HSM key + rotation_policy + preconditions + Crypto role grants offline).
Part B — full Terratest suite (offline, ~3 min)
bash cd tests/terratest && go test -count=1 -timeout 15m ./...
Expected: 31 top-level tests green.
Part C — integration apply (sandbox, ~10 min, ~$1)
- Run the build-tagged integration test — creates a Premium vault, grants the deployer Crypto Officer, provisions an HSM-backed RSA-HSM CMK with auto-rotation, asserts the key + versionless ID + rotation policy, then destroys:
Expected: PASS.
Known flake: the data-plane key create can return
403 Forbiddenif the deployer's Crypto Officer grant has not propagated yet. The fixture orders the grant before the key viadepends_on, but RBAC propagation is eventual. If a run 403s onazurerm_key_vault_key.cmk, re-run — the second apply picks up the propagated grant.
Part D — rotate-now drill (optional, ~15 min)
Proves the catalog criterion: "rotation event triggers; new key version used." The policy auto-rotates ~30d before a 365-day expiry, so force a rotation now.
- Apply the cmk fixture to stand up a vault + key you can rotate by hand (the fixture is self-contained — it creates the Premium vault + grants the deployer Crypto Officer; the example consumes an existing F5 vault instead):
cd tests/terratest/fixtures/cmk
terraform init -input=false
terraform apply -auto-approve \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
-var "resource_group_name=m2-drill-rg" \
-var "key_vault_name=snowops-cmk-$RANDOM"
- Capture the current version, then force a rotation and confirm a NEW version appears (and that the versionless ID still resolves to the latest):
VAULT=$(terraform output -raw key_versionless_id | sed -E 's#https://([^.]+)\..*#\1#')
BEFORE=$(terraform output -raw key_version)
echo "vault=$VAULT before=$BEFORE"
az keyvault key rotate --vault-name "$VAULT" --name snowops-cmk
az keyvault key show --vault-name "$VAULT" --name snowops-cmk \
--query "{current:key.kid}" -o json
Expected: the current key ID (key.kid) ends in a DIFFERENT version GUID than
$BEFORE — a new version was generated. Consumers wired to the versionless ID
transparently move to it.
- Confirm the rotation policy is what the module configured:
az keyvault key rotation-policy show --vault-name "$VAULT" --name snowops-cmk \
--query "{expiry:expiryTime, actions:lifetimeActions}" -o json
Expected: expiryTime = P365D; a rotate lifetime action timed
timeBeforeExpiry = P30D.
Pass criteria
- Part A — module + example validate;
TestCMKValidatepasses - Part B — full offline suite passes (31 top-level)
- (Part C)
TestCMKModuleprovisions the HSM key + rotation policy and asserts shape, then destroys clean - (Part D) a forced rotation produces a new key version; rotation policy shows P365D / P30D
- All test resources removed; vault purged (provider purges soft-delete on destroy)
Teardown
cd tests/terratest/fixtures/cmk # if Part D ran
terraform destroy -auto-approve \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
-var "resource_group_name=m2-drill-rg" \
-var "key_vault_name=<the-name-you-used>"
The fixture provider sets
purge_soft_delete_on_destroy = trueso the globally-unique vault name + key are reclaimable. Production CMK vaults must NOT set that — a destroyed CMK breaks any consumer still encrypting with it; detach consumers first.
Sign-off
- Tester: _ | Date: _ | Result: PASS / FAIL / N/A
- Notes: