An Interest In:
Web News this Week
- April 13, 2024
- April 12, 2024
- April 11, 2024
- April 10, 2024
- April 9, 2024
- April 8, 2024
- April 7, 2024
CI/CD CodePipeline Cross Account Deployment
Cross Account CI/CD Pipeline
Sometimes we need to run the Pipeline in an account, but deploy product into another account for
- Further sepratation between development and production envionment
- Protect production environment
- For customer in another account
This note follows the reference workshop here
Todo
- Python CDK version
- CloudFormation version
- Roles and policies created with CloudFormation ## Architecture
Setup CDK project
create an empty directory
mkdir cdk-test-cicd-pipeline
init cdk project
cdk init --language=typescript
Create a CodeCommit by a repository stack
create lib/repository-stack.ts
import { aws_codecommit } from "aws-cdk-lib";import { App, Stack, StackProps } from "aws-cdk-lib";export class RepositoryStack extends Stack { constructor(app: App, id: string, props?: StackProps) { super(app, id, props); new aws_codecommit.Repository(this, 'CodeCommitRepo', { repositoryName: `repo-${this.account}` }); }}
Create a lambda stack for testing purpose
create lib/lambda-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';import { Construct } from 'constructs';import { aws_lambda } from 'aws-cdk-lib';const path = require("path")export class LambdaStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); new aws_lambda.Function( this, "CdkTsLambdaFunctionTest", { runtime: aws_lambda.Runtime.PYTHON_3_8, handler: "handler.handler", code: aws_lambda.Code.fromAsset( path.join(__dirname, "lambda") ) } ) }}
Build and check CDK generated stacks
#!/usr/bin/env nodeimport 'source-map-support/register';import * as cdk from 'aws-cdk-lib';import { CdkTsCicdPipelineStack } from '../lib/cdk-ts-cicd-pipeline-stack';import { RepositoryStack } from '../lib/repository-stack';import { LambdaStack } from '../lib/lambda-stack';const app = new cdk.App();new CdkTsCicdPipelineStack(app, 'CdkTsCicdPipelineStack', {});new LambdaStack(app, "CdkTsLambdaStack", {})new RepositoryStack(app, 'CdkTsRepositoryStack', {})
build and check stacks
npm install npm run buildcdk ls
should see multiple stack
CdkTsCicdPipelineStackCdkTsLambdaStackCdkTsRepositoryStack
Deploy a stack and test output
cdk deploy CdkTsLambdaStack
Application Stack
This is a smple lambda function with code from local assset folder. The important thing is that
- CodeBuild will output DevApplicationStack.template.json in the artifact bucket
- CodeBuild will output ProdApplicationStack.template.json in the artifact bucket
- CloudFormation running in the production account need to access the artifact bucket in the dev environment that is why we need roles below. This is the application stack which is a simple lambda api.
import { aws_lambda } from "aws-cdk-lib";import { aws_apigateway } from "aws-cdk-lib";import { App, Stack, StackProps } from "aws-cdk-lib";import * as path from "path"export interface ApplicationStackProps extends StackProps { readonly stageName: string}export class ApplicationStack extends Stack { // lambad code from public readonly lambdaCode: aws_lambda.CfnParametersCode; // constructor constructor(app: App, id: string, props: ApplicationStackProps) { super(app, id, props); // lambda code this.lambdaCode = aws_lambda.Code.fromCfnParameters(); // create a lambda function const lambda_function = new aws_lambda.Function( this, `CdkTsLambdaApplicationFunction-${props.stageName}`, { runtime: aws_lambda.Runtime.NODEJS_12_X, handler: "index.handler", code: this.lambdaCode, environment: { STAGE_NAME: props.stageName } } ); // create an api gateway new aws_apigateway.LambdaRestApi( this, `CdkTsApiGatewayRestApi-${props.stageName}`, { handler: lambda_function, endpointExportName: `CdkTsLambdaRestApiEndpoint-${props.stageName}`, deployOptions: { stageName: props.stageName } } ); // lambda version alias function // codedeploy }}
CodePipeline Stack
- The CodePipeline lives in the dev account
- The CodePipeline need to deploy things into the production account
- Need to assume role which setup from the production account
This role enables creating resources inside the productiona account
const prodDeploymentRole = aws_iam.Role.fromRoleArn( this, "ProdDeploymentRole", `arn:aws:iam::product_account_id:role/CloudFormationDeploymentRole`, { mutable: false } )
and this role enables accessing the S3 artifact from the production account
const prodCrossAccountRole = aws_iam.Role.fromRoleArn( this, "ProdCrossAccountRole", `arn:aws:iam::product_account_id:role/CdkCodePipelineCrossAcccountRole`, { mutable: false} )
CodeBuild
- Build the lambda code
- Build the application stack
stageName: 'Build', actions: [ new aws_codepipeline_actions.CodeBuildAction({ actionName: 'Application_Build', project: lambdaBuild, input: sourceOutput, outputs: [lambdaBuildOutput], }), new aws_codepipeline_actions.CodeBuildAction({ actionName: 'CDK_Synth', project: cdkBuild, input: sourceOutput, outputs: [cdkBuildOutput], }), ],
CodeDeploy
- Deploy the application stack into the dev environment
stageName: 'Deploy_Dev', actions: [ new aws_codepipeline_actions.CloudFormationCreateUpdateStackAction({ actionName: 'Deploy', templatePath: cdkBuildOutput.atPath('DevApplicationStack.template.json'), stackName: 'DevApplicationDeploymentStack', adminPermissions: true, parameterOverrides: { ...props.devApplicationStack.lambdaCode.assign(lambdaBuildOutput.s3Location), }, extraInputs: [lambdaBuildOutput] }) ],
- Deploy the application stack into the production environment
stageName: 'Deploy_Prod', actions: [ new aws_codepipeline_actions.CloudFormationCreateUpdateStackAction({ actionName: 'Deploy', templatePath: cdkBuildOutput.atPath('ProdApplicationStack.template.json'), stackName: 'ProdApplicationDeploymentStack', adminPermissions: true, parameterOverrides: { ...props.prodApplicationStack.lambdaCode.assign(lambdaBuildOutput.s3Location), }, deploymentRole: prodDeploymentRole, cfnCapabilities: [CfnCapabilities.ANONYMOUS_IAM], role: prodCrossAccountRole, extraInputs: [lambdaBuildOutput], }), ],
Note to add the inline policy to the assume role
pipeline.addToRolePolicy( new aws_iam.PolicyStatement({ actions: ['sts:AssumeRole'], resources: ["arn:aws:iam::product_account_id:role/*"] }));
Note the KMS key which encrypt the S3 artifact bucket
new CfnOutput( this, "ArtifactBucketEncryptionKeyArn", { value: key.keyArn, exportName: "ArtifactBucketEncryptionKey"});
Policy CodePipelineCrossAccountRole
This allow the ProductionAccount when deploying CloudFormation can access the S3 ArtifactBucket where code for the Lambda function is stored.
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "cloudformation:*", "iam:PassRole" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "s3:Get*", "s3:Put*", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::artifact-bucket-{DEV_ACCOUNT_ID}", "arn:aws:s3:::artifact-bucket-{DEV_ACCOUNT_ID}/*" ], "Effect": "Allow" }, { "Action": [ "kms:DescribeKey", "kms:GenerateDataKey*", "kms:Encrypt", "kms:ReEncrypt*", "kms:Decrypt" ], "Resource": "{KEY_ARN}", "Effect": "Allow" } ]}
Policy CloudFormationDeploymentRole
This allow the CloudFormation in the ProductionAccount can create resources when deploying stacks developed from the develop account.
{ "Version": "2012-10-17", "Statement": [ { "Action": "iam:PassRole", "Resource": "arn:aws:iam::{PROD_ACCOUNT_ID}:role/*", "Effect": "Allow" }, { "Action": [ "iam:GetRole", "iam:CreateRole", "iam:AttachRolePolicy" ], "Resource": "arn:aws:iam::{PROD_ACCOUNT_ID}:role/*", "Effect": "Allow" }, { "Action": "lambda:*", "Resource": "*", "Effect": "Allow" }, { "Action": "apigateway:*", "Resource": "*", "Effect": "Allow" }, { "Action": "codedeploy:*", "Resource": "*", "Effect": "Allow" }, { "Action": [ "s3:GetObject*", "s3:GetBucket*", "s3:List*" ], "Resource": [ "arn:aws:s3:::artifact-bucket-{DEV_ACCOUNT_ID}", "arn:aws:s3:::artifact-bucket-{DEV_ACCOUNT_ID}/*" ], "Effect": "Allow" }, { "Action": [ "kms:Decrypt", "kms:DescribeKey" ], "Resource": "{KEY_ARN}", "Effect": "Allow" }, { "Action": [ "cloudformation:CreateStack", "cloudformation:DescribeStack*", "cloudformation:GetStackPolicy", "cloudformation:GetTemplate*", "cloudformation:SetStackPolicy", "cloudformation:UpdateStack", "cloudformation:ValidateTemplate" ], "Resource": "arn:aws:cloudformation:us-east-2:{PROD_ACCOUNT_ID}:stack/ProdApplicationDeploymentStack/*", "Effect": "Allow" } ]}
Connect to AWS CodeCommit by HTTPS
Goto AWS IAM console and download credential to access AWS CodeCommit
git config --global credential.helper '!aws codecommit credential-helper $@'git config --global credential.UseHttpPath true
Original Link: https://dev.to/entest/cicd-codepipeline-cross-account-deployment-5e9d
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To