【初級編】CodePipelineを使用して別のAWS アカウントにCloudFormationスタックをデプロイする

記事タイトルとURLをコピーする

みなさまこんにちは。
サーバーワークスの棚本です。

概要

掲題の通りCodePipelineを使用して別のAWSアカウントにCloudFormationスタックをデプロイする機会があったのですが、必要なリソースや権限の設定が理解しづらかったので本記事にまとめました。

本記事ではCloudFormationでAWSリソースを作成し、動作確認を行います。
YAMLファイルは以下で公開してます。
GitHub

構成図

ポイント

  • CodePipelineアカウントのCodeCommitに格納されたCloudFormationのテンプレートから、デプロイ対象アカウントにスタックを作成するCodePipelineを作成します。
  • アーティファクト用S3バケットはSSE-KMSで暗号化する必要があります。
    (AWS マネージドキーで暗号化されたオブジェクトは、キーポリシーを変更できないため、アカウント間で共有することはできない)
  • 復号のため、KMSのキーポリシーでクロスアカウント用IAMロールからのアクセスを許可する必要があります。
    (アーティファクト用S3バケットのバケットポリシーも同様です)

作成手順

CodePipelineアカウント側で実施①

  1. CodePipeline用サービスロール作成
  2. KMSキー作成
  3. CodeCommit作成

デプロイ対象アカウント側で実施

  1. CloudFormation用サービスロール作成
  2. クロスアカウント用IAMロール作成

CodePipelineアカウント側で実施②

  1. アーティファクト用バケット作成
  2. CodePipeline作成

手順1.CodePipeline用サービスロール作成
(CodePipelineアカウント)

IAMRole-CodePipelineServiceRole.ymlを使用します。
各パラメータには以下の値を入力します。

  • DeployAccountID:デプロイ対象のAWSアカウントID
  • CrossAccountRoleName:クロスアカウント用のIAMロール名を指定
  • CodePipelineServiceRoleName:CodePipeline用のIAMロール名を指定

このIAMロールには以下の2つのインラインポリシーを付与してます。
①クロスアカウント用のIAMロールをAssumeRoleするために必要な権限を付与

  AssumeRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: AssumeRole
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Resource:
              - !Sub arn:aws:iam::${DeployAccountID}:role/${CrossAccountRoleName}

②CodePipelineとしての動作に必要な権限を付与
今回CodeBuildはステージで使用しないため、CodeCommit・S3・iam:PassRoleの
アクションを付与してます。

  BasePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: BasePolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - codecommit:GetRepository
              - codecommit:ListBranches
              - codecommit:GetUploadArchiveStatus
              - codecommit:UploadArchive
              - codecommit:CancelUploadArchive
              - codecommit:GetBranch
              - codecommit:GetCommit
              - s3:*
              - iam:PassRole
            Resource: "*"

手順2.KMSキー作成(CodePipelineアカウント)

KMSKey.ymlを使用します。
各パラメータには以下の値を入力します。

  • DeployAccountID:デプロイ対象のAWSアカウントID

このKMSキーはキーポリシーで以下のプリンシパルに対して権限を付与してます。

  1. CodePipelineアカウントのIAM
  2. CodePipeline用のサービスロール
  3. デプロイ対象アカウントのIAM

ただし、2と3のIAMロールには暗号化と復号に必要な権限のみ付与してます。

Statement:
        - Sid: Enable Own Account IAM
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
          Action: kms:*
          Resource: '*'
        - Sid: CodePipelineServiceRole
          Effect: Allow
          Principal:
            AWS: !ImportValue CodePipelineServiceRoleArn
          Action:
            - kms:Encrypt
            - kms:Decrypt
            - kms:ReEncrypt*
            - kms:GenerateDataKey*
            - kms:DescribeKey
          Resource: '*'
        - Sid: CrossAccountRole
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${DeployAccountID}:root
          Action:
            - kms:Encrypt
            - kms:Decrypt
            - kms:ReEncrypt*
            - kms:GenerateDataKey*
            - kms:DescribeKey
          Resource: '*'

