746 words
4 minutes
Automating Cross-Account AWS Lambda Deployment with AWS CodePipeline and CloudFormation

Introduction#

In many organizations, it’s common to have multiple AWS accounts for different environments (development, staging, production) or business units. Deploying Lambda functions across these accounts can be a manual and error-prone process. This article outlines how to automate this process using AWS CodePipeline, AWS CloudFormation, and IAM roles, ensuring consistent and reliable deployments. We’ll focus on a scenario where the CodePipeline resides in a central “pipeline” account and deploys Lambda functions to target accounts.

Prerequisites#

Before you begin, ensure you have the following:

  • An AWS account for the CodePipeline (pipeline account).
  • One or more AWS accounts to deploy Lambda functions (target accounts).
  • Basic understanding of AWS IAM, Lambda, CloudFormation, CodePipeline, and S3.
  • AWS CLI configured with appropriate credentials for all accounts.

Architecture Overview#

The solution involves the following components:

  • AWS CodePipeline (Pipeline Account): Orchestrates the deployment process, triggered by code changes in a source repository (e.g., AWS CodeCommit, GitHub).
  • AWS CodeBuild (Pipeline Account): Builds the Lambda function package and generates CloudFormation templates.
  • AWS S3 Bucket (Pipeline Account): Stores the Lambda function package and CloudFormation templates.
  • AWS CloudFormation (Target Account): Deploys the Lambda function based on the provided template.
  • IAM Roles: Enable cross-account access for CodePipeline to deploy resources in the target accounts.

Step-by-Step Implementation#

1. Create IAM Roles#

First, we need to create the necessary IAM roles to allow cross-account access.

a. IAM Role in the Target Account#

This role will be assumed by the CodePipeline in the pipeline account to deploy resources in the target account.

# trust_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<pipeline_account_id>:role/CodePipelineRole"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}

Replace <pipeline_account_id> with the actual AWS account ID of your pipeline account. This trust policy allows the CodePipelineRole in the pipeline account to assume this role.

Create the role using the AWS CLI:

aws iam create-role --role-name CrossAccountDeploymentRole --assume-role-policy-document file://trust_policy.json

aws iam attach-role-policy --role-name CrossAccountDeploymentRole --policy-arn arn:aws:iam::aws:policy/AdministratorAccess # For simplicity, using Admin access.  In production, restrict permissions.

Important: In a production environment, avoid using AdministratorAccess. Instead, create a custom policy that grants only the necessary permissions for CloudFormation to create and manage Lambda functions and related resources. These permissions typically include lambda:*, iam:PassRole, cloudformation:*, s3:GetObject, and s3:PutObject.

b. IAM Role in the Pipeline Account#

This role will be used by CodePipeline and CodeBuild to perform actions in the pipeline account and assume the role in the target account.

# pipeline_role_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": "arn:aws:s3:::<pipeline_bucket_name>/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:BatchGetBuilds",
        "codebuild:StartBuild"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sts:AssumeRole"
      ],
      "Resource": "arn:aws:iam::<target_account_id>:role/CrossAccountDeploymentRole"
    },
    {
        "Effect": "Allow",
        "Action": [
            "iam:PassRole"
        ],
        "Resource": "arn:aws:iam::<pipeline_account_id>:role/CodePipelineRole"
    }
  ]
}

Replace <pipeline_bucket_name> with the name of your S3 bucket in the pipeline account and <target_account_id> with the AWS account ID of your target account. This policy allows CodePipeline to access the S3 bucket, trigger CodeBuild, and assume the role in the target account. The iam:PassRole permission allows CodePipeline to pass its own role to CloudFormation.

Create the CodePipelineRole in the pipeline account:

aws iam create-role --role-name CodePipelineRole --assume-role-policy-document file://trust_policy_codepipeline.json

aws iam put-role-policy --role-name CodePipelineRole --policy-name PipelinePolicy --policy-document file://pipeline_role_policy.json

trust_policy_codepipeline.json should allow CodePipeline service to assume the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codepipeline.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create a similar role for CodeBuild (CodeBuildRole) with the same policy as CodePipelineRole, but with a trust policy allowing CodeBuild service to assume it. The trust policy for CodeBuildRole will be:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

2. Create an S3 Bucket in the Pipeline Account#

Create an S3 bucket in the pipeline account to store the Lambda function package and CloudFormation templates.

aws s3api create-bucket --bucket <pipeline_bucket_name> --region <pipeline_region>

Replace <pipeline_bucket_name> with the desired bucket name and <pipeline_region> with the AWS region of your pipeline account.

3. Create the Lambda Function Code and CloudFormation Template#

Create the Lambda function code and a CloudFormation template to define the Lambda function and its associated resources.

a. Lambda Function Code (Python Example)#

# lambda_function.py
import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

b. CloudFormation Template (YAML Example)#

# lambda.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to deploy a Lambda function

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: MyLambdaFunction
      Description: A simple Lambda function
      Handler: lambda_function.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: !Ref S3BucketName
        S3Key: !Ref S3Key
      Runtime: python3.9
      Timeout: 30

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: LambdaExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: LambdaExecutionPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*

Parameters:
Automating Cross-Account AWS Lambda Deployment with AWS CodePipeline and CloudFormation
https://en.dymripper.com/posts/2025-05-24-automating-cross-account-aws-lambda-deployment-with-aws-codepipeline-and-cloudformation/
Author
DYMripper
Published at
2025-05-24