Skip to main content

No More AI API Keys in Env Vars: Reproducing Render's Short-Lived Anthropic and OpenAI Credentials on Your Own Kubernetes

9 min readDora NodaDora Noda
Share
On this page

Your AI API key is the worst secret in your environment variables. A leaked sk-ant- or sk- key is a blank check against your model budget until someone notices, rotates it, and redeploys every service that carried it. Three releases this summer are converging on the fix — kill the long-lived key entirely and mint short-lived credentials from platform identity at runtime:

  • June 17, 2026 — Anthropic made Workload Identity Federation (WIF) generally available on the Claude Platform: exchange an OIDC token from an identity provider you already run for short-lived API access, with per-workload Service Accounts carrying their own rate limits and audit trail.
  • May–June 2026 — OpenAI documented workload identity federation for GitHub Actions, Google Cloud, and Kubernetes: exchange an external OIDC JWT at auth.openai.com for an opaque access token that lives at most one hour.
  • July 24, 2026 — Render's changelog extended Managed OIDC beyond AWS to Anthropic and OpenAI: a deployed Render service authenticates to both model providers with automatically rotated short-lived tokens, no stored API key required.

The notable part is the trust direction. Model providers now accept third-party OIDC issuers the way AWS IAM has for years — your platform signs an identity token, the provider verifies it and mints access. This post works out both sides of that exchange concretely: exactly what Render's managed flow does for you, and the five-step recipe plus honest gap list for standing in the same trust position on Kubernetes machines you own.

How Render's managed flow actually works

Render operates one OIDC issuer per workspace at https://oidc.render.com/{WORKSPACE_ID}, mints a short-lived identity token for each service, and rotates it automatically. Your services never see a provider API key. The per-provider wiring differs only in the registration dance and the env vars:

ProviderRegister Render asAudienceEnv vars you setToken file Render injects
AnthropicCustom OIDC provider (Issuer tab) + federation Rule with expected audience api.anthropic.comapi.anthropic.comANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_IDANTHROPIC_IDENTITY_TOKEN_FILE
OpenAIWorkload Identity Provider (Org → Security tab), audience api.openai.com, plus a service-account mappingapi.openai.comOPENAI_IDENTITY_PROVIDER_ID, OPENAI_SERVICE_ACCOUNT_IDOPENAI_IDENTITY_TOKEN_FILE
AWS (pre-existing)IAM OIDC provider + role trust policy on oidc.render.com/{WORKSPACE_ID}:substs.amazonaws.comRole ARNAWS_WEB_IDENTITY_TOKEN_FILE

The pattern is identical in all three rows: during deploy, Render detects the marker env var and injects a second one pointing at the service's OIDC credential file. Application code reads the token from that path — the Anthropic SDK picks the env vars up with zero code changes (new Anthropic() just works), while the OpenAI client takes a small subject-token provider that reads the mounted file. Each service's token carries a sub claim identifying exactly which Render service is requesting access, so the provider side can scope rules per service.

Two caveats from Render's docs are worth knowing before you treat this as magic. Managed OIDC requires a Pro workspace or higher, and OIDC credentials are not available at build time for services that build from a Dockerfile (native runtimes get them at build time). Neither caveat matters much for the headline use case — a running web service or worker calling a model API — but both are the kind of platform-imposed boundary that disappears when you operate the issuer yourself.

The trust flip: model providers now do what AWS IAM has done for years

None of the cryptography here is new. AssumeRoleWithWebIdentity has let Kubernetes pods trade a projected service-account token for AWS credentials since 2019, and GitHub Actions runners have minted cloud credentials from OIDC without stored secrets for nearly as long. What changed in 2026 is on the model-provider side: Anthropic and OpenAI both stood up the other half of the exchange.

The shape is the same at both providers. First, a provider registry: you register your platform's OIDC issuer URL (Anthropic calls it an Issuer under Workload Identity Federation settings; OpenAI calls it a Workload Identity Provider under Organization Security). Second, a mapping layer: federation rules (Anthropic) or service-account mappings (OpenAI) decide which incoming identities may mint tokens, matched on claims like sub and aud, with OpenAI additionally allowing permission narrowing such as api.model.request. Third, a minting endpoint: the provider validates the incoming JWT's issuer, signature, audience, and expiration, then returns a short-lived provider access token — minutes-long on Anthropic's side, at most one hour on OpenAI's.

That third step is the whole security argument. A token that expires in minutes bounds the blast radius of every leak — logs, error trackers, a compromised replica — to a window, not a budget. There is nothing to rotate on a schedule and nothing standing by to revoke in an incident, because no long-lived credential exists anywhere in the loop. Static AI keys become the exception you keep for quick scripts, not the default your production fleet runs on.

The self-hosted recipe: five steps to the same keyless posture

Here is the part Render's docs never need to say: everything in the table above is reproducible on any modern Kubernetes cluster, because the cluster's API server is already an OIDC issuer. Projected service-account tokens are OIDC-compatible JWTs by default, and both providers document accepting a Kubernetes issuer directly. The recipe:

