Appearance
UDR Routing Design — Hub-Spoke Through pvtfw
Purpose
Documents the User-Defined Route (UDR) strategy that forces all spoke-to-spoke traffic through the PrivateFW (pvtfw) NVA. Authoritative for understanding why specific route table entries exist, which spoke pairs are direct-peered (no NVA), and why asymmetric routing causes stateful firewall drops.
Cross-reference:
architecture/network-topology.md— VNET CIDR rangesarchitecture/two-firewall-architecture.md— firewall rolesazure-infra/identity/net_vnet.tf— identity route table (authoritative)azure-infra/prod/net_vnet.tf— prod route tableazure-infra/dev/net_vnet.tf— dev route table
Design Principle
All spoke-to-spoke traffic must transit pvtfw (10.98.2.4) so the Palo Alto stateful engine sees both directions of every session. If the outbound path uses pvtfw but the return path bypasses it (asymmetric routing), PA drops the return packets as out-of-state.
Asymmetric routing = NLA/RDP failures. Domain auth, RDP NLA, and Kerberos all require a completed TCP handshake. If either direction misses pvtfw, the connection never completes.
pvtfw as the Hub
INTERNET
│
┌────┴────┐
│ PublicFW│ 10.99.0.0/16
└────┬────┘
│ peering
┌────┴────┐
│ pvtfw │ 10.98.0.0/16 ← all spoke-to-spoke
│(hub NVA)│ trust: 10.98.2.4
└──┬──┬──┘
┌──────────┘ └──────────┐
┌────┴────┐ ┌────┴────┐
│Identity │ │ Prod │
│10.10.0.0│ │10.20.0.0│
│ /16 │ │ /16 │
└────┬────┘ └────┬────┘
│ │
┌────┴────┐ ┌────┴────┐
│ Dev │ │ RODC │
│10.30.0.0│ │10.11.0.0│
│ /16 │ │ /16 │
└─────────┘ └─────────┘Each spoke VNet has a default route (0.0.0.0/0 → 10.98.2.4) and explicit spoke-to-spoke routes (per destination VNET CIDR → 10.98.2.4). This overrides Azure's system routes, which would create a direct VNet peering path that bypasses pvtfw.
Route Table Entries — Identity VNet (vnet-identity, 10.10.0.0/16)
| Route Name | Destination | Next Hop | Notes |
|---|---|---|---|
default-to-identity | 0.0.0.0/0 | 10.98.2.4 (pvtfw) | Default egress via pvtfw |
to-dev-spoke | 10.30.0.0/16 | 10.98.2.4 (pvtfw) | Dev AD auth must transit pvtfw |
Removed routes (with reason)
| Route | Reason Removed |
|---|---|
to-prod-spoke | Originally removed in favor of a direct identity↔prod peering; that peering has since also been removed — identity→prod now rides the 0.0.0.0/0 → 10.98.2.4 default (see § Direct-Peering Exceptions, retired) |
to-dashboard-spoke | 10.5.0.0/16 decommissioned — vnet-dashboard removed (note: prod still carries this route in code; tracked as an azure-infra issue) |
to-svcs-spoke | Same history as to-prod-spoke — direct peering later removed; legacy↔identity flows via pvtfw default routes |
Direct-Peering Exceptions (RETIRED — June 2026)
This section previously documented intentional direct peerings (identity↔prod for Kerberos latency, identity↔svcs for legacy auth). Both have since been removed — the removal comments live in identity/net_vnet.tf:190-196 and prod/net_vnet.tf ("identity_to_prod removed — pvtfw routes identity↔prod"). There are no remaining direct spoke↔spoke peerings: every spoke pair traverses a PA. Identity↔prod traffic now follows the 0.0.0.0/0 → 10.98.2.4 defaults on both sides.
Historical caution preserved for context: while those peerings existed, adding a matching UDR would have black-holed traffic at pvtfw (no session state). With the peerings gone, the inverse now applies — do not re-add direct peerings without also removing the pvtfw routes, or you recreate the bypass this design eliminates.
Why 10.99.2.4 (CIRIUS-SVCS-FW) Is Wrong
The CIRIUS-SVCS-FW was the original firewall before the two-firewall architecture. Its trust IP (10.99.2.4) still appears in old or misconfigured route tables. Any UDR pointing at 10.99.2.4 instead of 10.98.2.4 (pvtfw) will cause asymmetric routing because:
- Outbound dev → DC: pvtfw (
10.98.2.4) — PA creates session state - Return DC → dev: CIRIUS-SVCS-FW (
10.99.2.4) — PA sees unknown session, drops
This manifests as NLA authentication failures on RDP. The fix is correcting the next_hop_in_ip_address to 10.98.2.4 in the route table resource.
Twingate + Dev VNet Access Pattern
Dev VNet (10.30.0.0/16) does not have a direct Twingate resource. Direct Twingate resources for 10.30.x.x were removed because they created /16 system routes in the Twingate connector subnet that bypassed both PAs.
User → Twingate → pvtfw (pvt.ciriusgroup.com:7000, GlobalProtect)
└→ 10.30.x.x (dev servers)Access path: Twingate → pvtfw GlobalProtect → RDP to target IP.
Adding a New Spoke
When adding a new VNet that needs spoke-to-spoke connectivity:
- Peer the new VNet to
vnet-privatefw(bidirectional, allow forwarded traffic) - Add a UDR entry on every other spoke pointing at the new CIDR →
10.98.2.4 - Add a default route on the new spoke's route table →
10.98.2.4 - Add spoke-to-spoke routes on the new spoke for each existing spoke CIDR →
10.98.2.4 - If direct peering is intended (no NVA), document it in this file under Direct-Peering Exceptions and do not add cross-spoke UDRs for that pair
Last updated: May 2026 — reflects PR #1248 (identity route fix) and removal of decommissioned 10.5.0.0/16 routes.