hoop-inspect is an inspecting relay. It decodes the wire protocol between a client and a database or an API, evaluates every statement against policy, records an audit trail, and masks sensitive values in the response.
It routes nothing and terminates no client TLS. You run it behind something that already owns the network path and identity, which in most enterprises means Envoy. Envoy reaches it over a unix socket or a TCP port, your choice per lane.
Envoy answers reachability. hoop-inspect answers what the statement does, and what comes back.
This runs with no gateway, no agent, and no control-plane database. One config file is the whole setup.
Prerequisites
- The
hoopCLI installed. See CLI installation. - An Envoy you can add a cluster to, or any proxy that can forward plaintext to a local port.
- A backend to protect: PostgreSQL or an HTTP service.
Step 1: Write a config file
One listener is one upstream. Start with a single Postgres lane and nothing else. Pick a transport first. One field decides it, and the rest of the file is identical either way:- Unix socket
- TCP port
config.yaml
upstream_tls behave the same way, because the gate reads a net.Conn and never asks what kind it is. See Transport for the full comparison.
Step 2: Validate before you deploy
Nothing needs to be running. The validator builds every lane and reports every problem in one run:opa block reads + opa, one with masking reads + masking, and one with enforce: false reads observe-only.
Step 3: Run it
--config also reads HOOP_INSPECT_CONFIG, which is the shape a Kubernetes deployment wants: mount the ConfigMap, set the variable, pass no arguments.
Check it came up:
Step 4: Point Envoy at it
hoop-inspect is an ordinary upstream. There is no ext_proc, no WASM, no custom Envoy filter to install. You change the cluster your listener already routes to.
The cluster shape follows the transport you picked in Step 1:
A path resolves to nothing, so
STRICT_DNS on a pipe: endpoint fails at load.
- Unix socket
- TCP port
- HTTP lane
envoy.yaml
tcp_proxy. Every byte reaches the relay unexamined, which is the reason the relay earns its place here.Terminating client TLS
The relay reads plaintext. Whatever the client encrypts, something has to decrypt before the gate sees a statement, and the relay is not that something: it terminates no downstream TLS. On the HTTP lane Envoy already does it, because an HTTPS listener terminates TLS by definition. On the Postgres lane the stocktcp_proxy does not, which is why the client connects with PGSSLMODE=disable.
To encrypt the client’s Postgres leg, terminate it in Envoy with the postgres_proxy filter and a starttls transport socket:
envoy.yaml
SSLRequest packet and waits for a one-byte reply, which is why this needs the starttls socket rather than a plain DownstreamTlsContext. The client then connects with PGSSLMODE=require, Envoy decrypts, and the relay receives the plaintext it needs.
With all three legs covered, only the hop the relay reads is ever in the clear:
Keep the middle leg on loopback or a unix socket. It carries decrypted traffic by design, and a socket is the tighter boundary because no port exists to reach.
Step 5: Watch it work
With the lane above in place, a destructive statement never reaches the database:ErrorResponse carrying the message you wrote in config.yaml, so the developer reads it in psql instead of watching a socket drop. Envoy forwarded the same bytes as opaque TCP and consulted nobody.
Read what the relay recorded:
Confirm which transport bound
/stats reports the address each lane bound, not the string you configured, so it tells you what happened. A path means a socket, a host:port means TCP:
:::15432 and :::18080 beside it. From a peer, nc -z -w2 hoop-inspect 15432 reports closed or open to match.
Step 6: Add masking
Masking runs on responses. Turn on detection by naming the entity types your data holds, then say how each is rewritten:config.yaml
Run the whole thing on your laptop
The repository ships a compose stack that runs all of this end to end: Envoy terminating TLS, OPA answering reachability, the relay behind both, a seeded Postgres and an HTTP service behind that. Needsdocker, curl, openssl and python3.
- TCP (default)
- Unix socket (overlay)
:15432 and :18080 on the compose network. Neither is published to the host.
Inside the compose network that Postgres listener is
envoy:5432. The host publishes it on 5433, because a laptop tends to have something on 5432 already. Those are Envoy’s ports and the overlay leaves them alone: it removes the relay’s two, which were never published to the host.
Troubleshooting
Next
Config File Reference
Every section, every rule type, inheritance between lanes, and what startup refuses.
Components and Architecture
How a request flows through the relay, multi-lane and unix-socket deployments, Kubernetes.