AWS CodePipeline Example which deploys to multiple AWS Accounts - Part2
In Part 1 you find CloudFormation templates which help you to create an AWS CodePipeline that deploys to multiple AWS Accounts. In this Part 2 we will go into some more details how these CF templates work.
cloudformation/01central-prereqs.yaml
Let’s first look at the 01central-prereqs.yaml template which creates:
-
S3 Artifact Bucket
-
KMS Key for encryption
-
IAM Roles/Policies
-
CodeCommit Repo for the App
-
CodeBuild Project for App
Looking at the template reveals that the Dev/Test/Prod accounts get access to the KMS Key.
In the Central Account CodeBuild/CodePipeline roles will create the output artifacts, which will be encrypted with the help of the KMS Key.
Later on these artifacts will be used to deploy the application in your Dev/Test/Prod accounts, therefore the root in these accounts needs access to the KMS Key to decrypt the artifacts.
Same is needed for the artifact S3 bucket, but here the roles that will be created by the other template 02prereqs-accounts.yaml will need access to the bucket.
108 S3BucketPolicy:
109 Condition: AddPolicies
110 Type: AWS::S3::BucketPolicy
111 Properties:
112 Bucket: !Ref ArtifactBucket
113 PolicyDocument:
114 Statement:
115 -
116 Action:
117 - s3:GetObject
118 - s3:PutObject
119 Effect: Allow
120 Resource:
121 - !Sub arn:aws:s3:::${ArtifactBucket}
122 - !Sub arn:aws:s3:::${ArtifactBucket}/*
123 Principal:
124 AWS:
125 - !Sub arn:aws:iam::${TestAccount}:role/${Project}-CentralAcctCodePipelineCFRole
126 - !Sub arn:aws:iam::${TestAccount}:role/${Project}-cloudformationdeployer-role
127 - !Sub arn:aws:iam::${ProductionAccount}:role/${Project}-CentralAcctCodePipelineCFRole
128 - !Sub arn:aws:iam::${ProductionAccount}:role/${Project}-cloudformationdeployer-role
129 - !Sub arn:aws:iam::${DevAccount}:role/${Project}-CentralAcctCodePipelineCFRole
130 - !Sub arn:aws:iam::${DevAccount}:role/${Project}-cloudformationdeployer-role
131 - !Sub arn:aws:iam::${AWS::AccountId}:role/${Project}-codepipeline-Role
132 - !Sub arn:aws:iam::${AWS::AccountId}:role/${Project}-codebuild-Role
As you can see this policy is only added if the Condition "AddPolicies" is true, so for the first run of 01central-prereqs.yaml this S3 bucket policy will NOT be created.
Reason for this is that 02prereqs-accounts.yaml has to be deployed on the Dev/Test/Prod accounts first to create all these needed roles.
Right afterwards 01central-prereqs.yaml has to be run a second time with "AddPolicies" parameter set to true.
The template would fail to run if these roles are not present in the other accounts.
There are two roles per Account which will get access to the bucket, the CentralAcctCodePipelineCFRole and the cloudformationdeployer-role. Let’s switch to the next template 02prereqs-accounts.yaml and look at these roles.
cloudformation/02prereqs-accounts.yaml
Here you find the CentralAcctCodePipelineCFRole and as you can see, this will be the role which will be assumed by the Central Account CodePipeline to execute the CloudFormation commands.
17Resources:
18 CFRole:
19 Type: AWS::IAM::Role
20 Properties:
21 RoleName: !Sub ${Project}-CentralAcctCodePipelineCFRole
22 AssumeRolePolicyDocument:
23 Version: 2012-10-17
24 Statement:
25 -
26 Effect: Allow
27 Principal:
28 AWS:
29 - !Ref CentralAccount
30 Action:
31 - sts:AssumeRole
32 Path: /
Looking at the policy used for this role we can see that CloudFormation, S3, IAM and KMS actions are added.
33 CFPolicy:
34 Type: AWS::IAM::Policy
35 Properties:
36 PolicyName: !Sub ${Project}-CentralAcctCodePipelineCloudFormationPolicy
37 PolicyDocument:
38 Version: 2012-10-17
39 Statement:
40 -
41 Effect: Allow
42 Action:
43 - cloudformation:CreateStack
44 - cloudformation:DeleteStack
45 - cloudformation:UpdateStack
46 - cloudformation:DescribeStacks
47 - cloudformation:CreateChangeSet
48 - cloudformation:ExecuteChangeSet
49 - cloudformation:ListChangeSets
50 - cloudformation:DescribeChangeSet
51 - cloudformation:DeleteChangeSet
52 Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:*"
53 -
54 Effect: Allow
55 Action:
56 - s3:PutObject
57 - s3:GetObject
58 Resource:
59 - !Sub arn:aws:s3:::${S3Bucket}/*
60 - !Sub arn:aws:s3:::${S3Bucket}
61 -
62 Effect: Allow
63 Action:
64 - iam:PassRole
65 Resource: !Sub "arn:aws:iam::${AWS::AccountId}:*"
66 -
67 Effect: Allow
68 Action:
69 - kms:Decrypt
70 - kms:Encrypt
71 Resource:
72 - !Ref CMKARN
73 Roles:
74 -
75 !Ref CFRole
The CF actions are used to deploy the CF templates into this account, S3 actions are needed to get the artifacts from the Central Account and KMS actions are needed to decrypt the artifact.
Looking at the cloudformationdeployer-role or better the according policy we see that this role gets similar actions like CentralAcctCodePipelineCFRole. Supplementary some IAM, Lambda and API Gateway actions are added.
91 CFDeployerPolicy:
92 Type: AWS::IAM::Policy
93 Properties:
94 PolicyName: !Sub ${Project}-cloudformationdeployer-policy
95 PolicyDocument:
96 Version: 2012-10-17
97 Statement:
98 - Sid: cf
99 Effect: Allow
100 Action:
101 - cloudformation:CreateStack
102 - cloudformation:DeleteStack
103 - cloudformation:UpdateStack
104 - cloudformation:DescribeStacks
105 - cloudformation:CreateChangeSet
106 - cloudformation:ExecuteChangeSet
107 - cloudformation:ListChangeSets
108 - cloudformation:DescribeChangeSet
109 - cloudformation:DeleteChangeSet
110 Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:*"
111 - Sid: s3
112 Effect: Allow
113 Action:
114 - s3:PutObject
115 - s3:GetBucketPolicy
116 - s3:GetObject
117 - s3:ListBucket
118 Resource:
119 - !Sub "arn:aws:s3:::${S3Bucket}/*"
120 - !Sub "arn:aws:s3:::${S3Bucket}"
121 - Sid: iam
122 Effect: Allow
123 Action:
124 - iam:CreateRole
125 - iam:DeleteRole
126 - iam:AttachRolePolicy
127 - iam:DetachRolePolicy
128 - iam:getRolePolicy
129 - iam:PutRolePolicy
130 - iam:DeleteRolePolicy
131 - iam:GetRole
132 - iam:PassRole
133 - iam:CreateServiceLinkedRole
134 Resource:
135 - !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
136 - Sid: ssm
137 Effect: Allow
138 Action:
139 - ssm:GetParameters
140 Resource:
141 - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*"
142 - Sid: lambda
143 Effect: Allow
144 Action:
145 - lambda:CreateFunction
146 - lambda:DeleteFunction
147 - lambda:GetFunctionConfiguration
148 - lambda:AddPermission
149 - lambda:RemovePermission
150 - lambda:UpdateFunctionConfiguration
151 - lambda:UpdateFunctionCode
152 Resource:
153 - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*"
154 - Sid: apigw
155 Effect: Allow
156 Action:
157 - apigateway:POST
158 - apigateway:DELETE
159 - apigateway:PATCH
160 - apigateway:GET
161 Resource:
162 - !Sub "arn:aws:apigateway:${AWS::Region}::/*"
163 Roles:
164 -
165 !Ref CFDeployerRole
All these actions are needed to create our application:
-
IAM actions to create all needed roles/policies
-
Lambda actions to create our serverless functions
-
API Gateway actions to create endpoints of the application
If you will use this example to deploy your own applications to multiple AWS accounts this role/policy has to be customized to fit your needs.
All actions which will be needed to deploy your application have to be added here.
Let’s look at the last template,
cloudformation/03central-pipeline.yaml
This template which will be used in the Central account creates only one resource, the CodePipeline. As you can see this CodePipeline will use the KMS Key as encryption key and will use the S3 bucket as artifact store.
We see 5 stages:
-
Getting the source from CodeCommit
-
Build the templates with CodeBuild
-
Create/deploy change sets to Dev account
-
Create/deploy change sets to Test account
-
Create/deploy change sets to Prod account
CodeCommit and CodeBuild stages are easy to understand, no magic there, but let’s look at the create/deploy change sets to the different accounts:
57 - Name: Create_Change_Sets_and_Deploy_to_Dev
58 Actions:
59 - Name: CreateChangeSet_Dev
60 ActionTypeId:
61 Category: Deploy
62 Owner: AWS
63 Version: '1'
64 Provider: CloudFormation
65 Configuration:
66 ChangeSetName: cicd-codepipeline-ChangeSet-Dev
67 ActionMode: CHANGE_SET_REPLACE
68 StackName: cicd-codepipeline-Dev
69 Capabilities: CAPABILITY_IAM
70 ParameterOverrides: |
71 {
72 "Environment" : "dev"
73 }
74 TemplatePath: BuildArtifact::packaged.yml
75 RoleArn:
76 Fn::ImportValue:
77 !Sub "${Project}-Dev-cloudformationdeployer-role"
78 InputArtifacts:
79 - Name: BuildArtifact
80 RunOrder: 1
81 RoleArn:
82 Fn::ImportValue:
83 !Sub "${Project}-Dev-centralacctcodepipelineCFRole"
84 - Name: ExecuteChangeSet_Dev
85 ActionTypeId:
86 Category: Deploy
87 Owner: AWS
88 Provider: CloudFormation
89 Version: "1"
90 Configuration:
91 ActionMode: CHANGE_SET_EXECUTE
92 RoleArn:
93 Fn::ImportValue:
94 !Sub "${Project}-Dev-cloudformationdeployer-role"
95 StackName: cicd-codepipeline-Dev
96 ChangeSetName: cicd-codepipeline-ChangeSet-Dev
97 OutputArtifacts:
98 - Name: cicd-codepipeline-ChangeSet-Dev
99 RunOrder: 2
100 RoleArn:
101 Fn::ImportValue:
102 !Sub "${Project}-Dev-centralacctcodepipelineCFRole"
The key point here are the values used for RoleArn’s and that you can define roles for the creation/execution of CloudFormation change sets.
81 RoleArn:
82 Fn::ImportValue:
83 !Sub "${Project}-Dev-centralacctcodepipelineCFRole"
This is the role (CentralAcctCodePipelineCFRole) used by the CodePipeline in the Central account to execute the CloudFormation template in the Dev account.
The role was created on the Dev account with the 02prereqs-accounts.yaml template but the ARN value can be calculated.
This is done in the 01central-prereqs.yaml template and imported here in this template 03central-pipeline.yaml.
Snippet from 01central-prereqs.yaml:
326 DevCodePipelineCloudFormationRole:
327 Value: !Sub arn:aws:iam::${DevAccount}:role/${Project}-CentralAcctCodePipelineCFRole
328 Export:
329 Name: !Sub ${Project}-Dev-centralacctcodepipelineCFRole
The second role (cloudformationdeployer-role) is used to deploy the resources inside the Dev account which are defined in the CloudFormation template. That’s the same IAM Role setting which you will see if you manually deploy a CloudFormation stack under "Configure stack options" → "Permissions"
65 Configuration:
66 ChangeSetName: cicd-codepipeline-ChangeSet-Dev
67 ActionMode: CHANGE_SET_REPLACE
68 StackName: cicd-codepipeline-Dev
69 Capabilities: CAPABILITY_IAM
70 ParameterOverrides: |
71 {
72 "Environment" : "dev"
73 }
74 TemplatePath: BuildArtifact::packaged.yml
75 RoleArn:
76 Fn::ImportValue:
77 !Sub "${Project}-Dev-cloudformationdeployer-role"
As you can see the RoleArn is defined inside the Configuration part of the Create/Execute change set. This is the role/policy we saw before which has to be customized to fit your needs if you will use this example to deploy your own applications to multiple AWS accounts.
Hope this Part 2 gave you more details on how this example works & how you can customize it.
The magic happens at the RoleArn that are used at the different CodePipeline stages!
The beauty of this is that if you have a working solution like this you can reuse it almost everywhere.
Comments and questions are welcome or contact me on Twitter.