Whisper · Docs
Containers & cloud

Kubernetes

Every pod in your cluster egresses from a shared NAT gateway, so "which pod called that host" is a question your firewall cannot answer.

The Whisper operator gives each opted-in pod its own routable /128 identity and a governed door out, injected by a webhook you never have to touch by hand.

The pain

A Kubernetes Service and a NetworkPolicy are great at describing east-west traffic — pod to pod, namespace to namespace. They have nothing to say about north-south traffic once a pod calls out to the internet. In the default setup:

The fix isn't a bigger NAT gateway. It's giving each agent workload — not each node, not the whole cluster — its own address, so the identity is the address and the address is in the DNS. That's what the whisper-operator does for pods, the same way whisper init <tool> does it for a local process (see Connect) and the WireGuard recipe does it for a VM (see WireGuard egress).

What gets installed

whisper-operator is a standard controller-runtime (kubebuilder-layout) operator with one job: watch pod creation and, for pods that opted in, mutate the pod spec before it's scheduled. Concretely, on helm install you get:

None of this is magic outside Kubernetes' own primitives: it's a webhook, a sidecar, and a Secret. The value is that you get a routable, attributable, RFC-correct IPv6 identity per pod without writing any of that plumbing yourself.

Opt-in, not opt-out

Nothing is mutated unless you ask for it — label the namespace or the pod:

apiVersion: v1
kind: Namespace
metadata:
  name: agents
  labels:
    whisper.security/egress: enabled     # every pod in this namespace gets a sidecar

or, per-pod, inside a namespace that isn't globally opted in:

apiVersion: v1
kind: Pod
metadata:
  name: research-agent
  labels:
    whisper.security/egress: enabled
  annotations:
    whisper.security/agent: acef2002a323d40d4    # pin to an existing identity (optional)
spec:
  containers:
    - name: agent
      image: your-org/research-agent:latest

The webhook fires on CREATE, sees the label, and rewrites the pod: adds the whisper-egress sidecar container with all Linux capabilities dropped (securityContext.capabilities.drop: ["ALL"], no NET_ADMIN, no hostNetwork), reads the API key from a secretKeyRef you provide once per namespace, and sets HTTPS_PROXY/ALL_PROXY on the application container to point at the sidecar's loopback SOCKS5 port. The application container needs no changes, no root, and no awareness that Whisper exists beyond honoring the standard proxy env vars — which every HTTP client already does.

kubectl create secret generic whisper -n agents \
  --from-literal=api-key=whisper_live_xxxxxxxxxxxxxxxxxxxx

Install

helm install whisper-operator oci://ghcr.io/whisper-sec/charts/whisper-operator \
  --namespace whisper-system --create-namespace

That's the whole install — one chart, no CRDs to apply separately (Helm manages the optional WhisperEgress CRD for you), no cert-manager, no extra RBAC to hand-author. Source: github.com/whisper-sec/whisper-operator (MIT); the image is ghcr.io/whisper-sec/whisper-operator, published multi-arch (amd64/arm64) alongside a Helm chart at oci://ghcr.io/whisper-sec/charts/whisper-operator — same publishing pipeline as the standalone Docker image.

Proving it: without Whisper vs. with Whisper

The point of a per-pod identity is that it's verifiable from outside the cluster with nothing but stock tools — the pod's egress address resolves, reverse-resolves, and carries RDAP like any other internet host.

Prove the egress /128 — from inside the pod, ask what the world sees:

kubectl exec research-agent -n agents -- curl -s https://v6.ident.me
# 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4  — the agent's own /128, not a node IP

That address reverse-resolves, carries RDAP, and is DANE-pinned like any Whisper agent — verify it from anywhere, keyless, with the full chain on Verify an agent.

With Whisper (once the sidecar is running, from your own machine, no cluster access needed):

whisper verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
agent            yes
fqdn             acef2002a323d40d4.<tenant>.agents.whisper.online
operator         t9f3a1c2e7b4…
dane_ok          yes
jws_ok           yes
dane_tlsa_sha256 b653a4ef…fcb82d1d
whisper: acef2002a323d40d4.<tenant>.agents.whisper.online is a verified Whisper agent (DANE-anchored)

The /128 sits in AS219419's 2a04:2a01::/32, DANE binds the certificate to the name, and the DNSSEC chain grades all the way to the IANA root — the exact same table you'd get for any agent identity, cluster or not.

And to see the pod's own attributable log trail — every DNS lookup and connection it made, keyed to its identity, not its node's shared IP:

whisper agents logs --agent acef2002a323d40d4 --since 1h

which is the same whisper.agents({op:'logs'}) control verb documented in Control plane — the operator doesn't invent a parallel API, it just wires the identity that verb already understands into the pod lifecycle.

Why a webhook and not a CNI plugin

A CNI plugin operates below the pod, at the network-interface level, and would need to run privileged on every node — exactly the blast radius this design avoids. A mutating webhook operates at admission time, before the pod ever exists, so the sidecar it injects is a normal, unprivileged container subject to the same PodSecurityStandard as everything else in the namespace. The tradeoff is explicit: the operator can only shape pods it's told about (label opt-in), and it can't retrofit already-running pods — restart to pick up a new policy. That's the same tradeoff every mutating-webhook-based sidecar injector makes (Istio's istio-injection label works identically), and it's the one that keeps NET_ADMIN out of your cluster.

Proven end-to-end

The operator is validated the same way every Whisper integration is (see Integration standard): a real kind cluster, a real opted-in namespace, a real pod whose egress is checked from outside with curl https://v6.ident.me, dig -x, and a live RDAP lookup — never a structural "does the chart install" pass. If your workload can run in a pod, it can carry its own routable, revocable, logged identity without ever holding a network capability.

Next