Skip to content

feat: OIDC federation and secret management #27

@ms280690

Description

@ms280690

Context

Track: B — Org/infrastructure governance
Pillar: 3 — Modern Secret Management & Identity Federation
Parent: (see GitHub Actions platform security parent)

Why static credentials are the wrong model

A static AWS access key stored as a GitHub secret is valid until someone manually rotates it. If it leaks — via a log, a compromised runner, or a supply chain attack — it remains exploitable until noticed. OIDC eliminates the credential entirely: the workflow exchanges its GitHub-signed identity token for a short-lived cloud token scoped to the exact job.

What we already have (partial coverage)

This issue extends the same pattern to cloud provider authentication.

OIDC for cloud providers

AWS

jobs:
  deploy:
    permissions:
      id-token: write   # required for OIDC
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502  # v4
        with:
          role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/github-actions-deploy
          aws-region: ca-central-1
          role-session-name: ${{ github.repository }}-${{ github.run_id }}

AWS trust policy — bind to specific repo and branch, not the whole org:

{
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
      "token.actions.githubusercontent.com:sub": "repo:sparkgeo/prescient-platform:ref:refs/heads/main"
    }
  }
}

Custom property claims (org-level fine-graining)

GitHub org custom properties can be included in the OIDC token, enabling trust policies that filter on org metadata rather than repo name lists:

{
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:environment": "production"
    }
  }
}

This means a production deployment role is only assumable from a workflow running against a production GitHub environment — not from any arbitrary branch or fork.

Environment-scoped secrets

Replace broadly inherited org/repo secrets with environment-scoped secrets:

  • Org secrets: only for non-sensitive shared config (AWS_ACCOUNT_ID, SONAR_HOST_URL)
  • Environment secrets (production, staging): sensitive values scoped to their environment — a staging workflow cannot access production credentials
  • Reusable workflow secrets: must be explicitly passed by the caller; they no longer inherit automatically (GitHub policy change)
# Caller must explicitly pass secrets to reusable workflow
jobs:
  deploy:
    uses: sparkgeo/github-actions/.github/workflows/deploy.yml@main
    secrets:
      AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}   # explicit, not implicit

Migration checklist

  • Audit all org and repo secrets — identify static long-lived credentials (AWS keys, GCP service account JSON, deploy tokens)
  • Replace AWS access keys with OIDC role assumption — one role per environment, scoped to specific repos/branches
  • Replace GCP service account keys with Workload Identity Federation
  • Configure production and staging GitHub environments with environment protection rules (required reviewers, deployment branches)
  • Move sensitive secrets from org/repo level to environment level
  • Validate all reusable workflows explicitly pass required secrets rather than relying on inheritance
  • Document approved OIDC role ARNs in docs/oidc-trust-policies.md in this repo

Cross-references

Metadata

Metadata

Assignees

Labels

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions