# Azure Deployment Stacks Deep Dive: Safe IaC Lifecycle, Deny Settings, and Cleanup

This 90–120 minute advanced Azure Workshopz lab uses a **subscription-scoped Bicep template** and native Azure CLI Deployment Stacks to prove a safe Infrastructure-as-Code lifecycle. It creates only an empty hardened Storage account and two user-assigned managed identities in West Europe. No VM, public IP, data workload, or application is deployed.

## What you will prove

- A Deployment Stack can manage the lab resource group itself when it is deployed at subscription scope.
- `denyDelete` blocks deletion of a stack-managed identity.
- `denyWriteAndDelete` blocks a direct tag change, while an update applied through the stack succeeds.
- `detachAll` keeps a resource that was removed from the Bicep desired state; with child-scope deny disabled, the detached identity can be edited directly.
- Ordered cleanup deletes the detached identity, removes all remaining stack-managed resources, removes the stack-owned deny assignment, and compares a preflight baseline.

## Cost and safety

The workshop has no compute or public networking. The empty Standard_LRS Storage account may incur minimal usage-based charges until cleanup. The preflight stage is read-only and refuses an existing Workshopz resource group, an existing stack name, unavailable storage name, missing provider registration, or insufficient stack permissions.

All private runtime information is written under `work/`, which is ignored and excluded from the ZIP. The public evidence has only booleans, counts, and product names.

## Prerequisites

- Azure CLI 2.88.0 or later, with an active Azure sign-in.
- Bicep CLI 0.46.1 or later.
- PowerShell 7.6 or later.
- Owner or Azure Deployment Stack Owner at the selected subscription. Owner is required for the complete workshop because it includes Deployment Stack deny-setting management.
- `Microsoft.Resources`, `Microsoft.Storage`, and `Microsoft.ManagedIdentity` already registered. Preflight does not register providers.

## Architecture

```text
Subscription-scoped Deployment Stack
├── rg-awz-stacks-we (managed at subscription scope)
│   ├── hardened empty Storage account (managed)
│   ├── delete-probe user-assigned identity (managed)
│   └── detachable user-assigned identity (managed, then detached)
└── stack-owned deny assignment
    ├── denyDelete → direct identity delete blocked
    └── denyWriteAndDelete → direct Storage tag update blocked
```

The Bicep template intentionally keeps `deny-settings-apply-to-child-scopes` disabled. In the detach stage the identity is no longer an explicitly managed resource, so it is neither retained by the stack nor protected through the resource-group parent scope.

## Staged lab commands

Set the subscription once, then run one stage at a time. The scripts derive private unique names locally and never place them in public evidence.

```powershell
$subscriptionId = '<your-subscription-guid>'
az account set --subscription $subscriptionId
```

### 1. Read-only preflight

```powershell
pwsh ./Start-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Preflight
```

The stage checks the active context, CLI/Bicep/PowerShell versions, effective Owner or Deployment Stack Owner capability, provider registrations, stack and resource-group collisions, Storage name availability, and the protected baseline. It builds Bicep but makes zero Azure changes.

### 2. Initial stack deployment

```powershell
pwsh ./Start-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Deploy
```

The stack initially uses `none` deny settings. It manages the resource group, Storage account, and both identities.

### 3. Block a direct delete

```powershell
pwsh ./Start-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Protect
```

The stack is updated to `denyDelete`; the script then attempts to delete **only** the disposable lab identity. The operation must fail with deny-assignment authorization evidence.

### 4. Block a direct write, then let the stack update

```powershell
pwsh ./Start-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Enforce
```

The script promotes the stack to `denyWriteAndDelete`, tries a direct tag update of the managed Storage account, and requires the failure. It then verifies that the tag from the preceding stack update is present—proving that stack-authorized desired-state changes still work.

The negative probe uses `az storage account update --tags`, which performs the Storage resource write operation. `az tag update` calls the separate `Microsoft.Resources/tags/write` provider operation and is not an equivalent deny-setting proof for this resource boundary.

### 5. Detach safely

```powershell
pwsh ./Start-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Detach
```

The Bicep desired state no longer declares the detachable identity; `detachAll` retains it in Azure. The script proves that the identity remains, is no longer reported as a managed resource, and accepts a direct tag update.

### 6. Validate and write sanitized evidence

```powershell
pwsh ./Start-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Validate
```

This stage runs Bicep build, `az stack sub validate`, inventory checks, and writes only sanitized result fields to `evidence/live-validation.json`.

### 7. Cleanup

```powershell
pwsh ./Remove-AzureDeploymentStacksWorkshop.ps1 -SubscriptionId $subscriptionId -ConfirmCleanup
```

Cleanup deletes the detached identity directly, then deletes the stack with `deleteAll`. It waits for the lab resource group to disappear, verifies that the stack is gone, and compares the baseline captured before deployment.

## Troubleshooting

| Symptom | Likely cause | Safe response |
| --- | --- | --- |
| Preflight says the group or stack exists | A prior run was not cleaned up | Inspect only the named Workshopz artifact; run the supplied cleanup after confirming it belongs to this lab. |
| A direct test succeeds | The deny assignment has not propagated or the stack update did not bind | Stop the lab, inspect the stack’s deny settings, wait briefly, and rerun the stage. Do not claim the test passed. |
| The stack cannot set deny settings | The caller has Contributor rather than the required stack permission | Use Owner or the appropriate Deployment Stack Owner role at the selected scope. |
| A detached identity is still blocked | Child-scope deny may be enabled or the resource remains explicitly managed | Confirm `detachAll`, remove it from desired state, and keep child-scope deny disabled for this exercise. |
| Resource-group deletion is not blocked as expected | Deletion of a resource group can bypass a deny assignment from a resource-group-scoped stack | This lab deliberately uses subscription scope, and the article calls out the documented limitation. |

## Production guidance and limitations

- Use stacks to manage a well-defined IaC boundary; do not treat them as a substitute for least-privilege RBAC or data-plane security.
- Start with `denyDelete`, document an exception path, and introduce `denyWriteAndDelete` only when the operational update flow is mature.
- Review `actionOnUnmanage` deliberately. `detachAll` avoids destructive removals but leaves lifecycle ownership with a human or a new system.
- A deny assignment protects control-plane actions. It does not protect data-plane operations.
- Microsoft documents special considerations for resource-group deletion and for resource types that do not support deletion in stacks; review those limitations before broad production use.

## Local source validation and packaging

```powershell
pwsh ./Test-AzureDeploymentStacksWorkshop.ps1
pwsh ./Test-AzureDeploymentStacksWorkshop.ps1 -Package
```

The package task runs Bicep build, PowerShell parsing, Pester tests, a privacy scan, and produces `../azure-deployment-stacks-deep-dive-workshop.zip`.

## Microsoft references

- [Deployment Stacks overview](https://learn.microsoft.com/azure/azure-resource-manager/bicep/deployment-stacks)
- [Deployment Stacks known issues and limitations](https://learn.microsoft.com/azure/azure-resource-manager/bicep/deployment-stacks-known-issues)
- [Azure CLI `az stack sub` reference](https://learn.microsoft.com/cli/azure/stack/sub)