補足
キーポリシーでは存在しないIAMロールを指定するとエラーとなるため、一旦 arn:aws:iam::【デプロイ対象のAWSアカウントID】:root を指定してます。
本来クロスアカウント用IAMロールを作成後に修正するべきですが、ここでは検証のためこのまま進めたいと思います。

手順3.CodeCommit作成
(CodePipelineアカウント)

CodeCommit.ymlを使用します。
各パラメータには以下の値を入力します。

  • RepositoryName:CodeCommitのリポジトリ名を指定

このリポジトリには後のCodePipelineからデプロイするCloudFormationスタック用のテンプレートファイルを格納します。
今回は検証のため、マネージメントコンソールからVPC.ymlを作成しておきます。

手順4.CloudFormation用サービスロール作成
(デプロイ対象アカウント)

ここからはデプロイ対象アカウントでの作業です。

IAMRole-CloudFormationServiceRole.ymlを使用します。
各パラメータには以下の値を入力します。

  • CloudFormationServiceRoleName:CloudFormation用のIAMロール名を指定

このIAMロールにはCloudFormationでリソースを作成するために必要な権限を付与しており、今回はVPCを作成するための権限を付与してます。

  BasePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: BasePolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - ec2:CreateVpc
              - ec2:CreateRouteTable
              - ec2:CreateRoute
              - ec2:ModifyVpcAttribute
              - ec2:DescribeVpcs
              - ec2:Delete*
            Resource: "*"

手順5.クロスアカウント用IAMロール作成
(デプロイ対象アカウント)

IAMRole-CrossAccountRole.ymlを使用します。
各パラメータには以下の値を入力します。

  • CrossAccountRoleName:クロスアカウント用のIAMロール名を指定(手順2/3で指定したロール名)
  • CodePipelineServiceRoleName:手順1で作成したCodePipeline用のIAMロール名
  • ArtifactBucketName:アーティファクトバケット名
  • CodePipelineAccountID:CodePipelineアカウントのアカウントID
  • KMSKeyArn:手順2で作成したKMSキーのArn

CodePipelineアカウント側で作成したリソースは別アカウントのCloudFormationの
Exportから参照出来ないため、パラメータに直接入力してます。
ここで作成するクロスアカウント用のIAMロール名をKMSキーポリシー/S3バケットボリシーでプリンシパルに指定しているため、手順1と6で指定するロール名と同一にする必要があります。

このIAMロールには以下の4つのインラインポリシーを付与してます。

  1. アーティファクトバケットへのアクセス権限
  2. アーティファクトバケットを暗号化しているKMSキーへのアクセス権限
  3. CloudFormationのアクセス権限
  4. 手順4で作成したCloudFormation用サービスロールをPassRoleできる権限

