🎉

CodePipelineで推奨のEventBridgeがクロスアカウントだと中々大変な件

2024/03/26に公開

記事の内容

少し前の話なのですが、2023年8月頃からCodePipelineのCodeCommitの検出がポーリングで非推奨となっておりその対応としてEventBridgeによるイベント駆動にする必要がありました。
この関係でCodePipelineを使ってCI/CDを構築する際、イベント駆動にしたかったのですが、プロジェクトの環境的にCodeCommitとCodePipelineが異なるアカウント同士いわゆるクロスアカウントの関係で中々大変でした。
備忘録として今回の作業内容をまとめてみたいと思います。

https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-cross-account.html

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/update-change-detection.html

アーキテクチャ

今回のアーキテクチャイメージです。

before

CodePipelineのポーリングでCodeCommitソースにアクセスする場合、CodePipelineが能動的にCodeCommitの変更を確認するので、CodePipelineのIAM RoleとPolicyを気をつければよいです。
例えば、CI/CD上ではCodeCommitのソースはS3に展開された上でフローが実行されるため、CodeCommit側のアカウント(上記でいうcommon環境)のS3がある場合、CodePipelineはCodeCommit側のS3にアクセスできるようにする必要があります。

after

一方で、EventBridgeでCodeCommitの変更をトリガーするイベント駆動でCodePipelineがソースにアクセスする場合、クロスアカウントになるとCodeCommitアカウント側のEventBridgeのイベントをCodePipeline側のアカウント(上記でいうdev)に転送する必要があります。
この役割を担うのがEventBridgeのBusというやつになります。AWSアカウントにはdefaultのBusが既にあるのですが、これは基本的に自アカウント内で完結するイベントの受信に使うので、他アカウントのイベントの受信はできない状態です。
クロスアカウントの場合、他アカウントのEventBridgeをトリガーできるようには初めからなっていないのでアカウント間でこの設定もしてやる必要があります。

関係する技術・サービス・ツール

  • Amazon EventBridge
  • AWS CloudFormation
  • AWS CodePipeline
  • AWS CodeCommit
  • Amazon S3

前提

  • CodeCommitとS3は事前に作成済み

作業の流れ

  1. CodePipelineアカウントでCodePipelineサービスロールを作成
  2. CodeCommitアカウントでEventBridge関係のリソースを作成
  3. CodePipelineアカウントでEventBridge関係とCodePipelineのリソースを作成
  4. CodeCommitをトリガーしてCodePipeline動作確認

1. CodePipelineアカウントでCodePipelineサービスロールを作成

まずCodePipelineアカウント(dev環境)でCodePipelineのサービスロールを作成します。
今回はCloudFormationで作成しました。

AWSTemplateFormatVersion: "2010-09-09"

Description: CodePipeline Service Role for trigger AWS Account

Parameters:
  ProjectEnv:
    Description: Envirment of project
    Type: String
    AllowedValues:
      - prd
      - sta
      - dev
  TriggerAccount:
    Type: String
    Description: Account to trigger the pipeline
    Default: <CodeCommit AWSアカウントID>

