# Azure Network Security Perimeter: Learning Mode to Zero-Trust Enforcement

Build and validate an isolated Azure Network Security Perimeter around Storage, Key Vault, and Log Analytics. The workshop starts in Learning mode—called **Transition mode** in the Azure portal—then proves enforced denials and restores access through an explicit subscription-based rule.

**Level:** Advanced  
**Time:** 90–120 minutes, plus bounded log-ingestion time  
**Estimated cost:** A short-lived B1s VM, managed disk, Standard public IP, Storage account, Key Vault, and Log Analytics ingestion. Run cleanup when finished.

> [!WARNING]
> The lab deliberately returns HTTP 403 from both protected services. Run it only in the isolated `rg-awz-nsp-we` resource group created by the workshop. Never associate an existing production resource with this perimeter.

## What the workshop proves

- The preflight performs no Azure mutations and refuses namespace or soft-deleted-vault collisions.
- Storage, Key Vault, and Log Analytics can share one PaaS perimeter profile.
- Learning/Transition mode permits unmatched traffic through each resource's existing firewall rules and records `NspPublicInboundResourceRulesAllowed`.
- Enforced mode without an NSP rule returns HTTP 403 for both managed-identity data-plane operations.
- A subscription-based inbound rule restores network reachability while service RBAC remains required.
- Storage Shared Key access remains disabled and Key Vault uses Azure RBAC.
- The VM has no permitted inbound NSG rules; all tests use Azure VM Run Command and a user-assigned managed identity.
- Terraform reports zero drift and cleanup restores the protected baseline.

## Architecture

```text
Azure subscription
└── rg-awz-nsp-we
    ├── Network Security Perimeter
    │   └── Profile: paas-data
    │       ├── Storage association
    │       ├── Key Vault association
    │       ├── Log Analytics association
    │       └── Subscription inbound rule (final stage only)
    ├── Keyless Storage account
    ├── RBAC-enabled Key Vault
    ├── Log Analytics workspace → NSPAccessLogs
    └── Test VNet
        └── Ubuntu 24.04 B1s VM + user-assigned identity
            ├── Static outbound public IP
            ├── No custom inbound NSG rules
            └── Storage, Key Vault, and Log Analytics roles
```

## Tested versions

| Component | Version |
|---|---:|
| Terraform | 1.15.8 |
| AzureRM | 4.79.0 |
| AzAPI | 2.11.0 |
| random | 3.9.0 |
| TLS | 4.3.0 |
| Azure CLI | 2.88.0 |
| PowerShell | 7.6.3 |
| Ubuntu | 24.04 LTS |

AzAPI manages only the Storage account control-plane object. This avoids AzureRM's Shared Key data-plane read when `allowSharedKeyAccess=false`; all remaining lab resources use AzureRM. Provider versions and checksums are committed in `.terraform.lock.hcl`.

## Requirements

- An Azure subscription where the signed-in principal is Owner and User Access Administrator.
- PowerShell 7.4 or newer, Azure CLI 2.75 or newer, and Terraform 1.15.8.
- `Microsoft.Network`, `Microsoft.Storage`, `Microsoft.KeyVault`, `Microsoft.OperationalInsights`, `Microsoft.Insights`, `Microsoft.Compute`, and `Microsoft.ManagedIdentity` registered.
- `Standard_B1s` available in West Europe.
- Permission to create short-lived role assignments and the listed billable resources.

Sign in and keep the subscription identifier only in shell memory:

```powershell
az login
az account set --subscription "<subscription-id>"
$subscriptionId = (az account show --query id --output tsv).Trim()
```

## Run the stages

### 1. Read-only preflight

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

Preflight checks tools, roles, providers, B1s availability, existing `awz-nsp` resources, soft-deleted Key Vault collisions, and the protected resource/role/diagnostic baseline. It performs zero Azure mutations.

### 2. Deploy Learning/Transition mode

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

Terraform applies `stages/01-learning.tfvars`. The API value is `Learning`; the portal labels the same onboarding state **Transition mode**. Public endpoints stay enabled so unmatched traffic can fall back to each resource's firewall behavior.

### 3. Seed and observe fallback

```powershell
pwsh ./Start-AzureNetworkSecurityPerimeterWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -Stage Observe `
  -LogWaitSeconds 2700
```

The managed-identity VM creates and reads a non-sensitive proof blob and Key Vault secret. The script compares values without printing them, then waits for `NspPublicInboundResourceRulesAllowed` for both services.

### 4. Enforce with no rule

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

Terraform changes only the three association modes to `Enforced`. The repeated reads must return HTTP 403 from both Storage and Key Vault.

### 5. Add explicit Zero Trust access

```powershell
pwsh ./Start-AzureNetworkSecurityPerimeterWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -Stage Allow
```

The final stage input enables one inbound rule for managed identities originating from the active subscription. Both reads must return HTTP 200 and both proof comparisons must match. NSP grants network access; Storage and Key Vault RBAC still decide authorization.

### 6. Validate logs and zero drift

```powershell
pwsh ./Start-AzureNetworkSecurityPerimeterWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -Stage Validate `
  -LogWaitSeconds 2700
```

