Waterwheel turns a pod's identity into the API credentials it needs, so the
app does not need a long-lived API key. A sidecar exchanges the pod's
Kubernetes service account token for a short-lived provider token (Anthropic,
OpenAI, Google Cloud, Azure, RFC 7523, or a JSON-compatible RFC 8693 endpoint)
and adds it to the app's HTTPS requests. With SPIRE, it exchanges a JWT-SVID
instead, for the pod's WorkloadIdentity.
waterwheel-controller: serves each sidecar its bindings and injects the sidecar.waterwheel-proxy: the per-pod CONNECT proxy that adds the credentials.waterwheel-agent: Waterwheel's own SPIFFE agent, a later identity source; not deployed by default.
Status: early preview. The controller, proxy, chart, and a kind end-to-end demo are implemented and released as previews, with no production support claim yet. See the design and the milestone notes for scope and known limits.
This uses a fake token endpoint and API, so it needs no provider account or API
key. It creates a local kind cluster named waterwheel and verifies that the
same request fails without the proxy and succeeds after injection.
Prerequisites: Go 1.26 or newer, a running Docker daemon, kind, kubectl, Helm, and jq. On macOS or Linux:
git clone https://github.com/riptideslabs/waterwheel.git
cd waterwheel
LIMA_VM= make e2eThe last line should say all five steps passed (provider: fake, identity: serviceAccountToken). The demo sets automountServiceAccountToken: false and
checks that the app container has no mounted service account token. To run the
SPIRE variant, use LIMA_VM= E2E_IDENTITY=spire make e2e. To remove the demo
cluster, run LIMA_VM= make down.
For a real provider, its federation rule must trust the cluster's issuer and
signing keys; docs/oidc.md explains that setup. The demo's
E2E_PROVIDER=anthropic mode additionally needs Anthropic configuration; see
docs/milestone-1.md.
Each release publishes the chart and its images, for amd64 and arm64, to GHCR:
helm install waterwheel oci://ghcr.io/riptideslabs/charts/waterwheel --version 0.1.0 \
--namespace waterwheel-system --create-namespacedocs/getting-started.md goes on from there to an app that calls Anthropic with no key: the provider's trust in the cluster, the objects, the namespace label, and upgrades. Images and charts are signed; see docs/releasing.md.
Waterwheel mounts its projected identity tokens and optional SPIRE socket only
into the sidecar. It leaves Kubernetes' default service account token mount
unchanged. Set automountServiceAccountToken: false on the workload if the app
must have no default token mount; the demo does this. The sidecar does hold
a projected token (or obtain a JWT-SVID), keeps the exchanged provider token
in memory, and holds a per-pod TLS CA key. Someone who compromises the app can
still use its permitted API access through the proxy; Waterwheel reduces
long-lived key exposure, not misuse of an authorized workload. Pods sharing a
service account share its bindings unless SPIRE selectors distinguish them.
Today, intercepted destinations use HTTP/1.1; gRPC to a bound host is not
supported. A binding that needs a new token audience requires a pod restart.
Java apps don't pick up the sidecar's CA yet; make e2e-trust checks the CA
bundle in Debian, Ubuntu, Fedora, UBI, Amazon Linux, SUSE and distroless images.
A container that already sets SSL_CERT_FILE (or a similar variable) or mounts
its own CA bundle keeps it, so it won't trust the proxy.
See the design for details and
SECURITY.md for private vulnerability reports.
make dev # Lima VM waterwheel-dev, kind cluster waterwheel, images, Helm install
make generate # deep copies and CRDs after changing api/v1alpha1
make lint test
make e2e # the KCD demo's five steps on a fresh cluster, checked
make demo # the same steps, waiting for a key before each command
make site # the project page (site/), at http://localhost:4321make e2e runs against an offline fake provider. E2E_PROVIDER=anthropic
uses the real API with the IDs in a gitignored providers.env; see
docs/milestone-1.md. E2E_IDENTITY=spire installs
SPIRE and runs the same steps with JWT-SVIDs.
LIMA_VM= make dev uses Docker and kind on the local machine instead of Lima.
Behind a TLS-intercepting proxy (like a Riptides workstation with MITM on),
pass its CA, so that the VM's Docker and the sidecars trust it:
EXTRA_ROOTS_FILE="$HOME/Library/Application Support/riptides/mitm-ca.pem" make e2e
(or make dev).
kubectl uses .state/kubeconfig.
Apache-2.0.