Resources:
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub CodePipelineServiceRole-${ProjectEnv}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: CodePipelineServiceRolePolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource: !Sub arn:aws:iam::${TriggerAccount}:role/*

ParameterでCodeCommit AWSアカウントを指定できるようにしています。
これによりCodePipelineがCodeCommitのあるアカウントのIAM Roleを引き受けることできるようになります。
これは後ほどCodeCommmitアカウント側に作成するS3とCodeCommitにアクセスするロールを引き受けることになります。

2. CodeCommitアカウントでEventBridge関係のリソースを作成

次にCodeCommit側のアカウントでEventBridge関係のリソースやIAM Roleを作成します。

AWSTemplateFormatVersion: "2010-09-09"

Description: CodeCommit Trigger Event Rule for trigger AWS Account

Parameters:
  ProjectEnv:
    Description: Envirment of project
    Type: String
    AllowedValues:
      - prd
      - stg
      - dev
  PipelineAccount:
    Type: String
    Description: Pipeline Account
    Default: <CodePipeline AWSアカウントID>
  BranchName:
    Type: String
    Description: Branch Name
    Default: main

Resources:
  CodeCommitTriggerEventRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub CodeCommitTriggerEvent_${ProjectEnv}
      Description: CodeCommit Trigger Event Rule
      EventBusName: default
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - prefix: !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:test-codecommit-repo
        detail:
          referenceType:
            - branch
          event:
            - referenceCreated
            - referenceUpdated
          referenceName:
            - !Ref BranchName
      State: ENABLED
      Targets:
        # 受信者のeventbusを指定
        - Arn: !Sub arn:aws:events:${AWS::Region}:${PipelineAccount}:event-bus/CodeCommit-EventBus-${ProjectEnv}
          Id: CodeCommitTriggerEventRule
          RoleArn: !GetAtt EventTriggerRole.Arn
    
  EventTriggerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub CodeCommitEventTriggerRole-${ProjectEnv}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: EventInvokePolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - events:PutEvents
                # 受信者のeventbusを指定
                Resource: !Sub arn:aws:events:${AWS::Region}:${PipelineAccount}:event-bus/CodeCommit-EventBus-${ProjectEnv}

  CodeCommitAccessRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub CodeCommitAccessRole_${ProjectEnv}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Sub arn:aws:iam::${PipelineAccount}:role/CodePipelineServiceRole-${ProjectEnv}
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCodeCommitFullAccess
        - arn:aws:iam::aws:policy/AmazonS3FullAccess

CodeCommitの変更をトリガーできるEvent Ruleを作成しています。併せてEvent RuleはCodePipelineアカウント側のBusにイベントを送信するためにIAM Roleも必要です。

また、今回最終的にCodePipelineはCodeCommitからソースを取得してS3に保存する処理をするため、そのための権限を付与してIAM Roleも作成します。

ちなみにCodeCommitはレポ名でどのレポからのイベントをトリガーするか指定できます。今回はプレフィックスが
test-codecommit-repoのレポをトリガーすることにします。

3. CodePipelineアカウントでEventBridge関係とCodePipelineのリソースを作成

最後に再度CodePipeline側のアカウントでEventBridge関係のリソースとCodePipelineを作成します。

AWSTemplateFormatVersion: "2010-09-09"

Description: Create EventBridge EventBus for CodeCommit trigger CodePipeline

Parameters:
  ProjectEnv:
    Description: Envirment of project
    Type: String
    AllowedValues:
      - prd
      - sta
      - dev
  CodePipelineServiceRoleName:
    Type: String
    Description: CodePipeline Service Role
    Default: CodePipelineServiceRole-dev
  TriggerAccount:
    Type: String
    Description: Account to trigger the pipeline
    Default: <CodeCommit AWSアカウントID>
  RepoName:
    Type: String
    Description: Repository name to trigger the pipeline
    Default: test-codecommit-repo
  BranchName:
    Type: String
    Description: Branch name to trigger the pipeline
    Default: main
  BucketName:
    Type: String
    Description: Bucket name to deploy the code
    Default: saito-test-2024

Resources:
  CodeCommitEventBus:
    Type: AWS::Events::EventBus
    Properties:
      Name: !Sub CodeCommit-EventBus-${ProjectEnv}

  EventBusPolicy:
    Type: AWS::Events::EventBusPolicy
    Properties:
      EventBusName: !Ref CodeCommitEventBus
      StatementId: AllowEventForTriggerAccount
      Statement:
        Sid: AllowEventForTriggerAccount
        Effect: Allow
        Principal:
          AWS: !Sub arn:aws:iam::${TriggerAccount}:root
        Action: events:PutEvents
        Resource: "*"

  PipelineEventInvokeRule:
    Type: AWS::Events::Rule
    Properties:
      Description: CodeCommit Event Rule
      EventBusName: !Ref CodeCommitEventBus
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - prefix: !Sub arn:aws:codecommit:${AWS::Region}:${TriggerAccount}:${RepoName}
        detail:
          referenceType:
            - branch
          event:
            - referenceCreated
            - referenceUpdated
          referenceName:
            - !Ref BranchName
      State: ENABLED
      Targets:
        - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:pipeline-${ProjectEnv}
          RoleArn: !GetAtt EventBridgeCodePipelineInvokeRole.Arn
          Id: CodePipelineInvoke
    
  EventBridgeCodePipelineInvokeRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub EventBridgeCodePipelineInvokeRole-${ProjectEnv}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: EventBridgeCodePipelineInvokeRolePolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - codepipeline:StartPipelineExecution
                Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:pipeline-${ProjectEnv}

  BackupCodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub pipeline-${ProjectEnv}
      RestartExecutionOnUpdate: false
      RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/${CodePipelineServiceRoleName}
      ArtifactStore:
        Location: !Ref BucketName
        Type: S3
      Stages:
        - Name: Source
          Actions:
            - Name: BackupCodecommit
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              RoleArn: !Sub arn:aws:iam::${TriggerAccount}:role/CodeCommitAccessRole_${ProjectEnv}
              Configuration:
                RepositoryName: !Ref RepoName
                BranchName: !Ref BranchName
                PollForSourceChanges: false
              RunOrder: 1
              OutputArtifacts:
                - Name: codecommit-source
        - Name: Deploy
          Actions:
            - Name: DeployS3
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: S3
                Version: 1
              InputArtifacts:
                - Name: codecommit-source
              RoleArn: !Sub arn:aws:iam::${TriggerAccount}:role/CodeCommitAccessRole_${ProjectEnv}
              Configuration:
                BucketName: !Ref BucketName
                Extract: false
                ObjectKey:
                  !Sub backup/${BranchName}.zip
              RunOrder: 1

リソース多めなので補足しておきます。

  • CodeCommitEventBus、EventBusPolicy・・・CodeCommit側アカウントで作成したEvent Ruleを受信するためのBus。併せてBusにリソースベースポリシーも作成してアタッチします。

  • PipelineEventInvokeRule、EventBridgeCodePipelineInvokeRole・・・上記Event BusがEventを受信した際に実行されるEvent Rule。このRuleはCodePipelineを実行するので、併せてCodePipelineの実行権限をIAM ROLEで付与します。

  • BackupCodePipeline・・・CodeCommitトリガーで実行されるCodePipeline本体。CodeCommitとS3は他アカウントなので、RoleArnで手順1で作成したサービスロールを指定。さらに、StageでRoleArnも指定します。
    余談ですが、AWSコンソールからStageのRoleArnの指定できない気がします。入力欄がわからなかったので、詳しい方教えてください。

4. CodeCommitをトリガーしてCodePipeline動作確認

ここまでできたら動作を確認してみます。

CodeCommit側のアカウントにログインしてAWSコンソールでトリガーするCodeCommitを開きます。
対象のレポを選択して「ファイルの追加」から適当なファイルを作成してコミットしてみます。

コミットしたらCodePipelineアカウント側に切り替えて、CodePipelineの動作を確認します。

対象のコミットでCodePipelineが実行されフローが成功しました。
無事トリガーされていそうですね。

今回は以上になります。
CodePipelineのCodeCommitソースへのポーリングアクセスが非推奨になったものの、EventBrige + クロスアカウントの場合は設定が増えて少し大変でした。
ですが改めてクロスアカウントのロール引き受けについて理解が深めることができました。

NCDCエンジニアブログ

Discussion