Validation requires all three categories:

- `NspPublicInboundResourceRulesAllowed`
- `NspPublicInboundPerimeterRulesDenied`
- `NspPublicInboundPerimeterRulesAllowed`

It also checks the three associations, one access rule, diagnostic setting, keyless Storage posture, Key Vault RBAC, required roles, VM inbound isolation, and `terraform plan -detailed-exitcode = 0`.

You can run all build stages in order with `-Stage All` from a clean subscription.

### 7. Ordered cleanup

```powershell
pwsh ./Remove-AzureNetworkSecurityPerimeterWorkshop.ps1 `
  -SubscriptionId $subscriptionId `
  -ConfirmCleanup `
  -Confirm:$false
```

Cleanup destroys the Terraform graph, waits for the resource group to disappear, purges the known workshop Key Vault, checks for remaining workshop resources, and compares the subscription with the protected baseline.

## Inputs

| Input | Default | Purpose |
|---|---|---|
| `subscription_id` | required | Isolated live-test subscription |
| `location` | `westeurope` | Workshop region |
| `resource_prefix` | `awz-nsp` | Namespace prefix |
| `association_access_mode` | `Learning` | `Learning` or `Enforced` only |
| `enable_subscription_access_rule` | `false` | Creates the final inbound rule |
| `allowed_subscription_ids` | `[]` | Private runtime list for the rule |
| `vm_size` | `Standard_B1s` | Test VM size |
| `log_retention_days` | `30` | Workspace retention |

Terraform rejects unsupported access modes and rejects an enabled rule unless the mode is `Enforced` and at least one subscription ID is supplied.

## Evidence and privacy

State, plans, runtime values, VM scripts, tokens, SSH material, public IPs, resource IDs, raw CLI output, and raw portal captures remain under ignored `work/`. The public package contains only aggregate results, masked screenshots, and cleanup proof. Never add `work/` to a shared ZIP.

## Troubleshooting

| Symptom | Likely cause | Resolution |
|---|---|---|
| Preflight stops on a collision | A prior Workshopz NSP or soft-deleted vault exists | Clean only the known prior lab or use a clean subscription. Do not bypass the gate. |
| VM Run Command has no result | VM agent or role assignment is still propagating | Wait several minutes and rerun the same idempotent test stage. |
| Blob or secret returns 403 in Transition | Service RBAC has not propagated or the resource firewall was changed | Confirm the three UAMI roles and that public network access remains enabled during Learning mode. |
| `NSPAccessLogs` query returns HTTP 400 | The table has not materialized yet | Generate new traffic and retry after several minutes; the script waits up to `LogWaitSeconds`. |
| Enforced negative test returns 200 | Association promotion is still propagating | Wait and rerun Enforce. Do not create the allow rule until both services return 403. |
| Allow stage still returns 403 | Subscription rule or association state is still propagating | Confirm the rule is Inbound and contains the active subscription, then retry. |
| Final plan returns 2 | Live state differs from the final stage input | Review the private detailed plan and reconcile only workshop resources. |
| Cleanup baseline fails | An unrelated subscription change occurred during the lab | Compare the private before/after snapshots; never weaken the cleanup assertion. |

## Production hardening

- Replace the local backend with a private, locked, recoverable Azure Storage backend and workload identity federation.
- Separate perimeter ownership from protected-resource ownership and use PIM for privileged changes.
- Onboard resources in Transition mode, analyze traffic over a representative window, then promote through a controlled change.
- Keep access rules narrow, owned, documented, reviewed, and time-bounded; avoid subscription-wide rules when a more specific perimeter design is possible.
- Treat NSP as the network boundary, not an authorization system. Keep least-privilege RBAC, secret rotation, managed identities, and data-protection controls.
- Send logs to a workspace inside the same perimeter and add retention, alerts, Sentinel analytics, and ingestion health monitoring.
- Prefer private connectivity where supported by the application design; this workshop intentionally tests public inbound NSP behavior.
- Extend validation to exfiltration scenarios, outbound rules, platform services, disaster recovery, and cross-region dependencies before production enforcement.

## Microsoft references

- [Network Security Perimeter concepts](https://learn.microsoft.com/azure/private-link/network-security-perimeter-concepts)
- [Transition to a Network Security Perimeter](https://learn.microsoft.com/azure/private-link/network-security-perimeter-transition)
- [Network Security Perimeter diagnostic logs](https://learn.microsoft.com/azure/private-link/network-security-perimeter-diagnostic-logs)
- [Network Security Perimeter access rules](https://learn.microsoft.com/azure/private-link/network-security-perimeter-access-controls)
- [Managed identities for Azure resources](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)
- [Authorize access to blobs with Microsoft Entra ID](https://learn.microsoft.com/azure/storage/blobs/authorize-access-azure-active-directory)
- [Azure Key Vault RBAC](https://learn.microsoft.com/azure/key-vault/general/rbac-guide)

