top of page
7 hours ago
10 min read
Azure Workshopz: Entra-authenticated point-to-site VPN, private DNS, and route recovery

A VPN client can say Connected while an application remains unreachable. This workshop separates authentication, routing, DNS, and application connectivity by building a small private Azure environment and deliberately removing one of its Windows client routes.

Download the workshop bundle immediately below this introduction. It contains the PowerShell 7 orchestrator, visible Azure CLI/REST operations, a standard-library web service, route-recovery logic, KQL, and sanitized validation evidence.

1. What you will prove

This is an intermediate, single-session workshop. Allow roughly two to four hours, including gateway provisioning, Windows sign-in, testing and deletion. The gateway wait is bounded at 120 minutes; diagnostics can be polled for up to 30 minutes.

The test sequence is deliberately more demanding than “the tunnel connected”:

  1. While disconnected, a fresh request to the private VM fails.

  2. After Microsoft Entra authentication, private DNS resolves and HTTP succeeds.

  3. The workload-covering VPN route is removed, but the tunnel stays connected.

  4. DNS still works through a protected resolver host route, while HTTP fails.

  5. The recorded route is restored and a fresh HTTP request succeeds again.

  6. After disconnection, the VPN routes disappear and private access fails.

Every positive HTTP test checks a unique request nonce, the expected workshop marker and a fresh server timestamp. A cached page is not accepted as evidence. This is a route-removal exercise, not an NSG failure disguised as a routing test.

2. Prerequisites and cost warning

Use Windows 11, PowerShell 7, Azure CLI, an authorized disposable Azure subscription, and an existing Microsoft Entra member identity. This version uses Azure VPN Client and OpenVPN with Microsoft's registered Azure VPN Client application. It does not create a custom app, create a user or request manual tenant-wide admin consent. Follow the current Microsoft Entra gateway guidance.

The deployment preflight expects subscription-level Owner or Contributor. You must complete Entra sign-in, MFA and Windows elevation yourself. Existing Conditional Access and MFA protections remain unchanged; an authentication-policy blocker is a reason to stop, not a reason to switch authentication methods silently.

Charges continue while the gateway and resolver exist—even when the VPN is disconnected. Budget for VpnGw1AZ, its Standard public IP, the DNS resolver inbound endpoint, VM, managed disk, private DNS and Log Analytics. No NAT Gateway is deployed.

This run uses a €20 operational ceiling, a six-hour maximum lifetime and €5 reserved for cleanup/delays. The scripts reprice the resources and track elapsed-time estimates. These are operational safeguards, not a guaranteed Azure billing cap. Do not leave the environment running while waiting for publication or review.

Check for address overlap before deployment. The proposed Azure VNet is 10.180.0.0/16; the VPN client pool is 172.30.240.0/24. The preflight compares them with Azure VNets and this workstation's IPv4 routes, including existing VPN and VMware networks. A collision stops the deployment.

3. Architecture and security boundary

The gateway public IP terminates an authenticated VPN; it is not a public endpoint for the VM's web service. The workload VM has no public IP and no inbound SSH rule.

Component

Workshop configuration

Resource group

rg-p2s-entra-workshop-we

VNet

10.180.0.0/16

GatewaySubnet

10.180.0.0/27

Gateway

VpnGw1AZ, Generation1, route-based, active-passive

VPN

OpenVPN, Microsoft Entra authentication, split tunnel

P2S pool

172.30.240.0/24

Workload subnet

10.180.1.0/24, defaultOutboundAccess=false

Private web VM

Ubuntu 24.04, Standard_B1s, 10.180.1.4:8080

Resolver inbound subnet

Dedicated/delegated 10.180.2.0/28

Resolver inbound address

10.180.2.4

Private name

web.p2s.workshopz.test → 10.180.1.4

Diagnostics

Gateway logs → Log Analytics

The resolver needs only an inbound endpoint here. There is no outbound endpoint or forwarding ruleset. The private DNS zone is linked only to the workshop VNet. See DNS Private Resolver endpoints.

The web endpoint is a non-sensitive connectivity probe, not a production application. VPN authentication does not replace application authorization. A real service still needs application identity, authorization, TLS and appropriate logging.

