# Azure Monitor Private Link Deep Dive

Code-only Workshopz lab: AMPLS, AMA, DCRs, DCEs, private DNS, authenticated ingestion/query boundary, and DNS failure/recovery. No Portal steps or screenshots. The cover is conceptual artwork, not evidence.

## Prerequisites and cost

Use PowerShell 7, Azure CLI, OpenSSH `ssh-keygen`, and an authorized disposable Azure subscription. This implementation requires Owner control-plane permission at subscription scope for its conservative preflight, registered providers, two available VM cores and two public-IP slots. It never changes tenant identity, Conditional Access or existing DNS environments.

Charges accrue for two VMs/disks, two NAT Gateways and public IPs, a private endpoint, private DNS and Log Analytics ingestion. A daily cap is not a spending limit. Stop when finished and run cleanup. Budget 90–150 minutes including asynchronous Azure propagation; the script uses bounded waits rather than guaranteeing a completion time.

## Directory layout

Keep the extracted bundle in `outputs/azure-monitor-private-link-deep-dive`. The scripts derive the sibling `work/azure-monitor-private-link-deep-dive` directory for private runtime state. Do not put `work` in source control, share it or upload it. Its exact ownership state is required for safe cleanup.

```powershell
az login
$subscriptionId = '<your-subscription-id>'
az account set --subscription $subscriptionId
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Preflight
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -SubscriptionId $subscriptionId -Stage All
./Get-AzureMonitorPrivateLinkEvidence.ps1 -SubscriptionId $subscriptionId
```

`All` neither publishes nor cleans up. The default location is `westeurope`, VM size `Standard_B1s`, and evidence timeout 30 minutes. Custom location/size values must be passed consistently on every invocation.

## Stages

Run in order: `Foundation`, `PublicBaseline`, `PrivateLink`, `Lockdown`, `AMA`, `TestBoundary`, `TestDnsRecovery`, `Validate`.

```powershell
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage PublicBaseline
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage PrivateLink
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage Lockdown
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage AMA
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage TestBoundary
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage TestDnsRecovery
./Start-AzureMonitorPrivateLinkWorkshop.ps1 -Stage Validate
```

Stages reject an unowned resource group, record resource intent before creation, and never reopen public access on rerun. If a test fails, inspect private `last-error.json`, `last-cli-error.json` and `probe-*.json` locally. Fix the cause and rerun that stage. Do not share raw responses. A process interruption during the DNS drill is recorded; restore the link before proceeding. A fresh deployment is required to repeat the public baseline after lockdown.

## What counts as evidence

Before lockdown, both VMs must send HTTP 204 ingestion requests and successfully query their materialized markers. After lockdown, the connected VM must succeed over actual private-endpoint sockets. The outside VM must receive explicit service-side network denials for both operations with the same identity and unchanged roles. A timeout, HTTP 401 or generic HTTP 403 does not count.

The probe uses Python's standard library already present on Ubuntu, invoked through `az vm run-command invoke`. It gets managed-identity tokens from IMDS in guest memory. Querying uses the Logs data-plane endpoint, never ARM. AMA has separate configuration-DCE and syslog-DCR associations.

## Checks and cleanup

```powershell
./tests/Test-Workshop.ps1
python3 -m py_compile ./guest/probe.py
./Remove-AzureMonitorPrivateLinkWorkshop.ps1 -WhatIf
./Remove-AzureMonitorPrivateLinkWorkshop.ps1 -Confirm:$false
```

Cleanup disconnects the workspace/DCE from AMPLS before resource-group deletion. It verifies the group is gone before removing private runtime. Unexpected resources stop cleanup for review. Existing provider registrations and Network Watcher are preserved.

See `article.md` for the teaching sequence, `queries/proof.kql` for copyable queries, and `live-test-report.md` for actual execution status. Publication is gated on all live tests passing; expected-output examples are not proof.
