What is AWS Control Tower?

Service that sets up and governs a secure, multi-account AWS environment based on best practices. Automates the creation of a landing zone.

What It Sets Up

  • AWS Organizations (account structure)
  • Identity Center (SSO)
  • Guardrails (preventive + detective + proactive controls)
  • Log Archive account (centralized logging)
  • Audit account (security/compliance)
  • Account Factory (provision new accounts)
┌─────────────────────────────────────────────────────────────────┐
│ Management Account                                               │
│   └── Organizations                                              │
│         ├── Security OU                                          │
│         │     ├── Log Archive Account                           │
│         │     └── Audit Account                                 │
│         └── Workloads OU                                         │
│               ├── Dev Account                                   │
│               └── Prod Account                                  │
└─────────────────────────────────────────────────────────────────┘

Landing Zone

A well-architected, multi-account AWS environment. It’s the architecture pattern, not a service.

Components

  • Multi-account structure (Organizations + OUs)
  • Identity management (IAM Identity Center)
  • Centralized logging (CloudTrail, Config logs)
  • Security baseline (guardrails, SCPs)
  • Network architecture (shared VPCs, Transit Gateway)
  • Governance (tagging, cost allocation)

Control Tower vs Landing Zone

Control TowerLanding Zone
WhatAWS serviceArchitecture pattern
RelationshipCreates and managesThe thing being created

One Control Tower = One Landing Zone per AWS Organization.

  • Cannot have multiple landing zones in same Organization
  • Cannot run Control Tower multiple times in same Organization

If You Need Separate Environments

Option 1: Multiple OUs within one landing zone

  One Landing Zone
    ├── Production OU (strict guardrails)
    ├── Development OU (relaxed guardrails)
    └── Sandbox OU (minimal guardrails)

Option 2: Separate AWS Organizations

  ┌─────────────────────┐  ┌─────────────────────┐
  │ Organization A      │  │ Organization B      │
  │ Control Tower A     │  │ Control Tower B     │
  │ Landing Zone A      │  │ Landing Zone B      │
  └─────────────────────┘  └─────────────────────┘

You can also build a landing zone manually or with other tools (Terraform, CDK), but Control Tower is AWS’s managed solution.


Control Tower Controls (3 Types)

┌─────────────────────────────────────────────────────────────────┐
│  Preventive Controls                                             │
│  Implementation: SCPs                                            │
│  When: Blocks API calls before they happen                      │
├─────────────────────────────────────────────────────────────────┤
│  Detective Controls                                              │
│  Implementation: AWS Config Rules                                │
│  When: Detects non-compliance after resource exists             │
├─────────────────────────────────────────────────────────────────┤
│  Proactive Controls                                              │
│  Implementation: CloudFormation Hooks                            │
│  When: Validates during CFN deployment (before creation)        │
└─────────────────────────────────────────────────────────────────┘

Proactive controls only work for resources deployed via CloudFormation (not CLI/Console direct API calls).

Hooks vs Other Controls

CloudFormation HooksSCPsConfig Rules
WhenDuring deploymentAt API callAfter creation
ActionBlock/allow deploymentBlock APIDetect + remediate
ScopeCloudFormation onlyAll API callsAll resources

Audit Account vs Log Archive Account

AccountPurpose
AuditSecurity team works here, investigates, reviews
Log ArchiveStores logs (CloudTrail, Config), immutable storage

Audit Account Capabilities

  • SNS notifications for Config rule violations
  • Cross-account IAM roles for security auditing
  • Access to Security Hub (aggregated findings)
  • Access to GuardDuty (delegated admin)
  • CloudTrail organization trail access
  • Read-only access to member accounts (cannot modify workload resources)

CloudFormation Hooks

Custom code that runs before/after CloudFormation provisions a resource. Validates or modifies resources during deployment.

CloudFormation: "I'm about to create S3 bucket"
        │
        ▼
┌─────────────────────────────────────────────────────────────────┐
│ Hook (pre-create)                                                │
│                                                                  │
│ "Is encryption enabled?"                                        │
│   Yes → ALLOW (continue)                                        │
│   No  → FAIL (block deployment)                                 │
└─────────────────────────────────────────────────────────────────┘
        │
        ▼
CloudFormation creates S3 bucket (if allowed)

Hook Types

TypeWhen it runs
Pre-provisionBefore resource is created/updated/deleted
Post-provisionAfter resource is created/updated/deleted

AWS-Managed vs Custom Hooks

TypeDescription
AWS-managedPre-built by AWS (encryption, public access checks)
CustomYou build for your specific rules

