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

Last updated: 2019-04-11

How can I use AWS CodePipeline to deploy an AWS CloudFormation stack in a different account?

Resolution

Create a pipeline in AWS Identity and Access Management (IAM) for account A, and then deploy an AWS CloudFormation stack to account B.

Create a customer-managed key for account A

1.    Open the IAM console.

2.    In the navigation pane, choose Encryption keys.

3.    Choose Create key.

4.    Create an alias and description for your key, and then choose Next Step.

5.    (Optional) Add tags, and then choose Next Step.

6.    For encryptionKey_sections.management, choose your IAM user and any other users or groups that you want to serve as administrators for the key, and then choose Next.

7.    For keyUsage_sections.internal, add the IAM users in the account that should have access to the key (such as the CodePipeline service role).

8.    For keyUsage_sections.external, add the Amazon Resource Name (ARN) of the IAM role in account B.

9.    Choose Next Step, and then choose keyUsage.finish.

10.    In the navigation pane, choose encryptionKeys.keys.

11.    In the Alias column, choose your key.

12.    In the general_configuration section, for encryptionKey_details_tab_summary.keyArn, copy the ARN for your key.

Note: You will need the ARN for your key when you edit your pipeline and configure your policies.

Create a policy for the Amazon S3 SourceArtifacts bucket for account A

Your policy in Amazon Simple Storage Service (Amazon S3) allows account A to access account B.

1.    Open the Amazon S3 console.

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

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 code:

{
  "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.    In the code above, replace codepipeline-source-artifact with the SourceArtifact bucket name for CodePipeline.

7.    In the code above, replace ACCOUNT_B_NO with the account number of account B.

8.    Choose Save.

Create a service role policy for account A

This service role policy allows the CodePipeline service role in account A to assume the service role in account B.

1.    Open the IAM console for account A.

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 view, and then enter the following policy into the code editor:

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

6.    In the code above, replace ACCOUNT_B_NO with the number of account B.

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

Create a cross-account role policy for account B

The cross-account role policy allows account A to assume a role in account B and enables AWS CloudFormation actions.

1.    Open the IAM console.

2.    In the navigation pane, choose Policies.

3.    Choose Create policy.

4.    Choose the JSON view, and then enter the following policy into the code editor:

{
  "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/*"
      ]
    }
  ]
}

5.    In the code above, replace codepipeline-source-artifact with the ArtifactStore bucket name of the pipeline.

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

Create a key policy for account B

1.    Open the IAM console.

2.    In the navigation pane, choose Policies.

3.    Choose Create policy.

4.    Choose the JSON view, and then enter the following policy into the code editor:

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

5.    In the code above, replace arn:aws:kms:us-east-1:ACCOUNT_A_NO:key/key-id with the ARN of the key that you created in the Create a customer-managed key for account A section earlier.

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

Create a new cross-account role

1.    In the navigation pane, choose Roles.

2.    Choose Create role.

3.    Choose Another AWS account.

4.    For Account ID, enter the account ID of account A.

5.    Choose Next: Permissions, and then complete the steps to create the role.

6.    Attach the cross-account role policy and KMS key policy that you created earlier to the role.

Create a role for the AWS CloudFormation stack

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

2.    Apply permissions to your role based on your needs.

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

Update the CodePipeline configuration in account A

Note: 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, and then use the AWS Command Line Interface (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.

1.    In the AWS CLI, run the following command to get the pipeline JSON structure:

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

2.    In your local pipeline.json file, confirm that the encryptionKey ID contains the ID with the ARN of the key that you created in the Create a customer-managed key for account A section earlier.

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 AWS CloudFormation stack. The roleArn outside the action configuration JSON structure is the cross-account role that the pipeline assumes to operate an AWS CloudFormation stack.

4.    Verify that the role is updated for both RoleArn and roleArn.

In the following code example, RoleArn is the role passed to AWS CloudFormation to launch the stack, and 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_CFNSTACKROLE",
      "Capabilities": "CAPABILITY_IAM",
      "StackName": "test-cfn-sam",
      "TemplatePath": "MyApp::template.yaml"
    },
    "roleArn": "ARN_FOR_CROSS_ACCOUNT",
    "runOrder": 1
  }]
}

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

"metadata": {
  "pipelineArn": "arn:aws:codepipeline:us-east-1: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

Note: In your pipeline.json file, be sure to change the name of your new pipeline.