📝

CodePipeline を使用してクロスアカウントで CodeCommit から S3 へデプロイしてみた

2024/06/02に公開

今回は CodePipeline を使用してクロスアカウントの CodeCommit から S3 へデプロイする方法をご紹介します。

構成

アカウント A の CodePipeline - アカウント B の CodeCommit - アカウント A の S3

手順概要

  1. アカウント B で CodeCommit リポジトリを作成
  2. アカウント B で IAM ポリシーおよび IAM ロールを作成
  3. アカウント A で S3 バケットを作成
  4. アカウント A で KMS キーを作成
  5. アカウント A で IAM ポリシーおよび IAM ロールを作成
  6. アカウント A で CodePipeline パイプラインを作成

なお、コンソール上での詳細な手順については各セクションのドキュメントをご参照ください。

アカウント B での作業

  1. アカウント B で CodeCommit リポジトリを作成

Create an AWS CodeCommit repository - AWS CodeCommit
まずはアカウント B で CodeCommit リポジトリを作成します。
設定値はデフォルト、ファイルはコンソールから test.txt というファイルをコミットしました。

  1. アカウント B で IAM ポリシーおよび IAM ロールを作成

アカウント A の CodePipeline で使用する IAM ロールが、アカウント B の CodeCommit へアクセスするために、アカウント B に IAM ロールを作成します。

