如何使用 CodePipeline 在不同的账户中部署 AWS CloudFormation 堆栈?

上次更新时间:2019 年 12 月 9 日

如何使用 AWS CodePipeline 在不同的账户中部署 AWS CloudFormation 堆栈?

简短描述

要在不同的账户中部署 AWS CloudFormation 堆栈,您必须完成以下操作:

  1. 在一个账户(账户 A)中创建管道。此账户应该包括客户管理的 AWS Key Management Service (AWS KMS) 密钥、适用于构件的 Amazon Simple Storage Service (Amazon S3) 存储桶,以及允许其他账户(账户 B)访问的 S3 存储桶策略。
  2. 账户 B 中,配置允许以下操作的跨账户服务角色:1) AWS CloudFormation 操作;2) 访问账户 A 中的 S3 存储桶;3) 使用账户 A 中的客户管理的 KMS 密钥进行解密。
  3. 账户 A 中,允许管道服务角色担任账户 B 中的跨账户角色(使用 AssumeRole)。

解决方法

在账户 A 中创建客户管理的密钥

1.    在账户 A中,打开 AWS KMS 控制台

2.    从导航窗格中选择客户管理的密钥

3.    选择创建密钥,然后选择对称

注意:高级选项部分,请将源保留为 KMS

4.    对于别名,输入您的密钥的名称。

5.    (可选)添加标签,然后选择下一步

6.    在定义密钥管理权限页面上,对于密钥管理员,请选择您的 AWS Identity and Access Management (IAM) 用户以及您希望由其担任密钥管理员的任何其他用户或组,然后选择下一步

7.    在定义密钥使用权限页面上,对于此账户,请该账户中添加应拥有该密钥的访问权限的 IAM 用户(如 CodePipeline 服务角色)。

8.    在其他 AWS 账户部分,选择添加其他 AWS 账户,然后输入 账户 B 中的 IAM 角色的 Amazon 资源名称 (ARN)。

9.    选择下一步,然后选择完成

10.    在客户管理的密钥部分,选择您刚刚创建的密钥,然后复制该密钥的 ARN。

注意:当您编辑您的管道和配置您的策略时,必须使用您的密钥的 ARN。

在账户 A 中,选择或创建一个 Amazon S3 存储桶以作为 SourceArtifact 存储桶的管道,然后创建一个存储桶策略

账户 A 中的 S3 存储桶策略必须授予账户 B 访问权限。

1.    在账户 A 中,打开 Amazon S3 控制台

2.    选择现有的 S3 存储桶或创建新的 S3 存储桶以用作 CodePipeline 的 ArtifactStore

注意:构件可包含一个堆栈模板文件、一个模板配置文件或两者皆有。CodePipeline 将使用这些构件来处理 AWS CloudFormation 堆栈和更改集。在您的模板配置文件中,您必须指定模板参数值、堆栈策略和标签。

3.    在存储桶的 Amazon S3 详细信息页面中,选择权限

4.    选择存储桶策略

5.    在存储桶策略编辑器中,输入以下代码:

