Replace AWS Secrets in GitHub with OpenID Connect

When integrating GitHub Actions Workflows with an AWS account, you'll need a way to authorize Actions to assume an IAM role. A straightforward way to authenticate is by creating an IAM user with an access key and then sharing the access key ID and secret as GitHub Actions secrets. These secrets can either be used with the Configure AWS Credentials GitHub action for role assumption or directly incorporated into an Action that accesses AWS through the CLI or SDK. When I started working in AWS, I often relied on access keys while using GitHub Actions for deployment because they were a convenient starting point. While this approach works, it could be more secure. This post will explore the problems with access keys and how you can improve your security posture using OpenID Connect (OIDC).

In 2023, AWS started warning users about the risks associated with long-term access keys when they attempt to create them in the AWS console. Along with the warnings, AWS provides a link to their IAM access key documentation page, which urges users to explore alternative methods whenever possible.

A screenshot from the AWS Console's IAM Management page where users can create access keys for an IAM user.
A dialog warning the user the use of long-term credentials is not recommended and short-lived credentials should be used instead.

The risks of using long-lived credentials

Sharing AWS access keys as GitHub Actions secrets provides a basic level of security by allowing access to an AWS account without allowing public Internet exposure. GitHub secrets also allow collaboration in a repository while safeguarding sensitive keys.

However, access keys have their downsides:

Access keys are long-lived

AWS access keys remain active until manually deactivated or deleted. The longer keys remain active, the higher the risk of unintended exposure or misuse.

Secrecy is hard

Maintaining the secrecy of access keys is challenging. Unintentional secret disclosure in source control or other platforms is so prevalent multiple security tools exist to detect this problem, such as GitHub Advanced Security Code Scanning, GitLeaks, and GitGuardian. According to GitGuardian's 2022 Secrets Sprawl Report, git repositories leaked over six million secrets in 2022, a twofold increase from 2021. This difficulty in managing secrets is a major concern for security and compliance teams. Avoiding access keys and secrets entirely, whenever possible, is advisable.

Secret manager limitations

Secret management solutions like GitHub Secrets or Vault are far better than storing credentials in source control, but unfortunately, they're not perfect. Secrets can still be leaked as build output through CI/CD pipeline modifications.

Key rotation policies

Implementing and enforcing key rotation policies can make a secret less likely to become compromised. Key rotation policies are challenging to implement and enforce, especially in large organizations. Additionally, the process of rotating keys is both tedious and time-consuming.

Auditing complications

Auditing long-lived AWS credentials also has limitations. First, tracking the usage of keys isn't trivial, especially if they've been shared with multiple accounts, services, or users. Additionally, because actors can reuse keys numerous times, it is much more challenging to differentiate between legitimate and compromised account access.

Improving security with OpenID Connect

You can significantly improve your security posture when integrating GitHub Actions with AWS accounts by leveraging OIDC federation. Federation establishes a secure trust relationship between GitHub and AWS, eliminating the need to share secrets between the two systems.

At a high level, here is how this works:

  1. Establish Trust: First, you must establish an OIDC trust between AWS IAM and GitHub's OIDC provider (more on how to set this up later).
  2. Workflow Token Generation: GitHub's OIDC provider generates a secure token each time a GitHub workflow is run. Each generated JWT token is unique to the workflow run. This token contains claims about the workflow's identity, such as its associated organization, repository, and branch.
  3. AWS IAM Token Validation: A GitHub Action sends a request to AWS IAM to assume a particular IAM role along with the token issued from GitHub's OIDC provider. IAM then validates the token issued by GitHub's OIDC provider. If the GitHub token is authentic, IAM will evaluate the token's claims and determine if the Action is permitted to assume the requested IAM role. AWS will give the Action a short-lived AWS token if authorized to assume the requested role. The Action can then use this AWS-issued token to assume the role and perform actions scoped to the permissions granted in the IAM role.

How to implement

The Configure AWS Credentials GitHub Action is a popular Action that easily configures AWS credentials as environmental variables for use by other GitHub Actions in a workflow. Since this is so easy to use, I'll assume most people integrating GitHub Actions with AWS are using this Action. If you're not using this Action and configuring credentials manually, most of the steps outlined here still apply. If you haven't looked at the documentation for this Action recently, you'll notice that its documentation strongly encourages using OIDC.

Setup federation

