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)