4. Read-only preflight

Extract the ZIP so the bundle sits under outputs/azure-point-to-site-vpn-entra. Open PowerShell 7 in that directory. Live profiles, keys, identifiers and raw logs stay in the sibling workspace's work/azure-point-to-site-vpn-entra directory. Never upload that private directory.

PowerShell 7

$subscriptionId = Read-Host 'Azure subscription ID'
./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Preflight

The preflight reads authentication, permissions, providers, API versions, VM image and size, quota, public-IP capacity, prices, Windows/client state and address overlap. It creates no Azure resources. These underlying CLI reads are useful independently:

Azure CLI, called from PowerShell:

PowerShell 7

az account show --subscription $subscriptionId --query '{name:name,state:state}' -o table
az vm list-usage --location westeurope --subscription $subscriptionId -o table
az vm list-skus --location westeurope --size Standard_B1s --all --subscription $subscriptionId -o table
az vm image show --location westeurope --urn Canonical:ubuntu-24_04-lts:server:latest --subscription $subscriptionId
Get-NetRoute -AddressFamily IPv4
Get-DnsClientNrptPolicy -Effective

Raw CLI output can contain identifiers. Inspect it locally; publish only the allowlisted evidence files or privacy-reviewed screenshots.

5. Provision the Azure environment

PowerShell 7

./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage All

All creates and validates Azure infrastructure and prepares the client profile. It does not import or connect the workstation, inject a fault, publish content or delete resources. To resume a completed or interrupted stage, use its explicit name:

PowerShell 7

./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Gateway
./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Dns
./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Workload
./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Observability
./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage ClientProfile
./Start-AzureP2sVpnWorkshop.ps1 -SubscriptionId $subscriptionId -Stage Validate

The helpers record resource ownership before creation and reject unexpected pre-existing resources. A fresh copy of the bundle cannot adopt an existing group merely because its name matches. Keep the private state if execution is interrupted.

The gateway uses vpnAuthenticationTypes = AAD and vpnClientProtocols = OpenVPN. The audience is Microsoft's documented public application ID; tenant and issuer URLs are derived privately from the authenticated subscription's tenant. The issuer ends in /. No custom application or manual admin-consent workaround is used.

Inspect the live gateway without printing tenant identifiers:

PowerShell 7

$rg = 'rg-p2s-entra-workshop-we'
$gateway = 'vgw-p2s-entra-we'
az network vnet-gateway show -g $rg -n $gateway --subscription $subscriptionId `
  --query '{sku:sku.name,generation:vpnGatewayGeneration,state:provisioningState,activeActive:activeActive,protocols:vpnClientConfiguration.vpnClientProtocols,authentication:vpnClientConfiguration.vpnAuthenticationTypes}' -o json

The VM service is installed through VM Run Command. It uses Python's standard library already present in the Ubuntu image; there is no apt download and no need to add internet egress to the explicitly private workload subnet.

Portal gateway configuration: VpnGw1AZ with active-active disabled
Portal P2S configuration: OpenVPN and Microsoft Entra ID

PowerShell 7

az vm run-command invoke -g $rg -n vm-p2s-web-we --subscription $subscriptionId `
  --command-id RunShellScript --scripts @guest/install-web.sh

The service returns WORKSHOPZ_P2S_PRIVATE_WEB, echoes the request nonce and adds its UTC timestamp. It runs as an unprivileged systemd service. Its local guest check proves the listener works, but is not accepted as a workstation VPN test.

Check the subnet and VM NIC:

PowerShell 7

az network vnet subnet show -g $rg --vnet-name vnet-p2s-entra-we -n snet-workload `
  --subscription $subscriptionId --query '{prefix:addressPrefix,defaultOutboundAccess:defaultOutboundAccess}' -o json
az network nic show -g $rg -n nic-p2s-web-we --subscription $subscriptionId `
  --query 'ipConfigurations[].{privateIP:privateIPAddress,publicIP:publicIPAddress}' -o json

The NSG allows only 172.30.240.0/24 to 10.180.1.4 on TCP 8080. A priority-4096 deny overrides the broad default inbound allowances for other workload traffic.

