On 30 October, Zsolt Varga and I gave a talk at KCD Budapest called Every Workload Deserves an Identity Instead of an API Key. In the live demo, a pod called the Claude API without ever holding a key. SPIRE gave the pod an identity, tokenex exchanged that identity for a short-lived Anthropic token, and an egress proxy sidecar added the token to the request on its way out.
Today we’re releasing Waterwheel, the project that grew out of that demo. It’s open source under Apache-2.0.
Why not just an API key?
An API key in a pod can be read by anyone who gets into the pod. It sits in an environment variable or a Secret, where every process, every kubectl exec and every leaked backup can reach it, and it stays valid until someone rotates it.
Anthropic, OpenAI and the cloud providers now support workload identity federation instead. The workload proves who it is with a signed token and gets a short-lived credential back. Every pod already has such a token: Kubernetes signs one for its service account.
What Waterwheel does
Waterwheel injects a small proxy sidecar into the pods you opt in. The sidecar exchanges the pod’s identity for a short-lived provider token and adds it to the app’s HTTPS requests to that provider. The app sends the same requests as before, just without a key.
- Providers: Anthropic, OpenAI, Google Cloud and Azure, plus RFC 7523 token endpoints and RFC 8693 endpoints that accept JSON.
- Configuration: a
CredentialSourcedescribes a provider, and aCredentialBindingsays which pods get its credentials. A controller serves each sidecar its bindings.
Kubernetes service accounts or SPIFFE
Waterwheel can prove a pod’s identity in two ways:
- Kubernetes service account tokens, the default. Kubernetes signs a short-lived token for the pod’s service account, with the provider as its audience, and the provider trusts the cluster’s issuer. The pod’s identity is
system:serviceaccount:<namespace>:<name>, and you don’t need to install anything else. - SPIFFE, with SPIRE. The sidecar gets a JWT-SVID from SPIRE for the pod’s SPIFFE ID, and the provider trusts SPIRE’s issuer. A
WorkloadIdentitymaps pods to SPIFFE IDs by selectors such as their labels, so pods that share a service account can still get different credentials. This is how the KCD demo worked.
The talk’s five demo steps now run as Waterwheel’s end-to-end test, with both identity sources.
Try it
Waterwheel is an early preview, with no tagged release yet. The local demo uses a fake provider, so you don’t need an account with any of them. With Go 1.26 or newer, Docker, kind, kubectl, Helm and jq installed, run:
git clone https://github.com/riptideslabs/waterwheel.git
cd waterwheel
LIMA_VM= make e2e
It creates a kind cluster named waterwheel and checks that the same request fails without the sidecar and succeeds with it. Add E2E_IDENTITY=spire to run it with SPIRE instead of service account tokens.
Waterwheel takes long-lived keys out of the pod. It doesn’t stop a compromised app from using the access the pod is allowed through the proxy. The README lists the current limits.
Links
Issues and feedback are welcome on GitHub.
How exposed are your workloads?
Run the NHI Security Audit Checklist, 8 questions to map your credential exposure, attribution gaps, and lateral movement surface across your own environment. Takes about 15 minutes.
Run the ChecklistFollow us on LinkedIn and X for more updates.