IAM ポリシーは以下の定義です。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "codecommit",
      "Effect": "Allow",
      "Action": [
        "codecommit:Get*",
        "codecommit:UploadArchive",
        "codecommit:GitPull",
        "codecommit:CancelUploadArchive"
      ],
      "Resource": ["<source-account-repository-arn>"]
    },
    {
      "Sid": "s3",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": ["<target-account-artifact-s3-bucket-arn>/*"]
    },
    {
      "Sid": "kms",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt",
        "kms:Encrypt",
        "kms:GenerateDataKey",
        "kms:DescribeKey",
        "kms:ReEncrypt*"
      ],
      "Resource": ["<target-account-key-arn>"]
    }
  ]
}

<> の部分はそれぞれ以下のように置換してください。

  • <source-account-repository-arn>: アカウント B の CodeCommit リポジトリ ARN
  • <target-account-artifact-s3-bucket-arn>: アカウント A に作成予定のアーティファクト用 S3 バケット ARN
  • <target-account-key-arn>: 後続の手順 4 で作成するアカウント A の KMS キー ARN
    • この時点ではダミー値でよいです

IAM ロールの信頼ポリシーは以下の定義です。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Principal": {
        "AWS": "<target-account-id>"
      }
    }
  ]
}
  • <target-account-id>: アカウント A のアカウント ID

アタッチする IAM ポリシーは上記で作成した IAM ポリシーです。
IAM ロール ARN は後続の手順で必要なので控えておいてください。

アカウント A での作業

  1. アカウント A で S3 バケットを作成

Amazon S3 コンソールを使用したバケットポリシーの追加 - Amazon Simple Storage Service
アーティファクト用の S3 バケットとデプロイ先の S3 バケットを作成します。
アーティファクト用 S3 バケットをデフォルト設定で作成後、バケットポリシーを以下のように定義します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<source-account-iam-role-arn>"
      },
      "Action": "s3:PutObject",
      "Resource": "<target-account-artifact-s3-bucket-arn>/*"
    }
  ]
}
  • <source-account-iam-role-arn>: 手順 2 で作成したアカウント B の IAM ロール ARN
  • <target-account-artifact-s3-bucket-arn>: 作成したアーティファクト用 S3 バケット ARN

デプロイ用 S3 バケットにはバケットポリシーは不要なので、デフォルト設定で作成してください。

  1. アカウント A で KMS キーを作成

Creating keys - AWS Key Management Service
CodePipeline のアーティファクトストアである S3 にデプロイする際に使用する KMS キーを作成します。
デフォルト設定で作成後、キーポリシーを以下のように定義します。

{
  "Version": "2012-10-17",
  "Id": "key-consolepolicy-3",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<target-account-id>:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Allow use of the key",
      "Effect": "Allow",
      "Principal": {
        "AWS": "<source-account-iam-role-arn>"
      },
      "Action": [
        "kms:Decrypt",
        "kms:Encrypt",
        "kms:GenerateDataKey",
        "kms:ReEncryptTo",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    }
  ]
}
  • <target-account-id>: アカウント A のアカウント ID
  • <source-account-iam-role-arn>: 手順 2 で作成したアカウント B の IAM ロール ARN

キーポリシー定義後、手順 2 でダミー値にしていた <target-account-key-arn> を KMS キーの ARN に置換してください。

なお、KMS キーを作成する理由は後述します。

  1. アカウント A で IAM ポリシーおよび IAM ロールを作成

AWS のサービスにアクセス許可を委任するロールの作成 - AWS Identity and Access Management
アカウント A の CodePipeline で使用する IAM ロールを作成します。

IAM ポリシーは以下の定義です。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "sts",
      "Effect": "Allow",
      "Action": ["sts:AssumeRole"],
      "Resource": ["<source-account-iam-role-arn>"]
    },
    {
      "Sid": "s3get",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": ["<target-account-artifact-s3-bucket-arn>/*"]
    },
    {
      "Sid": "kms",
      "Effect": "Allow",
      "Action": ["kms:Decrypt"],
      "Resource": ["<target-account-key-arn>"]
    },
    {
      "Sid": "s3put",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": ["<target-account-deploy-s3-bucket-arn>/*"]
    }
  ]
}
  • <source-account-iam-role-arn>: 手順 2 で作成したアカウント B の IAM ロール ARN
  • <target-account-artifact-s3-bucket-arn>: 手順 3 で作成したアーティファクト用 S3 バケット ARN
  • <target-account-key-arn>: 手順 4 で作成したアカウント A の KMS キー ARN
  • <target-account-deploy-s3-bucket-arn>: 手順 3 で作成したデプロイ用 S3 バケット ARN

IAM ロールの信頼ポリシーは以下の定義です。

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

アタッチする IAM ポリシーは上記で作成した IAM ポリシーです。

  1. アカウント A で CodePipeline パイプラインを作成

create-pipeline — AWS CLI 2.15.62 Command Reference
アカウント A で CodePipeline パイプラインを作成しますが、コンソールからは設定できない項目があるため AWS CLI の create-pipeline コマンドを使用します。

事前に以下のような JSON ファイルを定義します。

codepipeline.json
{
  "pipeline": {
    "name": "pipeline-name",
    "roleArn": "<target-account-iam-role-arn>",
    "artifactStore": {
      "type": "S3",
      "location": "<target-account-artifact-s3-bucket-name>",
      "encryptionKey": {
        "id": "<target-account-key-arn>",
        "type": "KMS"
      }
    },
    "stages": [
      {
        "name": "Source",
        "actions": [
          {
            "name": "Source",
            "actionTypeId": {
              "category": "Source",
              "owner": "AWS",
              "provider": "CodeCommit",
              "version": "1"
            },
            "runOrder": 1,
            "configuration": {
              "BranchName": "main",
              "OutputArtifactFormat": "CODE_ZIP",
              "PollForSourceChanges": "false",
              "RepositoryName": "<source-account-repository-name>"
            },
            "outputArtifacts": [
              {
                "name": "SourceArtifact"
              }
            ],
            "roleArn": "<source-account-iam-role-arn>",
            "inputArtifacts": [],
            "region": "ap-northeast-1",
            "namespace": "SourceVariables"
          }
        ]
      },
      {
        "name": "Deploy",
        "actions": [
          {
            "name": "Deploy",
            "actionTypeId": {
              "category": "Deploy",
              "owner": "AWS",
              "provider": "S3",
              "version": "1"
            },
            "runOrder": 1,
            "configuration": {
              "BucketName": "<target-account-deploy-s3-bucket-name>",
              "Extract": "false",
              "ObjectKey": "<object-name>"
            },
            "outputArtifacts": [],
            "inputArtifacts": [
              {
                "name": "SourceArtifact"
              }
            ],
            "region": "ap-northeast-1",
            "namespace": "DeployVariables",
            "roleArn": "<target-account-iam-role-arn>"
          }
        ]
      }
    ],
    "version": 1,
    "executionMode": "QUEUED",
    "pipelineType": "V2"
  }
}
  • <target-account-iam-role-arn>: 手順 5 で作成したアカウント A の IAM ロール ARN
  • <target-account-artifact-s3-bucket-name>: 手順 3 で作成したアーティファクト用 S3 バケット名
  • <target-account-key-arn>: 手順 4 で作成したアカウント A の KMS キー ARN
  • <source-account-repository-name>: 手順 1 で作成したアカウント B の CodeCommit リポジトリ名
  • <source-account-iam-role-arn>: 手順 2 で作成したアカウント B の IAM ロール ARN
  • <target-account-deploy-s3-bucket-name>: 手順 3 で作成したデプロイ用 S3 バケット名
  • <object-name>: 任意のオブジェクト名 (test.zip など)

上記の JSON ファイルを指定して以下のコマンドを実行します。

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

CodePipeline コンソールでデプロイが成功していることを確認できれば完了です。

KMS キーを指定する理由

クロスアカウントなCodePipelineとCodeCommitの連携 #AWS - Qiita

しかし、Objectをダウンロードしようとすると、復号に失敗するエラーが発生します。
これは、CodeCommitRoleがs3へPutする際に、AアカウントのKMSキーを使って暗号化してs3にPutするために発生しています(おそらく)

CodePipeline のアーティファクトストアである S3 にデプロイする際に使用する KMS キーを明示的に指定しない場合、上記のサイトと同様の挙動になることが確認できました。
今回の場合、アカウント A の KMS キーを以下の部分で指定しない場合、アカウント A のアーティファクトストアとなる S3 バケットにアップロードされるオブジェクトは、アカウント B の AWS マネージドキーで暗号化されます。

"encryptionKey": {
    "id": "<target-account-key-arn>",
    "type": "KMS"
  }

まず、CodePipeline のデプロイフェーズでは以下のエラーが発生しました。

エラーコード
アクセス権限がありません

エラーメッセージ
You are missing permissions to call s3.getObject on the input artifact, <target-account-artifact-s3-bucket-name>/pipeline-name/SourceArti/y6bRd2B. Verify that the policy on the resource allows you to perform this task: User: arn:aws:sts::<target-account-id>:assumed-role/<target-account-iam-role-name>/1717321718812 is not authorized to perform: kms:Decrypt on the resource associated with this ciphertext because the resource does not exist in this Region, no resource-based policies allow access, or a resource-based policy explicitly denies access (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: 4QYET9KYT7FNHJXP; S3 Extended Request ID: ZpLz0XHylksF76MVUqnkYUtlJ4+iTi8HOfUiJbAi50BJSeGpck0miIr1JnCyXuts6tGg7S7D2UM=; Proxy: null)

CodePipeline で使用する IAM ロールがアーティファクトストアにアクセスを試みるも、「s3.getObject」および、「kms:Decrypt」のアクションが拒否されているというエラーメッセージです。
エラー発生時の状況は以下の通りでした。

  • IAM ロールには「AdministratorAccess」が付与されている
  • アーティファクトストアにはオブジェクトがアップロードされている
  • アカウント B の CloudTrail には「UploadArchive」イベントが記録されているがエラーの記録はない
  • デプロイ先の S3 バケットにはデプロイされていない
  • アーティファクトストアのオブジェクトのダウンロードをコンソールから試みても以下のエラーが発生する
User: arn:aws:sts::012345678901:assumed-role/role-name/user-name is not authorized to perform: kms:Decrypt on the resource associated with this ciphertext because the resource does not exist in this Region, no resource-based policies allow access, or a resource-based policy explicitly denies access

以上より、KMS へのアクセス権限が問題であると推測したため、アーティファクトストアにアップロードされたオブジェクトを確認したところ、暗号化キーにアカウント B の KMS キーが使用されていました。

当該キーは aws/s3 の AWS マネージドキーでしたので、クロスアカウントでの使用はできません。
KMS で暗号化された S3 バケットへのクロスアカウントアクセスのトラブルシューティング | AWS re:Post

AWS マネージド KMS キーポリシーは更新できないため、これらのキーポリシーについてクロスアカウント許可を付与することもできません。さらに、AWS マネージド KMS キーを使用して暗号化されたオブジェクトに、他の AWS アカウントからアクセスすることはできません。

つまり、以下のような状況が発生していたということです。

  • アカウント A の CodePipeline の IAM ロールはアカウント B の IAM ロールにスイッチ
  • アカウント B の IAM ロールが CodeCommit にアクセス
  • アカウント B の IAM ロールが UploadArchive API を使用してアカウント A のアーティファクトストアにオブジェクトをアップロード
    • この際、暗号化キーにはアカウント B の KMS キーが使用されていた
    • 使用されたキーは AWS マネージドキーであった
  • アカウント A の IAM ロールがアーティファクトストアのオブジェクトにアクセスを試みる
    • しかしながら、オブジェクトはアカウント B の AWS マネージドキーで暗号化されている
  • アカウント A の IAM ロールはアカウント B の AWS マネージドキーにアクセスできないため、オブジェクトを復号、取得できずエラーが発生する

上記トラブルシューティングが難航した要因としては、UploadArchive API に KMS キーのパラメーターが存在しないことでした。
以下は CloudTrail に記録された UploadArchive API のリクエストパラメーターです。

"requestParameters": {
    "repositoryName": "test",
    "commitId": "7a30c3bce89b60ea91e8f362aa4b6f44dec4a73d",
    "s3Key": "pipeline-name/SourceArti/y6bRd2B",
    "s3Bucket": "<bucket-name>",
    "archiveType": "ZIP"
}

上記の通り、KMS キーに関する情報が含まれておらず、ドキュメントにも記載されていませんでした。
そのため、アーティファクトストアのオブジェクトを確認することで別アカウントの KMS キーが使用されていることがわかりました。

クロスアカウントで KMS キーを使用するには、カスタマーマネージドキーを使用する必要があるため、アーティファクトストアで使用する KMS キーを明示的に指定することで解決しました。

まとめ

今回は CodePipeline を使用してクロスアカウントの CodeCommit から S3 へデプロイする方法をご紹介しました。
どなたかの参考になれば幸いです。

参考資料

Discussion