また、このIAMロールは手順1で作成したCodePipeline用のIAMロールからのみAssumeRole可能となっております。

  ArtifcatBucketAccess:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ArtifactBucketAccess
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - s3:*
            Resource:
              - !Sub arn:aws:s3:::${ArtifactBucketName}/*
          - Effect: Allow
            Action:
              - s3:ListBucket
            Resource:
              - !Sub arn:aws:s3:::${ArtifactBucketName}
      Roles:
        - !Ref CrossAccountRole
  KMSAccess:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: KMSKeyAccess
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - kms:Encrypt
              - kms:Decrypt
              - kms:ReEncrypt*
              - kms:GenerateDataKey*
              - kms:DescribeKey
            Resource:
              - !Ref KMSKeyArn
      Roles:
        - !Ref CrossAccountRole
  CloudFormationAccess:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: CloudFormationAccess
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cloudformation:*
              - iam:PassRole
            Resource:
              - "*"
      Roles:
        - !Ref CrossAccountRole
  PassRole:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: PassRole
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - iam:PassRole
            Resource:
              - !ImportValue CloudFormationServiceRoleArn

手順6.アーティファクトバケット作成
(CodePipelineアカウント)

ここから再びCodePipelineアカウントでの作業となります。

S3Bucket-Artifact.ymlを使用します。
各パラメータには以下の値を入力します。

  • DeployAccountID:デプロイ対象のAWSアカウントID
  • CrossAccountRoleName:クロスアカウント用のIAMロール名を指定
  • BucketName:バケット名を指定(小文字)

このS3バケットは手順2で作成したKMSキーで暗号化しています。
また、バケットポリシーでクロスアカウント用IAMロールからのアクセスを許可してます。

  ArtifactBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref ArtifactBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - s3:Put*
              - s3:Get*
            Resource:
              - !Sub arn:aws:s3:::${ArtifactBucket}/*
            Principal:
              AWS: !Sub arn:aws:iam::${DeployAccountID}:role:${CrossAccountRoleName}
          - Effect: Allow
            Action:
              - s3:ListBucket
            Resource:
              - !Sub arn:aws:s3:::${ArtifactBucket}
            Principal:
              AWS: !Sub arn:aws:iam::${DeployAccountID}:role:${CrossAccountRoleName}

補足
なぜ「CodePipelineアカウント側で実施①」のタイミングでS3バケットを作成しなかったのかと申しますと、バケットポリシーで存在しないプリンシパル(AWSアカウントやIAMロール)を指定するとエラーとなり、スタック作成に失敗するためです。
そのため、クロスアカウント用IAMロール作成後のこのタイミングで作成しております。

手順7. CodePipeline作成
(CodePipelineアカウント)

ここまででCodePipelineを実行するために必要な準備が整いました!
早速CodePipelineを作成しましょう。

CodePipeline.ymlを使用します。
各パラメータには以下の値を入力します。

  • DeployAccountID:デプロイ対象のAWSアカウントID
  • CrossAccountRoleName:クロスアカウント用のIAMロール名を指定
  • CloudFormationRoleName:CloudFormaiton用のサービスロール名を指定
  • CodePipelineName:CodePipelineのパイプライン名を指定

ここでもデプロイ対象アカウント側で作成したリソースは別アカウントのCloudFormationのExportから参照出来ないため、パラメータに直接入力してます。

CloudFormationのテンプレートでは以下がポイントとなります。

  • Actionsセクションで指定しているRoleArnはクロスアカウント用のIAMロール
    (ここで指定したIAMロールのアカウントでスタックが作成されます。)
  • Configurationセクションで指定しているRoleArnはCloudFormation用のサービスロール
  • TemplatePathでCodeCommitに格納したVPC.ymlを指定
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              InputArtifacts:
                - Name: SourceArtifact
              RoleArn: !Sub  arn:aws:iam::${DeployAccountID}:role/${CrossAccountRoleName}
              Configuration:
                ActionMode: CREATE_UPDATE
                RoleArn: !Sub arn:aws:iam::${DeployAccountID}:role/${CloudFormationServiceRoleName}
                StackName: Test-CrossAccount-Deploy
                TemplatePath: 'SourceArtifact::VPC.yml'
              RunOrder: 2

動作確認

手順7でCodePipelineが作成されると自動的に動き始めます。

成功しました!
デプロイ対象のアカウント側でも、無事にスタックとリソースが作成されていることを確認できます。

終わりに

今回はCodePipelineを使用して別のAWSアカウントにCloudFormationスタックをデプロイする仕組みを、シンプルな構成で動作確認しました。

実際にはこの構成に加えて以下のような要素が入り混じり、更に設定が複雑になるため、今後ご紹介できればと思います。

  • CodeBuildによるビルドステージ
  • クロスリージョンでのスタック展開
  • 更に複数アカウントでの連携

この記事がどなたかのお役に立てれば幸いです。

棚本 清也(執筆記事の一覧)

2024年7月 中途入社
「自動化」という言葉に惹かれます。