Ditch Static Secrets: A Practical Guide to GitHub Actions OIDC

Security tutorial - IT technology blog
Security tutorial - IT technology blog

The $5,000 Wake-Up Call

Few things ruin a morning like a notification that your AWS bill spiked by $5,000 overnight. This horror story usually begins with a single IAM Access Key—stuck in a private repo or forgotten in GitHub Secrets—falling into the wrong hands. I’ve spent my fair share of midnight shifts fighting off SSH brute-force attacks. Those experiences taught me a hard truth: any static credential is a liability. Whether it is a password or an API key, if it doesn’t expire, it’s a ticking time bomb.

For years, deploying from GitHub Actions to AWS or GCP required a long-lived service account key. You’d generate it, copy it, and paste it into GitHub Repository Secrets. It felt secure because it was ‘hidden,’ but that secret never expires. If a bad actor snags that key, they have a permanent backstage pass to your infrastructure. They can rack up charges or exfiltrate data until you manually rotate the key or revoke access.

Why Long-Lived Credentials Fail Under Pressure

The problem with CI/CD breaches isn’t usually a lack of encryption. It is the lifespan of the credentials. Static secrets have three fatal flaws:

  • Infinite Validity: A standard AWS Access Key is valid for years if you don’t delete it. Old, forgotten projects become your biggest security holes.
  • Rotation Friction: Security best practices suggest rotating keys every 90 days. In reality, most teams find this tedious. Rotation gets skipped, and secrets go stale.
  • Global Reach: If a secret leaks via a malicious dependency or a misconfigured log, an attacker can use it from any IP address at any time.

We need a system where GitHub Actions proves its identity without a permanent ‘passport.’ We need a temporary visitor badge that self-destructs.

The Shift: GitHub Secrets vs. OIDC

Understanding the transition requires looking at how we verify identity.

The Old Way: The Master Key

You generate an IAM Key or a JSON file and store it in GitHub. Every time your pipeline runs, GitHub sends this same key over the wire. It never changes. It is a master key that works forever.

The Modern Way: OpenID Connect (OIDC)

OIDC lets GitHub Actions trade a short-lived token for a temporary cloud credential. When a workflow starts, GitHub acts as an Identity Provider. It issues a unique, signed JWT (JSON Web Token) specifically for that job. Your cloud provider (AWS or GCP) verifies the signature and checks that the token belongs to your specific repository and branch. Once verified, it hands back a temporary token that typically expires in 60 minutes. Nothing is stored in GitHub.

Setting Up OIDC: A Step-by-Step Transition

Implementing this for major cloud providers involves teaching your cloud to ‘trust’ GitHub’s identity server.

1. Configuring AWS for OIDC

First, create an OIDC Identity Provider in the AWS IAM Console. This tells AWS to recognize tokens issued by GitHub.

# Create the OIDC provider via AWS CLI
aws iam create-open-id-connect-provider \
    --url "https://token.actions.githubusercontent.com" \
    --client-id-list "sts.amazonaws.com" \
    --thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"

Next, define an IAM Role for your GitHub Action. The Trust Policy is the gatekeeper. It ensures only your specific repository can assume this role.

{
  "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:YourUsername/YourRepoName:*"
        }
      }
    }
  ]
}

2. Configuring GCP for OIDC

Google Cloud uses Workload Identity Pools. It is a more abstract approach but offers granular control.

# 1. Initialize a Workload Identity Pool
gcloud iam workload-identity-pools create "github-pool" \
    --location="global" --display-name="GitHub Actions Pool"

# 2. Link the OIDC Provider
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --issuer-uri="https://token.actions.githubusercontent.com" \
    --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository"

# 3. Bind the Service Account to the GitHub Repository
gcloud iam service-accounts add-iam-policy-binding "[email protected]" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/YourUsername/YourRepoName"

3. The Workflow Update

Your YAML needs one specific change. You must grant the job id-token: write permissions. Without this, GitHub cannot generate the JWT required for the handshake.

name: Secure Cloud Deployment
on: [push]

permissions:
  id-token: write
  contents: read

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

      - name: AWS Authentication
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole
          aws-region: us-east-1

      - name: Verify Identity
        run: aws sts get-caller-identity

Bulletproofing Your Strategy

Switching to OIDC removes the headache of secret rotation and hardens your pipeline. To maximize security, follow these three rules:

  • Tighten the Scope: Don’t use wildcards like repo:YourUsername/*. Specify the exact repository and, if possible, the deployment branch (e.g., ref:refs/heads/main).
  • Enforce Least Privilege: Even with OIDC, your role shouldn’t be an Administrator. If the job only uploads to S3, only grant S3 PutObject permissions.
  • Watch the Logs: Use AWS CloudTrail or GCP Cloud Audit Logs. Monitor for any identity federation requests that look out of place.

Ephemeral security is the gold standard. By removing permanent secrets, you ensure that an intercepted token is worthless by the time an attacker tries to use it. It’s the best way to ensure you never wake up to an unexpected $5,000 bill again.

Share: