Skip to content

How outprobe works

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:

  • it decodes a configurable claim such as email, or reads a plain header;
  • it compares the extracted value with rules registered by connected clients;
  • it tunnels a match to the corresponding laptop;
  • it transparently passes an unmatched serving-mode request to the real Service.

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.

  1. A meshed caller’s sidecar evaluates the outprobe VirtualService.
  2. Candidate traffic is sent to the outprobe-relay Service.
  3. The relay checks connected clients in registration order.
  4. If a rule matches, the relay acquires an idle worker and writes the HTTP request onto it.
  5. The CLI forwards the request to localTarget, reads the response, and sends a deterministically framed response back.
  6. If no rule matches, the relay adds 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:8080

Every 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.

Depending 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 server
cmd/outprobe/ laptop CLI entrypoint
internal/cmds/ init, up, down, status, routes
internal/kube/ kubectl-backed object management
internal/portforward/ client-go port-forward
internal/match/ JWT claim and header extraction
internal/rules/ .outprobe.yaml parsing
internal/tunnel/ worker registry, request channel, TCP egress
internal/deploy/ embedded Kubernetes manifests and JSON Schema

Next, compare traffic modes to choose the right routing point and blast radius.