みなさまこんにちは。
サーバーワークスの棚本です。
概要
掲題の通りCodePipelineを使用して別のAWSアカウントにCloudFormationスタックをデプロイする機会があったのですが、必要なリソースや権限の設定が理解しづらかったので本記事にまとめました。
本記事ではCloudFormationでAWSリソースを作成し、動作確認を行います。
YAMLファイルは以下で公開してます。
GitHub
構成図
ポイント
- CodePipelineアカウントのCodeCommitに格納されたCloudFormationのテンプレートから、デプロイ対象アカウントにスタックを作成するCodePipelineを作成します。
- アーティファクト用S3バケットはSSE-KMSで暗号化する必要があります。
(AWS マネージドキーで暗号化されたオブジェクトは、キーポリシーを変更できないため、アカウント間で共有することはできない) - 復号のため、KMSのキーポリシーでクロスアカウント用IAMロールからのアクセスを許可する必要があります。
(アーティファクト用S3バケットのバケットポリシーも同様です)
作成手順
CodePipelineアカウント側で実施①
- CodePipeline用サービスロール作成
- KMSキー作成
- CodeCommit作成
デプロイ対象アカウント側で実施
- CloudFormation用サービスロール作成
- クロスアカウント用IAMロール作成
CodePipelineアカウント側で実施②
- アーティファクト用バケット作成
- 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キーはキーポリシーで以下のプリンシパルに対して権限を付与してます。
- CodePipelineアカウントのIAM
- CodePipeline用のサービスロール
- デプロイ対象アカウントの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つのインラインポリシーを付与してます。
- アーティファクトバケットへのアクセス権限
- アーティファクトバケットを暗号化しているKMSキーへのアクセス権限
- CloudFormationのアクセス権限
- 手順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月 中途入社
「自動化」という言葉に惹かれます。