43
Secure AWS-CDK deployments with GitHub Actions
GitHub actions enables continuous integration, and the aws-cdk enables infrastructure as code.
This guide provides a secure way to automate the deployments of aws-cdk stacks, from GitHub actions to your AWS account.
It's more secure because you do not have to store long lived credentials in your GitHub account, and, because only the open ID connect with GitHubs fingerprint can assume the deployment role 👍
- Originally published here
- Relevant documentation for:
We will utilise open ID connect, to grant GitHub a temporary federated identity. This identity will be trusted, to assume a role in your AWS account.
When the identity (GitHub) assumes the roles, we will secure it's access by doing two things:
- Granting a temporary aws secret key and access key, that expires in an hour.
- Using claims from the JWT presented by GitHub to AWS to narrow the scope of the allowed identities.
When we are done we will have:
- A one-off GitHub action, that creates the identity provider and trust relationship using an aws-cdk stack.
- Another GitHub action that uses the identity to gain temporary access, and deploy aws-cdk stacks.
We can create a new aws-cdk application:
mkdir bootstrap
npx [email protected] init app --language typescript
After that we will use two components from IAM to create a provider, and a principal.
We will use the principal to create a trust relationship between aws, and GitHub like so.
/**
* Create an Identity provider for GitHub inside your AWS Account. This
* allows GitHub to present itself to AWS IAM and assume a role.
*/
const provider = new OpenIdConnectProvider(this, 'MyProvider', {
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com'],
});
Then establish the trust relationship by defining the conditions for this provider to act as a principal.
I will provide an example that assumes you are https://github.com/simonireilly and you want to deploy from all repository branches of a repo called awesome-project
const githubOrganisation = "simonireilly"
// Change this to the repo you want to push code from
const repoName = "awesome-project"
/**
* Create a principal for the OpenID; which can allow it to assume
* deployment roles.
*/
const GitHubPrincipal = new OpenIdConnectPrincipal(provider).withConditions(
{
StringLike: {
'token.actions.githubusercontent.com:sub':
`repo:${githubOrganisation}/${repoName}:*`,
},
}
);
Finally you want to establish the role that can be assumed by the OIDC principal. This will allow GitHub actions to use the AWS Roles, and mutate the AWS Resources you give it access to.
/**
* Create a deployment role that has short lived credentials. The only
* principal that can assume this role is the GitHub Open ID provider.
*
* This role is granted authority to assume aws cdk roles; which are created
* by the aws cdk v2.
*/
new Role(this, 'GitHubActionsRole', {
assumedBy: GitHubPrincipal,
description:
'Role assumed by GitHubPrincipal for deploying from CI using aws cdk',
roleName: 'github-ci-role',
maxSessionDuration: Duration.hours(1),
inlinePolicies: {
CdkDeploymentPolicy: new PolicyDocument({
assignSids: true,
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
actions: ['sts:AssumeRole'],
resources: [`arn:aws:iam::${this.account}:role/cdk-*`],
}),
],
}),
},
});
🚨 These permissions may be too broad for your use case. Consider adding a permissions boundary, or, opting to use a role other than the role automatically created by the cdk for its deployments 🚨
With a set of created access keys, you can deploy the bootstrap. This enables someone with higher privilege to setup the link for your team 👍
This keeps you from storing long lived credentials in GitHub.
name: Bootstrap
on:
workflow_dispatch:
inputs:
AWS_ACCESS_KEY_ID:
description: "Access Key ID with Permissions to deploy IAM, and OIDC"
required: true
AWS_SECRET_ACCESS_KEY:
description: "Secret Access Key with Permissions to deploy IAM, and OIDC"
required: true
AWS_REGION:
description: "Region to deploy to."
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Git clone the repository
uses: actions/checkout@v1
- name: Configure aws credentials
uses: aws-actions/configure-aws-credentials@master
with:
aws-access-key-id: ${{ github.event.inputs.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ github.event.inputs.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ github.event.inputs.AWS_REGION }}
- uses: actions/setup-node@v2
with:
node-version: "14"
- run: yarn install
- name: Synth stack
run: yarn --cwd packages/bootstrap cdk synth
- name: Deploy stack
run: yarn --cwd packages/bootstrap cdk deploy --require-approval never
When you trigger this action the user must enter aws access keys and aws secrets that have the required privileges.
With this stack deployed you can now ship any aws-cdk v2 deployments from the trusted repository, to the linked AWS account, without storing long lived credentials.
All you need to do is instruct GitHUb actions to assume the github-ci-role role in your account, and it will get temporary credentials for one hour.
deploy-infrastructure:
runs-on: ubuntu-latest
steps:
- name: Git clone the repository
uses: actions/checkout@v1
- name: Assume role using OIDC
uses: aws-actions/configure-aws-credentials@master
with:
role-to-assume: arn:aws:iam::<your-account-id-here>:role/github-ci-role
aws-region: ${{ env.AWS_REGION }}
- uses: actions/setup-node@v2
with:
node-version: "14"
- run: yarn install
- name: Synth infrastructure stack
run: yarn --cwd packages/infrastructure cdk synth
- name: Deploy infrastructure stack
run: yarn --cwd packages/infrastructure cdk deploy --require-approval never
Create another bootstrapping aws cdk stack, that allows only deploying from the main
branch, and point this one at your production AWS account if you have one 👍
{
StringLike: {
'token.actions.githubusercontent.com:sub':
`repo:${githubOrganisation}/${repository}:ref:/refs/head/main`,
},
}
If you are interested in this stuff, you might like microteams!
A guide I am writing for scale ups, that are growing from one person AWS start-ups to multi-team organisations.
43