Introduction
In this article, we will explore how to perform cross-account AWS CDK Bootstrapping using AWS Lambda. Bootstrapping is a critical step when deploying infrastructure defined using AWS CDK, as it sets up the necessary resources for subsequent deployments.
What is CDK Bootstrapping?
CDK Bootstrapping involves creating a CloudFormation stack (CDKToolkit) that contains essential resources. The template for this stack can be found here.
There are two primary approaches to CDK Bootstrapping:
- Using CloudFormation StackSet: Create a StackSet with the provided template. Learn more in this AWS blog post.
- 
Using cdk bootstrap: Run the command directly whenever a new account is created.
Running cdk bootstrap in a Lambda function simplifies the process by eliminating the need to sync the template with a StackSet.
Lambda Function for CDK Bootstrapping
The Lambda function accepts an event containing a list of accounts to bootstrap. It logs into each account and executes the cdk bootstrap command.
Note: Ensure all required cross-account permissions are in place, and the Lambda function can assume a role in the target account.
Source Code
Here’s the implementation of the Lambda function:
index.ts
import { execSync } from 'child_process';
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
const getCredentials = async (accountId: string, roleName: string, region: string): Promise<any> => {
    console.log(`Assuming role: ${roleName} in account: ${accountId}`);
    const stsClient = new STSClient({ region: region });
    const response = await stsClient.send(new AssumeRoleCommand({
        RoleArn: `arn:aws:iam::${accountId}:role/${roleName}`, RoleSessionName: "CDKBootstrap"
    }));
    console.log(`Assumed role: ${roleName} in account: ${accountId}`);
    return {
        accessKeyId: response.Credentials?.AccessKeyId || '',
        secretAccessKey: response.Credentials?.SecretAccessKey || '',
        sessionToken: response.Credentials?.SessionToken || '',
    };
};
async function runBootstrap(accountId: string, roleName: string, region: string) {
    const credentials = await getCredentials(accountId, roleName, region)
    const env = {
        ...process.env,
        AWS_ACCESS_KEY_ID: credentials.accessKeyId,
        AWS_SECRET_ACCESS_KEY: credentials.secretAccessKey,
        AWS_SESSION_TOKEN: credentials.sessionToken
    }
    const multiline = [
        `pnpm cdk bootstrap aws://${accountId}/${region}`,
        '--cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess',
        '--trust 1111111111111',   // <- Replace with your account ID 
    ];
    const bootstrapCommand = multiline.join(' ');
    const output = execSync(bootstrapCommand, { env: env }).toString();
    // console.log(output);
}
export const handler = async (event: any): Promise<any> => {
    const output = execSync('pnpm cdk version').toString();
    for(const accountId in event.accountIds) {
        console.log(`Bootstrapping account ${accountId}`);
        await runBootstrap(accountId, "cdk-bootstrap-role", "eu-west-1");
        console.log(`Bootstrapped account ${accountId}`);
    }
    return {
        statusCode: 200,
        body: JSON.stringify({ cdkVersion: output.trim(), accountIds: event.accountIds }),
    };
};
Deployment
Define the Lambda function in your CDK application using TypeScript. Below is the Lambda function definition:
stack.ts
const lambdaFunction = new lambda.DockerImageFunction(this, 'CdkBootstrapLambda', {
  code: lambda.DockerImageCode.fromImageAsset(
    path.join(this._baseFolder, 'src/cdk_bootstrap')
  ),
  functionName: 'cdk-bootstrap-lambda',
  tracing: lambda.Tracing.ACTIVE,
  role: this.lambdaExecutionRole,  // <--- role that allows to assume roles in a target account
  environment: handlerEnvironmentParams,
  memorySize: 512,
  timeout: Duration.minutes(10),
  currentVersionOptions: {
    removalPolicy: RemovalPolicy.RETAIN,
  }
});
The DockerImageFunction enables the use of the CDK CLI. Below is the Dockerfile and .dockerignore for creating a lightweight Lambda image.
Dockerfile
FROM public.ecr.aws/lambda/nodejs:22 AS builder
WORKDIR /var/task
# Install pnpm
RUN npm install -g pnpm
# Copy package files and configs
COPY package.json pnpm-lock.yaml .npmrc tsconfig.json ./
COPY index.ts  ./
# Install dependencies and build
RUN pnpm install --frozen-lockfile
RUN pnpm build
FROM public.ecr.aws/lambda/nodejs:22
WORKDIR /var/task
# Install pnpm
RUN npm install -g pnpm
# Copy package files and built assets
COPY --from=builder /var/task/dist ./dist
COPY package.json pnpm-lock.yaml .npmrc ./
# Install production dependencies only
RUN pnpm install --frozen-lockfile --prod
# Set the Lambda handler
CMD ["dist/index.handler"]
.dockerignore
node_modules
npm-debug.log
dist
.git
.env
Package Configuration
Below is a sample package.json for the Lambda function:
{
  "name": "cdk-bootstrap",
  "description": "Lambda for cdk bootstrapping",
  "version": "1.0.0",
  "engines": {
    "node": ">=20.0.0"
  },
  "scripts": {
    "build": "pnpm clean && tsc",
    "clean": "rimraf dist"
  },
  "dependencies": {
    "@aws-sdk/client-sts": "^3.723.0",
    "aws-cdk": "^2.174.1"
  },
  "devDependencies": {
    "@types/aws-lambda": "^8.10.92",
    "@types/jest": "^29.5.11",
    "@types/node": "^20.0.0",
    "rimraf": "^5.0.5",
    "typescript": "^4.9.0"
  }
}
 
 
              
 
    
Top comments (0)