背景
業務で、CodePipelineを使い別AWSアカウントへのS3デプロイ構成を構築することがありました。
公式の記事を参考にしようとしたのですが、こちらだと最後の手順でAWS CLIを使用する必要あり。しかし諸事情で現在CLIを使えず…。
いっそのこと、学習も兼ねてCloudFormationで作ってみました。
概要
アカウントAでCodePipelineを構築し、アカウントBのS3バケットにデプロイします。
ポイント
・KMSカスタマー管理型のキーを作成し、用いることアーティファクト格納用バケットの暗号化に用いること
(理由)CodePipelineデフォルトの暗号化であるAWS管理型のキーは、別アカウントによる使用権限を持たせることができないため。
・アカウントBでデプロイ先S3にアクセス許可するprods3role
を作成し、アカウントAのCodePipelineでそのロールを使用する。(厳密には、CodePipelineサービスロールがprods3role
を引き受ける)
→prods3role
の信頼関係で、CodePipelineサービスロールを指定して引き受け許可しておく必要あり。
前提
以下2点はコードで書かず、すでに作成済みという前提です。
アカウントA:CodeCommitリポジトリ(今回はリポジトリ名"my-Repository"で作成)
アカウントB:デプロイ先S3バケット(今回はバケット名"crossaccount-deploy-test"で作成)
今回一部しか載せていないコードもありますが、全体は[GitHub] (https://github.com/Makoto-Taguchi/CFn-CodePipeline-CrossAccountDeploy)で公開しているのでよければご覧ください。
コード
1. 【アカウントA】CodePipelineのサービスロールを作成
3つに分けてコードを展開します。なぜかというと、後述2.でprods3roleのprincipalに、既に存在するリソースを指定する必要がありCodePipelineサービスロールはあらかじめ作成する必要があるためです。
AWSTemplateFormatVersion: 2010-09-09
Description: Create CodePipeline ServiceRole
Parameters:
NamePrefix:
Type: String
Default: myproject
DeployAccountID:
Type: String
CrossAccountRoleName:
Type: String
Default: prods3role
Resources:
# CodePipelineに適用するIAMRole
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${NamePrefix}-CodePipelineServiceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
AssumeRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub ${NamePrefix}-AssumeProdRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource:
- !Sub arn:aws:iam::${DeployAccountID}:role/${CrossAccountRoleName}
Roles:
- !Ref CodePipelineServiceRole
CodePipelineBasePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub ${NamePrefix}-CodePipelineBasePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codecommit:GetRepository
- codecommit:ListBranches
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
- codecommit:CancelUploadArchive
- codebuild:StartBuild
- codebuild:StopBuild
- codebuild:BatchGet*s
- codebuild:Get*
- codebuild:List*
- codecommit:GetBranch
- codecommit:GetCommit
- s3:*
- iam:PassRole
Resource: "*"
Roles:
- !Ref CodePipelineServiceRole
Outputs:
CodePipelineServiceRoleArn:
Value: !GetAtt CodePipelineServiceRole.Arn
後述の2.で作成するprods3roleをパラメータCrossAccountRoleName
として入力。
ポイントは以下の記述です。
AssumeRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub ${NamePrefix}-AssumeProdRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource:
- !Sub arn:aws:iam::${DeployAccountID}:role/${CrossAccountRoleName}
Roles:
- !Ref CodePipelineServiceRole
ResourceにアカウントBのprods3roleを指定します。
このポリシーをCodePipelineServiceRoleにアタッチすることで、CodePipelienデプロイステージでロールがsts:AssumeRole
によりprods3roleを引き受けるようにします。
2. 【アカウントB】クロスアカウントデプロイさせるprods3roleを作成
CodePipelineサービスロールが引き受ける、prods3roleを作ります。このロールが持つべき権限は以下。
・アカウントBのデプロイ先バケットへの書き込み権限
・アカウントAのKMSの使用権限(アーティファクトバケット復号用)
・アカウントBのアーティファクトバケットの読み取り権限
そして、これをCodePipelineが使用できるよう、信頼関係を記述します。
AWSTemplateFormatVersion: 2010-09-09
Description: Create CrossAccountRole which Deploy Stage Use
Parameters:
DeployBucketName:
Type: String
Default: crossaccount-deploy-test
PipelineAccountID:
Type: String
CodePipelineServiceRoleName:
Type: String
Default: myproject-CodePipelineServiceRole
Resources:
CrossAccountRole:
Type: AWS::IAM::Role
Properties:
RoleName: prods3role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${PipelineAccountID}:role/${CodePipelineServiceRoleName}
Action: sts:AssumeRole
DeployBucketAccessPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: outputbucketfullaccess
PolicyDocument:
Version: '2012-10-17'
Statement:
# Enable DeployBucket Access in DeployAccount
- Effect: Allow
Action:
- s3:*
Resource:
- !Sub arn:aws:s3:::${DeployBucketName}/*
- Effect: Allow
Action:
- s3:ListBucket
Resource:
- !Sub arn:aws:s3:::${DeployBucketName}
Roles:
- !Ref CrossAccountRole
KMSAccessPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub devkmss3access
PolicyDocument:
Version: '2012-10-17'
Statement:
# Enable EncryptionKey Access for ArtifactBucket in PipelineAccount
- Effect: Allow
Action:
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
- kms:DescribeKey
Resource:
- !Sub arn:aws:kms:ap-northeast-1:${PipelineAccountID}:key/*
# Enable ArtifactBucket Access in PipelineAccount
- Effect: Allow
Action:
- s3:Get*
- s3:ListBucket
Resource:
- arn:aws:s3:::*
Roles:
- !Ref CrossAccountRole
Resourceで、KMSキー名とアーティファクトバケット名を指定すべきですが、後述3.でそれらは作成するので、ここでは*
としてます。全て構築後に指定し直すと良いです。
(抜粋)
Principal:
AWS: !Sub arn:aws:iam::${PipelineAccountID}:role/${CodePipelineServiceRoleName}
CrossAccountRoleで、Principalに1.で作成したCodePipelineサービスロール名を指定します。これによってサービスロールがprods3roleを使用することを許可しています。
余談ですが、信頼関係の記述でPrincipalに特定アカウント中の全てのロールという意味でワイルドカード*
で指定するのは不可みたいです。試しに作ろうとするとエラーになりました。
Principal:
AWS: !Sub arn:aws:iam::${PipelineAccountID}:role/*
また、以下のように匿名ユーザを指定すると、どのアカウントからでもロール使用できるようになってしまうのでめちゃくちゃ危険です。絶対しないようにしましょう。
Principal: "*"
3. 【アカウントA】CodePipelineを構築
全部書くと長いのでCodePipelineの記述だけ抜粋します。
詳細は GitHub をご覧ください。
# CodePipeline
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub ${NamePrefix}-Pipeline
RoleArn: !Ref CodePipelineServiceRoleArn
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
EncryptionKey:
Id: !GetAtt KMSKey.Arn
Type: KMS
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeCommit
Configuration:
RepositoryName: !Ref CodeCommitRepositoryName
BranchName: !Ref CodeCommitBranchName
RunOrder: 1
OutputArtifacts:
- Name: SourceArtifact
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
- Name: Deploy
Actions:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: S3
Configuration:
BucketName: !Ref DeployBucketName
Extract: true
RunOrder: 1
InputArtifacts:
- Name: BuildArtifact
RoleArn: !Sub arn:aws:iam::${DeployAccountID}:role/${CrossAccountRoleName}
一番のポイントは最後の行で、CodePipelineデプロイステージで使用するロールにprod3sroleを使用することを記述しています。
(ちなみにBuildSpecは今回の主題と関係ないためかなりテキトーに書いてます。要はtest.txtがアーティファクトとして出力されます。)
実行確認
全部CFn構築できたら、AWSコンソール上で実行確認してみます。
まずアカウントAで、CodeCommitで適当にコミットします。
CodePipelineの画面を見ると、パイプライン起動し無事デプロイまで成功しました。
アカウントBにサインインし直し、デプロイ先S3バケットを見てみます。
test.txtがデプロイされていることが確認できました!
まとめ
AWS CLIを使えないためCloudFormationで構築してみました。CLIの代わりにAWS CloudShellで同じことができ、CFnなくても実現可能なことに構築後に気づきましたが。。
まあ、CFnで全体見ながら構築することでIAMの構造やKMSの使い方も理解できたので、良い機会になりました!