Cloud Landing Zone Design Cheat Sheet
Multi-account structure, guardrails, networking, and IAM patterns for designing a secure, scalable cloud landing zone.
Multi-Account Structure
The baseline organizational unit (OU) layout used by most landing zones.
- Management account- root of the org; billing and SCPs only, no workloads
- Security OU- log archive + audit/security-tooling accounts, cross-account read access
- Shared services OU- networking hub, CI/CD, DNS, directory services
- Workload OUs- split by environment (dev/stage/prod) or business unit
- Sandbox OU- isolated, auto-expiring accounts for experimentation
Preventive Guardrail (Service Control Policy)
Deny disabling of CloudTrail and leaving approved regions, applied at the OU level.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyCloudTrailDisable", "Effect": "Deny", "Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail"], "Resource": "*" }, { "Sid": "DenyOutsideApprovedRegions", "Effect": "Deny", "NotAction": ["iam:*", "organizations:*", "route53:*", "support:*"], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["us-east-1", "eu-west-1"] } } } ]}
Hub-and-Spoke Networking (Terraform sketch)
Central transit gateway with spoke VPCs attached per workload account.
resource "aws_ec2_transit_gateway" "hub" { description = "landing-zone-hub-tgw" default_route_table_association = "disable"}resource "aws_ec2_transit_gateway_vpc_attachment" "spoke" { for_each = var.spoke_vpcs transit_gateway_id = aws_ec2_transit_gateway.hub.id vpc_id = each.value.vpc_id subnet_ids = each.value.subnet_ids tags = { Name = "${each.key}-tgw-attachment" }}
Landing Zone Design Pillars
What a review of a landing zone design should cover, end to end.
- Identity- federated SSO, centralized IAM Identity Center / Entra ID, no long-lived keys
- Guardrails- preventive (SCP/Policy) and detective (Config rules, Security Hub) controls
- Networking- hub-and-spoke or mesh, centralized egress, private connectivity to on-prem
- Logging- centralized, immutable CloudTrail/Activity Log archive account
- Account vending- automated, templated account creation (Control Tower / Landing Zone Accelerator)
- Cost management- consolidated billing, budgets and tagging policy enforced at the org level
Landing Zone Accelerator Config Snippet
A trimmed global-config excerpt showing centralized logging and mandatory account baselining in a Landing Zone Accelerator on AWS deployment.
homeRegion: us-east-1enabledRegions: - us-east-1 - eu-west-1logging: cloudtrail: enable: true organizationTrail: true centralLogBucket: lifecycleRules: - transitionAfterDays: 90 storageClass: GLACIERcontrolTower: enable: true landingZone: version: '3.3'mandatoryAccounts: - name: LogArchive email: [email protected] organizationalUnit: Security - name: Audit email: [email protected] organizationalUnit: Security
Policy-as-Code Guardrail (OPA/Conftest)
Enforce that no Terraform-planned S3 bucket is public before it ever reaches an account, run in CI ahead of apply.
package terraform.s3deny[msg] { resource := input.resource_changes[_] resource.type == "aws_s3_bucket_public_access_block" resource.change.after.block_public_acls == false msg := sprintf("%v must block public ACLs", [resource.address])}deny[msg] { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" not has_public_access_block(resource.address) msg := sprintf("%v is missing a public_access_block resource", [resource.address])}has_public_access_block(bucket_address) { block := input.resource_changes[_] block.type == "aws_s3_bucket_public_access_block" startswith(block.address, bucket_address)}
Centralized Egress via Inspection VPC
Route spoke VPC traffic through a shared inspection/NAT VPC attached to the transit gateway hub, a common landing-zone network pattern.
resource "aws_route" "spoke_default_to_tgw" { route_table_id = aws_route_table.spoke_private.id destination_cidr_block = "0.0.0.0/0" transit_gateway_id = aws_ec2_transit_gateway.hub.id}resource "aws_ec2_transit_gateway_route" "to_inspection_vpc" { destination_cidr_block = "0.0.0.0/0" transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.egress.id transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.inspection.id}# Inspection VPC runs AWS Network Firewall or a 3rd-party NGFW,# then forwards allowed traffic out through its own NAT gateways.
Common Landing Zone Anti-Patterns
Mistakes that show up repeatedly in landing zone reviews, beyond the basic design pillars.
- Flat OU structure- every workload in one OU makes SCP targeting impossible without side effects
- Shared long-lived credentials- IAM users with static keys instead of federated roles via SSO/Identity Center
- Manual account creation- accounts provisioned outside the vending pipeline drift from baseline controls
- Single management account workloads- running actual applications in the org root account instead of keeping it billing/SCP-only
- No tag enforcement at creation- cost-center/owner tags added after the fact instead of via SCP/tag policy at vend time
- One giant VPC CIDR block- no room left for future spokes; plan CIDR allocation per OU/account up front
Verify Identity Federation Boundary
Query IAM Identity Center to confirm no account has local IAM users provisioned outside SSO, a common landing-zone drift check.
aws iam list-users --query 'Users[?UserName!=`break-glass-admin`]' \ --profile workload-account-role# Expect empty output in every workload account except the# designated emergency break-glass account, which should be# under a separate hardware MFA + CloudTrail alarm.
Bake guardrails into the account-vending pipeline itself (e.g. Control Tower Account Factory or a custom Terraform module) rather than applying SCPs after the fact — accounts should be compliant from the first API call, not retrofitted.