For each AWS account you wish to leverage OIDC federation, there's a one-time setup required that sets up a federation between GitHub's OIDC provider and an IAM identity provider. You could do this manually, but fortunately, AWS provides a CloudFormation Template to configure this for you here. You'll need to supply this template with the name of the GitHub organization or user (if not using GitHub Enterprise) you wish to establish trust with and the repository you want to trust. The other parameters are optional. However, if you use a non-standard AWS partition, such as AWS GovCloud (as I do at work), you must change the OIDC Audience (e.g., sts.us-gov-west-1.amazonaws.com for GovCloud). You only need to configure GitHub as a federated OIDC provider once per AWS account.

A screenshot of a CloudFormation template asking for parameters to be filled out prior to being ran.

Once this Cloud Formation template completes, it will create a new role (e.g., github-oidc-federation-example-Role-CT1ElR4DjK6y), allowing a GitHub workflow to assume a role in the AWS account without sharing any secrets between AWS and GitHub. This Cloud Formation template will also setup federation between GitHub's OIDC provider and AWS IAM.

A screenshot of an IAM role with a federated trust relationship with GitHub's OIDC provider

The screenshot above demonstrates that establishing a trust relationship is manageable without needing a CloudFormation template. The example github-oidc-federation-example-Role-CT1ElR4DjK6y role created by this template allows any GitHub workflow running on any branch within the GitHub repository named cebert/github-oidc-connect-example to assume this role with web identity.

You must carefully ensure that conditions are set to limit which GitHub workflows can assume a role. If you establish a trust relationship with GitHub without any conditions, it could be possible for any workflow in GitHub to assume the role, which is a huge security risk. If you're careful when establishing this trust relationship, OIDC is far more secure than sharing long-lived credentials.

GitHub outlines all claims included in its OIDC issued tokens in its well-known OIDC configuration. These claims can be used as conditions to restrict access, such as only allowing a particular workflow running in a specific organization and branch to have the ability to assume a role. The more restrictive you can be here, the better.

Assuming a role from GitHub

Once you have created an IAM role with a trust relationship with GitHub's OIDC, the role can be assumed by an Action running in a workflow. If we wanted the Configure AWS Credentials GitHub Action to assume the github-oidc-federation-example-Role-CT1ElR4DjK6y role that was created in the example above, we could do so in a few lines of code:

    - name: Configure AWS Credentials for Example
      uses: aws-actions/configure-aws-credentials@v4
      with:
        audience: sts.amazonaws.com
        aws-region: us-east-1
        role-to-assume: arn:aws:iam[OMITTED]:role/github-oidc-federation-example-Role-CT1ElR4DjK6y*
        role-session-name: example-session
        role-duration-seconds: 360

Additionally, you'll need to grant your GitHub Actions workflow job write id-token permissions so that it can interact with GitHub's OIDC sts endpoint:

jobs:
  deploy:
    name: Assume AWS role example
    runs-on: ubuntu-latest
    # We need to access GH's OIDC provider endpoint
    permissions:
      id-token: write
      contents: read
    steps:
    - name: Checkout
      uses: actions/checkout@v4
    - name: Configure AWS Credentials for Example
      uses: aws-actions/configure-aws-credentials@v4
      with:
        audience: sts.amazonaws.com
        aws-region: us-east-1
        role-to-assume: arn:aws:iam[OMITTED]:role/github-oidc-federation-example-Role-CT1ElR4DjK6y*
        role-session-name: example-session
        role-duration-seconds: 360
   ## Add steps that need AWS credentials here

Because of the trust relationship we established for this role between an AWS account and GitHub's OIDC provider, only GitHub Action workflows running within the repository cebert/github-oidc-connect-example will be allowed to assume this role. We could further restrict which workflows can assume this role by adding more conditions to our IAM role's trust relationship configuration.

Note: It's always recommended to provide both role-session-name and role-duration-seconds parameter values when assuming an IAM role with Configure AWS Credentials, even though these parameters are not strictly required. Specifying a session name helps with auditing events in AWS CloudTrail. Setting the lowest possible value for role-duration-seconds helps ensure the AWS token use to assume the IAM role is as short-lived as possible.

Conclusion

This post covered the inherent risks of using long-lived secrets to grant GitHub Actions workflows access to AWS accounts. By embracing OIDC federation, we improve our security posture while simplifying the integration between GitHub Actions and AWS. The steps I've outlined for setting up federation and role assumption in GitHub should provide a clear, actionable guide on adopting this authorization method.

If you're still sharing long-lived credentials with GitHub Actions, consider transitioning from access keys to OIDC next time you rotate your credentials. Configuring OIDC trust won't take much longer than rotating credentials, with the added benefit of improved security and not needing to rotate credentials again.