← Back to Blog

GitHub Actions to AWS with OIDC: Removing Long-Lived Cloud Keys

A small, high-impact security change: replace stored AWS access keys with short-lived identity federation from GitHub Actions.

Cover image for GitHub Actions to AWS with OIDC: Removing Long-Lived Cloud Keys
AWSGitHub ActionsSecurityDevSecOps

A deployment key stored in CI is a credential with a long memory and a wide blast radius. OpenID Connect lets GitHub Actions exchange a signed workflow identity for short-lived AWS credentials instead.

Trust the workflow, not the repository name

The IAM trust policy should restrict the repository, branch or environment, and workflow subject. A role that trusts every workflow in an organization has simply moved the problem to a more expensive file.

The problem we inherited

The deployment workflow used one long-lived access key shared by several jobs. It had survived staff changes, lived in more than one secret store, and nobody could prove which pipeline still needed it. Rotation was treated as a risky event because the blast radius was unknown.

The first proposal was to rotate the key more often. That would have reduced exposure time but preserved the same design: a static secret copied into automation. We chose to remove the dependency instead of managing it more carefully.

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: aws-actions/configure-aws-credentials@v6.3.0
        with:
          role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
          aws-region: eu-west-1

Keep deployment roles narrow

Separate build, artifact upload, and production deployment roles when their permissions differ. Review CloudTrail events for the assumed role and set a session name that points back to the workflow run.

Prove the old key is gone

After migration, search repository secrets, organization secrets, runner environments, and local documentation for the old access key. Revoke it and confirm a failed deployment cannot use it as a fallback.

The resolution

We added an OIDC trust limited to the production environment, gave the workflow a short-lived role, and separated read-only build access from deployment access. CloudTrail showed the workflow run in every assumed-role session, which made the next investigation much less archaeological.

The trade-off is that the trust policy is more precise and therefore easier to break when repository structure changes. We made that visible by testing the role assumption in pull requests and documenting the environment subject alongside the workflow.

Test the trust boundary, not just the happy path

Check the audience and subject conditions in the IAM trust policy, then test both an allowed production run and a denied pull-request or wrong-branch run. If the job uses a GitHub environment, protect that environment and make the trust subject match it. Keep id-token: write on the deployment job only; a build that never assumes AWS has no reason to mint an AWS-shaped identity.

GitHub issues a short-lived identity; AWS STS grants a role only after audience, repository, ref, and environment conditions match.
GitHub issues a short-lived identity; AWS STS grants a role only after audience, repository, ref, and environment conditions match.

Federation does not remove trust. It makes the trust decision explicit, reviewable, and short-lived, which is a much better place to have the argument.

The actual benefit of OIDC

The strongest credential rotation is deleting the credential class you no longer need.

← Back to BlogHome