1. Routing layer
divert and mirror use caller-side Istio routing. ingress: adds Gateway API routes. The inbound modes instead patch the target sidecar with a scoped EnvoyFilter.
outprobe puts a small, generic relay at a routing point the developer controls. Candidate requests reach the relay; the relay decides whether one belongs to a connected developer; the laptop is reached over connections that the laptop opened first.
An unmatched request takes a second path from the relay to the real target service. In mirror modes, the original request already stays on the real path and only a copy reaches the relay.
Istio VirtualService and Gateway API HTTPRoute can match raw HTTP headers. They cannot select a request by a claim inside a JWT. Projecting the claim into a header requires gateway or proxy configuration that often belongs to another team.
The relay solves that last mile:
email, or reads a plain header;1. Routing layer
divert and mirror use caller-side Istio routing. ingress: adds Gateway API routes. The inbound modes instead patch the target sidecar with a scoped EnvoyFilter.
2. Relay
A Go reverse proxy in the target namespace. It owns no developer match configuration; rules arrive over tunnel registrations.
3. Laptop CLI
Applies objects, starts a client-go port-forward, maintains workers, forwards requests to localTarget, and cleans up on Ctrl-C.
4. Reverse tunnel
A pool of persistent TCP connections from the laptop to the relay. No cluster component needs direct access to the laptop.
VirtualService.outprobe-relay Service.localTarget, reads the response, and sends a deterministically framed response back.x-outprobe-bypass: 1 and proxies to the real target Service. The bypass route prevents a loop.A feed: block can narrow step 1 to path or header conditions. The per-developer rule is still evaluated at step 3.
Each of the CLI’s 32 workers opens a connection through an ephemeral local port-forward and starts with one line:
OUTPROBE {"type":"jwt","key":"email","value":"you@example.com"}The relay groups workers with the same rule into a pool. A worker carries one HTTP request and response at a time, then returns to the idle pool. Dead workers reconnect automatically; a client’s registration disappears when its final worker disconnects.
The intercept path uses HTTP/1 request and response serialization. The CLI buffers request and response bodies and writes a deterministic Content-Length, allowing each connection to be reused safely.
Egress uses a separate handshake and then splices raw TCP bytes:
OUTPROBE-DIAL pricing.my-ns.svc.cluster.local:8080Every client registers a complete rule: type, property, key, and value. Developers can therefore use different claim namespaces or even different match types at the same time.
For each request, the relay checks connected client rules in registration order. The first matching rule wins if rules overlap. An absent or disconnected identity simply does not match, so its traffic follows the real path.
outprobe up createsDepending on configuration and mode, the CLI manages these namespace-scoped objects:
| Object | Purpose | When |
|---|---|---|
Deployment outprobe-relay |
Runs the relay image. | Always |
Service outprobe-relay |
Receives intercepted HTTP traffic on port 8080. | Always |
Istio VirtualService |
Diverts or mirrors meshed caller traffic. | divert, mirror |
Gateway API HTTPRoute |
Handles selected browser/ingress routes. | ingress.routes configured |
Istio EnvoyFilter |
Patches target-side inbound routing. | inbound-divert, inbound-mirror |
Every object carries outprobe.dev/tool=outprobe, which is how status, down, and Ctrl-C teardown find it.
Startup deploys and waits for the relay, opens the port-forward, starts workers, and only then applies routing. Teardown does the reverse where safety matters: HTTPRoutes, EnvoyFilters, and VirtualServices are deleted before the relay Deployment and Service.
This ordering avoids pointing live traffic at a relay that is not ready or has already disappeared.
cmd/relay/ in-cluster reverse proxy and tunnel servercmd/outprobe/ laptop CLI entrypointinternal/cmds/ init, up, down, status, routesinternal/kube/ kubectl-backed object managementinternal/portforward/ client-go port-forwardinternal/match/ JWT claim and header extractioninternal/rules/ .outprobe.yaml parsinginternal/tunnel/ worker registry, request channel, TCP egressinternal/deploy/ embedded Kubernetes manifests and JSON SchemaNext, compare traffic modes to choose the right routing point and blast radius.