Logo

dev-resources.site

for different kinds of informations.

Automating Cross-Account CDK Bootstrapping Using AWS Lambda

Published at
1/9/2025
Categories
awscdk
cdk
automation
aws
Author
maxkrivich
Categories
4 categories in total
awscdk
open
cdk
open
automation
open
aws
open
Author
10 person written this
maxkrivich
open
Automating Cross-Account CDK Bootstrapping Using AWS Lambda

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:

  1. Using CloudFormation StackSet: Create a StackSet with the provided template. Learn more in this AWS blog post.
  2. 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 }),
    };
};
Enter fullscreen mode Exit fullscreen mode

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,
  }
});
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

.dockerignore

node_modules
npm-debug.log
dist
.git
.env
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Additional Resources

cdk Article's
30 articles in total
Favicon
Invoking Private API Gateway Endpoints From Step Functions
Favicon
Automating Cross-Account CDK Bootstrapping Using AWS Lambda
Favicon
Deploy de NextJS utilizando CDK
Favicon
Config AWS Cloudwatch Application Signals Transaction Search with CDK
Favicon
[Solved] AWS Resource limit exceeded
Favicon
Create a cross-account glue Job using AWS CDK
Favicon
Enabling Decorators for Lambda Functions with CDK in an Nx Monorepo
Favicon
Run vs code on a private AWS ec2 instance without ssh (with AWS CDK examples)
Favicon
Config AWS Cloudwatch Application Signals for NodeJs Lambda with CDK
Favicon
Deploying a Simple Static Website on AWS with CDK and TypeScript
Favicon
AWS Architectural Diagrams on a Commit Base: Using AWS PDK Diagram Plugin with Python
Favicon
AWS CDK Aspects specifications have changed
Favicon
Relative Python imports in a Dockerized lambda function
Favicon
Building Scalable Infrastructure with AWS CDK: A Developer’s Guide to Best Practices
Favicon
API Gateway integration with AWS Services.
Favicon
DevSecOps with AWS- IaC at scale - Building your own platform – Part 3 - Pipeline as a Service
Favicon
AWS Cloud Development Kit (CDK) vs. Terraform
Favicon
Query your EventBridge Scheduled Events in DynamoDB
Favicon
AWS CDK context validation
Favicon
How to combine SQS and SNS to implement multiple Consumers (Part 2)
Favicon
Serverless Code-Signing (EV) with KMS and Fargate
Favicon
How to build an API with Lambdas, API Gateway and deploy with AWS CDK
Favicon
The Journey of CDK.dev: From Static Site to Bluesky
Favicon
Create an Asset Store with a Custom Domain using AWS CDK, Route53, S3 and CloudFront
Favicon
Crafting a Scalable Node.js API: Insights from My RealWorld Project with Express, Knex, and AWS CDK
Favicon
Techniques to Save Costs Using AWS Lambda Functions with CDK
Favicon
Deploy a Docker Image to ECS with Auto Scaling Using AWS CDK in Minutes
Favicon
AWS CDK + Localstack (API Gateway, Lambda, SQS, DynamoDB,TypeScript)
Favicon
Simplifying Cloud Infrastructure with AWS CDK
Favicon
Effortless Debugging: AWS CDK TypeScript Projects in VSCode

Featured ones: