AWS CodePipeline Example which deploys to multiple AWS Accounts - Part2

- aws codepipeline cloudformation

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:

01central-prereqs.yaml
  • 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.

AWS CodePipeline Example


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.

AWS CodePipeline Example

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.