aws
Markdown

cdk ct setup

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:

  1. testawsrahardja@gmail.com (existing - Management Account)
  2. testawsrahardjaaudit@gmail.com (create new)
  3. 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!