Complete AWS Control Tower + CDK v2 Guide (Singapore 2025 Edition)
This comprehensive guide creates a modern AWS Control Tower setup with Hello World applications using CDK v2.201.0+ in Singapore region with current 2025 best practices using dev/staging/prod environments.
Phase 1: Prerequisites and Modern Environment Setup
1.1 Verify System Requirements (2025 Standards)
# Check Node.js (CRITICAL: Need 20+ minimum, 22+ recommended)
node --version
# Expected: v22.x.x (recommended) or v20.x.x (minimum)
# NOTE: Node.js 18 will be deprecated November 30, 2025
# Check npm (need latest)
npm --version
# Expected: 10.x.x or higher
# Check AWS CLI (need v2.15+)
aws --version
# Expected: aws-cli/2.15.x or higher
# Check Git
git --version
# Expected: git version 2.40.x or higher
1.2 Install Latest Tools
# Install Node.js 22 (recommended for 2025)
# macOS with Homebrew
brew install node@22
brew link node@22
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
# Verify Node.js 22 installation
node --version
# Should show v22.x.x
# Install/Update AWS CLI v2 (latest)
# macOS
brew install awscli
brew upgrade awscli
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
-o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install --update
1.3 Configure AWS CLI for Singapore Region
# Configure AWS credentials for your management account
aws configure
# AWS Access Key ID: [Your management account access key]
# AWS Secret Access Key: [Your management account secret key]
# Default region name: ap-southeast-1
# Default output format: json
# Verify configuration
aws sts get-caller-identity
# Should show your account ID, user ARN, and user ID
# Enable MFA for CLI (recommended for production)
aws configure set mfa_serial arn:aws:iam::YOUR_ACCOUNT:mfa/YOUR_MFA_DEVICE
1.4 Install Latest CDK v2 with Modern Features
# Install CDK v2 globally (latest version)
npm install -g aws-cdk@latest
# Verify installation (should be 2.201.0+)
cdk --version
# Expected: 2.201.x or higher
# Verify CDK v2 features are available
cdk --help | grep -E "(migrate|rollback|watch)"
# Should show modern CDK commands
# Bootstrap all accounts in Singapore
echo "ποΈ Bootstrapping accounts with Singapore security standards..."
bootstrap_account $DEV_ACCOUNT "Development"
bootstrap_account $STAGING_ACCOUNT "Staging"
bootstrap_account $SHARED_ACCOUNT "Shared Services"
bootstrap_account $PROD_ACCOUNT "Production"
echo ""
echo "β
All Singapore accounts bootstrapped with modern CDK toolkit!"
echo "π Features enabled:"
echo " βββ Customer managed KMS keys"
echo " βββ Termination protection"
echo " βββ Cross-account trust"
echo " βββ Enhanced security policies"
echo " βββ Singapore data residency compliance"
echo " βββ PDPA/MAS/CSA compliance tags"
6.3 Enhanced Deployment Script for Singapore
Create scripts/deploy-applications-singapore.sh:
#!/bin/bash
# Load environment variables
source .env
echo "π Deploying Hello World Applications (Singapore 2025 Edition)"
echo "============================================================="
# Function to deploy to specific account in Singapore
deploy_to_account() {
local env_name="$1"
local account_id="$2"
local stack_name="HelloWorld-$env_name"
echo "π¦ Deploying $stack_name to account $account_id in Singapore..."
# Set context for the Singapore deployment
cdk deploy $stack_name \
--context accountId=$account_id \
--context qualifier=sgcdk2025 \
--context region=ap-southeast-1 \
--context country=Singapore \
--require-approval never \
--rollback false \
--outputs-file "outputs-$env_name-singapore.json" \
--tags Environment=$env_name \
--tags ManagedBy=CDK \
--tags Version=2025.1.0 \
--tags Region=ap-southeast-1 \
--tags Country=Singapore \
--tags DataResidency=Singapore \
--tags ComplianceFramework=PDPA-MAS-CSA
if [ $? -eq 0 ]; then
echo "β
$stack_name deployed successfully to Singapore"
# Extract API URL from outputs
API_URL=$(cat "outputs-$env_name-singapore.json" | \
jq -r ".[\"$stack_name\"].ApiUrl" 2>/dev/null)
if [ "$API_URL" != "null" ] && [ ! -z "$API_URL" ]; then
echo "π API URL: $API_URL"
# Test the endpoint
echo "π§ͺ Testing Singapore endpoint..."
RESPONSE=$(curl -s "$API_URL" 2>/dev/null)
if echo "$RESPONSE" | grep -q "Singapore"; then
echo "β
Singapore endpoint test successful"
echo "πΈπ¬ Response includes Singapore metadata"
else
echo "β οΈ Endpoint test failed or missing Singapore metadata"
fi
fi
else
echo "β Failed to deploy $stack_name to Singapore"
return 1
fi
echo ""
}
# Deploy to each environment in Singapore (dev -> staging -> shared -> prod)
echo "π― Starting Singapore deployments..."
deploy_to_account "dev" $DEV_ACCOUNT
deploy_to_account "staging" $STAGING_ACCOUNT
deploy_to_account "shared" $SHARED_ACCOUNT
deploy_to_account "prod" $PROD_ACCOUNT
echo "π All applications deployed successfully to Singapore!"
echo ""
echo "π Deployment Summary (Singapore):"
echo "βββ Development: HelloWorld-dev"
echo "βββ Staging: HelloWorld-staging"
echo "βββ Shared Services: HelloWorld-shared"
echo "βββ Production: HelloWorld-prod"
echo ""
echo "π Access your Singapore applications:"
if [ -f "outputs-dev-singapore.json" ]; then
echo "βββ Dev: $(cat outputs-dev-singapore.json | \\
jq -r '.["HelloWorld-dev"].ApiUrl' 2>/dev/null)"
fi
if [ -f "outputs-staging-singapore.json" ]; then
echo "βββ Staging: $(cat outputs-staging-singapore.json | \\
jq -r '.["HelloWorld-staging"].ApiUrl' 2>/dev/null)"
fi
if [ -f "outputs-shared-singapore.json" ]; then
echo "βββ Shared: $(cat outputs-shared-singapore.json | \\
jq -r '.["HelloWorld-shared"].ApiUrl' 2>/dev/null)"
fi
if [ -f "outputs-prod-singapore.json" ]; then
echo "βββ Prod: $(cat outputs-prod-singapore.json | \\
jq -r '.["HelloWorld-prod"].ApiUrl' 2>/dev/null)"
fi
echo ""
echo "πΈπ¬ Singapore Compliance Features:"
echo "βββ Data residency: ap-southeast-1 only"
echo "βββ PDPA compliance: Enabled for prod/staging/shared"
echo "βββ MAS compliance: Enabled for prod"
echo "βββ CSA certification: Enabled for prod/shared"
echo "βββ MTCS Level-3: AWS Singapore certified"
echo "βββ Local timezone: Asia/Singapore"
Phase 7: Enhanced Control Tower Stack (Singapore 2025)
7.1 Control Tower Stack with Dev/Staging/Prod Structure
Create lib/stacks/control-tower-stack.ts:
import { Stack, StackProps, CfnOutput, Aspects } from "aws-cdk-lib";
import { Construct } from "constructs";
import {
CfnLandingZone,
CfnEnabledControl,
} from "aws-cdk-lib/aws-controltower";
import {
CfnAccount,
CfnOrganization,
CfnOrganizationalUnit,
} from "aws-cdk-lib/aws-organizations";
import { ACCOUNTS, CORE_ACCOUNTS } from "../config/accounts";
import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag";
export class ControlTowerStack extends Stack {
public readonly accounts: Record<string, CfnAccount> = {};
public readonly landingZone: CfnLandingZone;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Add CDK-nag for security compliance
Aspects.of(this).add(new AwsSolutionsChecks({ verbose: true }));
// β
UPDATED 2025: IAM Identity Center is now automatically set up
// during Control Tower deployment
// Create Control Tower Landing Zone using L1 constructs
// (Singapore optimized)
this.landingZone = new CfnLandingZone(this, "ControlTowerLandingZone", {
version: "3.3", // Latest version as of 2025
manifest: {
// Enhanced manifest for Singapore 2025 best practices
governedRegions: [
"ap-southeast-1", "ap-southeast-2"
], // Singapore + Sydney for DR
organizationStructure: {
security: {
name: "Security",
},
sandbox: {
name: "Workloads",
},
},
centralizedLogging: {
accountId: "LOG_ARCHIVE", // Will be resolved by Control Tower
configurations: {
loggingBucket: {
retentionConfiguration: {
retentionPeriod: 2555, // 7 years for Singapore compliance
},
},
accessLoggingBucket: {
retentionConfiguration: {
retentionPeriod: 365, // 1 year for access logs
},
},
},
},
securityConfiguration: {
accountId: "AUDIT",
},
accessManagement: {
enabled: true, // IAM Identity Center automatically configured
},
// 2025 enhancement: Additional security controls for Singapore
kmsConfiguration: {
kmsEncryption: true,
},
// Singapore data residency controls
dataResidencyControls: {
enabled: true,
regions: ["ap-southeast-1"], // Restrict to Singapore only
// for sensitive data
},
},
tags: [
{ key: "ManagedBy", value: "CDK" },
{ key: "Version", value: "2025.1" },
{ key: "Environment", value: "Management" },
{ key: "Region", value: "ap-southeast-1" },
{ key: "Country", value: "Singapore" },
{ key: "DataResidency", value: "Singapore" },
{ key: "ComplianceFramework", value: "PDPA-MAS-CSA" },
],
});
// Create Organizational Units with enhanced structure for dev/staging/prod
const securityOU = new CfnOrganizationalUnit(this, "SecurityOU", {
name: "Security",
parentId: "ROOT", // Will be replaced with actual root ID
tags: [
{ key: "Purpose", value: "Security" },
{ key: "ManagedBy", value: "CDK" },
{ key: "DataResidency", value: "Singapore" },
],
});
const workloadsOU = new CfnOrganizationalUnit(this, "WorkloadsOU", {
name: "Workloads",
parentId: "ROOT",
tags: [
{ key: "Purpose", value: "Application-Workloads" },
{ key: "ManagedBy", value: "CDK" },
{ key: "DataResidency", value: "Singapore" },
],
});
// Create production and non-production sub-OUs
const prodOU = new CfnOrganizationalUnit(this, "ProductionOU", {
name: "Production",
parentId: workloadsOU.ref,
tags: [
{ key: "Environment", value: "Production" },
{ key: "CriticalityLevel", value: "High" },
{ key: "DataResidency", value: "Singapore" },
{ key: "PDPACompliant", value: "true" },
],
});
const nonProdOU = new CfnOrganizationalUnit(this, "NonProductionOU", {
name: "NonProduction",
parentId: workloadsOU.ref,
tags: [
{ key: "Environment", value: "NonProduction" },
{ key: "CriticalityLevel", value: "Medium" },
{ key: "DataResidency", value: "Singapore" },
],
});
// Create workload accounts using Organizations API with proper
// dev/staging/prod placement
Object.entries(ACCOUNTS).forEach(([key, config]) => {
// Production account goes to Production OU, dev and staging
// go to NonProduction OU
const parentOU =
config.environment === "prod"
? prodOU.ref
: config.environment === "shared"
? prodOU.ref // Shared services treated as production
: nonProdOU.ref;
this.accounts[key] = new CfnAccount(this, `${key}Account`, {
accountName: config.name,
email: config.email,
parentIds: [parentOU],
tags: [
{ key: "Environment", value: config.environment },
{ key: "ManagedBy", value: "CDK" },
{ key: "Project", value: "ControlTowerSingapore2025" },
{
key: "CostCenter",
value:
config.environment === "prod"
? "Production"
: config.environment === "staging"
? "PreProduction"
: "Development",
},
{ key: "ComplianceLevel", value: config.complianceLevel },
{
key: "BillingThreshold",
value: config.billingThreshold.toString(),
},
{ key: "Region", value: "ap-southeast-1" },
{ key: "Country", value: "Singapore" },
{ key: "DataResidency", value: "Singapore" },
{ key: "PDPACompliant", value: config.pdpaCompliant.toString() },
{ key: "MASCompliant", value: config.masCompliant.toString() },
{ key: "CSACertified", value: config.csaCertified.toString() },
],
});
// Add dependency on Landing Zone and OUs
this.accounts[key].addDependency(this.landingZone);
this.accounts[key].addDependency(parentOU);
});
// Enhanced Control Tower controls for Singapore 2025 compliance
const securityControls = [
// Data protection controls (PDPA compliance)
"AWS-GR_EBS_OPTIMIZED_INSTANCE",
"AWS-GR_ENCRYPTED_VOLUMES",
"AWS-GR_EBS_SNAPSHOT_PUBLIC_READ_PROHIBITED",
// Network security controls (CSA requirements)
"AWS-GR_SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED",
"AWS-GR_VPC_DEFAULT_SECURITY_GROUP_CLOSED",
// IAM security controls (MAS compliance)
"AWS-GR_ROOT_ACCESS_KEY_CHECK",
"AWS-GR_MFA_ENABLED_FOR_ROOT",
"AWS-GR_STRONG_PASSWORD_POLICY",
// Monitoring and logging (audit requirements)
"AWS-GR_CLOUDTRAIL_ENABLED",
"AWS-GR_CLOUDWATCH_EVENTS_ENABLED",
// Cost optimization controls
"AWS-GR_LAMBDA_FUNCTION_PUBLIC_READ_PROHIBITED",
"AWS-GR_S3_BUCKET_PUBLIC_READ_PROHIBITED",
// Singapore data residency controls
"AWS-GR_REGION_DENY", // Prevents deployment outside ap-southeast-1
];
securityControls.forEach((controlId, index) => {
new CfnEnabledControl(this, `Control${index}`, {
controlIdentifier: controlId,
targetIdentifier: workloadsOU.ref,
});
});
// Output account information with enhanced Singapore metadata
Object.entries(ACCOUNTS).forEach(([key, config]) => {
new CfnOutput(this, `${key}AccountId`, {
value: this.accounts[key].ref,
description: `Account ID for ${config.name} environment ` +
`(${config.environment}) - Singapore`,
exportName: `AccountId-${key}-sg`,
});
new CfnOutput(this, `${key}AccountEmail`, {
value: config.email,
description: `Email for ${config.name} account - Singapore`,
exportName: `AccountEmail-${key}-sg`,
});
});
// Control Tower metadata outputs
new CfnOutput(this, "ControlTowerLandingZoneId", {
value: this.landingZone.ref,
description: "Control Tower Landing Zone ID - Singapore",
});
new CfnOutput(this, "ControlTowerVersion", {
value: "3.3",
description: "Control Tower version deployed",
});
new CfnOutput(this, "HomeRegion", {
value: "ap-southeast-1",
description: "Control Tower home region (Singapore)",
});
new CfnOutput(this, "ManagementAccountEmail", {
value: CORE_ACCOUNTS.management,
description: "Management account email",
});
new CfnOutput(this, "AuditAccountEmail", {
value: CORE_ACCOUNTS.audit,
description: "Audit account email",
});
new CfnOutput(this, "LogArchiveAccountEmail", {
value: CORE_ACCOUNTS.logArchive,
description: "Log Archive account email",
});
// CDK-nag suppressions for Control Tower specific requirements
NagSuppressions.addResourceSuppressions(
this,
[
{
id: "AwsSolutions-CT1",
reason:
"Control Tower Landing Zone requires specific configuration " +
"that may not align with all rules",
},
{
id: "AwsSolutions-ORG1",
reason:
"Organizations structure is designed for Control Tower compliance",
},
],
true,
);
}
}
Phase 8: Enhanced Validation Script for Dev/Staging/Prod
8.1 Comprehensive Singapore Validation Script
Create scripts/validate-deployment-singapore.sh:
#!/bin/bash
echo "π Comprehensive Singapore Deployment Validation (2025 Edition)"
echo "==============================================================="
# Load environment variables
source .env 2>/dev/null || echo "β οΈ .env file not found, some checks may fail"
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
local status="$1"
local message="$2"
case $status in
"success") echo -e "${GREEN}β
$message${NC}" ;;
"warning") echo -e "${YELLOW}β οΈ $message${NC}" ;;
"error") echo -e "${RED}β $message${NC}" ;;
"info") echo -e "${BLUE}βΉοΈ $message${NC}" ;;
"singapore") echo -e "\033[0;35mπΈπ¬ $message${NC}" ;;
esac
}
# 1. Check Environment and Singapore Region
echo "π 1. Checking Singapore Environment..."
CDK_VERSION=$(cdk --version 2>/dev/null)
NODE_VERSION=$(node --version 2>/dev/null)
CURRENT_REGION=$(aws configure get region)
if echo "$CDK_VERSION" | grep -q "2\."; then
print_status "success" "CDK Version: $CDK_VERSION (CDK v2 β)"
else
print_status "error" "CDK Version: $CDK_VERSION (Expected CDK v2)"
fi
if echo "$NODE_VERSION" | grep -qE "v(20|22)\."; then
print_status "success" \
"Node.js Version: $NODE_VERSION (Singapore 2025 compatible β)"
elif echo "$NODE_VERSION" | grep -q "v18\."; then
print_status "warning" \
"Node.js Version: $NODE_VERSION (Will be deprecated Nov 30, 2025)"
else
print_status "error" \
"Node.js Version: $NODE_VERSION (Need 20+ for Singapore deployment)"
fi
if [ "$CURRENT_REGION" = "ap-southeast-1" ]; then
print_status "singapore" "Region: $CURRENT_REGION (Singapore β)"
else
print_status "warning" \
"Region: $CURRENT_REGION (Should be ap-southeast-1 for Singapore)"
fi
# 2. Check Singapore Account Structure
echo ""
echo "π 2. Checking Singapore Account Structure..."
ACCOUNT_COUNT=$(aws organizations list-accounts \\
--query 'Accounts[?Status==`ACTIVE`] | length(@)' \\
--output text 2>/dev/null)
if [ "$ACCOUNT_COUNT" -ge 3 ]; then
print_status "singapore" "Active Accounts: $ACCOUNT_COUNT"
echo ""
print_status "info" "Singapore Account Details:"
aws organizations list-accounts \\
--query 'Accounts[?Status==`ACTIVE`].[Name,Email,Id]' \\
--output table 2>/dev/null
else
print_status "warning" \
"Active Accounts: $ACCOUNT_COUNT (Expected 7+ for full Singapore setup)"
fi
# 3. Test Hello World Applications in Singapore (Dev/Staging/Prod)
echo ""
echo "π 3. Testing Hello World Applications in Singapore..."
environments=("dev" "staging" "shared" "prod")
declare -A env_names=(
["dev"]="Development"
["staging"]="Staging"
["shared"]="Shared Services"
["prod"]="Production"
)
declare -A env_priorities=(
["dev"]="Low"
["staging"]="Medium"
["shared"]="High"
["prod"]="Critical"
)
for env in "${environments[@]}"; do
echo ""
print_status "info" "Testing $env environment (${env_names[$env]}) - " \\
"Priority: ${env_priorities[$env]}..."
# Get API URL from Singapore outputs file or CloudFormation
API_URL=""
if [ -f "outputs-$env-singapore.json" ]; then
API_URL=$(cat "outputs-$env-singapore.json" | \\
jq -r ".\"HelloWorld-$env\".ApiUrl" 2>/dev/null)
fi
if [ -z "$API_URL" ] || [ "$API_URL" = "null" ]; then
API_URL=$(aws cloudformation describe-stacks \
--stack-name "HelloWorld-$env" \
--query 'Stacks[0].Outputs[?OutputKey==`ApiUrl`].OutputValue' \
--output text \
--region ap-southeast-1 2>/dev/null)
fi
if [ ! -z "$API_URL" ] && [ "$API_URL" != "None" ]; then
print_status "info" "API URL: $API_URL"
# Test main endpoint
RESPONSE=$(curl -s --max-time 10 "$API_URL" 2>/dev/null)
if echo "$RESPONSE" | grep -q "Singapore"; then
print_status "singapore" \
"Main endpoint working with Singapore metadata β"
# Extract Singapore-specific data
REGION_FROM_RESPONSE=$(echo "$RESPONSE" | \
jq -r '.location.country' 2>/dev/null)
if [ "$REGION_FROM_RESPONSE" = "Singapore" ]; then
print_status "singapore" \
"Singapore location validation passed β"
else
print_status "warning" \
"Singapore location validation failed: " \\
"got $REGION_FROM_RESPONSE"
fi
# Check compliance flags based on environment
PDPA_COMPLIANT=$(echo "$RESPONSE" | \
jq -r '.features.pdpaCompliant' 2>/dev/null)
MAS_COMPLIANT=$(echo "$RESPONSE" | \
jq -r '.features.masCompliant' 2>/dev/null)
CSA_CERTIFIED=$(echo "$RESPONSE" | \
jq -r '.features.csaCertified' 2>/dev/null)
# Validate compliance settings per environment
case $env in
"prod")
if [ "$PDPA_COMPLIANT" = "true" ]; then
print_status "singapore" "PDPA compliance: Enabled β"
else
print_status "error" \
"PDPA compliance should be enabled for production"
fi
if [ "$MAS_COMPLIANT" = "true" ]; then
print_status "singapore" "MAS compliance: Enabled β"
else
print_status "warning" \
"MAS compliance: $MAS_COMPLIANT " \\
"(consider enabling for production)"
fi
if [ "$CSA_CERTIFIED" = "true" ]; then
print_status "singapore" "CSA certification: Enabled β"
else
print_status "warning" \
"CSA certification: $CSA_CERTIFIED " \\
"(consider enabling for production)"
fi
;;
"staging")
if [ "$PDPA_COMPLIANT" = "true" ]; then
print_status "singapore" \
"PDPA compliance: Enabled (staging) β"
else
print_status "warning" \
"PDPA compliance: $PDPA_COMPLIANT " \\
"(consider enabling for staging)"
fi
print_status "info" \
"MAS compliance: $MAS_COMPLIANT (staging environment)"
print_status "info" \
"CSA certification: $CSA_CERTIFIED (staging environment)"
;;
"shared")
if [ "$PDPA_COMPLIANT" = "true" ]; then
print_status "singapore" \
"PDPA compliance: Enabled (shared services) β"
else
print_status "warning" \\
"PDPA compliance should be enabled for " \
"shared services"
fi
if [ "$CSA_CERTIFIED" = "true" ]; then
print_status "singapore" \
"CSA certification: Enabled (shared services) β"
else
print_status "warning" \
"CSA certification: $CSA_CERTIFIED"
fi
;;
"dev")
print_status "info" \
"PDPA compliance: $PDPA_COMPLIANT (dev environment)"
print_status "info" \
"MAS compliance: $MAS_COMPLIANT (dev environment)"
print_status "info" \
"CSA certification: $CSA_CERTIFIED (dev environment)"
;;
esac
# Check if using Node.js 22
RUNTIME=$(echo "$RESPONSE" | jq -r '.runtime' 2>/dev/null)
if [ "$RUNTIME" = "nodejs22.x" ]; then
print_status "success" "Runtime: $RUNTIME (2025 standard β)"
else
print_status "warning" \
"Runtime: $RUNTIME (should be nodejs22.x for 2025)"
fi
# Environment-specific memory and performance checks
MEMORY_LIMIT=$(echo "$RESPONSE" | \
jq -r '.metadata.memoryLimit' 2>/dev/null)
case $env in
"prod")
if [ "$MEMORY_LIMIT" = "512" ]; then
print_status "success" \
"Memory: ${MEMORY_LIMIT}MB (production optimized β)"
else
print_status "warning" "Memory: ${MEMORY_LIMIT}MB " \\
"(expected 512MB for production)"
fi
;;
"staging")
if [ "$MEMORY_LIMIT" = "384" ]; then
print_status "success" \
"Memory: ${MEMORY_LIMIT}MB (staging optimized β)"
else
print_status "info" \
"Memory: ${MEMORY_LIMIT}MB (staging environment)"
fi
;;
"dev")
if [ "$MEMORY_LIMIT" = "256" ]; then
print_status "success" \
"Memory: ${MEMORY_LIMIT}MB (dev cost-optimized β)"
else
print_status "info" \
"Memory: ${MEMORY_LIMIT}MB (dev environment)"
fi
;;
esac
else
print_status "error" \
"Main endpoint test failed or missing Singapore metadata"
print_status "info" "Response: ${RESPONSE:0:100}..."
fi
# Test health endpoint
HEALTH_URL="${API_URL%/}/health"
HEALTH_RESPONSE=$(curl -s --max-time 10 "$HEALTH_URL" 2>/dev/null)
if echo "$HEALTH_RESPONSE" | grep -q "Singapore"; then
print_status "singapore" \
"Health endpoint working with Singapore data β"
else
print_status "warning" \
"Health endpoint test failed or missing Singapore data"
fi
# Test info endpoint for Singapore debugging
INFO_URL="${API_URL%/}/info"
INFO_RESPONSE=$(curl -s --max-time 10 "$INFO_URL" 2>/dev/null)
if echo "$INFO_RESPONSE" | grep -q "Singapore"; then
print_status "singapore" \
"Info endpoint working with Singapore metadata β"
# Validate Singapore-specific configuration
SG_DATA_RESIDENCY=$(echo "$INFO_RESPONSE" | \\
jq -r '.singapore.dataResidency' 2>/dev/null)
if [ "$SG_DATA_RESIDENCY" = "ap-southeast-1" ]; then
print_status "singapore" \
"Data residency compliance: Singapore β"
else
print_status "warning" "Data residency: $SG_DATA_RESIDENCY"
fi
else
print_status "warning" \
"Info endpoint test failed or missing Singapore metadata"
fi
else
print_status "error" \
"Stack not found or not deployed: HelloWorld-$env in Singapore"
fi
done
# Summary for Dev/Staging/Prod deployment
echo ""
echo "π― Singapore Dev/Staging/Prod Validation Summary"
echo "==============================================="
print_status "info" \
"Validation completed at $(date) ($(TZ=Asia/Singapore date))"
echo ""
echo "π Environment Summary:"
echo "βββ π§ Development: Cost-optimized, basic security"
echo "βββ π§ͺ Staging: Pre-production testing, enhanced security"
echo "βββ π§ Shared Services: Production-grade, shared resources"
echo "βββ π Production: Full compliance, maximum security"
echo ""
echo "π‘ Next Steps:"
echo "βββ π Access your Singapore Hello World applications using the URLs above"
echo "βββ π Monitor costs per environment in AWS Cost Explorer"
echo "βββ π Review security findings in Singapore Security Hub"
echo "βββ π Check application metrics in Singapore CloudWatch"
echo "βββ π Verify data residency compliance (ap-southeast-1)"
echo "βββ π Set up CI/CD pipeline: dev β staging β prod"
echo "βββ π Deploy additional applications using Singapore patterns"
echo ""
echo "π Your Singapore AWS Control Tower + CDK v2 deployment is ready!"
echo ""
echo "π Key Singapore Features Deployed:"
echo "βββ β
Modern CDK v2 with aws-cdk-lib (Singapore region)"
echo "βββ β
Node.js 22 Lambda runtime (2025 standard)"
echo "βββ β
HTTP API Gateway (cost optimized for Singapore)"
echo "βββ β
ARM64 Lambda architecture (cost optimized)"
echo "βββ β
Dev/Staging/Prod environment structure"
echo "βββ β
Environment-specific resource sizing"
echo "βββ β
Graduated compliance controls"
echo "βββ β
Singapore data residency controls"
echo "βββ β
Singapore timezone and currency support"
echo ""
echo "πΈπ¬ Singapore Compliance Status by Environment:"
echo "βββ π Production: Full PDPA + MAS + CSA compliance"
echo "βββ π§ͺ Staging: PDPA compliance, pre-production testing"
echo "βββ π§ Shared: PDPA + CSA compliance, shared resources"
echo "βββ π» Development: Minimal compliance, cost-optimized"
echo "βββ ποΈ Data Residency: ap-southeast-1 (Singapore) β"
echo "βββ β° Local Time: Asia/Singapore timezone"
echo "βββ π° Currency: SGD cost tracking"
Phase 9: Complete Command Sequence (Singapore 2025)
9.1 Full Singapore Deployment Script with Dev/Staging/Prod
Create scripts/complete-setup-singapore.sh:
#!/bin/bash
set -e
echo "π Complete Singapore AWS Control Tower + CDK v2 Setup (2025 Edition)"
echo "====================================================================="
echo "ποΈ Environment Structure: Development β Staging β Production"
echo ""
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
print_step() {
echo -e "${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}β
$1${NC}"
}
print_warning() {
echo -e "${YELLOW}β οΈ $1${NC}"
}
print_error() {
echo -e "${RED}β $1${NC}"
}
print_singapore() {
echo -e "${PURPLE}πΈπ¬ $1${NC}"
}
# Step 1: Prerequisites for Singapore
print_step "π Step 1: Checking Prerequisites for Singapore..."
# Check Node.js version
NODE_VERSION=$(node --version)
if echo "$NODE_VERSION" | grep -qE "v(20|22)\."; then
print_success "Node.js version: $NODE_VERSION (Singapore 2025 compatible)"
else
print_error "Node.js version $NODE_VERSION not supported. " \\
"Need v20+ or v22+ for Singapore deployment"
echo "Install Node.js 22: https://nodejs.org/"
exit 1
fi
# Check AWS CLI
AWS_VERSION=$(aws --version 2>/dev/null | cut -d' ' -f1 | cut -d'/' -f2)
if [[ "$AWS_VERSION" > "2.15" ]]; then
print_success "AWS CLI version: $AWS_VERSION"
else
print_error \
"AWS CLI version $AWS_VERSION not supported. Need v2.15+ for Singapore deployment"
exit 1
fi
# Check Singapore region access
aws sts get-caller-identity --region ap-southeast-1 > /dev/null 2>&1
if [ $? -eq 0 ]; then
print_singapore "Singapore region access: Verified"
else
print_error "Cannot access Singapore region (ap-southeast-1)"
exit 1
fi
# Check CDK version
CDK_VERSION=$(cdk --version 2>/dev/null)
if echo "$CDK_VERSION" | grep -q "2\."; then
print_success "CDK version: $CDK_VERSION"
else
print_error \
"CDK version $CDK_VERSION not supported. Need CDK v2 for Singapore deployment"
exit 1
fi
# Step 2: Project Setup for Singapore
print_step "π Step 2: Setting up Singapore CDK Project..."
# Check if already in project directory
if [ ! -f "package.json" ]; then
print_error "Not in CDK project directory. Please run from project root."
exit 1
fi
# Build project
print_step "π¨ Building Singapore project..."
npm run build
if [ $? -eq 0 ]; then
print_success "Project built successfully"
else
print_error "Project build failed"
exit 1
fi
# Run tests
print_step "π§ͺ Running Singapore tests..."
npm run test
if [ $? -eq 0 ]; then
print_success "Tests passed"
else
print_warning "Some tests failed, continuing with deployment"
fi
# Step 3: CDK Synthesis for Singapore
print_step "π Step 3: Synthesizing CDK for Singapore..."
npm run synth
if [ $? -eq 0 ]; then
print_success "CDK synthesis completed for Singapore"
else
print_error "CDK synthesis failed"
exit 1
fi
# Step 4: Control Tower Check
print_step "π Step 4: Checking Control Tower Status in Singapore..."
CT_STATUS=$(aws controltower list-landing-zones \\
--region ap-southeast-1 \\
--query 'landingZones[0].status' \\
--output text 2>/dev/null || echo "NOT_FOUND")
if [ "$CT_STATUS" = "ACTIVE" ]; then
print_singapore "Control Tower: ACTIVE in Singapore"
# Get home region
HOME_REGION=$(aws controltower list-landing-zones \\
--region ap-southeast-1 \\
--query 'landingZones[0].deploymentMetadata.homeRegion' \\
--output text 2>/dev/null)
if [ "$HOME_REGION" = "ap-southeast-1" ]; then
print_singapore "Home Region: Singapore (ap-southeast-1) β"
else
print_warning "Home Region: $HOME_REGION (expected ap-southeast-1)"
fi
elif [ "$CT_STATUS" = "NOT_FOUND" ]; then
print_warning "Control Tower not found in Singapore. Manual setup required:"
echo ""
print_singapore "Manual Singapore Control Tower Setup:"
echo "1. π Go to: " \
"https://ap-southeast-1.console.aws.amazon.com/controltower/"
echo "2. π Click 'Set up landing zone'"
echo "3. π Select home region: Asia Pacific (Singapore) ap-southeast-1"
echo "4. π Additional regions: Asia Pacific (Sydney) " \
"ap-southeast-2 (for DR)"
echo "5. π Configure logging and monitoring:"
echo " - Log Archive Account: testawsrahardjalogs@gmail.com"
echo " - Audit Account: testawsrahardjaaudit@gmail.com"
echo "6. π Enable data residency controls for Singapore compliance"
echo "7. β
Review and click 'Set up landing zone'"
echo "8. β° Wait 30-45 minutes for completion"
echo ""
echo "π Re-run this script after Control Tower setup is complete"
exit 0
else
print_error "Control Tower status: $CT_STATUS"
exit 1
fi
# Step 5: Get Account IDs for Dev/Staging/Prod
print_step "π Step 5: Getting Singapore Account IDs..."
./scripts/get-account-ids-singapore.sh
if [ $? -eq 0 ]; then
print_success "Account IDs retrieved successfully"
source .env
else
print_error "Failed to get account IDs"
exit 1
fi
# Step 6: Bootstrap Accounts for Singapore
print_step "π Step 6: Bootstrapping Singapore Accounts..."
./scripts/bootstrap-accounts-singapore.sh
if [ $? -eq 0 ]; then
print_success "All accounts bootstrapped successfully"
else
print_error "Account bootstrap failed"
exit 1
fi
# Step 7: Deploy Applications to Dev/Staging/Prod
print_step \
"π Step 7: Deploying Applications to Singapore (Dev β Staging β Shared β Prod)..."
./scripts/deploy-applications-singapore.sh
if [ $? -eq 0 ]; then
print_success "All applications deployed successfully to Singapore"
else
print_error "Application deployment failed"
exit 1
fi
# Step 8: Validate Deployment
print_step "π Step 8: Validating Singapore Deployment..."
./scripts/validate-deployment-singapore.sh
if [ $? -eq 0 ]; then
print_success "Deployment validation completed"
else
print_warning \
"Some validation checks failed, but deployment may still be functional"
fi
# Step 9: Final Summary
print_step "π Step 9: Singapore Deployment Summary"
echo ""
print_singapore "π Singapore AWS Control Tower + CDK v2 Setup Complete!"
echo ""
echo "π Environment Structure Deployed:"
echo "βββ π» Development: Cost-optimized, minimal compliance"
echo "βββ π§ͺ Staging: Pre-production testing, enhanced security"
echo "βββ π§ Shared Services: Production-grade shared resources"
echo "βββ π Production: Full compliance, maximum security"
echo ""
echo "π Your Singapore Hello World Applications:"
if [ -f "outputs-dev-singapore.json" ]; then
DEV_URL=$(cat outputs-dev-singapore.json | \
jq -r '.["HelloWorld-dev"].ApiUrl' 2>/dev/null)
echo "βββ π» Development: $DEV_URL"
fi
if [ -f "outputs-staging-singapore.json" ]; then
STAGING_URL=$(cat outputs-staging-singapore.json | \\
jq -r '.["HelloWorld-staging"].ApiUrl' 2>/dev/null)
echo "βββ π§ͺ Staging: $STAGING_URL"
fi
if [ -f "outputs-shared-singapore.json" ]; then
SHARED_URL=$(cat outputs-shared-singapore.json | \\
jq -r '.["HelloWorld-shared"].ApiUrl' 2>/dev/null)
echo "βββ π§ Shared: $SHARED_URL"
fi
if [ -f "outputs-prod-singapore.json" ]; then
PROD_URL=$(cat outputs-prod-singapore.json | \\
jq -r '.["HelloWorld-prod"].ApiUrl' 2>/dev/null)
echo "βββ π Production: $PROD_URL"
fi
echo ""
echo "πΈπ¬ Singapore Compliance Features:"
echo "βββ ποΈ Data Residency: ap-southeast-1 (Singapore) β"
echo "βββ π PDPA Compliance: Enabled for staging/shared/production"
echo "βββ π¦ MAS Compliance: Enabled for production"
echo "βββ π CSA Certification: Enabled for shared/production"
echo "βββ π° Cost Optimization: Environment-specific resource sizing"
echo "βββ β° Local Time: Asia/Singapore timezone"
echo "βββ π± Currency: SGD cost tracking"
echo ""
echo "π Next Steps:"
echo "1. π Test all your Singapore applications using the URLs above"
echo "2. π Set up monitoring and alerting in CloudWatch"
echo "3. π Review security configurations in Security Hub"
echo "4. π° Monitor costs by environment in Cost Explorer"
echo "5. π Set up CI/CD pipeline: dev β staging β prod"
echo "6. π Add custom business applications to each environment"
echo "7. π Configure disaster recovery to Sydney (ap-southeast-2)"
echo "8. π Review compliance settings for your specific requirements"
echo ""
print_singapore "Singapore AWS Control Tower + CDK v2 deployment is ready! π"
Phase 10: Enhanced Package.json Scripts for Dev/Staging/Prod
10.1 Updated Package.json Scripts
Update package.json to add environment-specific scripts:
{
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"cdk": "cdk",
"synth": "cdk synth",
"deploy": "cdk deploy",
"destroy": "cdk destroy",
"diff": "cdk diff",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"security:check": "npm audit --audit-level moderate",
"cdk:watch": "cdk watch",
"cdk:hotswap": "cdk deploy --hotswap",
"validate": "npm run build && npm run test && npm run lint && cdk synth",
"bootstrap": "cdk bootstrap",
"doctor": "cdk doctor",
"singapore:validate": \
"npm run validate && echo 'Singapore deployment ready'",
"deploy:dev": "cdk deploy HelloWorld-dev --require-approval never",
"deploy:staging": "cdk deploy HelloWorld-staging --require-approval never",
"deploy:prod": "cdk deploy HelloWorld-prod --require-approval never",
"deploy:shared": "cdk deploy HelloWorld-shared --require-approval never",
"deploy:all": "npm run deploy:dev && npm run deploy:staging && \\
npm run deploy:shared && npm run deploy:prod",
"destroy:dev": "cdk destroy HelloWorld-dev --force",
"destroy:staging": "cdk destroy HelloWorld-staging --force",
"destroy:prod": "cdk destroy HelloWorld-prod --force",
"destroy:shared": "cdk destroy HelloWorld-shared --force",
"test:endpoints": "./scripts/validate-deployment-singapore.sh",
"setup:complete": "./scripts/complete-setup-singapore.sh"
},
"devDependencies": {
"@types/jest": "^29.5.5",
"@types/node": "^22.0.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
"aws-cdk": "2.201.0",
"eslint": "^8.50.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "~5.2.2"
},
"dependencies": {
"aws-cdk-lib": "2.201.0",
"constructs": "^10.3.0",
"@aws-sdk/client-organizations": "^3.421.0",
"@aws-sdk/client-sts": "^3.421.0",
"cdk-nag": "^2.27.138",
"@taimos/cdk-controltower": "^1.0.0",
"@pepperize/cdk-organizations": "^0.7.0"
}
}
Phase 11: Updated CDK App Entry Point
11.1 Main CDK App with Dev/Staging/Prod
Update bin/aws-control-tower-cdk-singapore-2025.ts:
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ControlTowerStack } from "../lib/stacks/control-tower-stack";
import { ApplicationStack } from "../lib/stacks/application-stack";
import { ACCOUNTS } from "../lib/config/accounts";
import { ENVIRONMENTS } from "../lib/config/environments";
const app = new cdk.App();
// Deploy Control Tower stack (only once in management account)
const controlTowerStack = new ControlTowerStack(app, "ControlTowerSingapore", {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-southeast-1", // Singapore
},
description:
"AWS Control Tower Landing Zone for Singapore (2025 Edition) " +
"with Dev/Staging/Prod structure",
tags: {
Project: "ControlTowerSingapore2025",
Environment: "Management",
Region: "ap-southeast-1",
Country: "Singapore",
DataResidency: "Singapore",
Structure: "Dev-Staging-Prod",
},
});
// Deploy application stacks for each environment (dev/staging/shared/prod)
Object.entries(ACCOUNTS).forEach(([key, accountConfig]) => {
const env = ENVIRONMENTS[key];
new ApplicationStack(app, `HelloWorld-${key}`, {
accountConfig: accountConfig,
env: {
account:
process.env[`${key.toUpperCase()}_ACCOUNT_ID`] ||
process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-southeast-1", // Singapore
},
description: `Hello World application for ${accountConfig.name} ` +
`environment (Singapore 2025 Edition)`,
stackName: `HelloWorld-${key}`,
tags: {
Project: "HelloWorldSingapore2025",
Environment: accountConfig.environment,
Account: accountConfig.name,
Region: "ap-southeast-1",
Country: "Singapore",
DataResidency: "Singapore",
CostCenter:
accountConfig.environment === "prod"
? "Production"
: accountConfig.environment === "staging"
? "PreProduction"
: "Development",
ComplianceLevel: accountConfig.complianceLevel,
PDPACompliant: accountConfig.pdpaCompliant.toString(),
MASCompliant: accountConfig.masCompliant.toString(),
CSACertified: accountConfig.csaCertified.toString(),
CostOptimization: accountConfig.costOptimizationLevel,
BillingThreshold: accountConfig.billingThreshold.toString(),
},
});
});
// Add global tags for Singapore compliance
cdk.Tags.of(app).add("ManagedBy", "CDK");
cdk.Tags.of(app).add("Version", "2025.1.0");
cdk.Tags.of(app).add("Region", "ap-southeast-1");
cdk.Tags.of(app).add("Country", "Singapore");
cdk.Tags.of(app).add("DataResidency", "Singapore");
cdk.Tags.of(app).add("Timezone", "Asia/Singapore");
cdk.Tags.of(app).add("Currency", "SGD");
cdk.Tags.of(app).add("Structure", "Dev-Staging-Prod");
cdk.Tags.of(app).add("ComplianceFramework", "PDPA-MAS-CSA");
Phase 12: Quick Start Commands
12.1 Quick Setup Commands for Singapore
# π Quick Start for Singapore AWS Control Tower + CDK v2 (Dev/Staging/Prod)
# 1. Prerequisites Check
node --version # Should be v20+ or v22+
aws --version # Should be v2.15+
cdk --version # Should be v2.201+
# 2. Project Setup
mkdir aws-control-tower-cdk-singapore-2025
cd aws-control-tower-cdk-singapore-2025
cdk init app --language typescript
# 3. Install Dependencies
npm install aws-cdk-lib@latest constructs@latest
npm install --save-dev cdk-nag@latest @types/node@latest
# 4. Configure Singapore Region
aws configure set region ap-southeast-1
# 5. Bootstrap CDK for Singapore
cdk bootstrap --qualifier "sgcdk2025"
# 6. Manual Control Tower Setup (Required)
# Go to: https://ap-southeast-1.console.aws.amazon.com/controltower/
# Set home region: ap-southeast-1 (Singapore)
# Add regions: ap-southeast-2 (Sydney for DR)
# Configure accounts:
# - Audit: testawsrahardjaaudit@gmail.com
# - Log Archive: testawsrahardjalogs@gmail.com
# 7. Create Workload Accounts (via AWS Console)
# - Development: testawsrahardjaa+dev@gmail.com
# - Staging: testawsrahardjaa+staging@gmail.com
# - Production: testawsrahardjaa+prod@gmail.com
# - Shared Services: testawsrahardjaa+shared@gmail.com
# 8. Get Account IDs and Bootstrap
./scripts/get-account-ids-singapore.sh
./scripts/bootstrap-accounts-singapore.sh
# 9. Deploy Applications (Dev β Staging β Shared β Prod)
./scripts/deploy-applications-singapore.sh
# 10. Validate Deployment
./scripts/validate-deployment-singapore.sh
# π Your Singapore deployment is ready!
12.2 Environment-Specific Quick Commands
# Development Environment (Cost-Optimized)
npm run deploy:dev
curl $(cat outputs-dev-singapore.json | jq -r '.["HelloWorld-dev"].ApiUrl')
# Staging Environment (Pre-Production Testing)
npm run deploy:staging
curl $(cat outputs-staging-singapore.json | \
jq -r '.["HelloWorld-staging"].ApiUrl')
# Production Environment (Full Compliance)
npm run deploy:prod
curl $(cat outputs-prod-singapore.json | jq -r '.["HelloWorld-prod"].ApiUrl')
# Shared Services Environment
npm run deploy:shared
curl $(cat outputs-shared-singapore.json | \
jq -r '.["HelloWorld-shared"].ApiUrl')
# Deploy All Environments at Once
npm run deploy:all
# Test All Endpoints
npm run test:endpoints
# Complete Setup (All Steps)
npm run setup:complete
Summary
This comprehensive guide provides a complete AWS Control Tower + CDK v2 setup for Singapore with a dev/staging/prod environment structure. Key features include:
ποΈ Environment Structure
- Development: Cost-optimized, minimal compliance
- Staging: Pre-production testing, enhanced security
- Production: Full compliance, maximum security
- Shared Services: Production-grade shared resources
πΈπ¬ Singapore-Specific Features
- Data residency compliance (ap-southeast-1)
- PDPA compliance for staging/shared/production
- MAS compliance for production
- CSA certification for shared/production
- Singapore timezone and currency support
π Modern 2025 Technologies
- CDK v2.201.0+ with aws-cdk-lib
- Node.js 22 Lambda runtime
- HTTP API Gateway (cost-optimized)
- ARM64 Lambda architecture
- Enhanced security and observability
π Compliance & Security
- Environment-specific compliance settings
- Graduated security controls
- Cost optimization per environment
- Comprehensive monitoring and alerting
The guide maintains the step-by-step structure while updating all references from "test" to "staging" environment, providing a more realistic enterprise deployment pattern for Singapore.
Enhanced CDK Bootstrap
# Bootstrap CDK with modern qualifiers for Singapore region
cdk bootstrap \
aws://$(aws sts get-caller-identity --query Account --output text)/ap-southeast-1 \\
--qualifier "cdk2025" \\
--cloudformation-execution-policies \
"arn:aws:iam::aws:policy/AdministratorAccess"
Phase 2: Singapore-Specific Account Setup
2.1 Prepare Email Accounts (Pre-configured)
NOTE Your Email Configuration for Singapore:
- Management Account (Root):
testawsrahardjaa@gmail.comβ - Audit Account:
testawsrahardjaaudit@gmail.comβ - Log Archive Account:
testawsrahardjalogs@gmail.comβ - Production Workload:
testawsrahardjaa+prod@gmail.com(Gmail alias) - Staging Workload:
testawsrahardjaa+staging@gmail.com(Gmail alias) - Development Workload:
testawsrahardjaa+dev@gmail.com(Gmail alias) - Shared Services:
testawsrahardjaa+shared@gmail.com(Gmail alias)
2.2 IAM Identity Center Setup (NOW AUTOMATED)
β UPDATED 2025: IAM Identity Center is now automatically set up during Control Tower deployment!
# Check if IAM Identity Center is already enabled
aws sso list-instances --region ap-southeast-1
# If Control Tower hasn't been deployed yet, this will be empty
# Identity Center will be automatically configured during Control Tower setup
2.3 Enhanced Security Setup for Singapore Compliance
# Enable CloudTrail for audit logging (MAS compliance requirement)
aws cloudtrail create-trail \
--name "PreControlTowerAuditTrail" \
--s3-bucket-name \
"pre-ct-audit-$(aws sts get-caller-identity --query Account --output text)" \
--include-global-service-events \
--is-multi-region-trail \
--region ap-southeast-1
# Enable GuardDuty in management account (CSA requirement)
aws guardduty create-detector \
--enable \
--finding-publishing-frequency FIFTEEN_MINUTES \
--region ap-southeast-1
Phase 3: Modern CDK Project Structure
3.1 Initialize Project with 2025 Best Practices
# Create project directory
mkdir aws-control-tower-cdk-singapore-2025
cd aws-control-tower-cdk-singapore-2025
# Initialize TypeScript CDK project with modern template
cdk init app --language typescript
# Wait for initialization
sleep 10
3.2 Install Modern Dependencies (CDK v2.201.0+)
# Install core CDK v2 dependencies
npm install aws-cdk-lib@latest constructs@latest
# Install modern development dependencies
npm install --save-dev \
@types/node@latest \
jest@^29.7.0 \
@types/jest@latest \
ts-jest@latest \
eslint@latest \
@typescript-eslint/eslint-plugin@latest \
@typescript-eslint/parser@latest
# Install additional CDK utilities for 2025
npm install --save-dev \
cdk-nag@latest \
@taimos/cdk-controltower@latest \
@pepperize/cdk-organizations@latest
# Install AWS SDK v3 for modern patterns
npm install @aws-sdk/client-organizations @aws-sdk/client-sts
# Verify modern package versions
npm list aws-cdk-lib
# Should show 2.201.x or higher
3.3 Create Modern Directory Structure
# Create comprehensive directory structure
mkdir -p lib/{stacks,constructs,config,aspects,utils}
mkdir -p test/{unit,integration}
mkdir -p scripts
mkdir -p docs
# Create all required files
touch lib/config/accounts.ts
touch lib/config/environments.ts
touch lib/constructs/account-baseline.ts
touch lib/constructs/hello-world-app.ts
touch lib/constructs/observability-stack.ts
touch lib/stacks/control-tower-stack.ts
touch lib/stacks/application-stack.ts
touch lib/aspects/security-aspects.ts
touch lib/aspects/cost-optimization-aspects.ts
touch scripts/deploy.sh
touch scripts/validate.sh
touch cdk.context.json
Phase 4: Singapore-Optimized Configuration
4.1 Account Configuration with Singapore Compliance
Create lib/config/accounts.ts:
export interface AccountConfig {
name: string;
email: string;
vpcCidr: string;
environment: "prod" | "staging" | "dev" | "shared";
billingThreshold: number;
helloWorldMessage: string;
// 2025 Singapore additions
enableGuardDuty: boolean;
enableSecurityHub: boolean;
enableVpcFlowLogs: boolean;
costOptimizationLevel: "basic" | "standard" | "aggressive";
complianceLevel: "dev" | "prod";
pdpaCompliant: boolean; // Singapore PDPA requirement
masCompliant: boolean; // MAS financial services compliance
csaCertified: boolean; // CSA cybersecurity certification
}
export const ACCOUNTS: Record<string, AccountConfig> = {
prod: {
name: "production",
email: "testawsrahardjaa+prod@gmail.com",
vpcCidr: "10.0.0.0/16",
environment: "prod",
billingThreshold: 1500, // SGD equivalent
helloWorldMessage: "Hello from Singapore Production! πΈπ¬π",
enableGuardDuty: true,
enableSecurityHub: true,
enableVpcFlowLogs: true,
costOptimizationLevel: "standard",
complianceLevel: "prod",
pdpaCompliant: true,
masCompliant: true,
csaCertified: true,
},
staging: {
name: "staging",
email: "testawsrahardjaa+staging@gmail.com",
vpcCidr: "10.1.0.0/16",
environment: "staging",
billingThreshold: 750,
helloWorldMessage: "Hello from Singapore Staging Environment! πΈπ¬π§ͺ",
enableGuardDuty: true,
enableSecurityHub: true,
enableVpcFlowLogs: true,
costOptimizationLevel: "standard",
complianceLevel: "prod",
pdpaCompliant: true,
masCompliant: false,
csaCertified: false,
},
dev: {
name: "development",
email: "testawsrahardjaa+dev@gmail.com",
vpcCidr: "10.2.0.0/16",
environment: "dev",
billingThreshold: 300,
helloWorldMessage: "Hello from Singapore Development! πΈπ¬π»",
enableGuardDuty: false, // Cost optimization for dev
enableSecurityHub: false,
enableVpcFlowLogs: false,
costOptimizationLevel: "aggressive",
complianceLevel: "dev",
pdpaCompliant: false,
masCompliant: false,
csaCertified: false,
},
shared: {
name: "shared-services",
email: "testawsrahardjaa+shared@gmail.com",
vpcCidr: "10.3.0.0/16",
environment: "shared",
billingThreshold: 450,
helloWorldMessage: "Hello from Singapore Shared Services! πΈπ¬π§",
enableGuardDuty: true,
enableSecurityHub: true,
enableVpcFlowLogs: true,
costOptimizationLevel: "basic",
complianceLevel: "prod",
pdpaCompliant: true,
masCompliant: false,
csaCertified: true,
},
};
export const CORE_ACCOUNTS = {
management: "testawsrahardjaa@gmail.com",
audit: "testawsrahardjaaudit@gmail.com",
logArchive: "testawsrahardjalogs@gmail.com",
};
// 2025 Feature: Singapore-optimized environment configuration
export const ENVIRONMENT_CONFIG = {
regions: {
primary: "ap-southeast-1", // Singapore
secondary: "ap-southeast-2", // Sydney for DR
},
features: {
enableCrossRegionBackups: true,
enableAutomatedPatching: true,
enableCostOptimization: true,
enableAdvancedMonitoring: true,
enablePDPACompliance: true,
enableMASCompliance: true,
},
timezone: "Asia/Singapore",
currency: "SGD",
};
4.2 Environment-Specific Configuration
Create lib/config/environments.ts:
import { Environment } from "aws-cdk-lib";
export interface EnvironmentConfig extends Environment {
name: string;
isProd: boolean;
enableDeletionProtection: boolean;
logRetentionDays: number;
backupRetentionDays: number;
timezone: string;
}
export const ENVIRONMENTS: Record<string, EnvironmentConfig> = {
dev: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-southeast-1", // Singapore
name: "development",
isProd: false,
enableDeletionProtection: false,
logRetentionDays: 7,
backupRetentionDays: 7,
timezone: "Asia/Singapore",
},
staging: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-southeast-1", // Singapore
name: "staging",
isProd: false,
enableDeletionProtection: false,
logRetentionDays: 14,
backupRetentionDays: 14,
timezone: "Asia/Singapore",
},
prod: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-southeast-1", // Singapore
name: "production",
isProd: true,
enableDeletionProtection: true,
logRetentionDays: 365,
backupRetentionDays: 90,
timezone: "Asia/Singapore",
},
shared: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-southeast-1", // Singapore
name: "shared-services",
isProd: true,
enableDeletionProtection: true,
logRetentionDays: 90,
backupRetentionDays: 30,
timezone: "Asia/Singapore",
},
};
Phase 5: Modern Hello World Application (Singapore 2025)
5.1 Enhanced Hello World Construct
Create lib/constructs/hello-world-app.ts:
import { Construct } from "constructs";
import {
aws_lambda as lambda,
aws_apigatewayv2 as apigatewayv2, // Using HTTP API (v2) for cost optimization
aws_apigatewayv2_integrations as integrations,
aws_logs as logs,
aws_xray as xray,
CfnOutput,
Duration,
RemovalPolicy,
} from "aws-cdk-lib";
import { AccountConfig } from "../config/accounts";
export interface HelloWorldAppProps {
accountConfig: AccountConfig;
}
export class HelloWorldApp extends Construct {
public readonly api: apigatewayv2.HttpApi;
public readonly lambda: lambda.Function;
constructor(scope: Construct, id: string, props: HelloWorldAppProps) {
super(scope, id);
const { accountConfig } = props;
// Create log group with proper retention
const logGroup = new logs.LogGroup(this, "HelloWorldLogGroup", {
logGroupName: `/aws/lambda/hello-world-${accountConfig.environment}`,
retention:
accountConfig.environment === "prod"
? logs.RetentionDays.ONE_YEAR
: accountConfig.environment === "staging"
? logs.RetentionDays.ONE_MONTH
: logs.RetentionDays.ONE_WEEK,
removalPolicy:
accountConfig.environment === "prod"
? RemovalPolicy.RETAIN
: RemovalPolicy.DESTROY,
});
// Create Lambda function with Node.js 22 (2025 standard)
this.lambda = new lambda.Function(this, "HelloWorldFunction", {
runtime: lambda.Runtime.NODEJS_22_X, // Latest runtime for 2025
handler: "index.handler",
code: lambda.Code.fromInline(`
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { instrumentations } = \
require('@opentelemetry/auto-instrumentations-node');
// Modern observability setup
const sdk = new NodeSDK({
instrumentations: [instrumentations()]
});
if (process.env.AWS_LAMBDA_FUNCTION_NAME) {
sdk.start();
}
exports.handler = async (event, context) => {
console.log('Event received:', JSON.stringify(event, null, 2));
// Enhanced response with Singapore 2025 patterns
const response = {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'X-Environment': '${accountConfig.environment}',
'X-API-Version': '2025.1',
'X-Region': 'ap-southeast-1',
'X-Country': 'Singapore',
'Cache-Control': 'no-cache, no-store, must-revalidate'
},
body: JSON.stringify({
message: '${accountConfig.helloWorldMessage}',
environment: '${accountConfig.environment}',
account: '${accountConfig.name}',
timestamp: new Date().toISOString(),
requestId: context.awsRequestId,
region: process.env.AWS_REGION,
version: '2025.1.0',
runtime: 'nodejs22.x',
location: {
country: 'Singapore',
region: 'Asia-Pacific',
timezone: 'Asia/Singapore',
currency: 'SGD'
},
// Enhanced metadata
metadata: {
lambdaVersion: context.functionVersion,
remainingTime: context.getRemainingTimeInMillis(),
memoryLimit: context.memoryLimitInMB,
logGroup: context.logGroupName,
architecture: process.arch,
nodeVersion: process.version
},
features: {
observability: 'OpenTelemetry',
security: 'Enhanced',
costOptimization: '${accountConfig.costOptimizationLevel}',
compliance: '${accountConfig.complianceLevel}',
pdpaCompliant: ${accountConfig.pdpaCompliant},
masCompliant: ${accountConfig.masCompliant},
csaCertified: ${accountConfig.csaCertified}
},
singapore: {
dataResidency: 'ap-southeast-1',
complianceFrameworks: ['PDPA', 'MAS', 'CSA'],
mtcsLevel3Certified: true
}
}, null, 2)
};
return response;
};
`),
environment: {
ENVIRONMENT: accountConfig.environment,
ACCOUNT_NAME: accountConfig.name,
LOG_LEVEL: accountConfig.environment === "prod" ? "WARN" : "DEBUG",
ENABLE_XRAY: accountConfig.enableGuardDuty ? "true" : "false",
AWS_REGION: "ap-southeast-1",
TIMEZONE: "Asia/Singapore",
PDPA_COMPLIANT: accountConfig.pdpaCompliant.toString(),
MAS_COMPLIANT: accountConfig.masCompliant.toString(),
},
description: `Hello World Lambda for ${accountConfig.name} ` +
`environment (Singapore 2025 edition)`,
timeout: Duration.seconds(30),
memorySize:
accountConfig.environment === "prod"
? 512
: accountConfig.environment === "staging"
? 384
: 256,
logGroup: logGroup,
// Enhanced security and performance
reservedConcurrentExecutions:
accountConfig.environment === "prod"
? 10
: accountConfig.environment === "staging"
? 5
: 2,
deadLetterQueueEnabled:
accountConfig.environment === "prod" ||
accountConfig.environment === "staging",
tracing: accountConfig.enableGuardDuty
? lambda.Tracing.ACTIVE
: lambda.Tracing.DISABLED,
insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_229_0,
// 2025 feature: Architecture optimization
architecture: lambda.Architecture.ARM_64, \
// Graviton2 for cost optimization
});
// Create HTTP API (v2) instead of REST API for cost optimization
this.api = new apigatewayv2.HttpApi(this, "HelloWorldApi", {
apiName: `Hello World API - ${accountConfig.environment} - Singapore`,
description: `Hello World HTTP API for ${accountConfig.name} ` +
`environment (Singapore 2025 edition)`,
corsPreflight: {
allowOrigins: ["*"],
allowMethods: [
apigatewayv2.CorsHttpMethod.GET,
apigatewayv2.CorsHttpMethod.POST,
],
allowHeaders: [
"Content-Type",
"X-Amz-Date",
"Authorization",
"X-Api-Key",
],
maxAge: Duration.days(1),
},
// Enhanced throttling for cost control
defaultIntegration: new integrations.HttpLambdaIntegration(
"DefaultIntegration",
this.lambda,
{
payloadFormatVersion: apigatewayv2.PayloadFormatVersion.VERSION_2_0,
},
),
});
// Add routes with modern patterns
this.api.addRoutes({
path: "/",
methods: [apigatewayv2.HttpMethod.GET],
integration: new integrations.HttpLambdaIntegration(
"RootIntegration",
this.lambda,
),
});
// Health check endpoint
const healthLambda = new lambda.Function(this, "HealthFunction", {
runtime: lambda.Runtime.NODEJS_22_X,
handler: "index.handler",
code: lambda.Code.fromInline(`
exports.handler = async (event, context) => {
const healthData = {
status: 'healthy',
environment: '${accountConfig.environment}',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: '2025.1.0',
location: {
region: 'ap-southeast-1',
country: 'Singapore',
timezone: 'Asia/Singapore'
},
checks: {
database: 'n/a',
cache: 'n/a',
dependencies: 'healthy',
compliance: {
pdpa: ${accountConfig.pdpaCompliant},
mas: ${accountConfig.masCompliant},
csa: ${accountConfig.csaCertified}
}
}
};
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Region': 'ap-southeast-1'
},
body: JSON.stringify(healthData, null, 2)
};
};
`),
timeout: Duration.seconds(10),
architecture: lambda.Architecture.ARM_64,
});
this.api.addRoutes({
path: "/health",
methods: [apigatewayv2.HttpMethod.GET],
integration: new integrations.HttpLambdaIntegration(
"HealthIntegration",
healthLambda,
),
});
// Info endpoint for debugging
const infoLambda = new lambda.Function(this, "InfoFunction", {
runtime: lambda.Runtime.NODEJS_22_X,
handler: "index.handler",
code: lambda.Code.fromInline(`
exports.handler = async (event, context) => {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
accountConfig: {
name: '${accountConfig.name}',
environment: '${accountConfig.environment}',
vpcCidr: '${accountConfig.vpcCidr}',
costOptimization: '${accountConfig.costOptimizationLevel}',
compliance: '${accountConfig.complianceLevel}',
pdpaCompliant: ${accountConfig.pdpaCompliant},
masCompliant: ${accountConfig.masCompliant},
csaCertified: ${accountConfig.csaCertified}
},
awsInfo: {
region: process.env.AWS_REGION,
accountId: event.requestContext?.accountId || 'unknown',
runtime: 'nodejs22.x',
architecture: 'arm64'
},
singapore: {
dataResidency: 'Compliant - ap-southeast-1',
mtcsLevel3: 'Certified',
localTime: new Date().toLocaleString('en-SG', {
timeZone: 'Asia/Singapore'
}),
complianceFrameworks: [
'PDPA', 'MAS', 'CSA', 'MTCS-SS584-Level3'
]
},
requestInfo: {
sourceIp: event.requestContext?.http?.sourceIp,
userAgent: event.requestContext?.http?.userAgent,
requestId: event.requestContext?.requestId,
domainName: event.requestContext?.domainName
},
performance: {
coldStart: !context.coldStartDetected,
remainingTime: context.getRemainingTimeInMillis(),
memoryLimit: context.memoryLimitInMB
}
}, null, 2)
};
};
`),
timeout: Duration.seconds(10),
architecture: lambda.Architecture.ARM_64,
});
this.api.addRoutes({
path: "/info",
methods: [apigatewayv2.HttpMethod.GET],
integration: new integrations.HttpLambdaIntegration(
"InfoIntegration",
infoLambda,
),
});
// Modern CloudFormation outputs
new CfnOutput(this, "ApiUrl", {
value: this.api.apiEndpoint,
description: `Hello World HTTP API URL for ` +
`${accountConfig.environment} environment (Singapore)`,
exportName: `HelloWorldApiUrl-${accountConfig.environment}-sg`,
});
new CfnOutput(this, "HealthCheckUrl", {
value: `${this.api.apiEndpoint}/health`,
description: \
`Health check URL for ${accountConfig.environment} environment (Singapore)`,
});
new CfnOutput(this, "InfoUrl", {
value: `${this.api.apiEndpoint}/info`,
description: \
`Info endpoint URL for ${accountConfig.environment} environment (Singapore)`,
});
new CfnOutput(this, "LambdaArn", {
value: this.lambda.functionArn,
description: \
`Lambda function ARN for ${accountConfig.environment} (Singapore)`,
});
}
}
Phase 6: Enhanced Deployment Scripts for Dev/Staging/Prod
6.1 Get Account IDs (After Control Tower Setup)
Create scripts/get-account-ids-singapore.sh:
#!/bin/bash
echo "π Getting account IDs from Singapore Control Tower deployment..."
# Function to get account ID by name
get_account_id() {
local account_name="$1"
aws organizations list-accounts \
--query "Accounts[?Name=='$account_name'].Id" \
--output text 2>/dev/null
}
# Get account IDs
PROD_ACCOUNT=$(get_account_id "production")
STAGING_ACCOUNT=$(get_account_id "staging")
DEV_ACCOUNT=$(get_account_id "development")
SHARED_ACCOUNT=$(get_account_id "shared-services")
# Store in environment file
cat > .env << EOF
# Singapore Account IDs (generated $(date))
PROD_ACCOUNT_ID=$PROD_ACCOUNT
STAGING_ACCOUNT_ID=$STAGING_ACCOUNT
DEV_ACCOUNT_ID=$DEV_ACCOUNT
SHARED_ACCOUNT_ID=$SHARED_ACCOUNT
# Management account
MANAGEMENT_ACCOUNT_ID=$(aws sts get-caller-identity \
--query Account --output text)
# Singapore configuration
AWS_REGION=ap-southeast-1
COUNTRY=Singapore
TIMEZONE=Asia/Singapore
CURRENCY=SGD
EOF
echo "π Singapore Account IDs found:"
echo "βββ Management: $(aws sts get-caller-identity \
--query Account --output text)"
echo "βββ Production: $PROD_ACCOUNT"
echo "βββ Staging: $STAGING_ACCOUNT"
echo "βββ Development: $DEV_ACCOUNT"
echo "βββ Shared Services: $SHARED_ACCOUNT"
# Verify accounts are in ACTIVE state
echo ""
echo "π Verifying account status in Singapore..."
aws organizations list-accounts \
--query 'Accounts[?Status==`ACTIVE`].[Name,Email,Status,Id]' \
--output table
echo "πΎ Singapore account IDs saved to .env file"
echo "π Region: ap-southeast-1 (Singapore)"
6.2 Enhanced Bootstrap Process for Singapore
Create scripts/bootstrap-accounts-singapore.sh:
#!/bin/bash
# Load environment variables
source .env
echo "π§ Singapore CDK Bootstrap Process (2025)"
echo "=========================================="
# β οΈ CRITICAL: Control Tower SCP workaround required
echo "β οΈ IMPORTANT: CDK Bootstrap requires AWSControlTowerExecution role"
echo "π You may need to assume the AWSControlTowerExecution role " \
"to bootstrap successfully"
# Function to bootstrap account with enhanced security for Singapore
bootstrap_account() {
local account_id="$1"
local account_name="$2"
echo "π Bootstrapping $account_name ($account_id) in Singapore..."
# Enhanced bootstrap with Singapore-specific settings
cdk bootstrap aws://$account_id/ap-southeast-1 \
--qualifier "sgcdk2025" \
--toolkit-stack-name "CDKToolkit-Singapore-2025" \
--cloudformation-execution-policies \
"arn:aws:iam::aws:policy/AdministratorAccess" \
--trust-accounts $MANAGEMENT_ACCOUNT_ID \
--bootstrap-customer-key \
--termination-protection \
--context "@aws-cdk/core:bootstrapQualifier=sgcdk2025" \
--tags Region=ap-southeast-1 \
--tags Country=Singapore \
--tags DataResidency=Singapore \
--tags ComplianceFramework=PDPA-MAS-CSA
if [ $? -eq 0 ]; then
echo "β
$account_name bootstrapped successfully in Singapore"
else
echo "β Failed to bootstrap $account_name"
echo "π‘ Try assuming AWSControlTowerExecution role first:"
echo " aws sts assume-role \\\\"
echo " --role-arn arn:aws:iam::$MANAGEMENT_ACCOUNT_ID:role/" \\
echo " AWSControlTowerExecution \\\\"
echo " --role-session-name CDKBootstrap"
return 1
fi
}
# Bootstrap