Learn How to Use a Lambda Function to Trigger a CodeBuild in Another Account

Learn How to Use a Lambda Function to Trigger a CodeBuild in Another Account

·

5 min read

One of the best practices to improve application lifecycle is by having multiple stages of quality assessment before the final product is released to the public. This can be accomplished by using multiple accounts with cloud providers, such as AWS, during the development and deployment stages to separate the deployments.

By organizing resources across multiple accounts, you can simplify your pipelines and builds, while also minimizing the amount of effort required. This is where cross-account role assume action comes into play.

Cross-account role assumption allows you to create roles and permissions in various accounts, known as workload accounts. These roles can then be utilized by another account, referred to as the pipeline account, for deployments. This approach saves time and boosts the efficiency of product development and release.

In this article, we'll explore how to use a Lambda function to trigger a CodeBuild in another account using cross-account role assume action.

For the purpose of this demonstration, we will be using AWS CDK to create any necessary infrastructure. You can find the link to the project on my GitHub here.

The main account IAM role

Let's start off by creating the IAM role in the main account which will be assumed by the Lambda function.

Since we aim to carry out a CodeBuild deployment, we will create an IAM role that will have a trust policy to allow the CodeBuild to assume it.

const mainRole = new iam.Role(this, 'MainRole', {
    roleName: `mainaccount-role`,
    assumedBy: new iam.CompositePrincipal(
      new iam.ServicePrincipal('codebuild.amazonaws.com'),
    ),
  });

Let's also add the permission to allow this role to trigger the CodeBuild.

  mainRole.addToPolicy(new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: [ 'codebuild:StartBuild' ],
    resources: [`arn:aws:codebuild:ca-central-1:${this.account}:project/codebuild-${stage}-01`],
  }));

We should also create a trust policy to allow the Lambda function's execution role to assume this newly created role but for that, that role should exist in the second account. So, we will add it later on.

The cross-account lambda function stack

Let's now create the Lambda function stack which will assume the main account role and trigger the CodeBuild in it.

First, let's create lambda's execution role to grant the necessary permissions for the Lambda function to execute.

const role = new iam.Role(this, 'SampleRole', {
      roleName: `lambda-execution-role`,
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('lambda.amazonaws.com'),
        new iam.ArnPrincipal(`arn:aws:iam::${mainAccountNumber}:role/mainaccount-role`),
      ),
        managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
      ],
    });

    role.addToPolicy(new iam.PolicyStatement({
      actions: [
        'sts:AssumeRole',
      ],
      resources: [`arn:aws:iam::${mainAccountNumber}:role/mainaccount-role`],
    }));

You can see that the above role has a trust policy that allows lambda service and the main account's role to assume it.

Now let's create our lambda.


    // Lambda function
    const triggerLambda = new lambda.Function(this, 'SampleLambda', {
      functionName: `lambda-crossaccountTrigger-${stage}`,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_16_X,
      timeout: cdk.Duration.seconds(60),
      environment: {
        PROJECT_NAME:`codebuild-${stage}-01`,
        ROLE_ARN: `arn:aws:iam::${mainAccountNumber}:role/mainaccount-role`,
      },
      role: role,
    });

We can pass the main account role ARN and the CodeBuild we want to trigger to the lambda function as environment variables so that lambda can assume the role and trigger the CodeBuild that we specify.

Lambda code

Let's now look at the lambda code.

We can use the AWS-SDK to make API calls that will perform the CodeBuild actions but it is possible only within one AWS account under normal cases.
Visit the AWS Documentation here for more details.

Let's go through how we can set it up for a different account.

var AWS = require('aws-sdk');
var codebuild = new AWS.CodeBuild();
const sts = new AWS.STS();

exports.handler = async (event, context) => {
    try {
    // assume role for main account
    const assumeRoleParams = {
        RoleArn: process.env.ROLE_ARN,
        RoleSessionName: 'CodeBuildTrigger',
    };
    const assumeRoleResponse = await sts.assumeRole(assumeRoleParams).promise();

    // set credentials for main account
    var config = new AWS.Config({
        accessKeyId: assumeRoleResponse.Credentials.AccessKeyId,
        secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey,
        sessionToken: assumeRoleResponse.Credentials.SessionToken,
    });

    // set main account credentials for codebuild
    codebuild = new AWS.CodeBuild(config);

    // Deploy to main account using CodeBuild with the assumed role
    const codebuildParams = {
        projectName: process.env.PROJECT_NAME,
        environmentVariablesOverride: [
            {
                name: 'DEPLOYER_ROLE',
                value: process.env.ROLE_ARN,
                type: 'PLAINTEXT',
            },
        ],
    };
    const codebuildResponse = await codebuild.startBuild(codebuildParams).promise();
    console.log('codebuildResponse: ', codebuildResponse);

    return {
        statusCode: 200,
        body: JSON.stringify('done'),
    };
    }catch (err) {
        console.log('err: ', err);
    }
};

As you can see, we can use the sts.assumeRole feature to get the credentials for another account and then use it to configure CodeBuild in another account.

Modify the main account IAM role

Now that we have our Lambda function stack deployed, let's complete the trust policy by updating the main account role with the ArnPrincipal of the lambda's execution role.

  const mainRole = new iam.Role(this, 'MainRole', {
    roleName: `mainaccount-role`,
    assumedBy: new iam.CompositePrincipal(
      new iam.ServicePrincipal('codebuild.amazonaws.com'),
      new iam.ArnPrincipal(`arn:aws:iam::${workloadAccountNumber}:role/lambda-execution-role`),
    ),
  });

Great! Once you deploy the changes, the trust policies will be ready.

Testing the lambda

Now that we have the entire setup ready, let's go ahead and test our lambda function.

You can either create a test event or attach your lambda as a source to some other service such as an AppConfig extension to trigger it when a new configuration is added to the AppConfig configuration.

lambda test result

We can see in the lambda execution results that the lambda successfully started the CodeBuild.

Let's cross-confirm by going into the main account.

successful build

Bingo! You can see the initiator of the CodeBuild as mainaccount-role/CodeBuildTrigger, which is what we configured in our lambda function.

Conclusion

This article outlines the steps involved in creating a Lambda function to trigger a cross account CodeBuild deployment.

This can come in handy when you want to trigger cross-account deployments out of different lambda events.

It can even be associated with the AWS AppConfig extension to trigger a deployment when new configuration data is added to AppConfig Application. This is a great way to automate observability and monitoring by using the AppConfig configuration data.

Feel free to comment if you have any doubts in the article. Follow me for more cloud and DevOps content.

See you in cloud!

Vishnu S.

Did you find this article valuable?

Support Learn More Cloud by becoming a sponsor. Any amount is appreciated!