AWS Control Tower CDK Complete Setup Guide
Prerequisites
System Requirements
- Node.js: 18.x or later
- AWS CLI: v2.x
- TypeScript: 4.x or later
- Git: For version control
Phase 1: Environment Setup
1. Install Required Tools
# Install Node.js (if not installed)
# Download from https://nodejs.org or use package manager
# For macOS:
brew install node
# For Ubuntu/Debian:
sudo apt update && sudo apt install nodejs npm
# Install AWS CLI v2
# For macOS:
brew install awscli
# For Linux:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Verify installations
node --version
npm --version
aws --version
2. Configure AWS CLI
# Configure AWS credentials for your management account
aws configure
# AWS Access Key ID: [Your access key]
# AWS Secret Access Key: [Your secret key]
# Default region name: us-east-1
# Default output format: json
# Verify configuration
aws sts get-caller-identity
3. Install AWS CDK
# Install CDK globally
npm install -g aws-cdk
# Verify installation
cdk --version
# Bootstrap CDK (one-time setup per region/account)
cdk bootstrap aws://YOUR-ACCOUNT-ID/us-east-1
4. Create Project Directory
# Create and navigate to project directory
mkdir aws-control-tower-cdk
cd aws-control-tower-cdk
# Initialize TypeScript CDK project
cdk init app --language typescript
# Install additional dependencies
npm install @aws-cdk/aws-organizations @aws-cdk/aws-controltower \
@aws-cdk/aws-ssoadmin @aws-cdk/aws-identitystore @aws-cdk/aws-ec2 \
@aws-cdk/aws-cloudtrail @aws-cdk/aws-cloudwatch @aws-cdk/aws-s3 \
@aws-cdk/aws-iam
Phase 2: Email Creation (Manual)
Required Gmail Accounts
Create these Gmail accounts manually:
testawsrahardja@gmail.com(existing - Management Account)testawsrahardjaaudit@gmail.com(create new)testawsrahardjalogs@gmail.com(create new)
NOTE Store credentials securely - you'll need them later for MFA setup.
Phase 3: CDK Project Structure
1. Create Project Structure
# Create directory structure
mkdir -p lib/stacks lib/constructs lib/config
# Create configuration file
touch lib/config/accounts.ts
touch lib/constructs/account-baseline.ts
touch lib/stacks/control-tower-stack.ts
touch lib/stacks/identity-center-stack.ts
touch lib/stacks/governance-stack.ts
2. Update Configuration Files
Create lib/config/accounts.ts:
export interface AccountConfig {
name: string;
email: string;
vpcCidr: string;
environment: "prod" | "test" | "dev" | "shared";
billingThreshold: number;
}
export const ACCOUNTS: Record<string, AccountConfig> = {
prod: {
name: "production",
email: "testawsrahardja+prod@gmail.com",
vpcCidr: "10.0.0.0/16",
environment: "prod",
billingThreshold: 1000,
},
test: {
name: "testing",
email: "testawsrahardja+test@gmail.com",
vpcCidr: "10.1.0.0/16",
environment: "test",
billingThreshold: 500,
},
dev: {
name: "development",
email: "testawsrahardja+dev@gmail.com",
vpcCidr: "10.2.0.0/16",
environment: "dev",
billingThreshold: 200,
},
shared: {
name: "shared-services",
email: "testawsrahardja+shared@gmail.com",
vpcCidr: "10.3.0.0/16",
environment: "shared",
billingThreshold: 300,
},
};
export const CORE_ACCOUNTS = {
management: "testawsrahardja@gmail.com",
audit: "testawsrahardja-audit@gmail.com",
logArchive: "testawsrahardja-logs@gmail.com",
};
3. Create Control Tower Stack
Create lib/stacks/control-tower-stack.ts:
import { Stack, StackProps, Duration } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as organizations from "aws-cdk-lib/aws-organizations";
import * as controltower from "aws-cdk-lib/aws-controltower";
import { ACCOUNTS, CORE_ACCOUNTS } from "../config/accounts";
export class ControlTowerStack extends Stack {
public readonly accounts: Record<string, organizations.CfnAccount> = {};
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Create Control Tower Landing Zone
const controlTower = new controltower.CfnLandingZone(this, "ControlTower", {
version: "3.3",
manifest: {
governanceBaselineConfiguration: {
enableIdentityCenter: true,
enableCloudTrail: true,
},
organizationalUnitConfiguration: [
{
name: "Security",
type: "CORE",
},
{
name: "Workloads",
type: "CUSTOM",
},
],
centralizedLoggingConfiguration: {
accountId: "LOG_ARCHIVE",
configurations: {
loggingBucket: {
retentionConfiguration: {
retentionPeriod: 365,
},
},
accessLoggingBucket: {
retentionConfiguration: {
retentionPeriod: 365,
},
},
},
},
securityConfiguration: {
accountId: "AUDIT",
},
},
});
// Create workload accounts
Object.entries(ACCOUNTS).forEach(([key, config]) => {
this.accounts[key] = new organizations.CfnAccount(this, `${key}Account`, {
accountName: config.name,
email: config.email,
tags: [
{
key: "Environment",
value: config.environment,
},
{
key: "ManagedBy",
value: "CDK",
},
],
});
// Add dependency on Control Tower
this.accounts[key].addDependency(controlTower);
});
}
}
4. Create Account Baseline Construct
Create lib/constructs/account-baseline.ts:
import { Construct } from "constructs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as cloudtrail from "aws-cdk-lib/aws-cloudtrail";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import { Duration, RemovalPolicy } from "aws-cdk-lib";
import { AccountConfig } from "../config/accounts";
export interface AccountBaselineProps {
accountConfig: AccountConfig;
logArchiveBucket?: string;
}
export class AccountBaseline extends Construct {
public readonly vpc: ec2.Vpc;
public readonly cloudTrail: cloudtrail.Trail;
constructor(scope: Construct, id: string, props: AccountBaselineProps) {
super(scope, id);
const { accountConfig } = props;
// Create VPC with appropriate configuration
this.vpc = new ec2.Vpc(this, "VPC", {
cidr: accountConfig.vpcCidr,
maxAzs: accountConfig.environment === "prod" ? 3 : 2,
natGateways: accountConfig.environment === "prod" ? 2 : 1,
subnetConfiguration: [
{
cidrMask: 24,
name: "Public",
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: "Private",
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 28,
name: "Database",
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
enableDnsHostnames: true,
enableDnsSupport: true,
});
// VPC Flow Logs
const flowLogRole = new iam.Role(this, "FlowLogRole", {
assumedBy: new iam.ServicePrincipal("vpc-flow-logs.amazonaws.com"),
});
this.vpc.addFlowLog("FlowLogs", {
destination: ec2.FlowLogDestination.toCloudWatchLogs(
undefined,
flowLogRole,
),
});
// CloudTrail for additional logging
if (props.logArchiveBucket) {
const trailBucket = s3.Bucket.fromBucketName(
this,
"TrailBucket",
props.logArchiveBucket,
);
this.cloudTrail = new cloudtrail.Trail(this, "AdditionalCloudTrail", {
bucket: trailBucket,
includeGlobalServiceEvents: true,
isMultiRegionTrail: true,
enableFileValidation: true,
});
}
// Billing Alarm
const billingAlarm = new cloudwatch.Alarm(this, "BillingAlarm", {
metric: new cloudwatch.Metric({
namespace: "AWS/Billing",
metricName: "EstimatedCharges",
dimensionsMap: {
Currency: "USD",
},
statistic: "Maximum",
period: Duration.days(1),
}),
threshold: accountConfig.billingThreshold,
evaluationPeriods: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
alarmDescription: `Billing alarm for ${accountConfig.name} account`,
});
// Security Group for SSH access (if needed)
const sshSecurityGroup = new ec2.SecurityGroup(this, "SSHSecurityGroup", {
vpc: this.vpc,
description: "Security group for SSH access",
allowAllOutbound: true,
});
// Restrict SSH to your IP (replace with your IP)
sshSecurityGroup.addIngressRule(
ec2.Peer.ipv4("0.0.0.0/0"), // Replace with your IP
ec2.Port.tcp(22),
"SSH access",
);
// Default security group restrictions
const defaultSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(
this,
"DefaultSecurityGroup",
this.vpc.vpcDefaultSecurityGroup,
);
// Remove default rules and add restrictive ones
defaultSecurityGroup.addIngressRule(
defaultSecurityGroup,
ec2.Port.allTraffic(),
"Self-referencing rule",
);
}
}
5. Create Identity Center Stack
Create lib/stacks/identity-center-stack.ts:
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ssoadmin from "aws-cdk-lib/aws-ssoadmin";
import * as identitystore from "aws-cdk-lib/aws-identitystore";
import { ACCOUNTS } from "../config/accounts";
export interface IdentityCenterStackProps extends StackProps {
accountIds: Record<string, string>;
}
export class IdentityCenterStack extends Stack {
constructor(scope: Construct, id: string, props: IdentityCenterStackProps) {
super(scope, id, props);
// Get Identity Center instance
const identityStoreInstance =
new identitystore.CfnInstanceAccessControlAttributeConfiguration(
this,
"IdentityStoreInstance",
);
// Create Permission Sets
const adminPermissionSet = new ssoadmin.CfnPermissionSet(
this,
"AdminPermissionSet",
{
instanceArn: identityStoreInstance.instanceArn,
name: "AdministratorAccess",
description: "Full administrative access",
sessionDuration: "PT2H", // 2 hours
relayStateType: "https://console.aws.amazon.com/",
},
);
const powerUserPermissionSet = new ssoadmin.CfnPermissionSet(
this,
"PowerUserPermissionSet",
{
instanceArn: identityStoreInstance.instanceArn,
name: "PowerUserAccess",
description: "Power user access (no IAM)",
sessionDuration: "PT4H", // 4 hours
},
);
const readOnlyPermissionSet = new ssoadmin.CfnPermissionSet(
this,
"ReadOnlyPermissionSet",
{
instanceArn: identityStoreInstance.instanceArn,
name: "ReadOnlyAccess",
description: "Read-only access",
sessionDuration: "PT8H", // 8 hours
},
);
const developerPermissionSet = new ssoadmin.CfnPermissionSet(
this,
"DeveloperPermissionSet",
{
instanceArn: identityStoreInstance.instanceArn,
name: "DeveloperAccess",
description: "Developer access with restricted permissions",
sessionDuration: "PT4H",
},
);
// Attach AWS managed policies to permission sets
new ssoadmin.CfnManagedPolicyAttachment(this, "AdminPolicyAttachment", {
instanceArn: identityStoreInstance.instanceArn,
permissionSetArn: adminPermissionSet.attrPermissionSetArn,
managedPolicyArn: "arn:aws:iam::aws:policy/AdministratorAccess",
});
new ssoadmin.CfnManagedPolicyAttachment(this, "PowerUserPolicyAttachment", {
instanceArn: identityStoreInstance.instanceArn,
permissionSetArn: powerUserPermissionSet.attrPermissionSetArn,
managedPolicyArn: "arn:aws:iam::aws:policy/PowerUserAccess",
});
new ssoadmin.CfnManagedPolicyAttachment(this, "ReadOnlyPolicyAttachment", {
instanceArn: identityStoreInstance.instanceArn,
permissionSetArn: readOnlyPermissionSet.attrPermissionSetArn,
managedPolicyArn: "arn:aws:iam::aws:policy/ReadOnlyAccess",
});
// Custom developer policy
const developerPolicy = new ssoadmin.CfnPermissionSet(
this,
"DeveloperCustomPolicy",
{
instanceArn: identityStoreInstance.instanceArn,
name: "DeveloperCustomAccess",
inlinePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Action: [
"ec2:*",
"lambda:*",
"s3:*",
"cloudformation:*",
"logs:*",
"cloudwatch:*",
],
Resource: "*",
},
{
Effect: "Deny",
Action: ["iam:*", "organizations:*", "account:*"],
Resource: "*",
},
],
}),
},
);
// Create Groups (Note: User creation typically done manually or via
// external IdP)
const adminGroup = new identitystore.CfnGroup(this, "AdminGroup", {
identityStoreId: identityStoreInstance.instanceArn.split("/")[1],
displayName: "Administrators",
description: "Administrative users with full access",
});
const developerGroup = new identitystore.CfnGroup(this, "DeveloperGroup", {
identityStoreId: identityStoreInstance.instanceArn.split("/")[1],
displayName: "Developers",
description: "Developer users with limited access",
});
const readOnlyGroup = new identitystore.CfnGroup(this, "ReadOnlyGroup", {
identityStoreId: identityStoreInstance.instanceArn.split("/")[1],
displayName: "ReadOnly",
description: "Read-only users",
});
// Account Assignments (assign permission sets to accounts)
Object.entries(props.accountIds).forEach(([accountKey, accountId]) => {
// Admin access to all accounts
new ssoadmin.CfnAccountAssignment(this, `AdminAssignment${accountKey}`, {
instanceArn: identityStoreInstance.instanceArn,
permissionSetArn: adminPermissionSet.attrPermissionSetArn,
principalId: adminGroup.attrGroupId,
principalType: "GROUP",
targetId: accountId,
targetType: "AWS_ACCOUNT",
});
// Developer access to dev and test accounts only
if (accountKey === "dev" || accountKey === "test") {
new ssoadmin.CfnAccountAssignment(
this,
`DeveloperAssignment${accountKey}`,
{
instanceArn: identityStoreInstance.instanceArn,
permissionSetArn: developerPermissionSet.attrPermissionSetArn,
principalId: developerGroup.attrGroupId,
principalType: "GROUP",
targetId: accountId,
targetType: "AWS_ACCOUNT",
},
);
}
// Read-only access to all accounts
new ssoadmin.CfnAccountAssignment(
this,
`ReadOnlyAssignment${accountKey}`,
{
instanceArn: identityStoreInstance.instanceArn,
permissionSetArn: readOnlyPermissionSet.attrPermissionSetArn,
principalId: readOnlyGroup.attrGroupId,
principalType: "GROUP",
targetId: accountId,
targetType: "AWS_ACCOUNT",
},
);
});
}
}
6. Create Governance Stack
Create lib/stacks/governance-stack.ts:
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as organizations from "aws-cdk-lib/aws-organizations";
export interface GovernanceStackProps extends StackProps {
workloadsOuId: string;
}
export class GovernanceStack extends Stack {
constructor(scope: Construct, id: string, props: GovernanceStackProps) {
super(scope, id, props);
// Deny Root User Policy
const denyRootUserPolicy = new organizations.CfnPolicy(
this,
"DenyRootUserPolicy",
{
name: "DenyRootUserAccess",
description: "Prevent root user access except for specific actions",
type: "SERVICE_CONTROL_POLICY",
content: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "DenyRootUserAccess",
Effect: "Deny",
Principal: {
AWS: "*",
},
Action: "*",
Resource: "*",
Condition: {
StringEquals: {
"aws:PrincipalType": "Root",
},
StringNotEquals: {
"aws:PrincipalServiceName": [
"cloudformation.amazonaws.com",
"config.amazonaws.com",
],
},
},
},
],
}),
},
);
// Region Restriction Policy
const restrictRegionsPolicy = new organizations.CfnPolicy(
this,
"RestrictRegionsPolicy",
{
name: "RestrictRegions",
description: "Restrict usage to specific regions",
type: "SERVICE_CONTROL_POLICY",
content: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "RestrictRegions",
Effect: "Deny",
Action: "*",
Resource: "*",
Condition: {
StringNotEquals: {
"aws:RequestedRegion": ["us-east-1", "us-west-2"],
},
"ForAllValues:StringNotEquals": {
"aws:PrincipalServiceName": [
"cloudformation.amazonaws.com",
"config.amazonaws.com",
"cloudtrail.amazonaws.com",
],
},
},
},
],
}),
},
);
// Enforce MFA Policy
const enforceMfaPolicy = new organizations.CfnPolicy(
this,
"EnforceMfaPolicy",
{
name: "EnforceMFA",
description: "Require MFA for sensitive actions",
type: "SERVICE_CONTROL_POLICY",
content: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "EnforceMFAForSensitiveActions",
Effect: "Deny",
Action: [
"iam:*",
"organizations:*",
"account:*",
"billing:*",
"aws-portal:*",
],
Resource: "*",
Condition: {
BoolIfExists: {
"aws:MultiFactorAuthPresent": "false",
},
},
},
],
}),
},
);
// Prevent Account Closure Policy
const preventAccountClosurePolicy = new organizations.CfnPolicy(
this,
"PreventAccountClosurePolicy",
{
name: "PreventAccountClosure",
description: "Prevent accidental account closure",
type: "SERVICE_CONTROL_POLICY",
content: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Sid: "PreventAccountClosure",
Effect: "Deny",
Action: ["account:CloseAccount", "organizations:CloseAccount"],
Resource: "*",
},
],
}),
},
);
// Attach policies to Workloads OU
new organizations.CfnPolicyAttachment(
this,
"DenyRootUserPolicyAttachment",
{
policyId: denyRootUserPolicy.ref,
targetId: props.workloadsOuId,
targetType: "ORGANIZATIONAL_UNIT",
},
);
new organizations.CfnPolicyAttachment(
this,
"RestrictRegionsPolicyAttachment",
{
policyId: restrictRegionsPolicy.ref,
targetId: props.workloadsOuId,
targetType: "ORGANIZATIONAL_UNIT",
},
);
new organizations.CfnPolicyAttachment(this, "EnforceMfaPolicyAttachment", {
policyId: enforceMfaPolicy.ref,
targetId: props.workloadsOuId,
targetType: "ORGANIZATIONAL_UNIT",
});
new organizations.CfnPolicyAttachment(
this,
"PreventAccountClosurePolicyAttachment",
{
policyId: preventAccountClosurePolicy.ref,
targetId: props.workloadsOuId,
targetType: "ORGANIZATIONAL_UNIT",
},
);
}
}
7. Update Main App File
Update lib/aws-control-tower-cdk-stack.ts:
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { ControlTowerStack } from "./stacks/control-tower-stack";
import { IdentityCenterStack } from "./stacks/identity-center-stack";
import { GovernanceStack } from "./stacks/governance-stack";
import { AccountBaseline } from "./constructs/account-baseline";
import { ACCOUNTS } from "./config/accounts";
export class AwsControlTowerCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Deploy Control Tower and create accounts
const controlTowerStack = new ControlTowerStack(this, "ControlTower", {
description: "AWS Control Tower Landing Zone and Account Creation",
});
// Extract account IDs for use in other stacks
const accountIds: Record<string, string> = {};
Object.keys(ACCOUNTS).forEach((key) => {
accountIds[key] = controlTowerStack.accounts[key].ref;
});
// Deploy Identity Center configuration
const identityCenterStack = new IdentityCenterStack(
this,
"IdentityCenter",
{
accountIds: accountIds,
description: "AWS IAM Identity Center configuration",
},
);
identityCenterStack.addDependency(controlTowerStack);
// Deploy governance policies
const governanceStack = new GovernanceStack(this, "Governance", {
workloadsOuId: "ou-root-workloads", // Get this from Control Tower
description: "Governance policies and SCPs",
});
governanceStack.addDependency(controlTowerStack);
// Deploy account baselines (these would be deployed to each account separately)
Object.entries(ACCOUNTS).forEach(([key, config]) => {
const baselineStack = new cdk.Stack(this, `${key}Baseline`, {
description: `Account baseline for ${config.name}`,
env: {
account: accountIds[key],
region: props?.env?.region || "us-east-1",
},
});
new AccountBaseline(baselineStack, "Baseline", {
accountConfig: config,
});
baselineStack.addDependency(controlTowerStack);
});
}
}
Update bin/aws-control-tower-cdk.ts:
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { AwsControlTowerCdkStack } from "../lib/aws-control-tower-cdk-stack";
const app = new cdk.App();
new AwsControlTowerCdkStack(app, "AwsControlTowerCdkStack", {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION || "us-east-1",
},
description: "Complete AWS Control Tower setup with CDK",
});
Phase 4: Deployment
1. Validate and Synthesize
# Install dependencies
npm install
# Compile TypeScript
npm run build
# Synthesize CloudFormation templates
cdk synth
# List all stacks
cdk ls
# Check for any issues
cdk doctor
2. Deploy Control Tower
# Deploy Control Tower stack first (this takes 60-90 minutes)
cdk deploy AwsControlTowerCdkStack/ControlTower
# Monitor deployment progress
aws logs tail /aws/controltower/CloudTrailLogs --follow
3. Manual Root User Security (CRITICAL)
⚠️ IMPORTANT This step cannot be automated and must be done manually:
For each of the 6 accounts created:
# Check your email for account creation notifications
# For each account:
# 1. Click verification link in email
# 2. Set root password (store in password manager)
# 3. Sign in as root user
# 4. Go to Account Settings → Security Credentials
# 5. Enable MFA (Virtual MFA device recommended)
# 6. Test MFA login
# 7. Store credentials securely
# 8. NEVER use root again after this setup
4. Deploy Remaining Stacks
# Deploy Identity Center configuration
cdk deploy AwsControlTowerCdkStack/IdentityCenter
# Deploy governance policies
cdk deploy AwsControlTowerCdkStack/Governance
# Deploy account baselines (deploy to each account)
cdk deploy AwsControlTowerCdkStack/prodBaseline --profile prod-account
cdk deploy AwsControlTowerCdkStack/testBaseline --profile test-account
cdk deploy AwsControlTowerCdkStack/devBaseline --profile dev-account
cdk deploy AwsControlTowerCdkStack/sharedBaseline --profile shared-account
5. Configure AWS CLI Profiles for Each Account
# Configure profiles for each account (after SSO is set up)
aws configure sso --profile management
aws configure sso --profile prod
aws configure sso --profile test
aws configure sso --profile dev
aws configure sso --profile shared
# Test access to each account
aws sts get-caller-identity --profile management
aws sts get-caller-identity --profile prod
aws sts get-caller-identity --profile test
aws sts get-caller-identity --profile dev
aws sts get-caller-identity --profile shared
Phase 5: Post-Deployment Configuration
1. Create Users in Identity Center
# This is typically done through the AWS Console
# Navigate to IAM Identity Center → Users → Add users
# Or integrate with external identity provider
# Add users to appropriate groups:
# - Administrators group
# - Developers group
# - ReadOnly group
2. Test SSO Access
# Login via SSO
aws sso login --profile prod
# Verify access
aws s3 ls --profile prod
aws ec2 describe-instances --profile prod
3. Set Up Monitoring and Alerts
# Deploy additional monitoring stack if needed
cdk deploy MonitoringStack --profile management
# Configure CloudWatch dashboards
# Set up SNS topics for alerts
# Configure billing alerts
4. Backup and Documentation
# Export CloudFormation templates for backup
cdk synth --output cdk.out
# Create documentation
echo "# AWS Control Tower Setup Documentation" > SETUP.md
echo "Setup completed on $(date)" >> SETUP.md
echo "Accounts created:" >> SETUP.md
echo "- Management: testawsrahardja@gmail.com" >> SETUP.md
echo "- Audit: testawsrahardja-audit@gmail.com" >> SETUP.md
echo "- Log Archive: testawsrahardja-logs@gmail.com" >> SETUP.md
echo "- Production: testawsrahardja+prod@gmail.com" >> SETUP.md
echo "- Test: testawsrahardja+test@gmail.com" >> SETUP.md
echo "- Development: testawsrahardja+dev@gmail.com" >> SETUP.md
echo "- Shared Services: testawsrahardja+shared@gmail.com" >> SETUP.md
# Commit to git
git add .
git commit -m "Initial AWS Control Tower CDK setup"
git tag -a v1.0.0 -m "Control Tower setup complete"
Phase 6: Ongoing Maintenance
1. Regular Updates
# Update CDK to latest version
npm update -g aws-cdk
# Update project dependencies
npm update
# Check for security vulnerabilities
npm audit
npm audit fix
# Update CDK constructs
cdk diff # Check what will change
cdk deploy # Deploy updates
2. Monitoring Commands
# Check Control Tower status
aws controltower get-landing-zone --landing-zone-identifier YOUR_LZ_ID
# Monitor account compliance
aws organizations list-accounts
# Check guardrail compliance
aws controltower list-enabled-controls --target-identifier YOUR_OU_ID
# View CloudTrail logs
aws logs filter-log-events --log-group-name CloudTrail/ControlTowerLogs
# Check billing across accounts
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-01-31 \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE
3. Backup and Disaster Recovery
# Regular backup of CDK state
cdk synth --output backup-$(date +%Y%m%d)
# Export account configurations
aws organizations describe-organization > org-backup-$(date +%Y%m%d).json
aws organizations list-accounts > accounts-backup-$(date +%Y%m%d).json
# Backup IAM policies
aws iam list-policies --scope Local > iam-policies-$(date +%Y%m%d).json
4. Troubleshooting Commands
# Check CDK deployment status
cdk deploy --verbose
# Debug CloudFormation stack issues
aws cloudformation describe-stack-events --stack-name STACK_NAME
# Check Control Tower drift
aws controltower get-landing-zone-operation --operation-identifier OP_ID
# Validate SSO configuration
aws sso-admin list-permission-sets --instance-arn SSO_INSTANCE_ARN
# Test account access
aws sts assume-role \
--role-arn arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME \
--role-session-name TestSession
Phase 7: Advanced Configuration
1. Custom Guardrails
Create lib/constructs/custom-guardrails.ts:
import { Construct } from "constructs";
import * as config from "aws-cdk-lib/aws-config";
import * as iam from "aws-cdk-lib/aws-iam";
export class CustomGuardrails extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
// Custom Config Rule: Ensure EBS volumes are encrypted
new config.ManagedRule(this, "EBSEncryptionRule", {
identifier: config.ManagedRuleIdentifiers.EBS_ENCRYPTED_VOLUMES,
description: "Ensure all EBS volumes are encrypted",
});
// Custom Config Rule: Ensure S3 buckets have versioning enabled
new config.ManagedRule(this, "S3VersioningRule", {
identifier: config.ManagedRuleIdentifiers.S3_BUCKET_VERSIONING_ENABLED,
description: "Ensure S3 buckets have versioning enabled",
});
// Custom Config Rule: Check for unused security groups
new config.ManagedRule(this, "UnusedSecurityGroupsRule", {
identifier:
config.ManagedRuleIdentifiers.EC2_SECURITY_GROUP_ATTACHED_TO_ENI,
description: "Check for unused security groups",
});
// Custom Lambda function for advanced compliance checks
const complianceRole = new iam.Role(this, "ComplianceRole", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicExecutionRole",
),
iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/ConfigRole"),
],
});
}
}
2. Cross-Account CI/CD Pipeline
Create lib/stacks/cicd-stack.ts:
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipelineActions from "aws-cdk-lib/aws-codepipeline-actions";
import * as codebuild from "aws-cdk-lib/aws-codebuild";
import * as iam from "aws-cdk-lib/aws-iam";
export interface CiCdStackProps extends StackProps {
targetAccounts: Record<string, string>;
}
export class CiCdStack extends Stack {
constructor(scope: Construct, id: string, props: CiCdStackProps) {
super(scope, id, props);
// CodeBuild project for CDK deployment
const cdkDeployProject = new codebuild.Project(this, "CDKDeployProject", {
buildSpec: codebuild.BuildSpec.fromObject({
version: "0.2",
phases: {
install: {
"runtime-versions": {
nodejs: "18",
},
commands: ["npm install -g aws-cdk", "npm install"],
},
pre_build: {
commands: ["npm run build"],
},
build: {
commands: [
"cdk synth",
"cdk deploy --all --require-approval never",
],
},
},
}),
environment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
},
});
// Cross-account deployment roles
Object.entries(props.targetAccounts).forEach(([env, accountId]) => {
const crossAccountRole = new iam.Role(this, `CrossAccountRole${env}`, {
assumedBy: cdkDeployProject.role!,
roleName: `CDKDeploymentRole-${env}`,
description: `Cross-account deployment role for ${env} environment`,
});
crossAccountRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName("PowerUserAccess"),
);
});
// CodePipeline for automated deployments
const sourceOutput = new codepipeline.Artifact();
const buildOutput = new codepipeline.Artifact();
new codepipeline.Pipeline(this, "CDKPipeline", {
stages: [
{
stageName: "Source",
actions: [
new codepipelineActions.GitHubSourceAction({
actionName: "GitHub_Source",
owner: "YOUR_GITHUB_USERNAME",
repo: "aws-control-tower-cdk",
oauthToken: cdk.SecretValue.secretsManager("github-token"),
output: sourceOutput,
branch: "main",
}),
],
},
{
stageName: "Build",
actions: [
new codepipelineActions.CodeBuildAction({
actionName: "CDK_Build",
project: cdkDeployProject,
input: sourceOutput,
outputs: [buildOutput],
}),
],
},
],
});
}
}
3. Security and Compliance Automation
# Add Security Hub and GuardDuty automation
npm install @aws-cdk/aws-securityhub @aws-cdk/aws-guardduty
Create lib/constructs/security-baseline.ts:
import { Construct } from "constructs";
import * as securityhub from "aws-cdk-lib/aws-securityhub";
import * as guardduty from "aws-cdk-lib/aws-guardduty";
import * as inspector from "aws-cdk-lib/aws-inspector";
export class SecurityBaseline extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
// Enable Security Hub
const securityHub = new securityhub.CfnHub(this, "SecurityHub", {
tags: [
{
key: "ManagedBy",
value: "CDK",
},
],
});
// Enable AWS Foundational Security Standard
new securityhub.CfnStandard(this, "AWSFoundationalStandard", {
standardsArn:
"arn:aws:securityhub:::ruleset/finding-format/aws-foundational-security-standard",
disabledStandardsControls: [],
});
// Enable GuardDuty
new guardduty.CfnDetector(this, "GuardDutyDetector", {
enable: true,
findingPublishingFrequency: "FIFTEEN_MINUTES",
});
// Enable Inspector V2
new inspector.CfnAssessmentTemplate(this, "InspectorTemplate", {
assessmentTargetArn: "assessment-target-arn",
durationInSeconds: 3600,
rulesPackageArns: [
"arn:aws:inspector:us-east-1:316112463485:rulespackage/0-gEjTy7T7",
"arn:aws:inspector:us-east-1:316112463485:rulespackage/0-rExsr2X8",
],
});
}
}
Phase 8: Testing and Validation
1. Automated Testing
Create test/control-tower.test.ts:
import * as cdk from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
import { ControlTowerStack } from "../lib/stacks/control-tower-stack";
test("Control Tower Stack creates required resources", () => {
const app = new cdk.App();
const stack = new ControlTowerStack(app, "TestStack");
const template = Template.fromStack(stack);
// Test that Control Tower is created
template.hasResourceProperties("AWS::ControlTower::LandingZone", {
Version: "3.3",
});
// Test that accounts are created
template.resourceCountIs("AWS::Organizations::Account", 4);
});
test("Identity Center Stack creates permission sets", () => {
const app = new cdk.App();
const stack = new IdentityCenterStack(app, "TestStack", {
accountIds: {
prod: "123456789012",
test: "123456789013",
dev: "123456789014",
},
});
const template = Template.fromStack(stack);
// Test permission sets are created
template.resourceCountIs("AWS::SSO::PermissionSet", 4);
});
2. Integration Testing
# Run CDK tests
npm test
# Validate CloudFormation templates
cdk synth --validation
# Test cross-account access
aws sts assume-role \
--role-arn arn:aws:iam::PROD_ACCOUNT:role/CrossAccountAccessRole \
--role-session-name TestSession \
--profile management
# Validate guardrails
aws controltower list-enabled-controls \
--target-identifier ou-root-workloads
# Test compliance rules
aws config get-compliance-details-by-config-rule \
--config-rule-name required-tags
3. Performance and Cost Optimization
# Analyze costs
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=$(date +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE
# Check resource utilization
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--start-time 2024-01-01T00:00:00Z \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 3600 \
--statistics Average
# Optimize CDK bundle size
npm run build -- --optimization
cdk synth --output optimized-output
Final Checklist
# Complete setup verification script
cat > verify-setup.sh << 'EOF'
#!/bin/bash
echo "🔍 Verifying AWS Control Tower CDK Setup..."
# Check CDK version
echo "CDK Version:"
cdk --version
# Check AWS CLI configuration
echo "AWS CLI Configuration:"
aws sts get-caller-identity
# Verify Control Tower deployment
echo "Control Tower Status:"
aws organizations describe-organization
# Check account creation
echo "Created Accounts:"
aws organizations list-accounts \
--query 'Accounts[?Status==`ACTIVE`].[Name,Email,Id]' --output table
# Verify SSO configuration
echo "SSO Instance:"
aws sso-admin list-instances
# Check guardrails
echo "Active Guardrails:"
aws controltower list-enabled-controls --target-identifier ou-root-workloads
# Test account access
echo "Testing Account Access:"
for profile in management prod test dev shared; do
echo "Testing $profile account..."
aws sts get-caller-identity --profile $profile 2>/dev/null && \
echo "✅ $profile access OK" || echo "❌ $profile access failed"
done
echo "✅ Setup verification complete!"
EOF
chmod +x verify-setup.sh
./verify-setup.sh
Summary
This comprehensive CDK guide covers:
- ✅ Complete environment setup with all required tools
- ✅ Structured CDK project with proper TypeScript organization
- ✅ Control Tower deployment with automated account creation
- ✅ Security configuration including manual root user MFA setup
- ✅ Identity Center (SSO) with permission sets and group assignments
- ✅ Governance policies with Service Control Policies
- ✅ Account baselines with VPC, monitoring, and security
- ✅ CI/CD pipeline for automated deployments
- ✅ Advanced security with GuardDuty, Security Hub, and Config
- ✅ Testing and validation procedures
- ✅ Ongoing maintenance and monitoring commands
The setup provides a production-ready AWS multi-account environment with proper security, governance, and automation using CDK!