# oidcfederation.cloudraker.com

> An explainer by CloudRaker: why stored cloud keys keep leaking from platforms, how OIDC workload identity federation replaces them with short-lived signed proofs, and an open-source issuer for Cloudflare Workers.

## The problem

A static cloud credential (an AWS access key, a GCP service-account key, a database password) is a copy. To use it you copy it to every place that needs it: a laptop, CI, the platform's environment variables, a backup. Every copy grants the full access of the key, from anywhere, until someone rotates it.

Hosting platforms store customer secrets as environment variables. When one of them is breached, every customer has to find and rotate every key it held, then redeploy, while the attacker may already be using them.

## How OIDC workload identity federation works

1. The workload asks a trusted issuer for a token over a private channel. Its identity (`sub`) and allowed audiences (`aud`) were fixed at deploy time.
2. The issuer signs a short-lived JWT (RS256, `iss`, `sub`, `aud`, `iat`, `nbf`, `exp`, `jti`; 300 s by default, 3600 s at most).
3. The workload sends the token to the cloud: AWS `AssumeRoleWithWebIdentity`, GCP Workload Identity Federation, Azure federated credentials, Vault JWT auth, the Claude API (`/v1/oauth/token`, jwt-bearer) or the OpenAI API (`auth.openai.com/oauth/token`, token exchange).
4. The cloud fetches the issuer's public keys from `/.well-known/jwks.json` and checks the signature and claims against a trust policy that pins `iss`, `sub` and `aud`.
5. The cloud returns temporary credentials scoped to one role, valid for the session length the role allows (typically 15 minutes to 1 hour).

Nothing long-lived is stored in the workload. A leaked token is valid for minutes, for one identity, for one audience. GitHub Actions, GitLab CI, Kubernetes, Vercel, Buildkite, CircleCI and HCP Terraform already issue such tokens. Cloudflare Workers has no built-in identity token.

## What federation does not fix

A workload compromised at runtime can still mint tokens as itself, for its own audiences, for the token lifetime. The issuer's signing key is the new sensitive asset: it must rotate, and the issuer must be small and hardened.

## workers-oidc-issuer

Open-source (MIT) issuer for Cloudflare Workers: https://github.com/CloudRaker/workers-oidc-issuer

- Consumer Workers declare `sub` and `aud` in `wrangler.jsonc` as `props` on a service binding. Only a deployer can set them. The signer reads `ctx.props`.
- Signing is an RPC entrypoint reachable only over a service binding. Public surface: `GET /.well-known/openid-configuration` and `GET /.well-known/jwks.json`.
- Keys live in Cloudflare Secrets Store. Manual mode: generated on your machine, rotated with Add → Promote → Remove in the CLI. Auto mode: generated inside a Durable Object and rotated on a cron; private keys never land in Durable Object storage.
- Zero runtime dependencies in the signer, `no_nodejs_compat`, `disable_ctx_exports`, non-extractable keys, TTL ceiling, 156 tests, a written threat model.
- Guides for AWS, GCP, Azure, Vault, the Claude API and OpenAI in the repo's `docs/` folder, with example consumers for AWS, Claude and OpenAI.

Quick start:

    git clone https://github.com/CloudRaker/workers-oidc-issuer
    cd workers-oidc-issuer && pnpm install
    pnpm --filter @workers-oidc-issuer/worker exec wrangler login   # skip if logged in
    pnpm --filter workers-oidc-issuer build
    node packages/cli/dist/bin.js

Consumer binding:

    "services": [{ "binding": "OIDC", "service": "oidc-issuer", "entrypoint": "Issuer",
                   "props": { "sub": "billing-worker", "aud": ["sts.amazonaws.com"] } }]

    const { token } = await env.OIDC.getToken({ aud: "sts.amazonaws.com" })

CloudRaker: https://cloudraker.com
