Logo
Cloud SecurityAdvanced28 min read

AWS Well-Architected Security & Landing Zones: The Complete Technical Deep Dive

From the Security Pillar to production-grade Landing Zones — with Control Tower, AFT, SCPs, and real Terraform you can deploy today.

AWSWell-ArchitectedLanding ZoneControl TowerAFTSCPTerraformOrganizations
Last updated: 2026-02-17

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

AreaKey QuestionsAWS ServicesLanding Zone Component
Security FoundationsHow do you organize workloads?Organizations, Control TowerAccount structure, SCPs
Identity & AccessHow do you manage human/machine identities?IAM Identity Center, IAM RolesSSO, permission sets
DetectionHow do you detect misconfig & threats?GuardDuty, Security Hub, ConfigCentralized security account
Infrastructure ProtectionHow do you protect networks & compute?VPC, Security Groups, WAFNetwork baselines
Data ProtectionHow do you classify & encrypt data?KMS, Macie, S3 policiesEncryption defaults
Incident ResponseHow do you respond to events?EventBridge, Lambda, ForensicsAutomated 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

bash
# 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 table

Control 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

hcl
# 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

hcl
# 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

hcl
# 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

json
{
  "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

bash
# 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 table

Phase 4: Centralized Security — The Security Account#

The Security Account is the nerve center. All detection, monitoring, and compliance tooling aggregates here.

Security Account Setup

bash
# 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 ORGANIZATION

Cross-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

hcl
# 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#

#ComponentStatusValidation
1Control Tower enabledaws controltower list-landing-zones
2Core OU (Security, Log, Network, Shared)aws organizations list-accounts
3Production/Staging/Dev OUsaws organizations list-organizational-units-for-parent
4AFT deployedCheck CodePipeline in AFT account
5SCPs on all OUsaws organizations list-policies-for-target
6GuardDuty delegatedaws guardduty list-organization-admin-accounts
7Security Hub delegatedaws securityhub list-organization-admin-accounts
8CloudTrail → Log Archiveaws cloudtrail describe-trails
9Config Aggregator in Securityaws configservice describe-configuration-aggregators
10Transit Gateway sharedaws ram get-resource-shares
11Network Firewall activeaws network-firewall list-firewalls
12IAM Identity Center configuredaws sso-admin list-instances
13S3 Block Public Access (org)aws s3control get-public-access-block
14EBS default encryptionaws ec2 get-ebs-encryption-by-default
15IMDSv2 enforced via SCPCheck 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.

Elastyx Platform

Skip the manual work. Let Elastyx do this continuously.

Everything in this guide — and 1,400+ more checks — running 24/7 across your entire cloud estate.

See Elastyx in Action