# how to lock down GitHub Actions with OIDC

## TL;DR

Stop storing cloud access keys as GitHub repo secrets. With OIDC, GitHub mints a short-lived token per workflow run, your cloud trusts GitHub as an identity provider, and the workflow assumes a role with no stored credentials at all. Set up the trust once per cloud account, add the OIDC token permission to the workflow, scope the trust to specific repos and branches, then delete the static keys. Leaked tokens expire in minutes instead of living forever in a secret.

```text
how to lock down GitHub Actions with OIDC
```

## Use this when

- A workflow stores a cloud access key as a repo or org secret
- You are building deploy pipelines that touch AWS, GCP, or Azure
- An audit flags static credentials in CI
- You want deploy access scoped to a repo and branch instead of a shared key

## Not for this skill when

- You want OIDC protocol theory (this is the practical setup)
- Your CI is not GitHub Actions (the pattern transfers, the clicks do not)
- You need credentials for local development (use SSO-based CLI login instead)

## Steps

### 1. Create the cloud-side trust (AWS example)

Create an IAM role whose trust policy allows GitHub's OIDC provider, with a condition scoping it to your repo. Then verify the trust looks right:

```bash
aws iam get-role --role-name [role-name] --query 'Role.AssumeRolePolicyDocument'
```

Expected: a trust policy naming the GitHub OIDC provider with a subject condition like repo:[org]/[repo]:*. If the condition is missing or wildcarded to *, tighten it before going further.

### 2. Add the minimal permissions block to the workflow

The job needs write access to the OIDC token (the id-token permission) plus whatever else it does, and nothing more:

```yaml
permissions:
  contents: read
  # plus the OIDC token permission (id-token) set to write,
  # so this job can mint short-lived cloud tokens
```

```bash
grep -n -A6 "permissions:" .github/workflows/deploy.yml
```

Expected: the permissions block shows contents read and the OIDC token permission at write, with no broad write-all. Reusable workflows called by this job need the permission too.

### 3. Assume the role with no stored keys

```yaml
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::[account-id]:role/[role-name]
    aws-region: [region]
```

```bash
grep -rn "aws-access-key-id" .github/workflows/ || echo "no static keys found"
```

Expected: "no static keys found". The action exchanges the OIDC token for temporary role credentials. There is no access key in secrets, in the workflow file, or in logs.

### 4. Scope the trust to repo and branch

The subject claim is where the real security lives. Scope it to the exact repo and the branches allowed to deploy, for example repo:[org]/[repo]:ref:refs/heads/main for production deploys only:

```bash
aws iam get-role --role-name [role-name] --query 'Role.AssumeRolePolicyDocument.Statement[0].Condition'
```

Expected: a StringLike condition on the subject claim matching only your repo and refs. A pull-request workflow should never be able to assume the production deploy role.

### 5. Delete the old static keys

```bash
gh secret list --repo [org]/[repo]
```

Expected: no cloud credential secrets remain. Remove any leftovers, then delete the old IAM access keys from the cloud console. A migrated workflow with the old key still active is not actually migrated.

### Variant: GCP workload identity federation

Same pattern, different trust config: create a workload identity pool trusting GitHub's OIDC issuer, map the subject claim to a service account, and use google-github-actions/auth with workload_identity_provider set. No service account keys anywhere.

### Variant: environment-scoped deploys

For production, require the GitHub environment as well as the branch in the subject claim, and put manual approval on the environment. The token then only exists for approved production runs.

### Variant: what breaks

Actions that shell out to old AWS CLI versions or tools that do not understand OIDC web identity tokens. Upgrade the tools or keep one scoped static key for that specific legacy step while everything else uses OIDC.

## Why this happens

Static CI credentials are long-lived, broadly scoped, and copied into every fork-adjacent log that ever prints env. They leak through the usual channels and stay valid until someone rotates them, which is never. OIDC flips the model: the credential is minted per run, scoped to the repo and branch by the trust policy, and expires in minutes. There is nothing to leak and nothing to rotate.

## Edge cases and pitfalls

- The OIDC token lives only for the job run. Long-running jobs that need fresh credentials must re-authenticate.
- The trust's audience must match what GitHub sends (sts.amazonaws.com for AWS). A mismatch fails silently at assume-role time.
- Fork PRs get a restricted OIDC token by design. Do not loosen the trust to make forks deploy.
- Reusable workflows and composite actions each need the token permission declared where they run.
- The role ARN is not secret, but a loosely scoped trust policy is a hole. Scope first, test, then delete static keys last.

Tool notes: works with GitHub Actions on github.com and GHES 3.4+. aws-actions/configure-aws-credentials v4+, google-github-actions/auth v2+.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_5ZzQiMzSJdvl9hvis52gKQ
