CAMPUX Cloud Bootcamp Lab · Identity & Governance ← All labs
Hands-On Lab · Advanced
~30 min · Free · Cloud Shell
CLI & Bicep · torn down at the end
Identity & Governance · Lab D

Make the wrong thing impossible.

Telling people "don't turn on public access" does not scale, and the one time it's forgotten, customer data faces the internet. Azure Policy makes it impossible instead: a rule Azure itself enforces, denying the bad resource at creation, across an entire scope, with no human in the loop.

Fig. 1 · Policy-as-code: Deny prevents
Resource group · rg-lab-policyallowedCustom policyeffect: DenyCompliant resourceBad resourceblocked at create
● Screen walkthrough Not yet recorded · ~7 min
Reel · 00:00 / 07:00

Define a policy in code, deploy it, then watch it deny a non-compliant resource on camera.

Placeholder — the page below stands alone until the reel lands
Why

Audit reports; Deny prevents

Azure ships hundreds of built-in policies, and you can write your own. The lever that matters is the effect. An Audit policy tells you, later, that something is wrong — useful, but the bad thing already exists. A Deny policy refuses the create outright, so the non-compliant resource never comes into being. This lab uses Deny, because it is both the stronger control and the one you can prove on the spot: you will try to create a storage account with public blob access and watch Azure reject it, then create a compliant one and watch it succeed. Guardrails, not a report you read after the incident.

A rule you can't break beats a rule you're told to keep.

Before you begin — one-time setup

You need a free Azure account, the Azure CLI (az), then be signed in with az login. First time? The 15-minute Set up your machine page covers the account, the installs (winget / brew / apt), and sign-in. Prefer zero installs? Run everything in Azure Cloud Shell (Bash), preinstalled and already signed in.

Before you start

You need Resource Policy Contributor or Owner on the subscription. Run it in Azure Cloud Shell (Bash). Cost: the storage accounts are deleted seconds after creation — effectively $0. Bicep version and this guide: github.com/kloudcaptain/campux-labs.

Step 0

Variables and a resource group

# Windows/Git Bash: stop it mangling /subscriptions/... arguments (harmless on macOS/Linux)
export MSYS_NO_PATHCONV=1

SUFFIX=$RANDOM
RG="campux-lab-policy-rg"
LOCATION="eastus"
DEF_NAME="campux-deny-public-blob"
ASSIGN_NAME="campux-deny-public-blob"

SUB=$(az account show --query id -o tsv)
RG_SCOPE="/subscriptions/$SUB/resourceGroups/$RG"
BAD_SA="campuxbad$SUFFIX"        # 3-24 lowercase alphanumeric
GOOD_SA="campuxgood$SUFFIX"

az group create --name "$RG" --location "$LOCATION"
Checkpoint Names echo (BAD_SA/GOOD_SA ≤24 chars, lowercase) and the group is Succeeded.
Step 1

The easy path: assign a built-in policy

You don't always write your own. Assign a built-in that audits storage accounts lacking secure transfer — looked up by display name so there's no fragile hard-coded ID.

BUILTIN=$(az policy definition list \
  --query "[?displayName=='Secure transfer to storage accounts should be enabled'].name | [0]" -o tsv)
[ -n "$BUILTIN" ] || echo "!! built-in not found — display name may have changed"

az policy assignment create \
  --name "campux-secure-transfer" \
  --policy "$BUILTIN" \
  --scope "$RG_SCOPE"
