Why This Matters#
Every AWS breach post-mortem reads the same: flat account structure, no guardrails, overprivileged identities, no centralized logging. The Well-Architected Security Pillar and Landing Zone pattern exist to prevent exactly this.
Yet 73% of enterprises still run workloads in a single AWS account or a poorly structured multi-account setup.
This guide connects the theory (Well-Architected Security Pillar) to the implementation (Landing Zones) with real configs you can deploy.
The Well-Architected Security Pillar#
The Security Pillar defines 7 design principles and 6 best practice areas. Let's map them to real controls.
The 6 Best Practice Areas — Mapped to AWS Services
| Area | Key Questions | AWS Services | Landing Zone Component |
|---|---|---|---|
| Security Foundations | How do you organize workloads? | Organizations, Control Tower | Account structure, SCPs |
| Identity & Access | How do you manage human/machine identities? | IAM Identity Center, IAM Roles | SSO, permission sets |
| Detection | How do you detect misconfig & threats? | GuardDuty, Security Hub, Config | Centralized security account |
| Infrastructure Protection | How do you protect networks & compute? | VPC, Security Groups, WAF | Network baselines |
| Data Protection | How do you classify & encrypt data? | KMS, Macie, S3 policies | Encryption defaults |
| Incident Response | How do you respond to events? | EventBridge, Lambda, Forensics | Automated playbooks |
Landing Zone Architecture#
A Landing Zone is the multi-account foundation that implements Well-Architected at scale. It's not optional — it's how serious organizations run AWS.
The Account Structure
Why This Structure?
Blast radius containment. If a dev account gets compromised, production is a completely separate AWS account with separate IAM, separate VPCs, separate everything. The attacker has to start from scratch.
Separation of duties. The team managing security tooling doesn't have access to production workloads. The team deploying apps can't modify logging infrastructure.
Compliance mapping. Auditors love it. Each OU maps to a compliance boundary with its own SCPs, and the log archive is immutable.
Phase 1: Control Tower Setup#
AWS Control Tower is the managed Landing Zone service. It sets up the foundational accounts and guardrails.
Deploy Control Tower
# Control Tower is console-driven for initial setup, but we can verify via CLI
# Check if Control Tower is enabled
aws controltower list-landing-zones --region us-east-1
# List managed accounts
aws organizations list-accounts \
--query 'Accounts[*].{Name:Name,Id:Id,Email:Email,Status:Status}' \
--output table
# List OUs
aws organizations list-organizational-units-for-parent \
--parent-id r-xxxx \
--query 'OrganizationalUnits[*].{Name:Name,Id:Id}' \
--output tableControl Tower Landing Zone — What Gets Created
Phase 2: Account Factory with Terraform (AFT)#
Control Tower creates accounts manually. Account Factory for Terraform (AFT) automates it — infrastructure-as-code for account provisioning.
AFT Architecture
Account Request — Terraform
# aft-account-request/terraform/main.tf
# This file defines a new AWS account via AFT
module "production_app_a" {
source = "./modules/aft-account-request"
control_tower_parameters = {
AccountEmail = "aws+prod-app-a@company.com"
AccountName = "Production - Application A"
ManagedOrganizationalUnit = "Production"
SSOUserEmail = "platform-team@company.com"
SSOUserFirstName = "Platform"
SSOUserLastName = "Team"
}
account_tags = {
Environment = "production"
CostCenter = "engineering"
DataClass = "confidential"
Compliance = "soc2,iso27001"
BusinessOwner = "cto@company.com"
TechOwner = "platform-lead@company.com"
}
# Custom fields passed to account customizations
account_customizations_name = "production-workload"
change_management_parameters = {
change_requested_by = "Platform Team"
change_reason = "New production workload for Application A"
}
}Global Customizations — Applied to Every Account
# aft-global-customizations/terraform/main.tf
# These settings apply to EVERY account in the organization
# Force S3 Block Public Access at account level
resource "aws_s3_account_public_access_block" "block_all" {
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable EBS default encryption
resource "aws_ebs_encryption_by_default" "enabled" {
enabled = true
}
# Enable IMDSv2 as default for all new EC2 instances
resource "aws_ec2_instance_metadata_defaults" "imdsv2" {
http_tokens = "required"
http_put_response_hop_limit = 1
}
# IAM password policy
resource "aws_iam_account_password_policy" "strict" {
minimum_password_length = 14
require_lowercase_characters = true
require_numbers = true
require_uppercase_characters = true
require_symbols = true
allow_users_to_change_password = true
max_password_age = 90
password_reuse_prevention = 24
}
# GuardDuty — auto-enable via delegated admin
# (configured in security account, not per-account)
# AWS Config rules — baseline compliance checks
resource "aws_config_config_rule" "s3_bucket_ssl" {
name = "s3-bucket-ssl-requests-only"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_SSL_REQUESTS_ONLY"
}
}
resource "aws_config_config_rule" "rds_encryption" {
name = "rds-storage-encrypted"
source {
owner = "AWS"
source_identifier = "RDS_STORAGE_ENCRYPTED"
}
}
resource "aws_config_config_rule" "iam_root_mfa" {
name = "root-account-mfa-enabled"
source {
owner = "AWS"
source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
}
}Account Customizations — Production Workload Baseline
# aft-account-customizations/production-workload/terraform/main.tf
# Production VPC with 3-tier architecture
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "production-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
database_subnets = ["10.0.201.0/24", "10.0.202.0/24", "10.0.203.0/24"]
enable_nat_gateway = true
one_nat_gateway_per_az = true
enable_flow_log = true
create_flow_log_cloudwatch_log_group = true
create_flow_log_iam_role = true
tags = {
Environment = "production"
}
}
# KMS key for workload encryption
resource "aws_kms_key" "workload" {
description = "Workload encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM policies"
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
Action = "kms:*"
Resource = "*"
},
{
Sid = "Allow Security Account for audit"
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::SECURITY_ACCOUNT_ID:root" }
Action = ["kms:Describe*", "kms:List*", "kms:Get*"]
Resource = "*"
}
]
})
}
resource "aws_kms_alias" "workload" {
name = "alias/workload-key"
target_key_id = aws_kms_key.workload.key_id
}Phase 3: Service Control Policies (SCPs) — The Guardrails#
SCPs are the most powerful security control in AWS. They set the maximum permissions boundary for an entire OU — even root can't bypass them.
SCP Architecture
Production SCP — The Full Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRegionsOutsideApproved",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-east-2",
"eu-west-1",
"me-south-1"
]
},
"ArnNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/AWSControlTowerExecution",
"arn:aws:iam::*:role/AWSAFTExecution"
]
}
}
},
{
"Sid": "DenyPublicS3Buckets",
"Effect": "Deny",
"Action": "s3:PutBucketPolicy",
"Resource": "*",
"Condition": {
"StringEquals": {
"s3:PublicAccessBlockConfiguration/RestrictPublicBuckets": "false"
}
}
},
{
"Sid": "DenyUnencryptedS3Uploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": ["aws:kms", "AES256"]
}
}
},
{
"Sid": "DenyUnencryptedRDS",
"Effect": "Deny",
"Action": "rds:CreateDBInstance",
"Resource": "*",
"Condition": {
"Bool": {
"rds:StorageEncrypted": "false"
}
}
},
{
"Sid": "EnforceIMDSv2",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
},
{
"Sid": "DenyCloudTrailModification",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail",
"cloudtrail:PutEventSelectors"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/AWSControlTowerExecution"
}
}
},
{
"Sid": "DenyRootAccountUsage",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
},
{
"Sid": "DenyIAMUserCreation",
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:CreateAccessKey",
"iam:CreateLoginProfile"
],
"Resource": "*"
},
{
"Sid": "DenyVPCModification",
"Effect": "Deny",
"Action": [
"ec2:CreateVpc",
"ec2:DeleteVpc",
"ec2:CreateInternetGateway",
"ec2:AttachInternetGateway"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/AWSAFTExecution",
"arn:aws:iam::*:role/NetworkAdmin"
]
}
}
}
]
}Apply SCP via CLI
# Create the SCP
aws organizations create-policy \
--name "production-guardrails" \
--description "Security guardrails for production OU" \
--type SERVICE_CONTROL_POLICY \
--content file://production-scp.json
# Attach to Production OU
aws organizations attach-policy \
--policy-id p-XXXXXXXXXX \
--target-id ou-XXXX-XXXXXXXX
# Verify attachment
aws organizations list-policies-for-target \
--target-id ou-XXXX-XXXXXXXX \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[*].{Name:Name,Id:Id}' \
--output tablePhase 4: Centralized Security — The Security Account#
The Security Account is the nerve center. All detection, monitoring, and compliance tooling aggregates here.
Security Account Setup
# Delegate GuardDuty administration to Security Account
aws guardduty enable-organization-admin-account \
--admin-account-id SECURITY_ACCOUNT_ID
# Delegate Security Hub
aws securityhub enable-organization-admin-account \
--admin-account-id SECURITY_ACCOUNT_ID
# Delegate AWS Config aggregation
aws configservice put-configuration-aggregator \
--configuration-aggregator-name "org-aggregator" \
--organization-aggregation-source '{"RoleArn":"arn:aws:iam::SECURITY_ACCOUNT_ID:role/ConfigAggregator","AllAwsRegions":true}'
# Delegate Macie
aws macie2 enable-organization-admin-account \
--admin-account-id SECURITY_ACCOUNT_ID
# Delegate IAM Access Analyzer (org-wide)
aws accessanalyzer create-analyzer \
--analyzer-name "org-analyzer" \
--type ORGANIZATIONCross-Account Security Data Flow
Phase 5: Network Architecture — Transit Gateway#
Don't peer VPCs manually. Use Transit Gateway for hub-and-spoke networking.
Transit Gateway Terraform
# network-account/main.tf
resource "aws_ec2_transit_gateway" "main" {
description = "Organization Transit Gateway"
default_route_table_association = "disable"
default_route_table_propagation = "disable"
dns_support = "enable"
vpn_ecmp_support = "enable"
tags = {
Name = "org-transit-gateway"
}
}
# Share TGW with organization via RAM
resource "aws_ram_resource_share" "tgw" {
name = "transit-gateway-share"
allow_external_principals = false
}
resource "aws_ram_resource_association" "tgw" {
resource_arn = aws_ec2_transit_gateway.main.arn
resource_share_arn = aws_ram_resource_share.tgw.arn
}
resource "aws_ram_principal_association" "org" {
principal = "arn:aws:organizations::MGMT_ACCOUNT:organization/o-XXXXXXXXXX"
resource_share_arn = aws_ram_resource_share.tgw.arn
}
# Route tables for segmentation
resource "aws_ec2_transit_gateway_route_table" "production" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = { Name = "production-routes" }
}
resource "aws_ec2_transit_gateway_route_table" "development" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = { Name = "development-routes" }
}
resource "aws_ec2_transit_gateway_route_table" "shared" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = { Name = "shared-services-routes" }
}
# Production can reach shared services but NOT development
# Development can reach shared services but NOT production
# Shared services can reach both (CI/CD, DNS, etc.)
# Inspection VPC for Network Firewall
resource "aws_networkfirewall_firewall" "inspection" {
name = "org-inspection-firewall"
firewall_policy_arn = aws_networkfirewall_firewall_policy.main.arn
vpc_id = module.inspection_vpc.vpc_id
dynamic "subnet_mapping" {
for_each = module.inspection_vpc.private_subnets
content {
subnet_id = subnet_mapping.value
}
}
}Network Segmentation
The Complete Landing Zone Checklist#
| # | Component | Status | Validation |
|---|---|---|---|
| 1 | Control Tower enabled | ☐ | aws controltower list-landing-zones |
| 2 | Core OU (Security, Log, Network, Shared) | ☐ | aws organizations list-accounts |
| 3 | Production/Staging/Dev OUs | ☐ | aws organizations list-organizational-units-for-parent |
| 4 | AFT deployed | ☐ | Check CodePipeline in AFT account |
| 5 | SCPs on all OUs | ☐ | aws organizations list-policies-for-target |
| 6 | GuardDuty delegated | ☐ | aws guardduty list-organization-admin-accounts |
| 7 | Security Hub delegated | ☐ | aws securityhub list-organization-admin-accounts |
| 8 | CloudTrail → Log Archive | ☐ | aws cloudtrail describe-trails |
| 9 | Config Aggregator in Security | ☐ | aws configservice describe-configuration-aggregators |
| 10 | Transit Gateway shared | ☐ | aws ram get-resource-shares |
| 11 | Network Firewall active | ☐ | aws network-firewall list-firewalls |
| 12 | IAM Identity Center configured | ☐ | aws sso-admin list-instances |
| 13 | S3 Block Public Access (org) | ☐ | aws s3control get-public-access-block |
| 14 | EBS default encryption | ☐ | aws ec2 get-ebs-encryption-by-default |
| 15 | IMDSv2 enforced via SCP | ☐ | Check SCP policy |
What Elastyx Adds to Your Landing Zone#
A Landing Zone gives you the structure. Elastyx gives you the continuous assurance.
- Drift detection — Someone modifies an SCP at 2 AM? Elastyx alerts in real-time.
- Cross-account posture — Single dashboard across all accounts, all OUs, all regions.
- Compliance mapping — Every finding mapped to ISO 27001, SOC 2, NIST, UAE IA, NCA controls.
- Auto-remediation — Elastyx doesn't just find the public S3 bucket — it blocks it.
- Account hygiene — Orphaned roles, unused access keys, permission creep across 50+ accounts.
Your Landing Zone is the foundation. Elastyx makes sure nobody cracks it.