Portal subnet inventory: gateway, workload and delegated resolver subnets
Workload subnet with private subnet enabled and no NAT Gateway
Private workload NIC with a static private address and no public IP
Portal NSG: allow P2S TCP 8080 and deny other inbound traffic

6. Prepare Azure VPN Client and private DNS

Install Azure VPN Client from Microsoft's documented distribution:

PowerShell 7

winget install --id Microsoft.AzureVPNClient --exact --source winget

Open Azure VPN Client, choose Import, and select the privately generated workshop-vpn.xml. Its unique name starts with Workshopz-P2S-. Leave automatic connection disabled. Do not modify existing VPN profiles or the physical adapter's DNS settings. See Windows client instructions.

The generated profile contains this non-secret client configuration fragment:

XML:

XML

<clientconfig>
  <dnsservers><dnsserver>10.180.2.4</dnsserver></dnsservers>
  <dnssuffixes><dnssuffix>.p2s.workshopz.test</dnssuffix></dnssuffixes>
  <includeroutes>
    <route><destination>10.180.2.4</destination><mask>32</mask></route>
  </includeroutes>
</clientconfig>

This fragment is not an importable profile by itself. The gateway-generated XML contains live server configuration and must stay private. The resolver /32 is intentional: removing the workload route later must not also break DNS.

Azure VPN Client uses Windows name-resolution policy. Verify the effective NRPT policy rather than relying only on ipconfig /all. See Microsoft's DNS and route configuration guidance.

Resolver inbound endpoint using the workshop's dedicated delegated subnet
Private DNS web record points to the workload VM
Private DNS zone link to the workshop VNet

7. Baseline: disconnected, then connected

Before connecting:

PowerShell 7

./Test-AzureP2sVpnWorkshop.ps1 -Action BeforeConnect

The test snapshots the original default routes and NRPT state. It requires the workshop VPN to be disconnected and a fresh private HTTP request to fail. Name resolution is recorded separately; a cached or independently resolvable name does not prove a usable private route.

Now connect only the unique workshop profile and complete Entra sign-in/MFA. Do not automate or share credentials. After Azure VPN Client says Connected:

PowerShell 7

./Test-AzureP2sVpnWorkshop.ps1 -Action Connected
Get-DnsClientNrptPolicy -Effective
Clear-DnsClientCache
Resolve-DnsName web.p2s.workshopz.test -Type A
Find-NetRoute -RemoteIPAddress 10.180.1.4
Test-NetConnection 10.180.1.4 -Port 8080
$nonce = [guid]::NewGuid().ToString('N')
Invoke-RestMethod "http://web.p2s.workshopz.test:8080/?nonce=$nonce" -NoProxy -DisableKeepAlive

The test requires an address from the P2S pool, the resolver host route, a workload-covering VPN route, the correct effective NRPT entry, private DNS and a fresh valid HTTP result. It also checks that the original default routes remain unchanged and a public HTTPS control request still succeeds. That is split-tunnel evidence, not merely a screenshot of the Connected label.

Azure VPN Client connected using Microsoft Entra ID, private DNS and split routes; live gateway hostname redacted

8. Deliberate failure: connected VPN, missing workload route

Run the following route action in an elevated PowerShell 7 window under the same user. The script never removes a route by destination alone: it identifies the workshop VPN interface, snapshots its workload-covering active routes and checks the protected DNS /32 first.

PowerShell 7

./Test-AzureP2sVpnWorkshop.ps1 -Action InjectMissingRoute
./Test-AzureP2sVpnWorkshop.ps1 -Action VerifyBroken

Only active routes on that interface, confined to the workshop VNet and covering 10.180.1.4, may be removed. The script refuses 0.0.0.0/0, unrelated prefixes and broader routes. It does not change VMware, adapter DNS, other VPN connections or persistent routes.

The VPN must remain connected, private DNS must still return 10.180.1.4, and fresh workload HTTP/TCP tests must fail. Find-NetRoute may now select the physical default route; that is not a usable route to the private Azure service.

PowerShell 7

