AWS CodePipelineからクロスアカウントのAWS CodePipelineを実行する構成をTerraformで自動構築する

# はじめに
2025年3月15日のWhat's Newで、AWS CodePipelineが他のパイプラインを直接実行できるようになったという発表があった。

https://aws.amazon.com/about-aws/whats-new/2025/03/aws-codepipeline-invoking-pipeline-new-action-type/

しかも

> The action also supports cross-account pipeline triggering.

ということで、従来は以前書いた以下の記事のような感じで、Amazon EventBridgeで連携をしなければパイプライン完了時のクロスアカウントのパイプライン呼び出しができなかったのが、AWS CodePipelineネイティブの機能で対応可能になったようだ。

https://qiita.com/neruneruo/items/0daf2b30c59582007c4e

一方で、本記事を書いている時点で、この新機能を使ったクロスアカウントのパイプライン呼び出しの方法がマニュアルには書かれていなくて、AI Agentに聞いても分からずで途方に暮れてしまう人が出てくると推測される。

本記事では、Terraformを使ってこの新機能のクロスアカウントのパイプライン呼び出しを自動化する。

前提知識としては、以下があると読みやすい。

- Terraformについてのある程度の知識がある
- AWS CodePipelineの構築経験がある
- IAMのクロスアカウント運用(stsによる権限移譲)に関するプラクティスを理解している

# IAMの設定
今回、Terraformでは、パイプライン実行元についてはデフォルトプロバイダ、実行先を```aws.crossaccount```のエイリアスで構築する。

## 実行先の設定
実行先では、実行元のアカウントに対して、```codepipeline:StartPipelineExecution```の権限を委譲するIAMロールを新規に作成しておく。
最小権限のポリシーに従うには、```resources```の設定を```aws_codepipeline.example_crossaccount.arn```にして、このパイプライン以外を実行できないように制限しておこう(IAMは何も宣言しない場合Denyになる)。

:::note warn
本記事では、実行先のAWS CodePiplineリソースである```aws_codepipeline.example_crossaccount```の構築については特に例示しない。以下のリンクに記載しているような最小構成のパイプラインを作って実験してみよう。
:::

https://qiita.com/neruneruo/items/c9f326861da391434665

```HCL
resource "aws_iam_role" "crossaccount_assume" {
  provider = aws.crossaccount

  name               = local.iam_crossaccount_assume_role_name
  assume_role_policy = data.aws_iam_policy_document.crossaccount_assume_assume.json
}

data "aws_iam_policy_document" "crossaccount_assume_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "AWS"
      identifiers = [
        aws_iam_role.codepipeline.arn,
      ]
    }
  }
}

resource "aws_iam_role_policy" "crossaccount_assume" {
  provider = aws.crossaccount

  name   = local.iam_crossaccount_assume_policy_name
  role   = aws_iam_role.crossaccount_assume.id
  policy = data.aws_iam_policy_document.crossaccount_assume_custom.json
}

data "aws_iam_policy_document" "crossaccount_assume_custom" {
  statement {
    sid = "AllowCodePipelineExecution"

    effect = "Allow"

    actions = [
      "codepipeline:StartPipelineExecution",
    ]

    resources = [
      aws_codepipeline.example_crossaccount.arn,
    ]
  }
}
```

## 実行元の設定
実行元では、既存のパイプラインの権限に、以下の権限を追加する。
上で作成した実行先のIAMロースにstsするための設定だ。

```diff_HCL
resource "aws_iam_role" "codepipeline" {
  name               = local.iam_codepipeline_role_name
  assume_role_policy = data.aws_iam_policy_document.codepipeline_assume.json
}

data "aws_iam_policy_document" "codepipeline_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "codepipeline.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy" "codepipeline" {
  name   = local.iam_codepipeline_policy_name
  role   = aws_iam_role.codepipeline.id
  policy = data.aws_iam_policy_document.codepipeline_custom.json
}

data "aws_iam_policy_document" "codepipeline_custom" {
  # 中略 他に必要なポリシーをここで設定しておく

+ statement {
+   sid = "AllowAssuneRole"
+
+   effect = "Allow"
+
+   actions = [
+     "sts:AssumeRole",
+   ]
+
+   resources = [
+     aws_iam_role.crossaccount_assume.arn,
+   ]
+  }
}
```

# AWS CodePipelineの設定
実行元のAWS CodePipelineには、以下のステージを追加しよう。
肝になるのが```role_arn = aws_iam_role.crossaccount_assume.arn```で、これを設定することで、ひとつ前の章で作った```aws.crossaccount```のアカウントのロールに対してstsを実行することが可能になる。

```name```は何を設定しても良いが、```category```, ```owner```, ```provider```は以下のように設定しないと正しく動作しないので注意が必要だ。

あとは、configurationで、実行先のAWS CodePipelineのパイプライン名を書いておけばよい。

:::note
なお、AWS CodePipelineは実行元と実行先で同じ名前が設定可能だが、こう設定すると、実行先のパイプライン名を正しく検知してトリガしていた。実装が謎だ……。
:::

```diff_HCL
resource "aws_codepipeline" "example" {
  name     = local.codepipeline_pipeline_name
  role_arn = aws_iam_role.codepipeline.arn

  pipeline_type = "V2"

  # 中略。triggerやartifact_storeに加えて、必要なステージを入れておく

+ stage {
+   name = "Invoke"
+    action {
+     run_order = 1
+     name      = "Invoke"
+     category  = "Invoke"
+     owner     = "AWS"
+     provider  = "CodePipeline"
+     version   = "1"
+     role_arn  = aws_iam_role.crossaccount_assume.arn
+
+     configuration = {
+       PipelineName = aws_codepipeline.example_crossaccount.name
+     }
+   }
+ }
}
```

従来の構成よりもかなりリソース数少なく設定可能になったと感じる。

# いざ、動かす
上記まで行ってから```terraform apply```すると、以下のようにパイプラインが順次起動して、両方正常終了するのが分かる。

:::note warn
なお、本機能でのパイプライン実行は非同期であるため、実行元アカウントで完了まで見届けるには別の方法を検討する必要があることを留意していただきた
:::

実行元
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/cd8aa3ef-e778-4f0d-bc1e-85d31a9874ed.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/8a1b9b73-8897-4e34-aa81-0e0fb9830a94.png)

実行先

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/657c2820-df95-464a-b7bb-b5baa584ac16.png)

この画像だけだと分かりにくいが、実行元の「パイプライン実行」の出力と、実行先のアクション実行IDが一致していることから、実際にクロスアカウントの呼び出しが正解したと判断して良いだろう。

これで、CI/CDパイプラインの構築が以前よりも楽に行えるようになった！