Johannes Eiglsperger

Zhuilu Suspension Bridge in Taroko Gorge

Use OpenID Connect to securely authenticate GitHub Actions with AWS

3 min read

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:

  1. AWS::IAM::OIDCProvider prepares AWS to use GitHub’s built-in OIDC provider.
  2. 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.

Header image source: Balon Greyjoy via Wikimedia Commons, CC0

Related articles