Get-NetRoute -AddressFamily IPv4 | Where-Object DestinationPrefix -Like '10.180.*'
Find-NetRoute -RemoteIPAddress 10.180.1.4
Resolve-DnsName web.p2s.workshopz.test -Type A
Test-NetConnection 10.180.1.4 -Port 8080

The diagnosis also uses Azure VM Run Command to prove that the web service is still healthy locally. A server failure and a client route failure are different causes. If Azure VPN Client reinserts a covering route, the script reports the fault as unproven; it does not substitute an NSG failure.

Azure VPN Client remains connected during the verified missing-route fault

The screenshot proves only the client connection state. The accompanying real PowerShell results prove that the workload route was missing: the resolver /32 remained, Find-NetRoute selected 0.0.0.0/0, and fresh TCP/HTTP failed while private DNS and the guest listener remained healthy. No synthetic terminal image is used as evidence.

9. Repair, verify and disconnect

In the elevated window:

PowerShell 7

./Test-AzureP2sVpnWorkshop.ps1 -Action Repair

Repair restores only the recorded routes on the same workshop interface and checks DNS, a new nonce-bearing HTTP response and the unchanged internet route. The saved fault record is retained if verification fails.

For a guided run, -Action Run wraps injection and verification in a try/finally repair block and pauses for your screenshots. If the PowerShell process is killed or Windows restarts, run Repair before starting another test.

Disconnect the workshop profile in Azure VPN Client, then:

PowerShell 7

./Test-AzureP2sVpnWorkshop.ps1 -Action AfterDisconnect

The P2S address and workshop routes must disappear, and a fresh VM request must fail again. The original internet default route must remain intact.

Azure VPN Client disconnected after recovery and final validation

10. Correlate gateway diagnostics

The Observability stage discovers the actual gateway diagnostic categories and requires P2SDiagnosticLog. It enables the discovered logs and AllMetrics in the workshop Log Analytics workspace. It does not assume a stale category list.

PowerShell 7

