マネコンだけでは作れない？クロスアカウントなCodePipeline構成


## はじめに

少し前にポストした、この件が解決したので記事にしました！

https://x.com/morisapporo2017/status/1839238710413635799

一度はそれぞれのアカウントにCodePipelineを配置し、クロスアカウントで利用可能なS3をトリガーとする構成としてましたが、きちんとした形ができたと思いますので共有します！

## 構成

目指す構成は以下のような形です。

今回はCodeDeployを使っていますが、BeanStalk等、他のデプロイ方法でも応用できると思います。

また、この記事で出てくるアカウントは以降、以下２つの記載の仕方をします。
* DEV環境(`888877776666`)
    * CodePipelineを配置する環境
* STG / PROD環境(`111122223333`)
    * CodeDeployのみを配置する環境

![env.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/91296dbd-d3f2-d85e-d868-a746e78e6dfb.png)

## 構築手順

:::note warn
手順はクロスアカウント構成のために必要な設定のみとし、基本的なところは省略させていただきます。
:::

### 最初にポイント！

この記事のポイントを先に書いておきます。

#### マネージメントコンソール単体では設定できない場所がある！

私の見落としかもしれませんが、一度作成済みのパイプラインの暗号化キーの指定や、ステージ単位のロールの指定ができないです。

クロスアカウントロールの指定が正しくできていても、マネージメントコンソール上で、CodeDeployのアプリケーションが選択できない。といった事象にかなり悩まされました。

#### KMSのカスタマーマスターキーが必要

AWSマネージドな`aws/s3`エイリアスのキーは、他アカウントでは利用できません。

そのため、複数のアカウントでアーティファクトを共有する場合、クロスアカウントで利用可能なカスタマーマスターキーを利用する必要があります。

### 1.カスタマーマスターキーの作成

#### なぜ必要？
CodePipeline上で作成したアーティファクトはアーティファクトストアと呼ばれるS3に格納されます。

この時、CodePipelineのデフォルト動作はエイリアス`aws/s3`のマネージドキーを利用しますが、キーボリシーの設定ができないことから、他のアカウントからは複合化ができないためです。

（`aws/s3`を使った`SSE-KMS`は`SSE-S3`とは違うのか？）
![スクリーンショット 2024-10-14 6.39.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/cecf7144-243d-8064-bdcd-34858785e16f.png)

#### 設定

以下のような感じに、CodePipelineがあるアカウント側に作成します。

![screencapture-ap-northeast-1-console-aws-amazon-kms-home-2024-10-13-09_51_58_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/b2db2dbb-cbb1-a242-0758-308ca38476b3.png)

大切なことは、ここで今回デプロイを行う別のアカウント(`888877776666`)に対し、アクセス許可を付与ことです。

![スクリーンショット 2024-10-13 9.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/de623cd8-daa8-1409-c3d1-45093b785aa3.png)

この設定後にキーポリシーを見てみると、STG/PRDOアカウント経由の暗号化が許可されていることがわかります。

```json
{
    "Version": "2012-10-17",
    "Id": "auto-s3-2",
    "Statement": [
        {
            "Sid": "Allow access through S3 for all principals in the account that are authorized to use S3",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "kms:ViaService": "s3.ap-northeast-1.amazonaws.com",
                    "kms:CallerAccount": "111122223333"
                }
            }
        },
        {
            "Sid": "Allow direct access to key metadata to the account",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111122223333:root"
            },
            "Action": [
                "kms:Describe*",
                "kms:Get*",
                "kms:List*"
            ],
            "Resource": "*"
        }
    ]
}
```

### 2.アーティファクトストアのバケットポリシーの設定

#### なぜ必要？

アーティファクトストアを他のアカウントから参照する際に必要なアクセス権限をバケットポリシーに付与します。

#### 設定

```json
{
    "Version": "2012-10-17",
    "Id": "SSEAndSSLPolicy",
    "Statement": [
        {
            "Sid": "AllowCrossAccountAccess",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111122223333:root"
            },
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListBucket",
                "s3:GetObject",
                "s3:PutObject",
                "s3:GetObjectVersion"
            ],
            "Resource": [
                "arn:aws:s3:::codepipeline-ap-northeast-1-888877776666",
                "arn:aws:s3:::ccodepipeline-ap-northeast-1-888877776666/*"
            ]
        }
    ]
}
```

### 3.各種ロールの設定（STG/PROD環境)

#### EC2のインスタンスプロファイル

アーティファクトストアからの資材の取得は、EC2内のCodeDeploy Agent経由で動作するので、インスタンスプロファイルにアーティファクトストア、KMSへのアクセス権限が必要です。

