Background
Three years on Azure, no foundational architecture. Each team managing their own subscriptions independently, security policies applied inconsistently across all of them, and every audit cycle requiring weeks of manual evidence gathering just to produce a basic picture of what was running.
It started as a governance review. Once the actual state of the environment was clear, a full landing zone build was the only path forward.
The Challenge
The problems were structural. Operational fixes wouldn't have reached them:
- No hub-spoke topology - 12 subscriptions with no centralised egress, DNS, or shared services
- Zero policy enforcement - Teams could deploy public IPs, open NSGs, and unencrypted storage with no automated guardrail
- Manual provisioning - Every new environment required 2 weeks of back-and-forth with the central IT team
- Audit exposure - No resource tagging standard meant cost attribution and compliance evidence required manual trawling
Approach
The architecture was designed to enforce security and governance by default, not by process.
Management Group Hierarchy
Tenant Root Group
Platform
Connectivity
Identity
Management
Landing Zones
Corp (internal workloads)
Online (internet-facing)
Sandbox
Azure Policy initiatives were assigned at each management group level, with deny effects for critical controls (public IPs, unencrypted storage, no-MFA access) and deployIfNotExists for monitoring and tagging.
Hub-Spoke Network Design
A centrally managed hub subscription provides:
- Azure Firewall Premium with IDPS enabled
- Private DNS zones for all PaaS services
- ExpressRoute/VPN gateway for on-premises connectivity
- Bastion for secure VM access with no public IPs
Spoke subscriptions peer to the hub and route all egress through the firewall. No spoke-to-spoke peering is permitted directly.
Terraform Automation
The entire landing zone was codified as modular Terraform:
module "management_groups" {
source = "./modules/management-groups"
tenant_id = var.tenant_id
structure = var.mg_structure
}
module "policy_assignments" {
source = "./modules/policy"
depends_on = [module.management_groups]
assignments = var.policy_assignments
}
module "connectivity_hub" {
source = "./modules/hub-network"
resource_group = azurerm_resource_group.connectivity.name
address_space = var.hub_address_space
enable_firewall = true
enable_bastion = true
}
A spoke vending module was built so teams could self-serve a new environment by raising a pull request with their parameters file. Pipeline approval triggered full provisioning automatically.
Security Controls
Every landing zone subscription is governed by a set of policy initiatives covering:
| Control | Enforcement |
|---|---|
| No public IP on VMs | Deny policy at Corp MG |
| Storage account HTTPS only | Deny policy at LZ root |
| Resource tagging (env, owner, cost-centre) | Append + audit |
| Diagnostic settings on key resources | DeployIfNotExists |
| Microsoft Defender for Cloud Standard | DeployIfNotExists |
Results
- Environment provisioning reduced from 2 weeks to under 2 hours
- 100% policy compliance across all subscriptions from day one
- Audit evidence generation automated via Azure Resource Graph queries
- Zero security exceptions required post-launch
- Full Terraform state managed in Azure Storage with state locking
