How do I deploy an AWS CloudFormation stack in a different account using CodePipeline?

Last updated: 2022-01-19

I want to use AWS CodePipeline to deploy an AWS CloudFormation stack in a different AWS account. How do I set that up?

Short description

To deploy a CloudFormation stack in a different AWS account using CodePipeline, do the following:

Note: This procedure assumes that you have two AWS accounts: account 1 and account 2.

1.    (In account 1) Create a customer managed AWS Key Management Service (AWS KMS) key that grants key usage permissions to the following:

  • Account 1's CodePipeline service role
  • Account 2

2.    (In account 1) Create an Amazon Simple Storage Service (Amazon S3) bucket with a bucket policy that grants account 2 access to the bucket.

3.    (In account 2) Create a cross-account AWS Identity and Access Management (IAM) role that allows the following:

  • CloudFormation API actions
  • Access to the Amazon S3 bucket in account 1
  • Decryption with the customer managed AWS KMS key in account 1

4.    (In account 1) Add the AssumeRole permission to account 1's CodePipeline service role to allow it to assume the cross-account role in account 2.

5.    (In account 2) Create a service role for the CloudFormation stack that includes the required permissions for the services deployed by the stack.

6.    (In account 1) Update the CodePipeline configuration in account 1 to include the resources associated with account 2.

Resolution

(In account 1) Create a customer managed AWS KMS key that grants usage permissions to account 1's CodePipeline service role and account 2

1.    In account 1, open the AWS KMS console.

2.    In the navigation pane, choose Customer managed keys.

3.    Choose Create key. Then, choose Symmetric.

Note: In the Advanced options section, leave the origin as KMS.

4.    For Alias, enter a name for your key.

5.    (Optional) Add tags based on your use case. Then, choose Next.

6.    On the Define key administrative permissions page, for Key administrators, choose your AWS Identity and Access Management (IAM) user. Also, any other users or groups that you want to serve as administrators for the key. Then, choose Next.

7.    On the Define key usage permissions page, for This account, add the IAM identities that you want to have access to the key. For example: The CodePipeline service role.

8.    In the Other AWS accounts section, choose Add another AWS account. Then, enter the Amazon Resource Name (ARN) of the IAM role in account 2.

9.    Choose Next. Then, choose Finish.

10.    In the Customer managed keys section, choose the key that you just created. Then, copy the key's ARN.

Important: You must have the AWS KMS key's ARN when you update your pipeline and configure your IAM policies.

(In account 1) Create an Amazon S3 bucket with a bucket policy that grants account 2 access to the bucket

1.    In account 1, open the Amazon S3 console.

2.    Choose an existing Amazon S3 bucket or create a new S3 bucket to use as the ArtifactStore for CodePipeline.

Note: Artifacts can include a stack template file, a template configuration file, or both. CodePipeline uses these artifacts to work with CloudFormation stacks and change sets. In your template configuration file, you must specify template parameter values, a stack policy, and tags.

3.    On the Amazon S3 details page for your bucket, choose Permissions.

4.    Choose Bucket Policy.

5.    In the bucket policy editor, enter the following policy:

Important: Replace codepipeline-source-artifact with the SourceArtifact bucket name for CodePipeline. Replace ACCOUNT_B_NO with account 2's account number.

