DevOpsCloud Hosting

Hardening CI/CD Runners with Least Privilege Cloud IAM and OIDC

Federating CI runner authentication with short-lived OpenID Connect IAM tokens

Open your GitHub repository secrets dashboard right now. Do you see AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY?

If so, your delivery pipeline is running on a time bomb. Static, long-lived API keys stored in CI systems are prime targets for attackers. If a third-party action in your pipeline is compromised or an untrusted pull request logs environment variables, an attacker gains permanent, unmonitored access to your cloud account.

Modern cloud engineering teams eliminate static cloud credentials completely using OpenID Connect (OIDC) and short-lived, least-privilege IAM roles.

Static Secrets vs. OIDC Federation

CapabilityStatic IAM Access KeysOIDC Federated IAM Roles
Credential LifespanMonths or years (until manually rotated)15 to 60 minutes (automatic expiry)
Rotation OverheadHigh (requires updating CI dashboards)Zero (generated dynamically per build)
Risk of LeakagePermanent compromise of cloud tenantUseless within minutes of job completion
Scope GranularityBound to static user identityScoped strictly to repository, branch, or tag
AuditabilityDifficult to associate actions with exact commitEvery API call logs the exact workflow run ID

How OIDC Federation Works

Instead of storing a secret key, your CI runner requests a signed cryptographic JSON Web Token (JWT) from GitHub, presents it to your cloud provider (AWS, GCP, or Azure), and receives temporary credentials:

[GitHub Runner]   --> Requests signed OIDC token from GitHub
[GitHub OIDC]     --> Issues cryptographically signed JWT with repo & branch claims
[AWS / GCP IAM]   --> Verifies signature against GitHub's public keys
[Cloud IAM Role]  --> Assumes role and returns 1-hour temporary STS credentials

Configuring AWS IAM Role for GitHub Actions OIDC

Configure an IAM Trust Policy that restricts role assumption strictly to your production repository and main branch:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012: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:my-organization/my-app:ref:refs/heads/main"
        }
      }
    }
  ]
}

Notice the StringLike condition: if an attacker forks your repository or opens a malicious pull request from a feature branch, the cloud provider rejects the token because the branch claim does not match refs/heads/main.

Consuming OIDC in Your Workflow

In your GitHub Actions workflow, request the id-token: write permission and authenticate without a single stored secret:

# .github/workflows/deploy.yml
name: Deploy Production
on:
  push:
    branches: [main]

permissions:
  id-token: write # Required for requesting the OIDC JWT
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS Credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-prod-deployer
          aws-region: ap-south-1

      - name: Deploy Infrastructure
        run: |
          echo "Authenticated using temporary STS token!"
          aws s3 sync ./dist s3://company-production-assets/

To learn how to modernize and protect your cloud delivery infrastructure, review our managed cloud services and discover why cloud account ownership and access boundaries matter after launch.

Related Articles

Let's grow your business together.

Book a free consultation — no obligation, just honest advice.

Get a Free Quote