$gatewayId = az network vnet-gateway show -g $rg -n $gateway `
  --subscription $subscriptionId --query id -o tsv
az monitor diagnostic-settings categories list --resource $gatewayId --subscription $subscriptionId
./Get-AzureP2sVpnEvidence.ps1 -TimeoutMinutes 30

Use your actual UTC client test window in KQL:

KQL

let testStart = datetime(2026-01-01T00:00:00Z);
let testEnd = datetime(2026-01-01T01:00:00Z);
AzureDiagnostics
| where TimeGenerated between (testStart .. testEnd)
| where ResourceProvider =~ "MICROSOFT.NETWORK"
| where ResourceType =~ "VIRTUALNETWORKGATEWAYS"
| where Resource =~ "VGW-P2S-ENTRA-WE"
| where Category == "P2SDiagnosticLog"
| extend DiagnosticMessage=tostring(column_ifexists("Message", ""))
| summarize EventCount=count(), ConnectRequests=countif(DiagnosticMessage contains "Connect request received"), EntraValidationEvents=countif(DiagnosticMessage contains "validation type: AAD"), SuccessfulDisconnects=countif(DiagnosticMessage contains "Disconnect Successful"), FirstEvent=min(TimeGenerated), LastEvent=max(TimeGenerated) by Category, OperationName
| order by FirstEvent asc

The example dates are placeholders for your own test, not evidence of a run. The evidence script supplies the recorded window automatically. Gateway logs corroborate the session; they do not prove a particular HTTP nonce reached the VM. Keep raw messages private because they can contain identities and public addresses. See the gateway monitoring reference.

Genuine Azure Portal KQL result: 103 P2S events with Entra validation and successful disconnection events

The author's correlated window returned 103 events, including two AAD validation events and three successful disconnect events. Counts include control-channel activity and retries; they are not counts of distinct users or successful web requests. The evidence collector uses the documented Logs query REST API and maps response columns by name, avoiding a dependency on a local CLI extension.

11. Troubleshooting map

Symptom

Check first

Safe response

Gateway still Updating

Provisioning state and elapsed limit

Wait within the bound; do not repeatedly replace it

Entra sign-in denied

Tenant, registered audience, issuer, existing policy

Stop for an authorized identity/policy review; do not weaken MFA

Connected, private name fails

Effective NRPT, resolver /32, VNet zone link

Fix the workshop profile/link only

Name resolves, HTTP fails

Find-NetRoute, NSG and guest listener

Separate client path from guest service health

Missing-route test still succeeds

Remaining covering VPN routes

Mark unproven; do not manufacture a different failure

Repair sees a different interface

VPN reconnected during the fault

Disconnect the workshop profile and use recovery state safely

No P2S logs yet

Diagnostic setting, correct UTC window, ingestion delay

Bounded polling; no invented log evidence

A local network overlaps

Preflight route comparison

Stop and choose an approved new lab address plan

12. Production hardening

This lab is not a complete remote-access production design. Before production, review access governance, Conditional Access, supported client versions, connection monitoring, DNS design, authorized address pools, application TLS/authorization, logging retention, gateway capacity and recovery procedures. Zone-redundant gateway infrastructure does not make every application dependency highly available.

Do not distribute a shared live VPN XML in a public ZIP. Do not treat connectivity as authorization. Do not force-tunnel this workstation or change its physical adapter DNS merely to make the experiment pass.

13. Cleanup is part of the exercise

Export sanitized evidence first. Restore any injected route, disconnect the workshop VPN and remove only its unique profile. Leave Azure VPN Client installed.

PowerShell 7

./Test-AzureP2sVpnWorkshop.ps1 -Action Repair
./Test-AzureP2sVpnWorkshop.ps1 -Action AfterDisconnect
./Remove-AzureP2sVpnWorkshop.ps1 -ClientProfileRemoved

Cleanup checks ownership and deletes only rg-p2s-entra-workshop-we. It verifies Azure deletion before removing live profiles, keys, raw captures and recovery state. Existing Network Watcher, provider registrations, unrelated VNets, tenant settings and other VPN profiles remain unchanged.

If a client handoff is delayed and costs must stop, use -CloudOnly to delete Azure first while retaining local recovery state. Then finish workstation cleanup. The reusable bundle and sanitized evidence remain available after deletion.

14. Validation record and final result

The real Windows workstation completed the following sequence on 21 September 2026. Times below are UTC; the German-language client screenshots display local Berlin time (UTC+2).

UTC

State

Actual result

12:08:48

Disconnected

No P2S address; HTTP timed out; no private A answer

12:50:33

Connected

Private DNS correct; fresh marker/nonce/timestamp valid; original internet route preserved

12:56:52

Workload route removed

VPN and DNS still working; TCP/HTTP failed; VM Run Command proved healthy service

12:57:48

Route restored

Private DNS and fresh HTTP proof succeeded again

12:59:12

Disconnected again

No workshop routes/address; HTTP failed; original routes, NRPT and adapter DNS restored

Copyable sanitized assertions from the real tests:

Actual sanitized output

Connected:     Passed=True; NonceMatched=True; OriginalInternetRoutePreserved=True
Missing route: Passed=True; VpnPoolAddressPresent=True; WorkloadVpnRoutePresent=False
Missing route: PrivateDns.ResolvedExpected=True; TcpSucceeded=False; GuestServiceHealthy=True
Recovered:     Passed=True; NonceMatched=True; OriginalInternetRoutePreserved=True
Disconnected:  Passed=True; WorkshopRoutesPresent=False; OriginalRoutesPreserved=True
Disconnected:  NrptRestored=True; AdapterDnsPreserved=True

These are allowlisted assertions from the included JSON evidence, not a simulated terminal session. The private HTTP browser capture was blocked by the automation browser; HTTP success is documented by the actual PowerShell nonce validation, not by a fabricated browser screenshot.

Cleanup was verified after the live tests: the Azure resource group is absent, the temporary VPN profile and private runtime files have been removed, and Azure VPN Client remains installed. Original routes, effective NRPT and adapter DNS were verified unchanged after disconnection. The included cleanup JSON records this result.

The lesson is the separation of four independent checks: the user authenticates, DNS resolves the private destination, Windows selects a usable VPN route, and the application returns a fresh response. A green VPN connection indicator proves only part of that path.

Microsoft references

Comments


bottom of page