Checkpoint az policy assignment show --name "campux-secure-transfer" --scope "$RG_SCOPE" --query name -o tsv returns campux-secure-transfer. (We query name, not displayName: since we assigned the built-in without an explicit --display-name, its display name is empty — the assignment is still there.) (This built-in uses the Audit effect; compliance results appear after Azure's next scan, up to ~30 min. The enforcement we prove comes from the custom Deny policy next.)
Step 2

Author your own custom policy (Deny)

The real skill: a rule that denies any storage account with public blob access enabled — if (type is storage account AND allowBlobPublicAccess == true) then deny.

az policy definition create \
  --name "$DEF_NAME" \
  --display-name "Campux: deny storage accounts with public blob access" \
  --description "Denies creation of storage accounts that allow public blob access." \
  --mode Indexed \
  --rules "{ 'if': { 'allOf': [ { 'field': 'type', 'equals': 'Microsoft.Storage/storageAccounts' }, { 'field': 'Microsoft.Storage/storageAccounts/allowBlobPublicAccess', 'equals': 'true' } ] }, 'then': { 'effect': 'deny' } }"
Checkpoint az policy definition show --name "$DEF_NAME" --query "{name:name, effect:policyRule.then.effect}" -o json shows your definition with "effect": "deny".
Step 3

Assign the custom policy to the group

az policy assignment create \
  --name "$ASSIGN_NAME" \
  --policy "$DEF_NAME" \
  --scope "$RG_SCOPE"
Checkpoint az policy assignment show --name "$ASSIGN_NAME" --scope "$RG_SCOPE" --query name -o tsv returns the name. The rule is now enforced for this resource group.
Step 4

Prove it: bad is blocked, good is allowed

Try to create a public-blob storage account — it must be denied. A freshly-assigned policy takes a few minutes to start enforcing, so this loop retries until it's actually blocked (cleaning up anything that slips through while it propagates):

for i in $(seq 1 20); do
  if az storage account create --name "$BAD_SA" --resource-group "$RG" \
       --location "$LOCATION" --sku Standard_LRS --allow-blob-public-access true -o none 2>/tmp/policyerr; then
    echo "attempt $i: not blocked yet — policy still propagating. Deleting and waiting 30s."
    az storage account delete --name "$BAD_SA" --resource-group "$RG" --yes -o none
    sleep 30
  else
    if grep -qi "RequestDisallowedByPolicy\|disallowed by policy" /tmp/policyerr; then
      echo ">>> DENIED on attempt $i — the policy is enforcing."; break
    else
      echo "attempt $i: failed for a NON-policy reason (not proof):"; cat /tmp/policyerr
      az storage account delete --name "$BAD_SA" --resource-group "$RG" --yes -o none 2>/dev/null; sleep 30
    fi
  fi
done

Now the compliant one — public blob access disabled — must succeed:

az storage account create --name "$GOOD_SA" --resource-group "$RG" \
  --location "$LOCATION" --sku Standard_LRS --allow-blob-public-access false -o none \
  && echo ">>> CREATED — compliant storage account allowed."
Checkpoint You see >>> DENIED (printed only when the reason is RequestDisallowedByPolicy — an unrelated failure keeps the loop going, so a green result is real proof), then >>> CREATED for the compliant account. The policy blocks only the insecure configuration, not legitimate resources.
Step 5

Policy as code (Bicep)

Clicking policies into the portal doesn't scale or survive an audit. The Bicep version declares the same custom definition and assignment as code. Unlike the identity labs, policy is fully ARM-native, so the whole thing is Bicep — no Graph split. One scope note: policy definitions live at subscription (or management-group) level, so that template targets the subscription, not a resource group.

Down

Tear it down

Assignments and definitions are not inside the resource group — delete them explicitly, then the group.

az policy assignment delete --name "$ASSIGN_NAME" --scope "$RG_SCOPE"
az policy assignment delete --name "campux-secure-transfer" --scope "$RG_SCOPE"
az policy definition delete --name "$DEF_NAME"
az group delete --name "$RG" --yes
Checkpoint az policy definition list --query "[?name=='$DEF_NAME'].name" -o tsv is empty and az group exists --name "$RG" is false.
End

What you can now honestly claim

You authored and assigned a custom Azure Policy with a Deny effect to enforce storage security, proved it blocks a non-compliant resource while allowing a good one, and saw how the same governance is deployed as code. Keep four things: policy enforces at creation time, so Deny makes a bad configuration impossible rather than merely discouraged; built-ins cover most needs, and when none fits you write an if/then rule with an effect; effects matter — Audit reports, Deny blocks; and scope controls blast radius — definitions at subscription or management-group level, assignments at a subscription, resource group, or below. This completes the Identity & Governance track. Its companion is the Resource Locks lab: policy stops non-compliant creation, locks stop deletion.