Skip to content

eSHARS Landing Zone Architecture

eSHARS Landing Zone Architecture

Last reviewed: 2026-04-21

eSHARS runs on two Azure Landing Zone hubs — one for nonprod, one for prod — following the Microsoft Cloud Adoption Framework (CAF) hub-and-spoke connectivity pattern within a single Azure subscription.

Design Principles

  1. Platform/workload separation — Shared networking (hub VNet, VPN Gateway, App Gateway, Bastion, Private DNS zones) lives in the hub RG. Workload resources (SQL, App Service, Key Vault, VMs) live in dedicated workload RGs.
  2. No per-environment -network RG — the hub RG IS the networking layer for each environment. A separate -network RG would duplicate the hub’s role (see Decisions Log).
  3. Private by default — Backend services (SQL, Key Vault, Redis) use private endpoints. Public access is disabled once the private path is verified.
  4. Symmetric NP/Prod — Nonprod and prod hubs mirror each other in subnet layout and naming. Prod gaps (App Gateway) are explicit items to complete.
  5. PE ownership by workload — Private endpoints live in the workload RG that owns the target resource (SQL PE in rg-eshars-{env}-data-cus, KV PE in rg-eshars-{env}-security-cus). The PE NIC is placed in the hub’s snet-private-endpoints subnet.
  6. Single DNS zone per service, per hub — One privatelink.database.windows.net zone per hub RG, linked to all VNets that need PE resolution. No duplicate zones across RGs.

Current State

LayerNonprodProd
Hub RGrg-eshars-core-nprg-eshars-core-prod
Hub VNetvnet-eshars-core-np (10.20.0.0/16)vnet-eshars-core (10.10.0.0/16)
VPN Gatewayvpngw-eshars-core-np (VpnGw1AZ) — no connections yetvpngw-eshars-core (VpnGw1AZ) — no connections yet
App Gatewayappgw-eshars-core-np (Standard_v2) ✅Not provisioned — add when Prod restructure begins
BastionAzureBastionSubnet reserved (10.20.5.0/26) — no host deployedAzureBastionSubnet reserved (10.10.5.0/26) — no host deployed
Hub Key Vaulteshars-core-np-kveshars-core-kv
DNS — SQLprivatelink.database.windows.net — 12 records, 0 VNet links ⚠️privatelink.database.windows.net — 1 record, 1 link ✅
DNS — KVprivatelink.vaultcore.azure.net — 1 record, 1 link ✅privatelink.vaultcore.azure.net — 1 record, 1 link ✅
DNS — Redisprivatelink.redis.cache.windows.net — 1 record, 1 link ✅privatelink.redis.cache.windows.net — 1 record, 1 link ✅
Legacy Spoke VNetrg-eshars-nonprod (10.1.0.0/16) — peered, useRemoteGateways=truerg-eshars-prod (10.0.0.0/16) — peered, useRemoteGateways=true
Workload Spoke VNetrg-eshars-nonprod-network-cus / vnet-eshars-nonprod-cus (10.22.0.0/16) — peered, useRemoteGateways=truerg-eshars-prod-network-cus / vnet-eshars-prod-cus (10.11.0.0/16) — peered, useRemoteGateways=true

Network Topology

graph TD
    internet(("Internet"))

    subgraph np_hub ["rg-eshars-core-np — Nonprod Hub (10.20.0.0/16)"]
        np_appgw["App Gateway WAF_v2\nappgw-eshars-core-np"]
        np_vpngw["VPN Gateway\nvpngw-eshars-core-np"]
        np_vnet["vnet-eshars-core-np\n10.20.0.0/16"]
        np_dns["Private DNS zones\ndatabase / vaultcore / redis"]
    end

    subgraph np_spoke ["rg-eshars-nonprod — Nonprod Spoke (legacy)"]
        np_svnet["Legacy Spoke VNet\n10.1.0.0/16"]
    end

    subgraph prod_hub ["rg-eshars-core-prod — Prod Hub (10.10.0.0/16)"]
        prod_vpngw["VPN Gateway\nvpngw-eshars-core"]
        prod_appgw["App Gateway\n(pending)"]
        prod_vnet["vnet-eshars-core\n10.10.0.0/16"]
        prod_dns["Private DNS zones\ndatabase / vaultcore / redis"]
    end

    subgraph prod_spoke ["rg-eshars-prod — Prod Spoke (legacy)"]
        prod_svnet["Legacy Spoke VNet\n10.0.0.0/16"]
    end

    internet -->|"HTTPS"| np_appgw
    np_vnet <-->|"peering\nallowGatewayTransit=true"| np_svnet
    prod_vnet <-->|"peering\nallowGatewayTransit=true"| prod_svnet