##### アーティファクトストア用KMSへのアクセス許可ポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowUseOfKMSKey",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:888877776666:key/cb99b732-473c-4ebb-aa94-bfba54805081"
            ]
        }
    ]
}
```
##### アーティファクトストアへのアクセス許可ポリシー

マネージドポリシー`AmazonEC2RoleforAWSCodeDeploy`を今回は使います。

https://docs.aws.amazon.com/ja_jp/aws-managed-policy/latest/reference/AmazonEC2RoleforAWSCodeDeploy.html

#### CodeDeployの信頼ポリシー

DEV環境でCodeDeployをトリガーするための信頼ポリシー。

この信頼ポリシーを引き受けたステージがアーティファクトストアにアクセスする必要もあるので、そのポリシーも付与しておきます。

* AWSCodeDeployDeployerAccess(マネージドポリシー)
    * CodeDeployに必要なロール
* AmazonEC2RoleforAWSCodeDeploy（マネージドポリシー）
    * アーティファクトストアがあるS3へのアクセスに必要

https://docs.aws.amazon.com/ja_jp/aws-managed-policy/latest/reference/AWSCodeDeployDeployerAccess.html

### 4.各種ロールの設定（DEV環境)

#### CodePipelineのサービスロール

STG/PROD環境で作成したCodeDeployの信頼ポリシーをAssumeRoleする権限を付与します。

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

### 5.CodePipelineの設定

ここが一番詰まったのですが、一部の設定がマネージメントコンソールからできず、別の方法で設定する必要があります。

今回はCLIで実施しましたが、おそらくIaCツールでも設定できるはずです。

#### まずは、現状のPipelineの設定をJSONにエクスポート。

```console
$ aws codepipeline get-pipeline --name web > webpipeline.json
```
#### 必要な設定をJSONファイルに追記

以下のような感じにマネージメントコンソール上で編集できないところを編集しています。

```diff_json
{
  "pipeline": {
    "name": "web",
    "roleArn": "arn:aws:iam::853835738220:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-web",
    "artifactStore": {
      "type": "S3",
      "location": "codepipeline-ap-northeast-1-111122223333",
+      "encryptionKey": {
+        "id": "arn:aws:kms:ap-northeast-1:853835738220:alias/CodePipelineArtifacts",
+        "type": "KMS"
+      }
    },
    "stages": [
      {
        "name": "Source",
        "actions": [
          {
            "name": "Source",
            "actionTypeId": {
              "category": "Source",
              "owner": "AWS",
              "provider": "S3",
              "version": "1"
            },
            "runOrder": 1,
            "configuration": {
              "PollForSourceChanges": "false",
              "S3Bucket": "chel-artifact-backet",
              "S3ObjectKey": "app.zip"
            },
            "outputArtifacts": [
              {
                "name": "SourceArtifact"
              }
            ],
            "inputArtifacts": [],
            "region": "ap-northeast-1",
            "namespace": "SourceVariables"
          }
        ]
      },
      {
        "name": "Development",
        "actions": [
          {
            "name": "Deploy",
            "actionTypeId": {
              "category": "Deploy",
              "owner": "AWS",
              "provider": "CodeDeploy",
              "version": "1"
            },
            "runOrder": 1,
            "configuration": {
              "ApplicationName": "Web",
              "DeploymentGroupName": "Web"
            },
            "outputArtifacts": [],
            "inputArtifacts": [
              {
                "name": "SourceArtifact"
              }
            ],
            "region": "ap-northeast-1",
            "namespace": "DeployStgVariables"
          }
        ]
      },
+      {
+        "name": "Staging",
+        "actions": [
+          {
+            "name": "Deploy",
+            "actionTypeId": {
+              "category": "Deploy",
+              "owner": "AWS",
+              "provider": "CodeDeploy",
+              "version": "1"
+            },
+            "runOrder": 1,
+            "configuration": {
+              "ApplicationName": "Web",
+              "DeploymentGroupName": "Web"
+            },
+            "outputArtifacts": [],
+            "inputArtifacts": [
+              {
+                "name": "SourceArtifact"
+              }
+            ],
+            "region": "ap-northeast-1",
+            "namespace": "DeployVariables",
+            "roleArn": "arn:aws:iam::111122223333:role/CrossAccountForCodeDeploy"
+          }
        ]
      }
    ],
    "version": 1,
    "executionMode": "QUEUED",
    "pipelineType": "V2"
-  },
+  }
-    "metadata": {
-        "pipelineArn": "arn:aws:codepipeline:ap-northeast--1:888877776666:web",
-        "created": "2024-10-12T20:07:31.782000+00:00",
-        "updated": "2024-10-12T21:53:31.662000+00:00"
-    }
}
```

##### 1.暗号化キーの指定

`pipeline`->`artifactStore`->`encryptionKey`を追加し、アーティファクトストアのデータ暗号化時に利用するカスタマーマスターキーを指定します。

これから新規にパイプラインを作成する場合には、高度な設定のところから設定が可能です。

![っs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3894042/c6d80ae0-7b88-c571-f3f5-43fcfd1eebf2.png)

#### 2.STG/PROD環境向けのステージを追加

上記の例でいうと、`Staging`のステージを追加します。

大切なところは、`roleArn`でこのステージ用のロールを指定するところです。

ここで指定するロールは、STG/PROD環境で作成したクロスアカウントロールです。
このロールを指定することで、このパイプラインがSTG/PROD環境のCodePipelineをトリガーすることが可能になります。

また既に記載済みですが、ステージ用のロールにクロスアカウントロールを利用できるようにするため、CodePipilneのサービスロールにクロスアカウントをAssumeRoleできるポリシーを付与することが必要です。

##### 3.metadataの削除

`metadata`が残っているとインポートに以下のエラーがでるので、削除しておきます。

```console
Parameter validation failed:
Unknown parameter in input: "metadata", must be one of: pipeline
```

#### 修正したJSONをPipelineにインポート

最後にインポートしたら完成です！

```console
$ aws codepipeline update-pipeline --cli-input-json file://webpipeline.json
```

### 最後に

いろいろ詰まったけど、CodePipeline周りに少し自信が出てきました！

また、ロールの設定も細かく検証でき、とっても勉強になった検証でした〜！