Skip to content
OIDC Issuers

OIDC Issuers

This section covers how to configure github-sts to validate OIDC tokens from identity providers. The verifier requires every token to:

  1. Have an iss claim that is in oidc.allowed_issuers.
  2. Carry a kid in the JWT header that matches a key in the issuer’s JWKS.
  3. Resolve a jwks_uri (via OIDC discovery) whose host either matches the issuer host or is opted in via oidc.trusted_jwks_hosts.
  4. Carry an aud claim that matches the policy’s audience: field (mandatory in every policy) and, if set, the server-wide oidc.required_audience.

Audience pitfall. Most OIDC libraries default aud to something useful only to the issuer (e.g., GitHub Actions defaults to the workflow repo URL). That default is not safe for an STS: every workflow in the org would be able to redeem its tokens here. Always pass an explicit audience that identifies this STS deployment, e.g. core.getIDToken('https://sts.example.com') for actions/github-script, and set the same value in the policy’s audience: field.

Per-provider setup

How to discover values for any issuer

Two commands, no tooling beyond curl and jq:

ISSUER="<the issuer URL>"

# 1. Print the discovery doc (sanity check).
curl -fsS "${ISSUER}/.well-known/openid-configuration" | jq .

# 2. Extract just the jwks_uri host.
curl -fsS "${ISSUER}/.well-known/openid-configuration" \
  | jq -r '.jwks_uri | sub("^https?://"; "") | sub("/.*$"; "")'

If the printed host does not equal the issuer’s host, that issuer needs an oidc.trusted_jwks_hosts entry.

Combined example: both providers

oidc:
  allowed_issuers:
    - https://token.actions.githubusercontent.com
    - https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0

Validating your config end-to-end

# 1. Mint a token from your CI provider.
# 2. Decode the token header to confirm 'kid' is set.
echo "$TOKEN" | cut -d. -f1 | base64 -d 2>/dev/null | jq .
# 3. Confirm 'iss' matches an entry in allowed_issuers.
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .iss

Failure modes to expect:

Symptom in logsCauseFix
issuer "X" not in allowed listiss not in allowed_issuersAdd the issuer URL exactly as it appears in the token.
token header missing kidToken has no kidIssuer is misconfigured; this service rejects kid-less tokens.
key "X" not found in JWKSIssuer rotated keysJWKS cache is 1h TTL: wait or restart pods.
jwks_uri host "X" is not the issuer host and not in the trusted JWKS host overrideIssuer’s JWKS is on a different hostAdd the host to trusted_jwks_hosts.
audience mismatch (server required_audience)Token’s aud does not contain oidc.required_audiencePass the right audience to core.getIDToken('<value>').
audience check failed (per-policy)Token’s aud does not match the policy’s audience: fieldSet audience: in .sts.yaml to match the workflow.
github_identity_invalidGitHub identity claims are missing, have wrong types, disagree, or use a legacy subject while immutable format is requiredOpt the repository in to immutable subjects, verify owner/repository IDs, or use the visible legacy opt-out temporarily. Correlate trace_id for the finite reason.

Maintenance: detecting JWKS host drift

Cloud providers occasionally change JWKS hosts. Run this check on a schedule and alert on diff: do not auto-update.

#!/usr/bin/env bash
# scripts/check-jwks-drift.sh
set -euo pipefail

VALUES="${1:-values.yaml}"

for iss in $(yq '.oidc.allowed_issuers[]' "$VALUES"); do
  iss_host=$(echo "$iss" | sed -E 's#^https?://([^/]+).*#\1#')
  observed=$(curl -fsS "${iss}/.well-known/openid-configuration" \
    | jq -r '.jwks_uri | sub("^https?://"; "") | sub("/.*$"; "")') || {
      echo "FAIL: discovery for $iss did not respond"
      continue
    }

  if [ "$observed" = "$iss_host" ]; then
    continue
  fi

  pinned=$(yq ".oidc.trusted_jwks_hosts[\"$iss\"][]" "$VALUES" 2>/dev/null || true)
  if ! grep -qx "$observed" <<< "$pinned"; then
    echo "DRIFT: $iss now publishes JWKS at $observed (not in trusted_jwks_hosts)"
  fi
done

Wire it into a weekly CI job; treat any DRIFT line as a security review item.