AWS DevOps & Developer Productivity Blog
Cross-account and cross-region deployment using GitHub actions and AWS CDK
GitHub Actions is a feature on GitHub’s popular development platform that helps you automate your software development workflows in the same place you store code and collaborate on pull requests and issues. You can write individual tasks called actions, and combine them to create a custom workflow. Workflows are custom automated processes that you can set up in your repository to build, test, package, release, or deploy any code project on GitHub.
A cross-account deployment strategy is a CI/CD pattern or model in AWS. In this pattern, you have a designated AWS account called tools, where all CI/CD pipelines reside. Deployment is carried out by these pipelines across other AWS accounts, which may correspond to dev, staging, or prod. For more information about a cross-account strategy in reference to CI/CD pipelines on AWS, see Building a Secure Cross-Account Continuous Delivery Pipeline.
In this post, we show you how to use GitHub Actions to deploy an AWS Lambda-based API to an AWS account and Region using the cross-account deployment strategy.
Using GitHub Actions may have associated costs in addition to the cost associated with the AWS resources you create. For more information, see About billing for GitHub Actions.
Prerequisites
Before proceeding any further, you need to identify and designate two AWS accounts required for the solution to work:
- Tools – Where you create an AWS Identity and Access Management (IAM) user for GitHub Actions to use to carry out deployment.
- Target – Where deployment occurs. You can call this as your dev/stage/prod environment.
You also need to create two AWS account profiles in ~/.aws/credentials
for the tools and target accounts, if you don’t already have them. These profiles need to have sufficient permissions to run an AWS Cloud Development Kit (AWS CDK) stack. They should be your private profiles and only be used during the course of this use case. So, it should be fine if you want to use admin privileges. Don’t share the profile details, especially if it has admin privileges. I recommend removing the profile when you’re finished with this walkthrough. For more information about creating an AWS account profile, see Configuring the AWS CLI.
Solution overview
You start by building the necessary resources in the tools account (an IAM user with permissions to assume a specific IAM role from the target account to carry out deployment). For simplicity, we refer to this IAM role as the cross-account role, as specified in the architecture diagram.
You also create the cross-account role in the target account that trusts the IAM user in the tools account and provides the required permissions for AWS CDK to bootstrap and initiate creating an AWS CloudFormation deployment stack in the target account. GitHub Actions uses the tools account IAM user credentials to the assume the cross-account role to carry out deployment.
In addition, you create an AWS CloudFormation execution role in the target account, which AWS CloudFormation service assumes in the target account. This role has permissions to create your API resources, such as a Lambda function and Amazon API Gateway, in the target account. This role is passed to AWS CloudFormation service via AWS CDK.
You then configure your tools account IAM user credentials in your Git secrets and define the GitHub Actions workflow, which triggers upon pushing code to a specific branch of the repo. The workflow then assumes the cross-account role and initiates deployment.
The following diagram illustrates the solution architecture and shows AWS resources across the tools and target accounts.
Creating an IAM user
You start by creating an IAM user called git-action-deployment-user
in the tools account. The user needs to have only programmatic access.
- Clone the GitHub repo aws-cross-account-cicd-git-actions-prereq and navigate to folder
tools-account
. Here you find the JSON parameter filesrc/cdk-stack-param.json
, which contains the parameterCROSS_ACCOUNT_ROLE_ARN
, which represents the ARN for the cross-account role we create in the next step in the target account. In the ARN, replace<target-account-id>
with the actual account ID for your designated AWS target account. - Run
deploy.sh
by passing the name of the tools AWS account profile you created earlier. The script compiles the code, builds a package, and uses the AWS CDK CLI to bootstrap and deploy the stack. See the following code:
cd aws-cross-account-cicd-git-actions-prereq/tools-account/
./deploy.sh "<AWS-TOOLS-ACCOUNT-PROFILE-NAME>"
You should now see two stacks in the tools account: CDKToolkit
and cf-GitActionDeploymentUserStack
. AWS CDK creates the CDKToolkit
stack when we bootstrap the AWS CDK app. This creates an Amazon Simple Storage Service (Amazon S3) bucket needed to hold deployment assets such as a CloudFormation template and Lambda code package. cf-GitActionDeploymentUserStack
creates the IAM user with permission to assume git-action-cross-account-role
(which you create in the next step). On the Outputs tab of the stack, you can find the user access key and the AWS Secrets Manager ARN that holds the user secret. To retrieve the secret, you need to go to Secrets Manager. Record the secret to use later.
Creating a cross-account IAM role
In this step, you create two IAM roles in the target account: git-action-cross-account-role
and git-action-cf-execution-role
.
git-action-cross-account-role
provides required deployment-specific permissions to the IAM user you created in the last step. The IAM user in the tools account can assume this role and perform the following tasks:
- Upload deployment assets such as the CloudFormation template and Lambda code package to a designated S3 bucket via AWS CDK
- Create a CloudFormation stack that deploys API Gateway and Lambda using AWS CDK
AWS CDK passes git-action-cf-execution-role
to AWS CloudFormation to create, update, and delete the CloudFormation stack. It has permissions to create API Gateway and Lambda resources in the target account.
To deploy these two roles using AWS CDK, complete the following steps:
- In the already cloned repo from the previous step, navigate to the folder
target-account
. This folder contains the JSON parameter filecdk-stack-param.json
, which contains the parameterTOOLS_ACCOUNT_USER_ARN
, which represents the ARN for the IAM user you previously created in the tools account. In the ARN, replace<tools-account-id>
with the actual account ID for your designated AWS tools account. - Run
deploy.sh
by passing the name of the target AWS account profile you created earlier. The script compiles the code, builds the package, and uses the AWS CDK CLI to bootstrap and deploy the stack. See the following code:
cd ../target-account/
./deploy.sh "<AWS-TARGET-ACCOUNT-PROFILE-NAME>"
You should now see two stacks in your target account: CDKToolkit
and cf-CrossAccountRolesStack
. AWS CDK creates the CDKToolkit
stack when we bootstrap the AWS CDK app. This creates an S3 bucket to hold deployment assets such as the CloudFormation template and Lambda code package. The cf-CrossAccountRolesStack
creates the two IAM roles we discussed at the beginning of this step. The IAM role git-action-cross-account-role
now has the IAM user added to its trust policy. On the Outputs tab of the stack, you can find these roles’ ARNs. Record these ARNs as you conclude this step.
Configuring secrets
One of the GitHub actions we use is aws-actions/configure-aws-credentials@v1. This action configures AWS credentials and Region environment variables for use in the GitHub Actions workflow. The AWS CDK CLI detects the environment variables to determine the credentials and Region to use for deployment.
For our cross-account deployment use case, aws-actions/configure-aws-credentials@v1 takes three pieces of sensitive information besides the Region: AWS_ACCESS_KEY_ID
, AWS_ACCESS_KEY_SECRET
, and CROSS_ACCOUNT_ROLE_TO_ASSUME
. Secrets are recommended for storing sensitive pieces of information in the GitHub repo. It keeps the information in an encrypted format. For more information about referencing secrets in the workflow, see Creating and storing encrypted secrets.
Before we continue, you need your own empty GitHub repo to complete this step. Use an existing repo if you have one, or create a new repo. You configure secrets in this repo. In the next section, you check in the code provided by the post to deploy a Lambda-based API CDK stack into this repo.
- On the GitHub console, navigate to your repo settings and choose the Secrets tab.
- Add a new secret with name as
TOOLS_ACCOUNT_ACCESS_KEY_ID
. - Copy the access key ID from the output
OutGitActionDeploymentUserAccessKey
of the stackGitActionDeploymentUserStack
in tools account. - Enter the ID in the Value field.
- Repeat this step to add two more secrets:
-
-
TOOLS_ACCOUNT_SECRET_ACCESS_KEY
(value retrieved from the AWS Secrets Manager in tools account) -
CROSS_ACCOUNT_ROLE
(value copied from the outputOutCrossAccountRoleArn
of the stackcf-CrossAccountRolesStack
in target account)
-
You should now have three secrets as shown below.
Deploying with GitHub Actions
As the final step, first clone your empty repo where you set up your secrets. Download and copy the code from the GitHub repo into your empty repo. The folder structure of your repo should mimic the folder structure of source repo. See the following screenshot.
We can take a detailed look at the code base. First and foremost, we use Typescript to deploy our Lambda API, so we need an AWS CDK app and AWS CDK stack. The app is defined in app.ts
under the repo root folder location. The stack definition is located under the stack-specific folder src/git-action-demo-api-stack
. The Lambda code is located under the Lambda-specific folder src/git-action-demo-api-stack/lambda/ git-action-demo-lambda
.
We also have a deployment script deploy.sh
, which compiles the app and Lambda code, packages the Lambda code into a .zip file, bootstraps the app by copying the assets to an S3 bucket, and deploys the stack. To deploy the stack, AWS CDK has to pass CFN_EXECUTION_ROLE
to AWS CloudFormation; this role is configured in src/params/cdk-stack-param.json
. Replace <target-account-id>
with your own designated AWS target account ID.
Finally, we define the Git Actions workflow under the .github/workflows/
folder per the specifications defined by GitHub Actions. GitHub Actions automatically identifies the workflow in this location and triggers it if conditions match. Our workflow .yml file is named in the format cicd-workflow-<region>.yml
, where <region>
in the file name identifies the deployment Region in the target account. In our use case, we use us-east-1
and us-west-2
, which is also defined as an environment variable in the workflow.
The GitHub Actions workflow has a standard hierarchy. The workflow is a collection of jobs, which are collections of one or more steps. Each job runs on a virtual machine called a runner, which can either be GitHub-hosted or self-hosted. We use the GitHub-hosted runner ubuntu-latest
because it works well for our use case. For more information about GitHub-hosted runners, see Virtual environments for GitHub-hosted runners. For more information about the software preinstalled on GitHub-hosted runners, see Software installed on GitHub-hosted runners.
The workflow also has a trigger condition specified at the top. You can schedule the trigger based on the cron settings or trigger it upon code pushed to a specific branch in the repo. See the following code:
name: Lambda API CICD Workflow
# This workflow is triggered on pushes to the repository branch master.
on:
push:
branches:
- master
# Initializes environment variables for the workflow
env:
REGION: us-east-1 # Deployment Region
jobs:
deploy:
name: Build And Deploy
# This job runs on Linux
runs-on: ubuntu-latest
steps:
# Checkout code from git repo branch configured above, under folder $GITHUB_WORKSPACE.
- name: Checkout
uses: actions/checkout@v2
# Sets up AWS profile.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.TOOLS_ACCOUNT_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.TOOLS_ACCOUNT_SECRET_ACCESS_KEY }}
aws-region: ${{ env.REGION }}
role-to-assume: ${{ secrets.CROSS_ACCOUNT_ROLE }}
role-duration-seconds: 1200
role-session-name: GitActionDeploymentSession
# Installs CDK and other prerequisites
- name: Prerequisite Installation
run: |
sudo npm install -g aws-cdk@1.31.0
cdk --version
aws s3 ls
# Build and Deploy CDK application
- name: Build & Deploy
run: |
cd $GITHUB_WORKSPACE
ls -a
chmod 700 deploy.sh
./deploy.sh
For more information about triggering workflows, see Triggering a workflow with events.
We have configured a single job workflow for our use case that runs on ubuntu-latest
and is triggered upon a code push to the master branch. When you create an empty repo, master branch becomes the default branch. The workflow has four steps:
- Check out the code from the repo, for which we use a standard Git action actions/checkout@v2. The code is checked out into a folder defined by the variable
$GITHUB_WORKSPACE
, so it becomes the root location of our code. - Configure AWS credentials using aws-actions/configure-aws-credentials@v1. This action is configured as explained in the previous section.
- Install your prerequisites. In our use case, the only prerequisite we need is AWS CDK. Upon installing AWS CDK, we can do a quick test using the AWS Command Line Interface (AWS CLI) command
aws s3 ls
. If cross-account access was successfully established in the previous step of the workflow, this command should return a list of buckets in the target account. - Navigate to root location of the code
$GITHUB_WORKSPACE
and run thedeploy.sh
script.
You can check in the code into the master branch of your repo. This should trigger the workflow, which you can monitor on the Actions tab of your repo. The commit message you provide is displayed for the respective run of the workflow.
You can choose the workflow link and monitor the log for each individual step of the workflow.
In the target account, you should now see the CloudFormation stack cf-GitActionDemoApiStack
in us-east-1
and us-west-2
.
The API resource URL DocUploadRestApiResourceUrl
is located on the Outputs tab of the stack. You can invoke your API by choosing this URL on the browser.
Clean up
To remove all the resources from the target and tools accounts, complete the following steps in their given order:
- Delete the CloudFormation stack
cf-GitActionDemoApiStack
from the target account. This step removes the Lambda and API Gateway resources and their associated IAM roles. - Delete the CloudFormation stack
cf-CrossAccountRolesStack
from the target account. This removes the cross-account role and CloudFormation execution role you created. - Go to the
CDKToolkit
stack in the target account and note theBucketName
on the Output tab. Empty that bucket and then delete the stack. - Delete the CloudFormation stack
cf-GitActionDeploymentUserStack
from tools account. This removescross-account-deploy-user
IAM user. - Go to the
CDKToolkit
stack in the tools account and note theBucketName
on the Output tab. Empty that bucket and then delete the stack.
Security considerations
Cross-account IAM roles are very powerful and need to be handled carefully. For this post, we strictly limited the cross-account IAM role to specific Amazon S3 and CloudFormation permissions. This makes sure that the cross-account role can only do those things. The actual creation of Lambda, API Gateway, and Amazon DynamoDB resources happens via the AWS CloudFormation IAM role, which AWS CloudFormation assumes in the target AWS account.
Make sure that you use secrets to store your sensitive workflow configurations, as specified in the section Configuring secrets.
Conclusion
In this post we showed how you can leverage GitHub’s popular software development platform to securely deploy to AWS accounts and Regions using GitHub actions and AWS CDK.
Build your own GitHub Actions CI/CD workflow as shown in this post.
About the author
Damodar Shenvi Wagle is a Cloud Application Architect at AWS Professional Services. His areas of expertise include architecting serverless solutions, ci/cd and automation.