Reference architecture and runbook (Part 1: HTTP-only) for Hub-Spoke networking with private Application Gateway (AGIC), Azure Firewall DNAT, and Azure Front Door Premium (WAF)
0. When and Why to Use This Architecture
Series note: This document is Part 1 and uses HTTP to keep the focus on routing and control points. A follow-up Part 2 will extend the same architecture to HTTPS (end-to-end TLS) with the recommended certificate and policy configuration.
What this document contains
Scope: Architecture overview and traffic flow, build/run steps, sample Kubernetes manifests, DNS configuration, and validation steps for end-to-end connectivity through Azure Front Door → Azure Firewall DNAT → private Application Gateway (AGIC) → AKS.
Typical scenarios
- Private-by-default Kubernetes ingress: You want application ingress without exposing a public Application Gateway or public load balancer for the cluster.
- Centralized hub ingress and inspection: You need a shared Hub VNet pattern with centralized inbound control (NAT, allow-listing, inspection) for one or more spoke workloads.
- Global entry point + edge WAF: You want a globally distributed frontend with WAF, bot/rate controls, and consistent L7 policy before traffic reaches your VNets.
- Controlled origin exposure: You need to ensure only the edge service can reach your origin (firewall public IP), and all other inbound sources are blocked.
Key benefits (the “why”)
- Layered security: WAF blocks common web attacks at the edge; the hub firewall enforces network-level allow lists and DNAT; App Gateway applies L7 routing to AKS.
- Reduced public attack surface: Application Gateway and AKS remain private; only Azure Front Door and the firewall public IP are internet-facing.
- Hub-spoke scalability: The hub pattern supports multiple spokes and consistent ingress controls across workloads.
- Operational clarity: Clear separation of responsibilities (edge policy vs. network boundary vs. app routing) makes troubleshooting and governance easier.
When not to use this
- Simple dev/test exposure: If you only need quick internet access, a public Application Gateway or public AKS ingress may be simpler and cheaper.
- You require end-to-end TLS in this lab: This runbook is HTTP-only for learning; production designs should use HTTPS throughout.
- You do not need hub centralization: If there is only one workload and no hub-spoke standardization requirement, the firewall hop may be unnecessary.
Prerequisites and assumptions
- Series scope: Part 1 is HTTP-only to focus on routing and control points. Part 2 will cover HTTPS (end-to-end TLS) and the certificate/policy configuration typically required for production deployments.
- Permissions: Ability to create VNets, peerings, Azure Firewall + policy, Application Gateway, AKS, and Private DNS (typically Contributor on the subscription/resource groups).
- Networking: Hub-Spoke VNets with peering configured to allow forwarded traffic, plus name resolution via Private DNS.
- Tools: Azure CLI, kubectl, and permission to enable the AKS AGIC addon.
Architecture Diagram
1. Architecture Components and Workflow
Workflow (end-to-end request path)
Client → Azure Front Door (WAF + TLS, public endpoint) → Azure Firewall public IP (Hub VNet; DNAT) → private Application Gateway (Spoke VNet; AGIC-managed) → AKS service/pods.
1.1 Network topology (Hub-Spoke)
Connectivity
Hub and Spoke VNets are connected via VNet peering with forwarded traffic allowed so Azure Front Door traffic can traverse Azure Firewall DNAT to the private Application Gateway, and Hub-based validation hosts can resolve private DNS and reach Spoke private IPs.
Hub VNet (10.0.0.0/16)
Purpose: Central ingress and shared services. The Hub hosts the security boundary (Azure Firewall) and optional connectivity/management components used to reach and validate private resources in the Spoke.
- Azure Firewall in AzureFirewallSubnet (10.0.1.0/24); example private IP 10.0.1.4 with a Public IP used as the Azure Front Door origin and for inbound DNAT.
- Azure Bastion (optional) in AzureBastionSubnet (10.0.2.0/26) for browser-based access to test VMs without public IPs.
- Test VM subnet (optional) testvm-subnet (10.0.3.0/24) for in-VNet validation (for example, nslookup and curl against the private App Gateway hostname).
Spoke VNet (10.224.0.0/12)
Purpose: Hosts private application workloads (AKS) and the private layer-7 ingress (Application Gateway) that is managed by AGIC.
- AKS subnet aks-subnet: 10.224.0.0/16 (node pool subnet for the AKS cluster).
- Application Gateway subnet appgw-subnet: 10.238.0.0/24 (dedicated subnet for a private Application Gateway; example private frontend IP 10.238.0.10).
- AKS + AGIC: AGIC programs listeners/rules on the private Application Gateway based on Kubernetes Ingress resources.
1.2 Azure Front Door (Frontend)
Role: Public entry point for the application, providing global anycast ingress, TLS termination, and Layer 7 routing to the origin (Azure Firewall public IP) while keeping Application Gateway private.
- SKU: Use Azure Front Door Premium when you need WAF plus advanced security/traffic controls; Standard also supports WAF, but Premium is typically chosen for broader capabilities and enterprise patterns.
- WAF support: Azure Front Door supports WAF with managed rule sets and custom rules (for example, allow/deny lists, geo-matching, header-based controls, and rate limiting policies).
- What WAF brings: Adds edge protection against common web attacks (for example OWASP Top 10 patterns), reduces attack surface before traffic reaches the Hub, and centralizes L7 policy enforcement for all apps onboarded to Front Door.
Security note: Apply WAF policy at the edge (managed + custom rules) to block malicious requests early; origin access control is enforced at the Azure Firewall layer (see Section 1.3).
1.3 Azure Firewall Premium (Hub security boundary)
Role: Security boundary in the Hub that exposes a controlled public ingress point (Firewall Public IP) for Azure Front Door origins, then performs DNAT to the private Application Gateway in the Spoke.
- Why Premium: Use Firewall Premium when you need advanced threat protection beyond basic L3/L4 controls, while keeping the origin private.
- IDPS (intrusion detection and prevention): Premium can add signature-based detection and prevention to help identify and block known threats as traffic traverses the firewall.
- TLS inspection (optional): Premium supports TLS inspection patterns so you can apply threat detection to encrypted flows when your compliance and certificate management model allows it.
Premium feature note (DNAT scenarios): These security features still apply when Azure Firewall is used for DNAT (public IP) scenarios. IDPS operates in all traffic directions; however, Azure Firewall does not perform TLS inspection on inbound internet traffic, so the effectiveness of IDPS for inbound encrypted flows is inherently limited. That said, Threat Intelligence enforcement still applies, so protection against known malicious IPs and domains remains in effect.
Hardening guidance: Enforce origin lockdown here by restricting the DNAT listener to AzureFrontDoor.Backend (typically via an IP Group) so only Front Door can reach the firewall public IP; use Front Door WAF as the complementary L7 control plane at the edge.
2. Build Steps (Command Runbook)
2.1 Set variables
$HUB_RG="HUB-VNET-Rgp"
$AKS_RG="AKS-VNET-RGp"
$LOCATION="eastus"
$HUB_VNET="Hub-VNet"
$SPOKE_VNET="Spoke-AKS-VNet"
$APPGW_NAME="spoke-appgw"
$APPGW_PRIVATE_IP="10.238.0.10"
Note: The commands below are formatted for PowerShell. When capturing output from an az command, use $VAR = (az ...).
2.2 Create resource groups
az group create --name $HUB_RG --location $LOCATION
az group create --name $AKS_RG --location $LOCATION
2.3 Create Hub VNet + AzureFirewallSubnet + Bastion subnet + VM subnet
# Create Hub VNet with AzureFirewallSubnet
az network vnet create -g $HUB_RG -n $HUB_VNET -l $LOCATION --address-prefixes 10.0.0.0/16 --subnet-name AzureFirewallSubnet --subnet-prefixes 10.0.1.0/24
# Create Azure Bastion subnet (optional)
az network vnet subnet create -g $HUB_RG --vnet-name $HUB_VNET -n "AzureBastionSubnet" --address-prefixes "10.0.2.0/26"
# Deploy Bastion (optional; requires AzureBastionSubnet)
az network public-ip create -g $HUB_RG -n "bastion-pip" --sku Standard --allocation-method Static
az network bastion create -g $HUB_RG -n "hub-bastion" --vnet-name $HUB_VNET --public-ip-address "bastion-pip" -l $LOCATION
# Create test VM subnet for validation
az network vnet subnet create -g $HUB_RG --vnet-name $HUB_VNET -n "testvm-subnet" --address-prefixes "10.0.3.0/24"
# Create a Windows test VM in the Hub (no public IP)
$VM_NAME = "win-testvm-hub"
$ADMIN_USER = "adminuser"
$ADMIN_PASS = ""
$NIC_NAME = "win-testvm-nic"
az network nic create --resource-group $HUB_RG --location $LOCATION --name $NIC_NAME --vnet-name $HUB_VNET --subnet "testvm-subnet"
az vm create --resource-group $HUB_RG --name $VM_NAME --location $LOCATION --nics $NIC_NAME --image MicrosoftWindowsServer:WindowsServer:2022-datacenter-azure-edition:latest --admin-username $ADMIN_USER --admin-password $ADMIN_PASS --size Standard_D2s_v5
2.4 Create Spoke VNet + AKS subnet + App Gateway subnet
# Create Spoke VNet
az network vnet create -g $AKS_RG -n $SPOKE_VNET -l $LOCATION --address-prefixes 10.224.0.0/12
# Create AKS subnet
az network vnet subnet create -g $AKS_RG --vnet-name $SPOKE_VNET -n aks-subnet --address-prefixes 10.224.0.0/16
# Create Application Gateway subnet
az network vnet subnet create -g $AKS_RG --vnet-name $SPOKE_VNET -n appgw-subnet --address-prefixes 10.238.0.0/24
2.5 Validate and delegate the App Gateway subnet (required)
# Validate subnet exists
az network vnet subnet show -g $AKS_RG --vnet-name $SPOKE_VNET -n appgw-subnet
az network vnet subnet show -g $AKS_RG --vnet-name $SPOKE_VNET -n appgw-subnet --query addressPrefix -o tsv
# Delegate subnet for Application Gateway (required)
az network vnet subnet update -g $AKS_RG --vnet-name $SPOKE_VNET -n appgw-subnet --delegations Microsoft.Network/applicationGateways
2.6 Create the private Application Gateway
az network application-gateway create -g $AKS_RG -n $APPGW_NAME --sku Standard_v2 --capacity 2 --vnet-name $SPOKE_VNET --subnet appgw-subnet --frontend-port 80 --http-settings-protocol Http --http-settings-port 80 --routing-rule-type Basic --priority 100 --private-ip-address $APPGW_PRIVATE_IP
2.7 Create AKS (public, Azure CNI overlay)
$AKS_SUBNET_ID = (az network vnet subnet show -g $AKS_RG --vnet-name $SPOKE_VNET -n aks-subnet --query id -o tsv)
$AKS_NAME = "aks-public-overlay"
az aks create -g $AKS_RG -n $AKS_NAME -l $LOCATION --enable-managed-identity --network-plugin azure --network-plugin-mode overlay --vnet-subnet-id $AKS_SUBNET_ID --node-count 2 --node-vm-size Standard_DS3_v2 --dns-name-prefix aks-overlay --generate-ssh-keys
2.8 Enable AGIC and attach the existing Application Gateway
$APPGW_ID = (az network application-gateway show -g $AKS_RG -n $APPGW_NAME --query id -o tsv)
az aks enable-addons -g $AKS_RG -n $AKS_NAME --addons ingress-appgw --appgw-id $APPGW_ID
2.9 Connect to the cluster and validate AGIC
az aks get-credentials -g $AKS_RG -n $AKS_NAME --overwrite-existing
kubectl get nodes
# Validate AGIC is running
kubectl get pods -n kube-system | findstr ingress
# Inspect AGIC logs (optional)
$AGIC_POD = (kubectl get pod -n kube-system -l app=ingress-appgw -o jsonpath="{.items[0].metadata.name}")
kubectl logs -n kube-system $AGIC_POD
2.10 Create and link Private DNS zone (Hub) and add an A record
Create a Private DNS zone in the Hub, link it to both VNets, then create an A record for app1 pointing to the private Application Gateway IP.
$PRIVATE_ZONE = "clusterksk.com"
az network private-dns zone create -g $HUB_RG -n $PRIVATE_ZONE
$HUB_VNET_ID = (az network vnet show -g $HUB_RG -n $HUB_VNET --query id -o tsv)
$SPOKE_VNET_ID = (az network vnet show -g $AKS_RG -n $SPOKE_VNET --query id -o tsv)
az network private-dns link vnet create -g $HUB_RG -n "link-hub-vnet" -z $PRIVATE_ZONE -v $HUB_VNET_ID -e false
az network private-dns link vnet create -g $HUB_RG -n "link-spoke-aks-vnet" -z $PRIVATE_ZONE -v $SPOKE_VNET_ID -e false
az network private-dns record-set a create -g $HUB_RG -z $PRIVATE_ZONE -n "app1" --ttl 30
az network private-dns record-set a add-record -g $HUB_RG -z $PRIVATE_ZONE -n "app1" -a $APPGW_PRIVATE_IP
2.11 Create VNet peering (Hub Spoke)
az network vnet peering create -g $HUB_RG --vnet-name $HUB_VNET -n "HubToSpoke" --remote-vnet $SPOKE_VNET_ID --allow-vnet-access --allow-forwarded-traffic
az network vnet peering create -g $AKS_RG --vnet-name $SPOKE_VNET -n "SpokeToHub" --remote-vnet $HUB_VNET_ID --allow-vnet-access --allow-forwarded-traffic
2.12 Deploy sample app + Ingress and validate App Gateway programming
# Create namespace
kubectl create namespace demo
# Create Deployment + Service (PowerShell)
@' apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace: demo spec: replicas: 2 selector: matchLabels: app: app1 template: metadata: labels: app: app1 spec: containers: - name: app1 image: hashicorp/http-echo:1.0 args: - "-text=Hello from app1 via AGIC" ports: - containerPort: 5678 --- apiVersion: v1 kind: Service metadata: name: app1-svc namespace: demo spec: selector: app: app1 ports: - port: 80 targetPort: 5678 type: ClusterIP '@ | Set-Content .\app1.yaml
kubectl apply -f .\app1.yaml
# Create Ingress (PowerShell)
@' apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app1-ing namespace: demo annotations: kubernetes.io/ingress.class: azure/application-gateway appgw.ingress.kubernetes.io/use-private-ip: "true" spec: rules: - host: app1.clusterksk.com http: paths: - path: / pathType: Prefix backend: service: name: app1-svc port: number: 80 '@ | Set-Content .\app1-ingress.yaml
kubectl apply -f .\app1-ingress.yaml
# Validate Kubernetes objects
kubectl -n demo get deploy,svc,ingress
kubectl -n demo describe ingress app1-ing
# Validate App Gateway has been programmed by AGIC
az network application-gateway show -g $AKS_RG -n $APPGW_NAME --query "{frontendIPConfigs:frontendIPConfigurations[].name,listeners:httpListeners[].name,rules:requestRoutingRules[].name,backendPools:backendAddressPools[].name}" -o json
# If rules/listeners are missing, re-check AGIC logs from step 2.9
kubectl logs -n kube-system $AGIC_POD
2.13 Deploy Azure Firewall Premium + policy + public IP
Firewall deployment (run after sample Ingress is created)
$FWPOL_NAME = "hub-azfw-pol-test"
$FW_NAME = "hub-azfw-test"
$FW_PIP_NAME = "hub-azfw-pip"
$FW_IPCONF_NAME = "azfw-ipconf"
# Create Firewall Policy (Premium)
az network firewall policy create -g $HUB_RG -n $FWPOL_NAME -l $LOCATION --sku Premium
# Create Firewall public IP (Standard)
az network public-ip create -g $HUB_RG -n $FW_PIP_NAME -l $LOCATION --sku Standard --allocation-method Static
# Deploy Azure Firewall in Hub VNet and associate policy + public IP
az network firewall create -g $HUB_RG -n $FW_NAME -l $LOCATION --sku AZFW_VNet --tier Premium --vnet-name $HUB_VNET --conf-name $FW_IPCONF_NAME --public-ip $FW_PIP_NAME --firewall-policy $FWPOL_NAME
$FW_PUBLIC_IP = (az network public-ip show -g $HUB_RG -n $FW_PIP_NAME --query ipAddress -o tsv)
$FW_PUBLIC_IP
2.14 (Optional) Validate from Hub test VM
Optional: From the Hub Windows test VM (created in step 2.3), confirm app1.clusterksk.com resolves privately and the app responds through the private Application Gateway.
# DNS should resolve to the private App Gateway IP
nslookup app1.clusterksk.com
# HTTP request should return the sample response (for example: "Hello from app1 via AGIC")
curl http://app1.clusterksk.com
# Browser validation (from the VM)
# Open: http://app1.clusterksk.com
2.15 Restrict DNAT to Azure Front Door (IP Group + DNAT rule)
$IPG_NAME = "ipg-afd-backend"
$RCG_NAME = "rcg-dnat"
$NATCOLL_NAME = "dnat-afd-to-appgw"
$NATRULE_NAME = "afd80-to-appgw80"
# 1) Get AzureFrontDoor.Backend IPv4 prefixes and create an IP Group
$AFD_BACKEND_IPV4 = (az network list-service-tags --location $LOCATION --query "values[?name=='AzureFrontDoor.Backend'].properties.addressPrefixes[] | [?contains(@, '.')]" -o tsv)
az network ip-group create -g $HUB_RG -n $IPG_NAME -l $LOCATION --ip-addresses $AFD_BACKEND_IPV4
# 2) Create a rule collection group for DNAT
az network firewall policy rule-collection-group create -g $HUB_RG --policy-name $FWPOL_NAME -n $RCG_NAME --priority 100
# 3) Add NAT collection + DNAT rule (source = AFD IP Group, destination = Firewall public IP, 80 → 80)
az network firewall policy rule-collection-group collection add-nat-collection -g $HUB_RG --policy-name $FWPOL_NAME --rule-collection-group-name $RCG_NAME --name $NATCOLL_NAME --collection-priority 1000 --action DNAT --rule-name $NATRULE_NAME --ip-protocols TCP --source-ip-groups $IPG_NAME --destination-addresses $FW_PUBLIC_IP --destination-ports 80 --translated-address $APPGW_PRIVATE_IP --translated-port 80
3. Azure Front Door Configuration
In this section, we configure Azure Front Door Premium as the public frontend with WAF, create an endpoint, and route requests over HTTP (port 80) to the Azure Firewall public IP origin while preserving the host header (app1.clusterksk.com) for AGIC-based Ingress routing.
- Create Front Door profile: Create an Azure Front Door profile and choose Premium. Premium enables enterprise-grade edge features (including WAF and richer traffic/security controls) that you’ll use in this lab.
- Attach WAF: Enable/associate a WAF policy so requests are inspected at the edge (managed rules + any custom rules) before they’re allowed to reach the Azure Firewall origin.
- Create an endpoint: Add an endpoint name to create the public Front Door hostname (<endpoint>.azurefd.net) that clients will browse to in this lab.
- Create an origin group: Create an origin group to define how Front Door health-probes and load-balances traffic to one or more origins (for this lab, it will contain a single origin: the Firewall public IP).
- Add an origin: Add the Azure Firewall as the origin so Front Door forwards requests to the Hub entry point (Firewall Public IP), which then DNATs to the private Application Gateway.
- Origin type: Public IP address
- Public IP address: select the Azure Firewall public IP
- Origin protocol/port: HTTP, 80
- Host header: app1.clusterksk.com
- Create a route: Create a route to connect the endpoint to the origin group and define the HTTP behaviors (patterns, accepted protocols, and forwarding protocol) used for this lab.
- Patterns to match: /*
- Accepted protocols: HTTP
- Forwarding protocol: HTTP only (this lab is HTTP-only)
- Then you need to add the Route
- Review + create, then wait for propagation: Select Review + create (or Create) to deploy the Front Door configuration, wait ~30–40 minutes for global propagation, then browse to http://<endpoint>.azurefd.net/.
4. Validation (Done Criteria)
- app1.clusterksk.com resolves to 10.238.0.10 from within the Hub/Spoke VNets (Private DNS link working).
- Azure Front Door can reach the origin over HTTP and returns a 200/expected response (origin health is healthy).
- Requests to http://app1.clusterksk.com/ (internal) and http://<your-front-door-domain>/ (external) are routed to app1-svc and return the expected http-echo text (Ingress + AGIC wiring correct).
Author: Kumar shashi kaushal (Sr Digital cloud solutions architect Microsoft)