Introduction
Previously, I wrote an article titled “Implementing Continuous Delivery for Monorepo and Microservice with GitHub Actions.” In this article, I would like to dive deeper into a pattern that utilizes AWS as the infrastructure.
Environment
In this article, the following tools will be used:
- GitHub Actions
- AWS Account
- AWS CDK
Infrastructure Definition (IaC)
To perform Continuous Delivery (CD) on your infrastructure, you need to define your infrastructure as code (IaC). Although there are various tools available for IaC, here we will use AWS CDK.
AWS CDK
Since in my previous article the repository was set up assuming TypeScript, we will also configure AWS CDK with TypeScript. However, as this article focuses on CD, please refer to another article for detailed explanations on AWS CDK.
We aim for the following directory structure, so run the following command in the services/infrastructure directory:
./
├── .github/
│ └── workflows/
├── package.json
└── serivces/
├── infrastructure/
├── service-a/
└── service-b/
Execute the following command to initialize AWS CDK:
npx aws-cdk init app --language typescript
This will generate the following files:
my-cdk-app/
├── bin/
│ └── infrastructure.ts
├── lib/
│ └── infrastructure-stack.ts
├── node_modules/
├── package.json
├── tsconfig.json
└── cdk.json
As a sample, the file infrastructure-stack.ts defines a stack that creates an S3 bucket:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class MyCdkAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create an S3 Bucket
new s3.Bucket(this, 'MyBucket', {
bucketName: 'my-unique-bucket-name-12345', // ※ Change this to a globally unique name
removalPolicy: cdk.RemovalPolicy.DESTROY, // Delete the bucket when the stack is deleted
autoDeleteObjects: true, // Delete objects in the bucket if removalPolicy is DESTROY
});
}
}
Bootstrapping the Environment
Before the initial deployment, you must bootstrap the target AWS account and region so that AWS CDK can deploy your stack. If you need to deploy to multiple regions, perform the bootstrap for each region. Since this is a one-time process per AWS account and region, it does not need to be managed via CD (GitHub) and can be executed from your local development machine.
cdk bootstrap --region ap-northeast-1
Obtaining AWS Credentials
In order to deploy resources to AWS from GitHub Actions, you need to obtain credentials from AWS. Although you could use AWS_ACCESS_KEY and AWS_SECRET_KEY (i.e., access keys), there are several disadvantages to that approach, which is why the OIDC method described later is recommended:
-
Management cost of secrets
- Access keys require regular key rotation, leading to additional operational overhead.
-
Management cost for IAM users
- Since access keys are tied to IAM users, you would need to create IAM users for actions and periodically audit those accounts.
-
Security risks during operations
- Access keys are intended for long-term use, so if they are compromised, they can be misused for an extended period.
-
Inability for fine-grained control on AWS side
- AWS cannot restrict access based on the repository or branch when using access keys, and it is difficult to change permissions on a per-workflow basis.
Advantages of Using IAM Roles with OpenID Connect
When using AWS from GitHub Actions, obtaining a role session via OpenID Connect (OIDC) offers the following advantages:
-
No need to manage long-term credentials
- With OIDC, temporary credentials are issued for each session. This eliminates the need to manage access keys on GitHub, reducing the risk of key leakage and the burden of key rotation.
-
Increased security and minimal permissions
- Temporary sessions obtained via OIDC have short validity. Once the session expires, no further actions can be taken, significantly lowering the security risk. By creating an AWS IAM role with only the necessary permissions and obtaining a session for that role, you can minimize the risk of permission leakage.
-
Seamless integration between GitHub Actions and AWS
- You can restrict usage on a per-repository or per-branch basis—ensuring that only requests from specific repositories are valid, or even narrowing it down by branch or tag. Additionally, roles can be finely controlled per workflow (e.g., separate roles for deployment and testing), ensuring only the minimal required permissions are granted.
OIDC Configuration (AWS)
To accept OIDC tokens from GitHub Actions, you must create an OpenID Connect Provider on the AWS side.
Creating an IAM OIDC Provider with CDK
In CDK, you can use the OpenIdConnectProvider from the @aws-cdk/aws-iam module. Open your CDK stack file (e.g. lib/github-oidc-stack.ts) and modify it as follows:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';
export class CdkGithubOidcStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create the GitHub OIDC Provider
const gitHubOidcProvider = new iam.OpenIdConnectProvider(this, 'GitHubOidcProvider', {
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com'],
thumbprints: ['6938fd4d98bab03faadb97b34396831e3780aea1'],
});
}
}
The parameters are set as follows:
- url: Specify the GitHub Actions OIDC token endpoint:
- clientIds: Specify sts.amazonaws.com.
- thumbprints: Specify the SHA-1 fingerprint of GitHub’s root certificate.
- Although it changed once in the past, it is currently 6938fd4d98bab03faadb97b34396831e3780aea1. This creates the OIDC provider required for federated authentication between GitHub and AWS.
Creating an IAM Role for Use from GitHub Actions
Creating the OIDC provider alone does not grant access to AWS resources. You also need to create an IAM role that GitHub Actions jobs can assume. In the role’s trust policy (assume role policy), you must specify that the entity possessing the GitHub Actions OIDC token is trusted.
Edit your CDK stack file (e.g. lib/github-oidc-stack.ts) as follows:
const githubRole = new iam.Role(this, 'GitHubActionsRole', {
roleName: 'github-actions-deploy-role',
assumedBy: new iam.WebIdentityPrincipal(
gitHubOidcProvider.openIdConnectProviderArn,
{
StringEquals: {
['token.actions.githubusercontent.com:aud']: 'sts.amazonaws.com',
},
StringLike: {
['token.actions.githubusercontent.com:sub']: 'repo:your-org/your-repo:/*'
}
}
),
// Actions allowed for this role (e.g., S3 management permissions)
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCloudFormationFullAccess'),
],
});
Deploy the CDK stack and note the role ARN (e.g., arn:aws:iam::123456789012:role/github-actions-deploy-role).
Configuring GitHub Actions
After configuring AWS, you need to set up GitHub Actions to assume the IAM role. GitHub Actions provides an official AWS action, aws-actions/configure-aws-credentials, which supports OIDC.
You can choose the trigger conditions for the workflow; in this example, it will be triggered manually. For more details, see the GitHub documentation on manually running a workflow.
Finally, don’t forget to add the permissions section.
Your repository’s .github/workflows/deploy.yml file should be configured as follows:
name: Deploy to AWS
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
# Specify the role ARN created earlier in CDK
role-to-assume: arn:aws:iam::your-aws-account-id:role/github-actions-push-image-role
aws-region: ap-northeast-1
- name: Run deploy command
run: npm run cdk deploy
- role-to-assume: Specify the ARN of the IAM role you created.
- aws-region: Specify the AWS region to use (e.g., ap-northeast-1).
This configuration automatically obtains an OIDC token, allowing GitHub Actions to use AWS CLI with a temporary STS session. Since there is no need to store access keys (AccessKey/SecretKey) in the repository secrets, the process is both more secure and reduces the key management burden.
Implementing Environment Switching and Approval Steps Using GitHub Environments
By using GitHub Environments, you can define settings and manage secrets for each environment (such as staging or production) and even implement an approval process.
For more details, see Managing Environments for Deployment.
Setting Up Environments
Within your GitHub repository, define environments such as “staging” and “production,” and configure secrets (e.g., AWS account, region, parameters) for each environment.
By setting up Required Reviewers, when a deployment workflow is executed, the workflow will pause until the specified reviewers approve it.
For example, if you define an environment called production, add the following to your workflow:
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production # Specify the environment name to use
Switching Environments
Since you will likely use multiple environments, you may want to switch between environments like “staging” and “production” within the same workflow.
A workflow can accept an environment as an input, so you can switch environments (and thus the deployment target) by configuring it as follows:
on:
workflow_dispatch:
inputs:
env:
description: AWS Environment
required: true
type: choice
options:
dev
staging
production
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ inputs.env }}
steps:
# ...
This allows you to dynamically switch settings such as the AWS account ID defined in GitHub environments, enabling you to manage multiple environments within a single workflow.
Additional Information
Extending the Session Duration
The AWS session obtained using the above configuration is valid for up to 1 hour. If your deployment workflow takes longer and the session expires, you can extend the session duration by adding additional settings to the role. For example:
const githubRole = new iam.Role(this, 'GitHubActionsRole', {
roleName: 'github-actions-deploy-role',
maxSessionDuration: cdk.Duration.hours(2), // Valid for 2 hours
// ... other configurations
});
Top comments (0)