Subnet Layout

Both hubs use identical subnet naming with environment suffix (-np / no suffix for prod).

SubnetNP CIDRProd CIDRPurposeNSG / Delegation
snet-appgw-eshars-core-np10.20.1.0/2410.10.1.0/24App Gateway WAF_v2NSG: allow 80/443 inbound; 65200-65535 GatewayManager
snet-apps-eshars-core-np10.20.2.0/2410.10.2.0/24App Service VNet integration (outbound)Delegation: Microsoft.Web/serverFarms
snet-private-endpoints-eshars-core-np10.20.3.0/2410.10.3.0/24Private endpoints (SQL, KV, Redis, etc.)None
snet-vms-eshars-core-np10.20.4.0/2410.10.4.0/24Internal VMs (SSRS, job scheduler)NSG: allow 80 from snet-appgw only; deny all public
AzureBastionSubnet10.20.5.0/2610.10.5.0/26Azure Bastion host (to be deployed)/26 minimum required by Azure
GatewaySubnet10.20.6.0/2710.10.6.0/27VPN GatewayCannot have NSG

Private DNS Strategy

All private DNS zones are created in and owned by the hub RG. No duplicate zones in workload RGs.

ZoneOwned ByLinked To
privatelink.database.windows.netHub RGHub VNet + all spoke VNets with SQL PEs
privatelink.vaultcore.azure.netHub RGHub VNet + all spoke VNets with KV PEs
privatelink.redis.cache.windows.netHub RGHub VNet + all spoke VNets with Redis PEs

When a private endpoint is created, its A record is added to the hub RG’s zone. All VNets linked to that zone then resolve the PE name to the private IP — no public DNS fallback needed.


Environment RG Structure

Resource GroupContents
rg-eshars-core-npPlatform — hub VNet, VPN GW, App GW, Bastion, private DNS zones, hub KV, hub managed identity
rg-eshars-nonprod-data-cusSQL servers, databases, elastic pools, Data Factory, SQL private endpoints
rg-eshars-nonprod-compute-cusApplication VMs, NICs, OS/data disks, VM extensions
rg-eshars-nonprod-ops-cusAutomation accounts, WAC gateway VM (vm-ops-nonprod), hybrid workers
rg-eshars-nonprod-web-cusApp Service plans, web apps + slots, Function Apps, App Insights, Log Analytics
rg-eshars-nonprod-security-cusKey Vaults, managed identities, certificates, KV private endpoints
rg-eshars-nonprod-monitoring-cusMetric alerts, action groups, dashboards, autoscale rules
rg-eshars-nonprod-backup-cusRecovery Services vaults

What Goes Where

ResourceResource GroupSubnet
Hub VNetHub RGIS the VNet
App Gateway WAF_v2Hub RGsnet-appgw
VPN GatewayHub RGGatewaySubnet
Azure BastionHub RGAzureBastionSubnet
Private DNS zonesHub RGLinked (not in a subnet)
Hub Key VaultHub RG
App Service (web app)*-web-cussnet-apps (VNet integration, outbound)
Function App*-web-cussnet-apps
SQL Server*-data-cus
SQL Private Endpoint*-data-cussnet-private-endpoints (hub)
Key Vault (workload)*-security-cus
KV Private Endpoint*-security-cussnet-private-endpoints (hub)
Redis Cache*-data-cus
Redis Private Endpoint*-data-cussnet-private-endpoints (hub)
SSRS / Report Server VM*-compute-cussnet-vms (hub)
Job Scheduler VM*-compute-cussnet-vms (hub)
TMHP SFTP VM*-compute-cussnet-vms-vnet-eshars-prod-cus (workload spoke)
Azure Automation accounts*-ops-cus
WAC Gateway VM*-ops-cussnet-vms
Metric alerts / action groups*-monitoring-cus
Recovery Services vault*-backup-cus