Register Custom Hook

# 1. Create hook project
cfn init  # Choose "Hook", select language (Python/Java/Go)

# Project structure:
# my-hook/
#   ├── src/
#   │     └── handlers.py      ← Your validation code
#   ├── hook-role.yaml         ← IAM role for hook
#   └── my-hook.json           ← Hook schema

# 2. Build and submit to CloudFormation registry
cfn submit --dry-run    # Validate
cfn submit              # Register

# 3. Activate hook
aws cloudformation set-type-configuration \
  --type HOOK \
  --type-name MyOrg::S3::EncryptionCheck \
  --configuration '{
    "CloudFormationConfiguration": {
      "HookConfiguration": {
        "TargetStacks": "ALL",
        "FailureMode": "FAIL"
      }
    }
  }'

Hook Configuration Options

SettingOptionsMeaning
TargetStacksALLValidates all stacks
NONEHook disabled
FailureModeFAILBlock deployment
WARNLog warning, allow deployment

Hook Scope

Regional: Hooks must be activated in each region separately.

us-east-1: Hook activated ✓ → Validates stacks
us-west-2: Hook not activated → No validation
eu-west-1: Hook activated ✓ → Validates stacks

All stacks: Once activated, validates ALL CloudFormation stacks in that account/region. Individual stacks cannot bypass hooks.

Control Tower proactive controls: When enabled on an OU, automatically activates across all governed regions.

Stack-Level Filter

You can exclude specific stacks in hook schema:

{
  "handlers": {
    "preCreate": {
      "targetNames": ["AWS::S3::Bucket"],
      "targetFilters": {
        "exclude": {
          "stackNames": ["LegacyStack-*"]
        }
      }
    }
  }
}

Simple Hook Handler Example (Python)

def handler(event, context):
    # Get resource properties CFN is about to create
    resource_properties = event['hookContext']['targetModel']['resourceProperties']
    
    # Your validation logic
    if 'BucketEncryption' not in resource_properties:
        return {
            'status': 'FAILED',
            'message': 'S3 bucket must have encryption enabled'
        }
    
    return {'status': 'SUCCESS'}

Account Factory Customization (AFC)

Built-in Control Tower feature that lets you customize new accounts during provisioning using Service Catalog blueprints.

Account Factory vs CfCT

Account Factory CustomizationCfCT
Built into Control TowerYesNo (separate solution to deploy)
When it runsDuring account creationAfter account creation
ConfigurationService Catalog blueprintsmanifest.yaml + templates
Use caseAccount baseline at birthOngoing customizations

How AFC Works

┌─────────────────────────────────────────────────────────────────┐
│  Control Tower Account Factory                                   │
│                                                                  │
│  1. Admin creates blueprint (CFN template) in Service Catalog   │
│  2. Admin enables blueprint in Account Factory                  │
│  3. User provisions new account                                 │
│  4. Account Factory deploys blueprint to new account            │
└─────────────────────────────────────────────────────────────────┘

Timeline:
  Account creation starts
        │
        ├── Control Tower baseline (guardrails, IAM Identity Center)
        │
        ├── AFC blueprints deploy (your custom resources)
        │
        ▼
  Account ready with customizations

Service Catalog Concepts

ConceptWhat it is
ProductA CloudFormation template wrapped for self-service
PortfolioCollection of products, with access controls
BlueprintProduct specifically for Account Factory customization
Provisioned ProductInstance of a product deployed to an account

Setting Up AFC

Step 1: Create blueprint product in Service Catalog (Hub account)

Hub account = Management account or delegated admin account

Service Catalog → Products → Create product
  - Product name: "Security Baseline"
  - Template: your CloudFormation template
  - Version: "v1"

Step 2: Create portfolio and add product

Service Catalog → Portfolios → Create portfolio
  - Name: "Account Factory Blueprints"
  - Add product: "Security Baseline"

Step 3: Enable in Account Factory

Control Tower → Account Factory → Blueprints
  - Select portfolio
  - Enable blueprints

Step 4: Provision account with blueprint

Control Tower → Account Factory → Create account
  - Account details (name, email, OU)
  - Select blueprints to apply
  - Create

Blueprint Template Example

AWSTemplateFormatVersion: '2010-09-09'
Description: Security baseline for new accounts

Parameters:
  AccountEmail:
    Type: String
    Description: Account email (passed by Account Factory)

Resources:
  SecurityAlertTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: security-alerts
      
  CloudTrailBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'cloudtrail-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  ConfigRecorder:
    Type: AWS::Config::ConfigurationRecorder
    Properties:
      RoleARN: !GetAtt ConfigRole.Arn
      RecordingGroup:
        AllSupported: true