Step 1 — Project a token into the pod with the provider's audience. Legacy service-account tokens stored in Secrets are explicitly unsupported by OpenAI's federation; you need a projected volume. The audience must match what you configure provider-side:

yaml
serviceAccountName: openai-wif
volumes:
  - name: ksa-token
    projected:
      sources:
        - serviceAccountToken:
            path: token
            audience: "https://api.openai.com/v1"
            expirationSeconds: 3600

Mount that volume at something like /var/run/secrets/tokens and each pod gets a JWT minted for that audience, refreshed by the kubelet before expiry.

Step 2 — Read your cluster's issuer URL. One command:

bash
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer

On a stock cluster this is https://kubernetes.default.svc unless you set --service-account-issuer to something else. Whatever it returns must byte-match the iss claim in the projected token and the issuer you register provider-side.

Step 3 — Register the issuer with each provider. In Anthropic's console this is a Custom OIDC provider with your issuer URL plus a federation rule carrying the expected audience; in OpenAI's console it is a Workload Identity Provider with the OIDC issuer URL and the same opaque audience string from Step 1. This is the exact screen where Render's https://oidc.render.com/{WORKSPACE_ID} goes for managed users — you are substituting your cluster's issuer for theirs.

Step 4 — Upload the JWKS (the self-hosted gotcha). Here the managed and self-hosted paths diverge. Render's issuer is a public HTTPS URL supporting OIDC Discovery, so providers fetch its signing keys themselves. Your cluster's issuer is not publicly reachable, so OpenAI supports only local JWKS mode for self-hosted clusters: you paste the output of kubectl get --raw /openid/v1/jwks into the provider config, and OpenAI verifies tokens against those keys without ever calling your discovery endpoint. It still compares the configured issuer against the token's iss — but key rotation is now your chore. When the cluster rotates its service-account signing keys, tokens signed by the new key are rejected until you re-upload the JWKS. Automate this upload or calendar it; it is the single most likely cause of a mysterious midnight auth failure in this setup.

Step 5 — Map claims to provider identities and narrow permissions. On OpenAI, add a service-account mapping matching the token's subject — system:serviceaccount:<namespace>:<service-account-name> — and select permissions like api.model.request to scope what minted tokens can do. On Anthropic, the federation rule plays the same role. Then wire the SDK: the provider reads the mounted token file and exchanges it per request lifecycle. Before you blame the provider for a rejection, decode one projected token locally and compare its iss, aud, and sub against your registration — OpenAI's own guide notes most configuration issues are visible in those three claims before any exchange is attempted.

That is everything, and no new infrastructure was purchased: the issuer is your API server, the token delivery is a kubelet-managed volume, and the exchange endpoint belongs to the provider.

The honest gap list: what Render still operates for you

"Reproducible" is not "free." Running your own issuer moves real operational load onto your platform team. Side by side:

ChoreRender managedSelf-hosted issuer
Issuer endpointPublic URL with OIDC Discovery; providers fetch keys themselvesCluster-internal issuer; JWKS upload per provider, re-upload on key rotation
Token rotationAutomatic per serviceKubelet refreshes projected volumes automatically — but audience/expiry are your YAML's job
Per-service identityDistinct sub per Render service out of the boxOne ServiceAccount per app (or namespace) by your own convention; sloppy sharing collapses your scoping
Claim mappingSame provider screens either waySame provider screens either way — mapping discipline is on you in both
Issuer stabilityRender's URL never changesCluster rebuilds, issuer-flag changes, and multi-cluster fleets each mean re-registration
Build-time availabilityMissing for Dockerfile buildsWhatever your build pods project — your CI's design decision

Two rows deserve emphasis. First, per-service identity: Render hands every service a distinct subject claim with no design effort, while on your cluster the granularity of system:serviceaccount:ns:name mapping is only as good as your ServiceAccount hygiene. Sharing one ServiceAccount across apps to save YAML-writing quietly turns per-app federation rules into fleet-wide ones. Second, the fleet question: one cluster means one issuer registration per provider, but a multi-cluster fleet means N issuers × M providers of registrations and JWKS uploads, which is exactly the toil a platform layer should automate rather than document.

Keyless is the end state; the issuer is the only new thing you run

Step back and the 2026 story is simple: static model API keys had a good run as the default, and both major providers have now built the off-ramp. The exchange protocol is the same OIDC flow operators already trust for cloud credentials, the SDK support is a mounted-file read away, and the only net-new component a self-hosted platform takes on is operating its cluster issuer as a registered identity — audience discipline, JWKS rotation hygiene, and per-app ServiceAccounts.

That is a small, bounded chore next to what it retires: no key generation runbooks, no rotation calendars, no leaked-key incident response against your model budget. The one Render feature AI-heavy teams would actually miss on the way out the door turns out to be reproducible on machines you own, in five steps, with parts Kubernetes already ships.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex