Have you rotated your access keys today? If not, it is time to make your life easier and your GitHub workflows more secure with GitHub’s built-in OpenID Connect (OIDC) provider.
Access keys are risky – they are long-lived secrets that can easily fall into the wrong hands (especially in public repositories). Creating, rotating, storing and revoking access keys can be time-consuming and error-prone. With OIDC, your workflows request short-lived credentials from AWS at runtime using GitHub’s built-in OIDC provider.
sequenceDiagram participant Workflow as GitHub
Workflow participant IdP as GitHub
OIDC Provider participant IAM as AWS IAM IdP --> IAM: OIDC trust relationship Workflow ->>+ IdP: Token request IdP -->>- Workflow: Token Workflow ->>+ IAM: Token,
IAM role ARN IAM -->>- Workflow: Temporary credentials
Set up AWS IAM resources
You need two resources:
AWS::IAM::OIDCProvider
prepares AWS to use GitHub’s built-in OIDC provider.AWS::IAM::Role
creates a trust relationship with the OIDC provider and grants access to your cloud resources using policies.
While AWS provides a little CloudFormation template, many engineers prefer a higher-level tool like AWS CDK or Terraform.
Both AWS CDK and Terraform provide excellent ways to define your cloud resources in code, making the code reusable and maintainable. This is how it can look like with AWS CDK for TypeScript:
import { type App, Stack, type StackProps } from 'aws-cdk-lib';
import {
OpenIdConnectProvider,
Role,
WebIdentityPrincipal,
PolicyDocument,
PolicyStatement
} from 'aws-cdk-lib/aws-iam';
class OidcIamRoleStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
const oidcProvider = new OpenIdConnectProvider(this, 'GitHubOidcProvider', {
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com']
});
new Role(this, 'GitHubOidcRole', {
assumedBy: new WebIdentityPrincipal(
oidcProvider.openIdConnectProviderArn,
{
StringEquals: {
'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com'
},
StringLike: {
// Replace with your GitHub org and repo:
'token.actions.githubusercontent.com:sub': 'repo:<YOUR_GITHUB_ORG>/<YOUR_REPO>:*'
}
}
),
roleName: 'GitHubOidcRole',
inlinePolicies: {
GitHubActionsPolicy: new PolicyDocument({
statements: [
// Replace with the policy statements your workflow needs:
new PolicyStatement({
actions: ['s3:*'],
resources: ['arn:aws:s3:::<YOUR_BUCKET_NAME>/*']
})
]
})
}
});
}
}
Following the principle of least privilege, you can further restrict the token subject (token.actions.githubusercontent.com:sub
).
For example, you can restrict access to the main
branch: repo:<YOUR_GITHUB_ORG>/<YOUR_REPO>:ref:refs/heads/main
.
It is also possible to restrict by tags, environments and events.
Refer to the GitHub documentation for a list of possible values and examples.
Deploy both IAM resources and note the IAM role ARN for the next step.
Update your workflow YAML
Now that you have the IAM role, update your GitHub workflow to use it with the AWS GitHub Action.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # This allows your job to request an OIDC token
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: <YOUR_ROLE_ARN> # Replace with the IAM role ARN
aws-region: <YOUR_REGION> # Replace with your AWS region
- name: Deploy your app to S3 (example)
run: aws s3 sync ./public s3://<YOUR_BUCKET_NAME>
With OIDC, you are embracing a secure and flexible way to manage AWS access in your GitHub workflows. Now you can stop worrying about rotating your workflow’s access keys and start focusing on what matters: Shipping great features.