{
  "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.    在上述代码中,将 codepipeline-source-artifact 替换为 CodePipeline 的 SourceArtifact 存储桶名称。

7.    在上述代码中,将 ACCOUNT_B_NO 替换为账户 B 的账号。

8.    选择保存

在账户 B 中,创建一个允许账户 A 在 SourceArtifact 中执行 S3 和 KMS 相关操作的跨账户角色 (CROSS_ACCOUNT_ROLE)

此跨账户角色策略允许账户 A 中的管道代入账户 B 中的角色。此策略还允许 AWS CloudFormation 操作,以及对 SourceArtifact 和 KMS 相关操作的访问权限。

1.    在账户 B 中,打开 IAM 控制台

2.    在导航窗格中,选择策略

3.    选择创建策略

4.    选择 JSON 视图,然后在代码编辑器中输入以下策略:

{
  "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.    将上述代码中的 codepipeline-source-artifact 替换为管道的 ArtifactStore 存储桶名称

6.    选择审核策略,然后创建该策略。

7.    在导航窗格中,选择策略

8.    选择创建策略

9.    选择 JSON 视图,然后在代码编辑器中输入以下策略:

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

10.    将上述代码中的 arn:aws:kms:REGION:ACCOUNT_A_NO:key/key-id 替换为您之前在在账户 A 中创建客户管理的密钥部分创建的密钥的 ARN。

11.    选择审核策略,然后创建该策略。

12.    在导航窗格中,选择角色

13.    选择创建角色

14.    选择其他 AWS 账户

15.    对于账户 ID,输入账户 A 的账户 ID。

16.    选择下一步: 权限,然后完成相关步骤以创建该角色。

17.    将跨账户角色策略和 KMS 密钥策略附加到您创建的角色。

在账户 A 中,将 AssumeRole 权限添加到 CodePipeline 服务角色

您的策略允许账户 A 中的 CodePipeline 服务角色代入您之前在账户 B 中创建的跨账户角色。

1.    在账户 A 中,打开 IAM 控制台

2.    在导航窗格中,选择角色

3.    选择您正在用于 CodePipeline 的 IAM 服务角色。

4.    选择添加内联策略

5.    选择 JSON 视图,然后在代码编辑器中输入以下策略:

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

6.    将上述代码中的 ACCOUNT_B_NO 替换为账户 B 的账号。

7.    选择审核策略,然后创建该策略。

在账户 B 中,创建为 AWS CloudFormation 堆栈创建一个角色 (CFN_STACK_ROLE)

服务角色直接在账户 B 中的 AWS CloudFormation 堆栈上配置,必须包含对该堆栈部署的服务的权限。

1.    在账户 B 中,打开 IAM 控制台

2.    在导航窗格中,选择角色

3.    为 AWS CloudFormation 创建代表您启动服务时将使用的角色。

4.    根据您的需要对您的角色应用权限

重要提示:确保您的信任策略适用于 AWS CloudFormation,并且您的角色有权访问由堆栈部署的服务。

在账户 A 中更新 CodePipeline 配置

注意:您不能通过 CodePipeline 控制台来创建或编辑将使用与其他账户相关联的资源的管道。但是,您可以通过控制台来创建管道的一般结构。然后,您可以使用 AWS 命令行界面 (AWS CLI) 来编辑管道并添加与其他账户关联的资源。您也可以使用新管道的资源来更新当前管道。

1.    在 AWS CLI 中,运行以下命令以获取管道 JSON 结构:

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

2.    在您的本地 pipeline.json 文件中,确认 encryptionKey ID 包含您之前创建的密钥的 ID 和 ARN。

3.    在 pipeline.json 文件中,更新 AWS CloudFormation 操作配置

注意:管道的操作配置 JSON 结构内的 RoleArn 是用于 AWS CloudFormation 堆栈的角色 (CFN_STACK_ROLE)。操作配置 JSON 结构外的 roleArn 是管道运行 AWS CloudFormation 堆栈时将代入的跨账户角色 (CROSS_ACCOUNT_ROLE)。

4.    验证 RoleArnroleArn 的角色都已更新。

在以下代码示例中,RoleArn 是传递给 AWS CloudFormation 用于启动堆栈的角色。CodePipeline 使用 roleArn 来运行 AWS CloudFormation 堆栈。

{
  "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.    从 pipeline.json 文件删除 metadata 配置:

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

重要提示:为了符合正确的 JSON 格式,请删除 metadata 区域之前的逗号。

6.    (可选)要创建管道并更新 JSON 结构,请运行以下命令以使用新配置文件更新管道:

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

7.    (可选)要使用当前管道并更新 JSON 结构,请运行以下命令以创建新管道:

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

注意:在您的 pipeline.json 文件中,请更改新管道的名称。