Tired of juggling AWS keys, GCP service account JSONs, and other static secrets in your GitHub repository? Leaked credentials are a top security threat, and managing secret rotation is a painful operational chore. What if you could grant your CI/CD workflows secure, temporary access to cloud resources without ever storing a long-lived password?
This is where OpenID Connect (OIDC) changes the game. By moving from static, long-lived secrets to dynamic, short-lived tokens, you can eliminate a major vulnerability in your software supply chain and simplify your CI/CD pipeline management.
This guide will walk you through exactly why and how to secure your GitHub Actions pipelines using OIDC. We'll cover the core concepts, provide a step-by-step implementation guide for AWS, and share best practices for a truly secure, passwordless CI/CD setup.
The Problem with Static Secrets in CI/CD
The High Cost of a Single Leak
Storing long-lived credentials like AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as GitHub secrets is a widespread practice, but it creates a significant security liability. If a developer with access to the repository inadvertently pushes code to a public fork, those secrets can be exposed within seconds. Automated scanners constantly search sites like GitHub for leaked credentials. A compromised key with broad permissions provides an attacker with a direct entry point into your cloud infrastructure, potentially leading to data exfiltration, resource hijacking, or catastrophic financial and reputational damage. The blast radius is enormous because the static credential holds its power until it is manually revoked.
Operational Nightmares: Rotation and Management
Beyond the security risks, managing static secrets is an operational burden. Security best practices dictate that credentials should be rotated regularly, often every 90 days. This manual process is tedious, error-prone, and often neglected. The complexity multiplies as you scale. You end up with different sets of secrets for development, staging, and production environments (DEV_AWS_KEY, PROD_AWS_KEY), creating a matrix of credentials that must be carefully managed and assigned to the correct workflows. Auditing access becomes a challenge; you can see who has access to the GitHub repository secrets, but it's disconnected from your cloud provider's central identity and access management (IAM) system.
The Weakest Link in Your Supply Chain
Software supply chain attacks are on the rise, and CI/CD pipelines are a prime target. Attackers know that these systems often hold powerful credentials needed to build, test, and deploy applications. By compromising a single developer account or exploiting a vulnerability in a third-party Action, they can gain access to your pipeline's secrets. With these static credentials in hand, an attacker can not only steal data but also inject malicious code into your artifacts, compromising your software and your customers. Static secrets are the keys to the kingdom, and leaving them stored in your pipeline makes them the weakest link.
Understanding OIDC: Your Key to a Passwordless Workflow
What is OIDC in Plain English?
Think of OIDC as providing your GitHub Actions workflow with a single-use, time-limited valet key for your cloud account. Instead of giving the workflow a master key that works anytime, anywhere (a static secret), you give it a temporary key that only works for a specific task (like deploying to S3) and expires the moment the job is done.
In technical terms, OIDC is an identity layer built on top of OAuth 2.0. It allows clients (your GitHub Actions workflow) to verify the identity of a user or service (the workflow itself) based on authentication performed by an Identity Provider (IdP). Here are the key terms:
- Identity Provider (IdP): The service that authenticates the identity and issues tokens. In our case, GitHub is the IdP.
- JSON Web Token (JWT): A compact, digitally signed token that contains information about the workflow's identity. This token is the 'valet key'.
- Claims: The pieces of information contained within the JWT, such as the repository name (
my-org/my-repo), the branch (refs/heads/main), the triggering event, and more. Your cloud provider uses these claims to decide whether to grant access.
The OIDC Flow in GitHub Actions
The magic of OIDC lies in a secure, automated token exchange process that happens in seconds:
- Token Request: Your GitHub Actions workflow starts and requests a specially crafted JWT from GitHub's internal OIDC provider.
- JWT Presentation: The workflow sends this JWT to your cloud provider (e.g., AWS Security Token Service - STS). It essentially says, 'GitHub has identified me as a job running for the
my-org/my-reporepository on themainbranch. Here is the signed proof.' - Validation: The cloud provider, which you've configured to trust GitHub as an IdP, validates the JWT's signature. It then inspects the claims inside the token to ensure they match the conditions you've defined in a trust policy (e.g., 'Only allow access from this specific repository and branch').
- Token Issuance: If the validation succeeds, the cloud provider generates and sends back short-lived access credentials (e.g., a temporary AWS access key, secret key, and session token). Your workflow uses these credentials to perform its tasks, and they automatically expire shortly after the job completes.
Core Benefits: Why Make the Switch?
Adopting OIDC for your CI/CD workflows offers immediate and transformative advantages:
- Eliminates Secret Storage: You no longer need to store any long-lived cloud credentials in GitHub Secrets. This single change drastically reduces your attack surface.
- Automatic, Short-Lived Credentials: Access tokens are generated on-demand for each workflow run and expire automatically. A compromised token has a very limited window of usefulness.
- Centralized and Granular Access Control: You define access policies directly within your cloud provider's IAM system, where it belongs. You can enforce the Principle of Least Privilege with surgical precision, granting a workflow just enough permission to do its job and nothing more. This also makes auditing straightforward.
Step-by-Step Implementation: GitHub Actions and AWS
Step 1: Configure the Trust Relationship in AWS IAM
First, you need to tell AWS to trust identity tokens coming from GitHub.
- In the AWS IAM console, navigate to Identity providers and click Add provider.
- Select OpenID Connect.
- For Provider URL, enter
https://token.actions.githubusercontent.com. - Click Get thumbprint to verify the server certificate.
- For Audience, enter
sts.amazonaws.com. - Click Add provider.
Next, create an IAM Role that your GitHub Actions workflow will assume. Navigate to Roles and click Create role.
- For Trusted entity type, select Web identity.
- Choose the Identity provider you just created (
token.actions.githubusercontent.com). - For Audience, select
sts.amazonaws.com. - After creating the role, go to its Trust relationships tab and edit the policy. This is where you lock down access to a specific repository and branch. A secure policy looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::YOUR_AWS_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_ORG/YOUR_REPO_NAME:ref:refs/heads/main"
}
}
}
]
}This policy only allows role assumption if the request comes from a workflow running on the main branch of the YOUR_GITHUB_ORG/YOUR_REPO_NAME repository.
Step 2: Attach Granular Permissions to the IAM Role
With the trust relationship established, you must now define what this role is allowed to do. Always adhere to the Principle of Least Privilege: grant only the permissions absolutely necessary for the workflow's task. For example, if your workflow only needs to upload build artifacts to an S3 bucket, create a policy like this and attach it to the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-production-build-artifacts/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-production-build-artifacts"
}
]
}This policy grants permissions to read and write objects within a specific S3 bucket (my-production-build-artifacts) and nothing else.
Step 3: Update Your GitHub Actions Workflow (.yml)
Finally, modify your workflow YAML file to use OIDC. The key changes are adding a permissions block to allow the workflow to fetch the OIDC token and using the aws-actions/configure-aws-credentials action to perform the role assumption.
name: Deploy to S3 via OIDC
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
# These permissions are required to allow the workflow to fetch an OIDC token.
permissions:
id-token: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
# The ARN of the role you created in Step 1
role-to-assume: arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/GitHubActionsDeployRole
aws-region: us-east-1
- name: Upload file to S3
run: |
touch example.txt
aws s3 cp example.txt s3://my-production-build-artifacts/example.txt
echo "Upload successful!"Note the permissions block at the job level. The id-token: write permission is mandatory. The aws-actions/configure-aws-credentials action handles the entire OIDC token exchange, making temporary AWS credentials available to subsequent steps in the job.
Advanced Patterns and Best Practices
Beyond the Basics: Using OIDC with GCP and Azure
The concepts behind OIDC are cloud-agnostic. While the implementation details differ, both Google Cloud and Microsoft Azure offer similar functionality:
- Google Cloud Platform (GCP): The feature is called Workload Identity Federation. You can use the official
google-github-actions/authaction to authenticate your workflows. - Microsoft Azure: This is implemented via Workload Identity Federation in Azure Active Directory. The official
azure/loginaction supports OIDC-based authentication.
Migrating to OIDC on these platforms follows the same pattern: configure a trust relationship in the cloud provider, create a service account with scoped permissions, and update your workflow YAML to use the appropriate authentication action.
Fine-Graining Access with Custom Claims
The sub (subject) claim in the JWT offers powerful control. You can create different roles for different events within the same repository. For example, you might want a role with read-only permissions for pull request validation and a separate role with write permissions for deployments from the main branch. You can achieve this by creating two roles with different trust policies:
Trust Policy for Pull Requests (Read-Only Role):
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-app:pull_request"
}
}Trust Policy for Main Branch Deployments (Write Role):
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-app:ref:refs/heads/main"
}
}You can even scope access to specific GitHub Environments, providing a robust separation of duties between development, staging, and production workflows.
Common Pitfalls and Troubleshooting
When configuring OIDC, you might encounter errors like Not authorized to perform sts:AssumeRoleWithWebIdentity. If you see this, run through the following checklist:
- Check Workflow Permissions: Did you forget to add the
permissions: id-token: writeblock to your workflow file? This is the most common cause of failure. - Verify IAM Role Trust Policy: Double-check the trust policy JSON in your IAM role. Is the federated principal ARN correct? Does the
subclaim string in theConditionblock exactly match the repository and branch format (e.g.,repo:org/repo:ref:refs/heads/branch)? - Confirm Role ARN and Region: Ensure the
role-to-assumeARN andaws-regionin your workflow YAML file are correct and do not contain typos. - Repository Visibility: The format of the
subclaim can vary slightly for public repositories. Refer to the official GitHub documentation on 'Configuring OIDC in AWS' for the precise claim formats if you are working with a public repo.
Conclusion
By replacing static, long-lived secrets with OIDC's dynamic, short-lived tokens, you fundamentally strengthen your CI/CD pipeline's security. This approach eliminates the risk of leaked credentials and removes the operational burden of secret management.
You've learned how OIDC works, how to implement it step-by-step with GitHub Actions and AWS, and how to apply advanced patterns for granular control. The move to a passwordless CI/CD is no longer a complex ideal but an achievable and essential security upgrade.
Take the first step today. Audit one of your key repositories and migrate a single workflow to use OIDC. Share your experience or questions in the comments below!
At ToolShelf, we believe in building secure and private developer tools. This commitment to security is why we advocate for modern practices like OIDC to protect the software supply chain. All our tools work locally in your browser—your data never leaves your device.
Stay secure & happy coding,
— ToolShelf Team