SDP Clouds
← All posts
Security·3 min read

Kill the Long-Lived Cloud Keys in Your Pipeline

An access key sitting in CI secrets has no expiry and no owner. Replacing it with OIDC federation means the pipeline authenticates per run instead of carrying a permanent password.


There was an access key in our CI secret store for four years. It had permissions nobody could fully enumerate, it had been created by a contractor who left in year two, and it had never been rotated because rotating it meant a deployment window and nobody wanted to own that.

If it leaked, it leaked silently. Access keys do not expire, do not announce themselves, and do not care that the pipeline that uses them was last edited in 2022.

Federation removes the key entirely rather than managing it better.

The exchange, in one paragraph

The CI provider has its own identity — GitHub Actions knows which repository and workflow is calling. It mints a short-lived token describing that identity, presents it to your cloud provider, and your cloud provider trades it for temporary credentials scoped to a role you defined. Nothing long-lived is stored anywhere. The credentials exist for the duration of the job and then stop existing.

The trust lives in one place: the policy that decides which tokens are acceptable.

The role and its trust policy

For GitHub Actions on AWS the trust condition pins the caller to a specific repository and, ideally, a specific workflow file:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:acme/platform:ref:refs/heads/main"
        }
      }
    }
  ]
}

That sub claim is the whole security story. Pin it to one repository, and tighten it further to one branch or one workflow path when the role is powerful. A sub of repo:acme/* means any repository in the organisation can assume this role — a compromise in a low-value repo becomes a compromise of your deployment credentials.

The job side

yaml
permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::111122223333:role/ci-deploy
          aws-region: ap-south-1

id-token: write is what lets the runner request the token. The credentials are then injected as environment variables for this job only. There is no AWS_ACCESS_KEY_ID in repository secrets, because there is nothing to put there.

Finding the keys you already have

Before the migration, inventory what exists:

bash
aws iam list-users --query 'Users[].UserName' --output text
aws iam generate-credential-report

The credential report shows every key, its age, and whether it has been used in the last 90 days. Expect a few rows you cannot account for — that list is your rotation queue, and you work through it by migrating the pipeline that uses each key and then deactivating it. Deactivate before you delete; you want the ability to reverse one step if a job still calls it.

Once the federation path is in place, prevent regressions with a bucket policy or an SCP that denies iam:*AccessKey* outside a break-glass path, so a new permanent key cannot quietly appear.

The same idea elsewhere

GCP calls it Workload Identity Federation, Azure calls them federated credentials, and every serious CI system supports the pattern. The shape does not change: an identity the platform vouches for, a role that trusts that identity, and credentials that expire on their own.

What I deliberately left alone

A secrets manager as a first step for CI credentials — it improves rotation but keeps the underlying problem, which is that a permanent secret exists at all. Also skip the grand credential overhaul; migrate one pipeline at a time and deactivate each key as you go.

Summary

Long-lived keys are the worst kind of secret: no expiry, broad reach, and a history nobody remembers. Federated authentication replaces them with a short-lived token minted per run, trusted through a policy you can read and pin to a single repository and workflow. Inventory what you have, migrate pipeline by pipeline, deactivate as you go, and close the door on new permanent keys. The secret store gets emptier, and a leak stops being an incident.

#security#ci-cd#aws#devops

SDP Clouds Team

DevOps and cloud engineers writing practical, battle-tested guides on CI/CD, Kubernetes, infrastructure as code, and production operations — every article is based on real incidents and real pipelines, not docs-page rewrites.

More about us →

Related articles