Security Posture

ResourceCurrent StateTarget
App Service (web)Public + VNet integrationBehind App GW WAF_v2; IP restriction to snet-appgw CIDR
Function AppPublicBehind App GW WAF_v2; same IP restriction
SQL ServerPublic (firewall rules only)Private endpoint only; public access disabled
Key VaultPublic + RBACPrivate endpoint; public access disabled
RedisPublicPrivate endpoint; public access disabled
SSRS VMInternal (legacy VNet)snet-vms; no public IP; accessed via Bastion or VPN
Admin RDP/SSHBastion (legacy spoke)Bastion host in AzureBastionSubnet of hub

ALZ Reference Mapping

ALZ ConstructeSHARS Implementation
Platform — Connectivityrg-eshars-core-np / rg-eshars-core-prod
Platform — Management(future) — Log Analytics workspace, Azure Policy, Defender for Cloud
Platform — Identity(future) — dedicated RG if Entra ID DS is needed
App Landing Zone — NetworkingLegacy spoke VNets (rg-eshars-nonprod, rg-eshars-prod) — peered to hub; workload spoke VNet (rg-eshars-prod-network-cus) for VMs with controlled outbound access
App Landing Zone — Workloadsrg-eshars-{env}-{tier}-cus workload RGs

Migration Path from Legacy Spokes

The legacy spoke VNets (rg-eshars-nonprod 10.1.0.0/16, rg-eshars-prod 10.0.0.0/16) stay in place and remain peered to their hubs. Resources migrate to workload RGs and hub subnets on their own lifecycle.

Near-term DNS cleanup (nonprod — HM-8556 Phase 5):

  1. Link rg-eshars-core-np / privatelink.database.windows.net to both vnet-eshars-core-np and the legacy spoke VNet.
  2. Migrate records from rg-eshars-nonprod-network-cus / privatelink.database.windows.net to the hub zone.
  3. Migrate records from rg-eshars-nonprod-network-cus / privatelink.blob.core.windows.net to a new blob zone in rg-eshars-core-np (or retire if no longer needed).
  4. Delete rg-eshars-nonprod-network-cus (5 resources total).
  5. Delete rg-eshars-nonprod-vnet (10.5.0.0/24 in southcentralus — orphaned, wrong region, never peered).

Long-term (TBD): Whether legacy spoke VNets should retire and workloads consolidate into hub subnets is an open question. Current decision: retain hub-spoke topology for clean workload/platform separation.


Decisions Log

DateDecisionRationale
2026-03-12Single App Gateway over per-workload gatewaysCost (~$250-350/mo vs N × $250/mo); simpler cert and routing management
2026-03-12App Gateway WAF_v2 over Azure Front DoorSingle region; no CDN needed; same OWASP 3.2 WAF ruleset; lower cost
2026-03-12Private by default for SQL / KV / RedisSecurity posture; Defender for Cloud recommendation; compliance alignment
2026-03-12Clean VNet migration (not peering with old per-workload VNets)Legacy VNets have conflicting config; clean break is simpler
2026-04-21No per-environment -network RGSingle-sub ALZ: hub RG is the connectivity layer per CAF guidance. A separate -network RG is only justified with a dedicated NetOps team, cross-subscription topology, or multiple workloads sharing an environment network substrate — none apply here.
2026-04-21App Gateway in hub RG, not a standalone -appgw RGApp Gateway is platform infrastructure. Splitting it from the hub adds RG management overhead with no operational benefit.