{
  "Id": "Policy1553183091390",
  "Version": "2012-10-17",
  "Statement": [{
      "Sid": "",
      "Action": [
        "s3:Get*",
        "s3:Put*"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::codepipeline-source-artifact/*",
      "Principal": {
        "AWS": [
          "arn:aws:iam::ACCOUNT_B_NO:root"
        ]
      }
    },
    {
      "Sid": "",
      "Action": [
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::codepipeline-source-artifact",
      "Principal": {
        "AWS": [
          "arn:aws:iam::ACCOUNT_B_NO:root"
        ]
      }
    }
  ]
}

6.    Choose Save.

(In account 2) Create a cross-account IAM role

Create an IAM policy that allows the following:

  • The pipeline in account 1 to assume the cross-account IAM role in account 2
  • CloudFormation API actions
  • Amazon S3 API actions related to the SourceArtifact

1.    In account 2, open the IAM console.

2.    In the navigation pane, choose Policies. Then, choose Create policy.

3.    Choose the JSON tab. Then, enter the following policy into the JSON editor:

Important: Replace codepipeline-source-artifact with your pipeline's Artifact store's bucket name.

{
  "Version": "2012-10-17",
  "Statement": [{
      "Effect": "Allow",
      "Action": [
        "cloudformation:*",
        "iam:PassRole"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:Get*",
        "s3:Put*",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::codepipeline-source-artifact/*"
      ]
    }
  ]
}

4.    Choose Review policy.

5.    For Name, enter a name for the policy.

6.    Choose Create policy.

Create a second IAM policy that allows AWS KMS API actions

1.    In account 2, open the IAM console.

2.    In the navigation pane, choose Policies. Then, choose Create policy.

3.    Choose the JSON tab. Then, enter the following policy into the JSON editor:

Important: Replace arn:aws:kms:REGION:ACCOUNT_A_NO:key/key-id with your AWS KMS key's ARN that you copied earlier.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "kms:DescribeKey",
      "kms:GenerateDataKey*",
      "kms:Encrypt",
      "kms:ReEncrypt*",
      "kms:Decrypt"
    ],
    "Resource": [
      "arn:aws:kms:REGION:ACCOUNT_A_NO:key/key-id"
    ]
  }]
}

4.    Choose Review policy.

5.    For Name, enter a name for the policy.

6.    Choose Create policy.

Create the cross-account IAM role using the policies that you created

1.    In account 2, open the IAM console.

2.    In the navigation pane, choose Roles.

3.    Choose Create role.

4.    Choose Another AWS account.

5.    For Account ID, enter account 1's account ID.

6.    Choose Next: Permissions. Then, complete the steps to create the IAM role.

7.    Attach the cross-account role policy and KMS key policy to the role that you created. For instructions, see Adding and removing IAM identity permissions.

(In account 1) Add the AssumeRole permission to account 1's CodePipeline service role to allow it to assume the cross-account role in account 2

1.    In account 1, open the IAM console.

2.    In the navigation pane, choose Roles.

3.    Choose the IAM service role that you're using for CodePipeline.

4.    Choose Add inline policy.

5.    Choose the JSON tab. Then, enter the following policy into the JSON editor:

Important: Replace ACCOUNT_B_NO with account 2's account number.

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "sts:AssumeRole",
    "Resource": [
      "arn:aws:iam::ACCOUNT_B_NO:role/*"
    ]
  }
}

6.    Choose Review policy, and then create the policy.

(In account 2) Create a service role for the CloudFormation stack that includes the required permissions for the services deployed by the stack

Note: This service role is configured directly on the CloudFormation stack in account 2. The role must include the permissions for the services deployed by the stack.

1.    In account 2, open the IAM console.

2.    In the navigation pane, choose Roles.

3.    Create a role for AWS CloudFormation to use when launching services on your behalf.

4.    Apply permissions to your role based on your use case.

Important: Make sure that your trust policy is for AWS CloudFormation, and that your role has permissions to access services deployed by the stack.

(In account 1) Update the CodePipeline configuration to include the resources associated with account 2

Note: If you receive errors when running AWS Command Line Interface (AWS CLI) commands, make sure that you’re using the most recent AWS CLI version.

You can't use the CodePipeline console to create or edit a pipeline that uses resources associated with another account. However, you can use the console to create the general structure of the pipeline. Then, you can use the AWS CLI to edit the pipeline and add the resources associated with the other account. Or, you can update a current pipeline with the resources for the new pipeline. For more information, see Create a pipeline in CodePipeline.

1.    Get the pipeline JSON structure by running the following AWS CLI command:

aws codepipeline get-pipeline --name MyFirstPipeline >pipeline.json

2.    In your local pipeline.json file, confirm that the encryptionKey ID under artifactStore contains the ID with the AWS KMS key's ARN.

Note: For more information on pipeline structure, see create-pipeline in the AWS CLI Command Reference.

3.    In the pipeline.json file, update the AWS CloudFormation action configuration.

Note: The RoleArn inside the action configuration JSON structure for your pipeline is the role for the CloudFormation stack (CFN_STACK_ROLE). The roleArn outside the action configuration JSON structure is the cross-account role that the pipeline assumes to operate a CloudFormation stack (CROSS_ACCOUNT_ROLE).

4.    Verify that the role is updated for both of the following:

  • The RoleArn inside the action configuration JSON structure for your pipeline.
  • The roleArn outside the action configuration JSON structure for your pipeline.

Note: In the following code example, RoleArn is the role passed to AWS CloudFormation to launch the stack. CodePipeline uses roleArn to operate an AWS CloudFormation stack.

{
  "name": "Prod_Deploy",
  "actions": [{
    "inputArtifacts": [{
      "name": "MyApp"
    }],
    "name": "test-cfn-x",
    "actionTypeId": {
      "category": "Deploy",
      "owner": "AWS",
      "version": "1",
      "provider": "CloudFormation"
    },
    "outputArtifacts": [],
    "configuration": {
      "ActionMode": "CHANGE_SET_REPLACE",
      "ChangeSetName": "test",
      "RoleArn": "ARN_FOR_CFN_STACK_ROLE",
      "Capabilities": "CAPABILITY_IAM",
      "StackName": "test-cfn-sam",
      "TemplatePath": "MyApp::template.yaml"
    },
    "roleArn": "ARN_FOR_CROSS_ACCOUNT_ROLE",
    "runOrder": 1
  }]
}

5.    Remove the metadata configuration from the pipeline.json file. For example:

"metadata": {
  "pipelineArn": "arn:aws:codepipeline:REGION:ACC:my_test",
  "updated": 1551216777.183,
  "created": 1551207202.964
}

Important: To align with proper JSON formatting, remove the comma before the metadata section.

6.    (Optional) To create a pipeline and update the JSON structure, run the following command to update the pipeline with the new configuration file:

aws codepipeline update-pipeline --cli-input-json file://pipeline.json

7.    (Optional) To use a current pipeline and update the JSON structure, run the following command to create a new pipeline:

aws codepipeline create-pipeline --cli-input-json file://pipeline.json

Important: In your pipeline.json file, make sure that you change the name of your new pipeline.