AFC vs Manual Service Catalog

AspectAFC (via Account Factory)Manual Service Catalog
TriggerAutomatic during account creationManual launch by user
TargetNew accounts onlyAny account
IntegrationBuilt into Control Tower workflowStandalone
ParametersAccount Factory passes account infoUser provides all params

Multiple Blueprints

You can apply multiple blueprints to one account:

New Account
    │
    ├── Blueprint: Network Baseline (VPC, subnets)
    ├── Blueprint: Security Baseline (Config, GuardDuty)
    └── Blueprint: Logging Baseline (CloudWatch, S3)

Each blueprint = separate StackSet instance in the account.

Blueprint Parameters

Account Factory automatically passes these parameters to blueprints:

ParameterValue
AccountIdNew account’s ID
AccountEmailNew account’s email
AccountNameNew account’s name
SSOUserEmailSSO user’s email
SSOUserFirstNameSSO user’s first name
SSOUserLastNameSSO user’s last name

Your template can use these:

Parameters:
  AccountId:
    Type: String
  AccountEmail:
    Type: String

Resources:
  AlertTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Protocol: email
          Endpoint: !Ref AccountEmail

When to Use AFC vs CfCT

ScenarioUse
Resources needed at account birthAFC
Resources that change over timeCfCT
Simple baseline (VPC, IAM roles)AFC
Complex customizations with dependenciesCfCT
Want Control Tower native solutionAFC
Need SCPs per OUCfCT

You can use both: AFC for initial setup, CfCT for ongoing management.


Customizations for AWS Control Tower (CfCT)

AWS solution that deploys custom CloudFormation templates and SCPs to accounts managed by Control Tower. Automates customizations when new accounts are created.

Problem It Solves

Without CfCT:

  1. Control Tower creates new account
  2. You manually deploy custom resources
  3. Repeat for every new account

With CfCT:

  1. Control Tower creates new account
  2. CfCT automatically deploys your customizations

CfCT Is Not Built Into Control Tower

CfCT is a separate AWS Solution you deploy yourself via CloudFormation template from AWS Solutions page.

CfCT Architecture

┌─────────────────────────────────────────────────────────────────┐
│  S3/CodeCommit ──► CodePipeline ──► CodeBuild ──► Lambda ──► StackSets
│  (source)          (orchestrate)    (validate)    (deploy)
└─────────────────────────────────────────────────────────────────┘

manifest.yaml Example

region: us-east-1
version: 2021-03-15

resources:
  # Deploy CloudFormation template
  - name: SecurityBaseline
    resource_file: templates/security-baseline.yaml
    deployment_targets:
      organizational_units:
        - Workloads
    parameters:
      - parameter_key: Environment
        parameter_value: production

  # Apply SCP
  - name: DenyS3Public
    resource_file: policies/deny-s3-public.json
    deployment_targets:
      organizational_units:
        - Workloads
    deploy_method: scp

CfCT Components

ComponentRole
S3/CodeCommitStore manifest + templates + policies
CodePipelineOrchestrate stages in order
CodeBuildValidate syntax, lint templates, parse manifest
SCP LambdaCreate/attach SCPs via Organizations API
StackSet LambdaCreate/deploy StackSets via CloudFormation API
Lifecycle LambdaAuto-apply customizations to new accounts
EventBridge RuleTriggers Lifecycle Lambda on Control Tower events

CfCT Component Details

CodePipeline

Orchestrates the entire workflow. Defines stages and order of execution.

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│ Source   │───►│ Build    │───►│ Deploy   │───►│ Deploy   │
│          │    │          │    │ (SCPs)   │    │(StackSets)│
│ S3 or    │    │ CodeBuild│    │ Lambda   │    │ Lambda   │
│ CodeCommit    │ validates│    │          │    │          │
└──────────┘    └──────────┘    └──────────┘    └──────────┘

If any stage fails → pipeline stops → you get notified

CodeBuild

Validates and prepares your customizations.

  1. Parse manifest.yaml (validate YAML syntax, check required fields)
  2. Validate CloudFormation templates (cfn-lint, verify files exist)
  3. Validate SCP JSON (syntax, policy structure)
  4. Create structured output for Lambda stages

CodeBuild doesn’t compile anything—CFN templates and JSON don’t need compilation. “Package” means organizing and structuring data for Lambda stages.

SCP Lambda

Creates/updates SCPs and attaches to target OUs.

# What SCP Lambda does
organizations.create_policy(Content=deny-s3-public.json)
organizations.attach_policy(PolicyId, TargetId=WorkloadsOU)

