Securing CI/CD Pipelines with GitHub OIDC: Eliminating Long-Lived AWS Credentials
I did what most of us do when we first wire up CI/CD for a project. I made an IAM user. access keys that were generated. I put them in GitHub Secrets.
The pipeline operated flawlessly. The pipeline could:
- “Run database migrations”
- “Push container images to ECR”
- “Deploy static assets to S3”
There were no issues with functionality. However, there was a problem with security.
The Problem with Static AWS Keys
Static credentials have drawbacks, even if you rotate them every few months.
They just sit there at first. enduring. They can be used outside of CI/CD until you rotate them if someone with repository access leaks them, whether on purpose or by accident.
Permissions also have a tendency to increase over time. S3 access is required for one workflow. ECR is required by another. The same key is doing far more than it should before you realize it.
Third, it’s difficult to track them down. You see an IAM user rather than the workflow run that caused it if something suspicious appears in CloudTrail.
The blast radius is subtly increased by that combination.

This diagram should show:
GitHub → Static Access Keys → AWS
Highlighting credential storage and long-lived exposure risk.
The Turning Point
“What happens if one of those secrets gets exposed?” was a question posed during a security review. That query persisted. Because the truthful response was, “It depends on how fast we notice.”
And it’s not a good place to be.
At that point, I became very interested in GitHub’s OIDC integration with AWS.
The Shift: Federated Identity Instead of Stored Secrets
By switching from static AWS keys to OIDC federation, we have simplified our security and greatly decreased our attack surface. During a workflow run, GitHub generates a signed OIDC token (JWT). AWS verifies that the token includes signatures and claims. AWS STS ensures that access automatically expires and that no long-term keys are ever stored or manually rotated by issuing temporary, short-lived credentials for a particular IAM role after authentication. We’ve switched to a “keyless” model by removing persistent secrets from GitHub, which hardens our entire infrastructure and eliminates the need for manual maintenance.
What Actually Changed
The implementation wasn’t complicated, but it required understanding how identity flows between systems.
First, we registered GitHub’s OIDC provider in AWS IAM:
https://token.actions.githubusercontent.com
That tells AWS:
“I trust tokens signed by GitHub.”
Then we created an IAM role with a trust policy that allowed only our repository and a specific branch:
repo:my-org/my-repo:ref:refs/heads/main
This is important. The role doesn’t trust “GitHub in general.”
It trusts a very specific repo and branch.
When the workflow runs, this is what happens behind the scenes:
- The workflow requests an OIDC token from GitHub (enabled via id-token: write).
- GitHub returns a signed JWT containing claims like:
- Repository
- Branch
- Workflow
- The configure-aws-credentials action calls:
- AssumeRoleWithWebIdentity
- AWS verifies:
- The token signature
- The issuer
- The audience (sts.amazonaws.com)
- The repository and branch claims
- If everything matches the trust policy, AWS STS issues temporary credentials.
Those credentials:
- Are scoped to the IAM role
- Have a limited session duration (typically up to 1 hour)
- Expire automatically
There’s nothing to delete at the end. They simply become invalid.

This diagram should show:
GitHub Workflow
→ OIDC Token
→ AWS STS (AssumeRoleWithWebIdentity)
→ Temporary IAM Role Credentials
→ AWS Services
Also, show CloudTrail logging for the role assumption.
What Changed in Practice
The impact was immediate.
There are no static AWS keys in GitHub anymore.
The pipeline requests credentials only when it runs.
Auditability improved as well. In CloudTrail, we now see:
- AssumeRoleWithWebIdentity events
- The role of ARN
- Session context tied to the workflow
Instead of a generic IAM user, we see a role assumption tied to CI/CD.
The blast radius is smaller, too. Even if something goes wrong inside the workflow, the credentials:
- Are short-lived
- Are bound to that role
- Cannot be reused later
What I Learned
Transitioning to OIDC is more than a simple configuration update; it’s a fundamental shift that requires a rigorous evaluation of trust boundaries. The core of this migration lies in the IAM trust policies, where the stakes are high: overly permissive policies undermine the security benefits, while overly restrictive ones lead to opaque pipeline failures. Striking the right balance between accessibility and security was the most critical phase of our transition, but the result is a deployment process that is both significantly cleaner and inherently more secure.
The Bigger Picture
Moving from static credentials to OIDC wasn’t just about “modernizing” the pipeline.
It was about shifting the security model.
From:
- Long-lived credentials
- Manual rotation
- Implicit trust
To:
- Federated identity
- Short-lived, auditable sessions
- Explicit trust relationships
The pipelines didn’t just become more secure.
They became easier to reason about.
And we eliminated an entire category of risk: long-lived AWS access keys living inside a source control system.