この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
中山です
今日はタイトルの通り、別のAWSアカウントにあるCodeCommit RepositoryをソースとするCodePipelineをCloudFormationで構築してみたので、その内容をまとめてみました。
背景
まず、構築してみた経緯を少し述べます。
マルチアカウント戦略
AWSを利用する際、どのようにAWSアカウントを利用するか事前に戦略を定めて運用を開始することが多いのではないかと思います。 個人的な印象では、環境毎(Production/Staging/Develop,etc)にAWSアカウントを作成して運用するケースが多いです。 実際、AWSからもLanding ZoneというソリューションやControl Towerというサービスといった形でAWSアカウントの分割と統制の方法が述べられています。
ここ数年は多くのサービスがOrganizationsと連携したりResource Access Manager/Transit Gateway/PrivateLink/AWS SSOなどのサービスが登場したことでアカウント分割のデメリットは緩和されています。 アカウントを分割することによるデメリットであるネットワークトポロジーの複雑さ上昇や管理系サービスの設定が煩雑になることが、以前に比べると気にならなくなってきています。 もちろん、今でもそれなりに面倒ではあるのですが。
アカウント戦略の具体的な例は以下の記事によくまとまっていると思いますので、是非ご覧ください。
環境ではなくサービスに紐付くソースコードリポジトリ
あらゆるリソースを環境単位できれいに分けることができればいいのですが、それが難しいものもあります。 ソースコードリポジトリ(AWSリソースとしては、CodeCommitのGitリポジトリ)はそのひとつです。 デプロイ先の環境やデプロイのためのパイプラインは環境毎に作成すればいいですが、リポジトリを環境毎に分割するのは現実的ではありません。
そうなると、この記事のタイトルのようにCodePipelineから他のAWSアカウントにあるCodeCommitのリポジトリを参照する必要性が生じます。
(そこの貴方!「GitHubとか使えばいいやろ」とか、思いましたね?私もそう思います。)
やってみた
ということで、実際にやってみました。 なお、今回は再利用性が高そうなネタだったのでCloudFormationテンプレートを作ってみました。
参考情報
以下の記事を参考にしています。 大まかな流れはこの記事とほぼ同じです。
全体像
先に、今回構築する環境の図を示します。
構成時のポイント
今回の構成で気をつけるべきポイントを先に解説します。
Source StageでAction Roleを指定
パイプライン上のSource Stageでアクションを定義する際にRoleを指定することができます。 CodeCommitのGit Repositoryは別のアカウントに存在するため、リポジトリのあるアカウントにCodeCommitを操作できるIAM Roleを作成しておく必要があります。 また、そのRoleはCodePipelineのService RoleからAssumeRoleが可能である必要もあります。
AWS::CodePipeline::Pipeline ActionDeclaration
また、Source StageのAction Roleはパイプラインで指定するArtifact用のストレージにアクセスできる必要もあります(こちらについては次のポイントで少し補足)。 Artifact用のストレージは別のアカウントに存在するため、IAM Roleだけでなくリソースポリシー(今回の場合はBucket Policy)でもアクセスを許可する必要があります。
ArtifactLocation(S3)でKMSを利用する必要がある(CodePipelineの仕様)
CodePipelineでは、ステージ間でアーティファクトをやりとりするためにS3 Bucketを利用します。 パイプラインを作成する際、アーティファクトを暗号化するためにAWSマネージドのCMK / カスタマーマネージドのCMKのいずれかを選択する必要があります。 暗号化しないという選択肢はありません。
そこで意識する必要があることが、Source Stageで利用するRoleにCMKによる暗号化・復号の権限を付与する必要があるという点です。 RoleにもKey Policyにもアクセス許可設定を実施する必要があります。
CloudFormationテンプレートの構成
今回、CloudFormationテンプレートを3つに分けました。
- 1.事前準備
- CodePipeline Service Role
- CodeDeploy Service Role
- S3 Bucket / Bucket Policy
- Customer Master Key
- 2.Git Repository
- CodeCommit Repository
- CodePipeline (Source Stage Action Role)
- 3.CI/CD Pipeline and Environment
- EC2 Instance / Security Group
- CodeDeploy Application / Deployment Group
- CodePipeline (Pipeline)
1. 事前準備
Pipeline側のAWSアカウントにPipelineで利用するいくつかのリソースを事前に作成します。
29-33行目では、CodePipelineのService Roleに対してSource StageのAction RoleにAssumeRoleするための許可を追加しています。 なお、Action Roleの信頼ポリシーにはPipeline側のAWSアカウントを信頼する設定を別途追加します。
200-216行目では、Artifact用のS3 BucketのBucket PolicyにRepository側のAWSアカウントからアーティファクトを保存する権限を追加します。 なお、Source StageのAction Roleにもアーティファクトを保存する権限を別途追加します。
256,271行目では、アーティファクトを保存する際に暗号化するための権限をCMKのKey Policyに追加します。 なお、Source StageのAction Roleにもアーティファクトを暗号化のための権限を別途追加します。
AWSTemplateFormatVersion: 2010-09-09
Description: Step 1, Pre-requirements (in Production Account)
Parameters:
RepositoryAccountId:
Description: Repository Account ID
MaxLength: 12
MinLength: 12
Type: String
Resources:
PipelineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: Pipeline
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource:
- !Sub arn:aws:iam::${RepositoryAccountId}:role/* # Cross Account Access
- Effect: Allow
Action:
- iam:PassRole
Resource:
- "*"
Condition:
StringEqualsIfExists:
iam:PassedToService:
- cloudformation.amazonaws.com
- elasticbeanstalk.amazonaws.com
- ec2.amazonaws.com
- ecs-tasks.amazonaws.com
- Effect: Allow
Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Resource:
- "*"
- Effect: Allow
Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
Resource:
- "*"
- Effect: Allow
Action:
- elasticbeanstalk:*
- ec2:*
- elasticloadbalancing:*
- autoscaling:*
- cloudwatch:*
- s3:*
- sns:*
- cloudformation:*
- rds:*
- sqs:*
- ecs:*
Resource:
- "*"
- Effect: Allow
Action:
- lambda:InvokeFunction
- lambda:ListFunctions
Resource:
- "*"
- Effect: Allow
Action:
- opsworks:CreateDeployment
- opsworks:DescribeApps
- opsworks:DescribeCommands
- opsworks:DescribeDeployments
- opsworks:DescribeInstances
- opsworks:DescribeStacks
- opsworks:UpdateApp
- opsworks:UpdateStack
Resource:
- "*"
- Effect: Allow
Action:
- cloudformation:CreateStack
- cloudformation:DeleteStack
- cloudformation:DescribeStacks
- cloudformation:UpdateStack
- cloudformation:CreateChangeSet
- cloudformation:DeleteChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:ExecuteChangeSet
- cloudformation:SetStackPolicy
- cloudformation:ValidateTemplate
Resource:
- "*"
- Effect: Allow
Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
Resource:
- "*"
- Effect: Allow
Action:
- devicefarm:ListProjects
- devicefarm:ListDevicePools
- devicefarm:GetRun
- devicefarm:GetUpload
- devicefarm:CreateUpload
- devicefarm:ScheduleRun
Resource:
- "*"
- Effect: Allow
Action:
- servicecatalog:ListProvisioningArtifacts
- servicecatalog:CreateProvisioningArtifact
- servicecatalog:DescribeProvisioningArtifact
- servicecatalog:DeleteProvisioningArtifact
- servicecatalog:UpdateProduct
Resource:
- "*"
- Effect: Allow
Action:
- cloudformation:ValidateTemplate
Resource:
- "*"
- Effect: Allow
Action:
- ecr:DescribeImages
Resource:
- "*"
DeployRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codedeploy.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole
S3Bucket:
# DeletionPolicy: Retain
Description: Creating Amazon S3 bucket for AWS CodePipeline artifacts
Properties:
BucketName: !Join
- "-"
- - artifacts
- !Ref AWS::Region
- !Ref AWS::AccountId
- "pipeline"
VersioningConfiguration:
Status: Enabled
Type: AWS::S3::Bucket
S3ArtifactBucketPolicy:
Type: AWS::S3::BucketPolicy
Description: Setting Amazon S3 bucket policy for AWS CodePipeline access
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Statement:
- Action:
- s3:PutObject
Effect: Deny
Principal: "*"
Resource:
- !Sub arn:aws:s3:::${S3Bucket}/*
Condition:
StringNotEquals:
s3:x-amz-server-side-encryption: aws:kms
- Action:
- s3:*
Effect: Deny
Principal: "*"
Resource:
- !Sub arn:aws:s3:::${S3Bucket}/*
Condition:
Bool:
aws:SecureTransport: false
- Action:
- s3:Get*
- s3:Put*
Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${RepositoryAccountId}:root
Resource:
- !Sub arn:aws:s3:::${S3Bucket}/*
- Action:
- s3:ListBucket
Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${RepositoryAccountId}:root
Resource:
- !Sub arn:aws:s3:::${S3Bucket}
Version: 2012-10-17
Key:
Type: AWS::KMS::Key
Properties:
Description: An example symmetric CMK
KeyPolicy:
Version: 2012-10-17
Id: key-default-1
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${AWS::AccountId}:root
Action: kms:*
Resource: "*"
- Sid: Allow administration of the key
Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${AWS::AccountId}:user/cm-nakayama.nobuhiro
Action:
- kms:Create*
- kms:Describe*
- kms:Enable*
- kms:List*
- kms:Put*
- kms:Update*
- kms:Revoke*
- kms:Disable*
- kms:Get*
- kms:Delete*
- kms:ScheduleKeyDeletion
- kms:CancelKeyDeletion
Resource: "*"
- Sid: Allow use of the key
Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${RepositoryAccountId}:root
- !GetAtt PipelineRole.Arn
- !GetAtt DeployRole.Arn
Action:
- kms:DescribeKey
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey
- kms:GenerateDataKeyWithoutPlaintext
Resource: "*"
- Sid: Allow attachment of persistent resources
Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${RepositoryAccountId}:root
- !GetAtt PipelineRole.Arn
- !GetAtt DeployRole.Arn
Action:
- kms:CreateGrant
- kms:ListGrants
- kms:RevokeGrant
Resource: "*"
Condition:
Bool:
kms:GrantIsForAWSResource: true
Alias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/CodePipelineArtifact
TargetKeyId:
Ref: Key
2. Git Repository
Repository側のAWSアカウントにCodeCommit Repositoryとそれらを操作するIAM Roleを作成します。
29-31行目では、CodePipelineのService RoleによるAssumeRoleを許可する権限を追加しています。
40-45行目では、Source StageのAction Roleに対してArtifactをS3に保存するための権限を追加しています。
46-54行目では、Source StageのAction Roleに対してArtifactを暗号化して保存するための権限を追加しています。
AWSTemplateFormatVersion: 2010-09-09
Description: Step 2, CodeCommit (in Repository Account)
Parameters:
ProductionAccountId:
Description: Production Account ID
MaxLength: 12
MinLength: 12
Type: String
S3BucketARN:
Description: Production Account S3 Bucket ARN for Artifact (Created by 01-requirement.yml)
Type: String
CmkArn:
Description: Production Account CMK ARN (Created by 01-requirement.yml)
Type: String
Resources:
Repo:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: !Sub SampleRepository-${AWS::AccountId}
RepositoryDescription: "Sample Repository"
Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${ProductionAccountId}:root
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: source
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:PutObjectAcl
Resource:
- !Sub ${S3BucketARN}/*
- Effect: Allow
Action:
- kms:DescribeKey
- kms:GenerateDataKey*
- kms:Encrypt
- kms:ReEncrypt*
- kms:Decrypt
Resource:
- !Ref CmkArn
- Effect: Allow
Action:
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:UploadArchive
- codecommit:GetUploadArchiveStatus
- codecommit:CancelUploadArchive
Resource:
- !GetAtt Repo.Arn
3. CI/CD Pipeline and Environment
Pipeline側のAWSアカウントにデプロイ先となるAWSアカウントとPipelineに関連するリソース一式(CodeDeployのリソースを含む)を作成します。
132行目では、Source StageのAction RoleとしてRepository側のAWSアカウントで作成したIAM Roleを指定しています。
AWSTemplateFormatVersion: 2010-09-09
Description: Step 3, Environment and Pipeline (in Production Account)
Parameters:
SubnetId:
Description: Public Subnet (Internet routable)
Type: String
VpcId:
Type: String
ArtifactBucketName:
Description: Production Account S3 Bucket Name for Artifact (Created by 01-requirement.yml)
Type: String
CmkArn:
Description: Production Account CMK ARN (Created by 01-requirement.yml)
Type: String
CodePipelineRoleArn:
Description: Production Account CodePipeline Service Role ARN (Created by 01-requirement.yml)
Type: String
CodeDeployRoleArn:
Description: Production Account CodeDeploy Service Role ARN (Created by 01-requirement.yml)
Type: String
CodeCommitRoleArn:
Description: Repository Account CodeCommit Action Role ARN (Created by 02-codecommit.yml)
Type: String
RepositoryName:
Description: Repository Account CodeCommit Repository Name (Created by 02-codecommit.yml)
Type: String
Resources:
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow http to client host
VpcId:
Ref: VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0ee1410f0644c1cac # Tokyo Region
IamInstanceProfile: !Ref InstanceProfile
InstanceType: t3.micro
SecurityGroupIds:
- !Ref InstanceSecurityGroup
SubnetId: !Ref SubnetId
Tags:
- Key: "Name"
Value: "CodePipelineApp"
UserData: !Base64 |
#!/bin/bash
yum -y update
yum install -y ruby
yum install -y aws-cli
cd /home/ec2-user
aws s3 cp s3://aws-codedeploy-ap-northeast-1/latest/install . --region ap-northeast-1
chmod +x ./install
./install auto
InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
-
Ref: "Role"
Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeploy
Policies:
- PolicyName: kms
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- kms:DescribeKey
- kms:GenerateDataKey*
- kms:Encrypt
- kms:ReEncrypt*
- kms:Decrypt
Resource:
- !Ref CmkArn
CodeDeployApplication:
Type: AWS::CodeDeploy::Application
Properties:
ComputePlatform: Server
DeploymentGroup:
Type: AWS::CodeDeploy::DeploymentGroup
Properties:
ApplicationName: !Ref CodeDeployApplication
Ec2TagFilters:
- Type: KEY_AND_VALUE
Key: Name
Value: CodePipelineApp
ServiceRoleArn: !Ref CodeDeployRoleArn
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !Ref CodePipelineRoleArn
Stages:
-
Name: Source
Actions:
- Name: Source
InputArtifacts: []
OutputArtifacts:
- Name: SourceOutput
Configuration:
RepositoryName: !Ref RepositoryName
BranchName: master
PollForSourceChanges: false
RunOrder: 1
ActionTypeId:
Version: 1
Provider: CodeCommit
Category: Source
Owner: AWS
RoleArn: !Ref CodeCommitRoleArn
-
Name: Release
Actions:
-
Name: ReleaseAction
InputArtifacts:
-
Name: SourceOutput
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CodeDeploy
Configuration:
ApplicationName: !Ref CodeDeployApplication
DeploymentGroupName: !Ref DeploymentGroup
RunOrder: 1
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucketName
EncryptionKey:
Id: !Ref CmkArn
Type: KMS
構築手順
こんな感じのテンプレートを使って以下の手順で構築を実施します。
- テンプレート1でスタック作成
- テンプレート2でスタック作成
- サンプルアプリケーションをリポジトリにプッシュ
- テンプレート3でスタック作成
- パイプラインを実行(変更をリリース)
サンプルアプリケーションは、公式ドキュメントで紹介されているものを利用しました。
例 1: AWS CloudFormation を使用して AWS CodeCommit パイプラインを作成する
また、2つ目以降のCloudFormation Stackを作成する際に指定するパラメーターには、前の手順で作成したリソース名やARNを設定します。
動作確認
パイプラインを使ってリリースします。 こんな感じになれば無事成功です。
デプロイ先にアクセスすると、以下の画面が表示されるはずです。
まとめ
今回のポイントは、クロスアカウントであるが故にプリンシパル(IAM Roleなど、操作の主体)にもリソースポリシーにもアクセスを許可する設定を実施する必要がある点ではないかと思います。 それ以外にはいくつかの仕様をおさえておけば、そんなに難しくないかと思います。
現場からは以上です。