StackSet Lambda

Creates/updates StackSets and deploys to target accounts.

# What StackSet Lambda does
cloudformation.create_stack_set(TemplateBody=vpc.yaml)
cloudformation.create_stack_instances(
    StackSetName,
    DeploymentTargets={OrganizationalUnitIds}
)

EventBridge Rule

CfCT creates an EventBridge rule that listens for Control Tower lifecycle events.

{
  "source": ["aws.controltower"],
  "detail-type": ["AWS Service Event via CloudTrail"],
  "detail": {
    "eventName": ["CreateManagedAccount", "UpdateManagedAccount"]
  }
}

Target: Lifecycle Lambda

Lifecycle Lambda

Triggered by EventBridge when Control Tower creates new account or moves account to different OU.

Control Tower creates account
        │
        ▼
EventBridge receives "CreateManagedAccount" event
        │
        ▼
Rule matches → triggers Lifecycle Lambda
        │
        ▼
Lambda applies customizations to new account

What Lifecycle Lambda does:

def handler(event):
    account_id = event['detail']['serviceEventDetails']['createManagedAccountStatus']['account']['accountId']
    ou_id = get_ou_for_account(account_id)
    
    # Read manifest to find which StackSets apply to this OU
    stacksets = get_stacksets_for_ou(ou_id)
    
    for stackset in stacksets:
        # Add new account to existing StackSet
        cloudformation.create_stack_instances(
            StackSetName=stackset['name'],
            Accounts=[account_id],
            Regions=stackset['regions']
        )
    
    # SCPs: Nothing to do! Account inherits from OU automatically

“Add to StackSet instances” = Call create_stack_instances() to deploy existing StackSet template to the new account.

Why Lifecycle Lambda doesn’t handle SCPs: SCPs are attached to OUs, not accounts. When new account joins OU, it automatically inherits OU’s SCPs.


Different Triggers, Different Lambdas

TriggerWhat runsWhat happens
S3/CodeCommit changeFull pipeline (CodeBuild → SCP Lambda → StackSet Lambda)Update all SCPs and StackSets
New account createdLifecycle Lambda onlyAdd account to existing StackSets
Account moved to different OULifecycle Lambda onlyAdd/remove from StackSets based on new OU

Lifecycle Lambda works independently. It calls AWS APIs directly, not other Lambdas.


StackSet Permissions

StackSets need two IAM roles to deploy across accounts.

Management Account:
  StackSet Administration Role
  - Trust: cloudformation.amazonaws.com
  - Permission: sts:AssumeRole on execution roles

Target Account (each member account):
  StackSet Execution Role
  - Trust: Management Account
  - Permission: Create actual resources (EC2, VPC, IAM...)

Permission Models

ModelHow it works
Self-managedYou create both roles manually
Service-managedAWS Organizations creates roles automatically

With Control Tower: Use service-managed permissions (recommended).

cloudformation.create_stack_set(
    PermissionModel="SERVICE_MANAGED",
    AutoDeployment={
        "Enabled": True,
        "RetainStacksOnAccountRemoval": False
    }
)

CfCT Setup Steps

1. Deploy CfCT solution (CloudFormation template from AWS)
   https://aws.amazon.com/solutions/implementations/
   customizations-for-aws-control-tower/

2. CfCT creates:
   - CodePipeline
   - CodeBuild
   - S3 bucket (or CodeCommit repo)
   - Lambda functions
   - EventBridge rule

3. Upload your customizations:
   s3://cfct-bucket/
     ├── manifest.yaml
     ├── templates/
     │     └── security-baseline.yaml
     └── policies/
           └── deny-s3-public.json

4. Pipeline triggers automatically on upload

Common Customizations

CustomizationExample
NetworkingVPCs, Transit Gateway attachments, VPC endpoints
SecurityIAM roles, Config rules, GuardDuty, Security Hub
LoggingCloudWatch log groups, Kinesis streams
GovernanceService Catalog portfolios, tagging policies
SCPsAdditional preventive controls

Summary

TermWhat it is
Control TowerAWS service that builds/manages landing zones
Landing ZoneMulti-account architecture pattern
Preventive ControlsSCPs (block API calls)
Detective ControlsConfig Rules (detect after creation)
Proactive ControlsCloudFormation Hooks (validate during deployment)
CfCTSolution to deploy custom templates/SCPs automatically
Audit AccountSecurity team investigation
Log Archive AccountImmutable log storage
Lifecycle LambdaAuto-applies customizations to new accounts via EventBridge