31 ‐ Azure Hub & Spoke: Public Hub Private Spoke - SanjeevOCI/Study GitHub Wiki
Azure Hub & Spoke: Public Hub + Private Spoke
Goal
Centralize internet egress & admin in the Hub VNet, keep Spoke VNet private (no direct internet), while enabling:
- Blob Storage / Azure PaaS privately (Private Endpoints or Service Endpoints)
- OS updates / public repos via Hub NAT Gateway
- Admin via Bastion in Hub
- Logging, Monitoring, and Governance via Azure best practices
Quick Architecture
+--------------------+
| On-Prem / VPN |
| (Optional Future) |
+---------+----------+
|
v
+-------------+
| VPN/ER |
| (Transit) |
+------+------+
|
+---------------------------+---------------------------+
| |
v v
+-------------------+ +-------------------+
| HUB VNet | | SPOKE VNet |
| 10.0.0.0/16 | | 10.1.0.0/16 |
| | | |
| +-------------+ | | +-------------+ |
| | Hub-Public |<-------- Admin (SSH) ---->| Spoke-Priv |
| | 10.0.1.0/24 | | | | 10.1.1.0/24 | |
| | Bastion | | | | VM (no PIP) | |
| +-------------+ | | +-------------+ |
| | | |
| +-------------+ | | |
| | Hub-Priv | | | |
| | 10.0.2.0/24 | | | |
| | NAT GW ---> IGW | | |
| +-------------+ | | |
| | | +-------------+ |
| +-------------+ | | | PEP-SPOKE | |
| | PEP-HUB(opt)|---X | | (Blob/PaaS) | |
| +-------------+ | +-------------+ |
+-------------------+ +-------------------+
Subscription
└─ Resource Groups
├─ Network-Hub
│ └─ VNet HUB 10.0.0.0/16
│ ├─ Subnet Hub-Public 10.0.1.0/24 [IGW] (Bastion, optional tools)
│ ├─ Subnet Hub-Private 10.0.2.0/24 [NAT path/NVA optional]
│ ├─ IGW, NAT GW, VPN/ER, (PEP-HUB optional for hub workloads)
└─ App-Spoke
└─ VNet SPOKE 10.1.0.0/16
├─ Subnet Spoke-Private 10.1.1.0/24 (VMs, no public IPs)
├─ PEP-SPOKE (required for private PaaS access)
└─ Hub-Spoke Peering
Key change vs standalone VNets:
- NAT Gateway is in the Hub (not in Spoke).
- Spoke sends
0.0.0.0/0
to Hub via peering (centralized egress). - Each VNet that needs private PaaS must have its own Private Endpoint or Service Endpoint (not transitive).
- Logging & Monitoring enabled at NSG, Bastion, and VM level.
Addressing (example)
-
HUB VNet:
10.0.0.0/16
- Hub-Public:
10.0.1.0/24
- Hub-Private:
10.0.2.0/24
- Hub-Public:
-
SPOKE VNet:
10.1.0.0/16
- Spoke-Private:
10.1.1.0/24
- Spoke-Private:
Build Steps (High-Level)
A) Create Hub VNet (Internet-enabled)
-
VNet (10.0.0.0/16) with subnets:
- Hub-Public (10.0.1.0/24) for Bastion
- Hub-Private (10.0.2.0/24) for NAT path / future NVA
-
Gateways in Hub:
- Internet Gateway (default Azure routing)
- NAT Gateway
- VPN Gateway/ER Gateway
- (Optional) Private Endpoint in Hub if workloads need private PaaS
-
Route Tables (Hub):
- Hub-Public RT:
0.0.0.0/0 → IGW
- Hub-Private RT:
0.0.0.0/0 → NAT GW
- (optional) Service Endpoints → PEP-HUB
- Hub-Public RT:
B) Deploy Bastion in Hub
- Place Azure Bastion in Hub-Public subnet.
- Use Bastion session to reach Spoke VM (no public IP on Spoke VM).
- Enable Bastion diagnostics for audit.
C) Create Spoke VNet (No Internet)
- VNet (10.1.0.0/16) with Spoke-Private (10.1.1.0/24)
- PEP-SPOKE (Private Endpoint/Service Endpoint) — required for Blob Storage/Azure PaaS
- Peer Spoke VNet to Hub VNet
- Enable NSG Flow Logs for visibility.
D) Hub-Spoke Peering Configuration
- Create VNet Peering: Hub VNet & Spoke VNet
- Route Propagation:
- Spoke → Hub:
0.0.0.0/0
(for internet egress) - Hub → Spoke:
10.1.0.0/16
(for return traffic) - (Optional) Add on-prem later via VPN/ER
- Spoke → Hub:
E) Spoke Route Table (attach to Spoke-Private)
0.0.0.0/0 → Hub VNet
(centralized egress via Hub NAT)- Service Endpoints → PEP-SPOKE (private PaaS)
F) NSGs
Spoke (subnet or NIC-level NSG):
- Egress:
- Service Tags:
Storage
, TCP 443 (Blob) 0.0.0.0/0
, TCP 443 (NAT path via Hub)
- Service Tags:
- Ingress:
- From Hub Bastion/VPN CIDR on admin ports (22/3389 as required)
- No inbound from internet
Hub (Bastion + NAT path):
- Bastion subnet: allow inbound from trusted admin IPs (SSH/RDP session establishment)
- If using NVA/Azure Firewall: allow Spoke CIDR → NVA, and NVA → IGW as required
G) Spoke VM
- Create VM in Spoke-Private, no public IP
- Connect via Bastion (Hub-Public)
Verification (from Spoke VM via Bastion)
Blob Storage via Private Endpoint (private path):
az storage account show --name <storageaccount> --query privateEndpointConnections
Outbound updates via Hub NAT (public repos):
curl https://ifconfig.me # shows Hub NAT GW public IP
sudo apt-get update && sudo apt-get install -y wget
No broad internet (if restricted to 443):
curl http://example.com:80 # should fail
Path sanity:
- Spoke NIC → Effective Routes show:
0.0.0.0/0 → Hub NAT
- Service Endpoints → PEP-SPOKE
- Hub NAT subnet RT → 0.0.0.0/0 → IGW
Why NAT GW (when Hub has IGW)?
- NAT GW = outbound-only for private workloads; no unsolicited inbound possible.
- Using IGW for Spoke traffic opens inbound paths unless heavily restricted (breaks “no-internet Spoke” posture).
- Best practice:
- Public subnets → IGW
- Private subnets → NAT GW
Interview Soundbite
“We centralize egress by moving NAT to the Hub and point the Spoke’s
0.0.0.0/0
to the Hub via VNet peering.
This preserves the Spoke’s no-internet posture while enabling updates, and aligns with Azure’s hub-spoke design best practices.”
Common Pitfalls (and Fixes)
- Trying to use Hub PEP for Spoke: Not supported (Private Endpoints are scoped per VNet).
- Missing route propagation in peering: Spoke won’t see default route. Fix peering config.
- Asymmetric routing with an NVA: Ensure return routes in Hub point back to Spoke CIDR and to IGW for internet.
- Wrong route table attached to Spoke subnet: Double-check association.
- NSG rules too open: Keep Spoke egress TCP/443 only; prefer NSGs for per-VM control.
Interview Nuggets (Prove Hands-On)
- Gateways are VNet-level (NAT/PEP): create gateway → reference in subnet RT.
- VNet Peering provides transit between Hub/Spoke; no transitive peering.
- Private Endpoints are per VNet: every VNet needs its own.
- Bastion in Hub Public for admin; Spoke VMs never get public IPs.
- Effective routes & NSG Flow logs: verify NIC routes and use NSG diagnostics.
- Egress least privilege: 443 only; switch to NVA/Azure Firewall for FQDN/domain allow-listing.
- Enable Azure Policy and tags for governance.
Quick Checklist
- Hub: IGW, NAT GW, VPN/ER, Bastion in Hub-Public
- Spoke: PEP-SPOKE, VNet Peering to Hub; no IGW
- Peering: Export
0.0.0.0/0
(Hub → Spoke), import Spoke CIDR (Spoke → Hub) - Routes (Spoke):
0.0.0.0/0 → Hub
, Service Endpoints → PEP-SPOKE - Routes (Hub): Hub-Public → IGW, Hub-Private → NAT GW (+ returns to Spoke CIDR)
- Security: Spoke egress TCP/443 to PEP + Hub; admin from Bastion/VPN only
- Governance: Enable Azure Policy, tagging, and flow logs
- Validate:
az storage account show --name <storageaccount> curl ifconfig.me apt-get update