Bouncer Intercept Posture — TLS, JWT, and WebSocket
Audience: Platform engineers / DevOps
Time: ~20 min
Prerequisites: Bouncer deployed and registered, Deployment overview
The Bouncer enforces all controls at the network perimeter. From Sprint 3 of the bank-pilot readiness programme, the default posture is:
- Ingress TLS: on by default — the Bouncer terminates TLS (or mTLS) on its listener. The container will not start if no TLS material is configured and plaintext has not been explicitly opted in.
- JWT validation: policy decides — Envoy verifies JWT signatures natively when configured; verified claims are forwarded to your controls so Rego decides access.
- WebSocket traffic: inline — WebSocket upgrade handshakes pass through the same OPA enforcement path as HTTP.
- Upstream mTLS — the Bouncer can present a client certificate to the resource it protects.
All behaviour is driven by environment variables. The Bouncer binary has no knowledge of application paths, vendor headers, or tenant identifiers.
1 — TLS on the ingress listener (~5 min)
File-based TLS (default)
Set the following variables before starting the Bouncer container:
| Variable | Purpose | Default |
|---|---|---|
BOUNCER_INGRESS_PORT | Listener port | 8080 |
BOUNCER_TLS_CERT_FILE | Path to PEM server certificate | (required for TLS) |
BOUNCER_TLS_KEY_FILE | Path to PEM private key | (required for TLS) |
BOUNCER_TLS_CA_FILE | Trust bundle to verify client certificates (enables mTLS) | (optional) |
BOUNCER_TLS_REQUIRE_CLIENT_CERT | Require a client certificate on every connection | false |
BOUNCER_TLS_MIN_VERSION | Minimum TLS protocol version | TLSv1_2 |
Fail-closed: if neither TLS material nor the plaintext opt-in is configured, the config generator exits non-zero and the Bouncer container does not start. This is intentional — a Bouncer protecting production traffic should never start silently without TLS.
SPIRE rotating mTLS (recommended for production)
Set SPIRE_ENABLED=true to replace file-based certificates with SPIRE SDS rotating mTLS. When set, the BOUNCER_TLS_* file variables are ignored.
See Cryptographic workload identity for SPIRE setup.
Plaintext opt-in (edge-terminated or in-cluster sidecar only)
Set BOUNCER_PLAINTEXT_INGRESS=true only when:
- A TLS-terminating load balancer or service mesh fronts the Bouncer, or
- The Bouncer runs as a sidecar on a loopback interface.
Never set
BOUNCER_PLAINTEXT_INGRESS=trueon an internet-facing Bouncer.
Verification
After the Bouncer starts, confirm TLS is active:
openssl s_client -connect <bouncer-host>:8080 -showcerts </dev/null 2>&1 \
| grep -E "subject=|issuer=|Protocol|Cipher"
Expected output includes the certificate subject, issuer, and protocol version (≥ TLSv1.2).
Troubleshooting — Bouncer container exits immediately on startup: The Envoy config generator exited non-zero because no TLS material is configured and
BOUNCER_PLAINTEXT_INGRESSis not set. SetBOUNCER_TLS_CERT_FILE+BOUNCER_TLS_KEY_FILE(orSPIRE_ENABLED=true), or explicitly setBOUNCER_PLAINTEXT_INGRESS=truefor an edge-terminated deployment. Full reference: Troubleshooting.
2 — JWT / OIDC validation at the Bouncer (~10 min)
The Bouncer can verify JWT tokens natively at the Envoy listener before a request reaches OPA. JWT validation is off by default. Set PEP_JWT_ISSUER to enable it.
| Variable | Purpose | Default |
|---|---|---|
PEP_JWT_ISSUER | Expected iss claim — enables JWT validation when set | (unset = disabled) |
PEP_JWT_JWKS_URI | URL of the JWKS endpoint | (required when issuer is set) |
PEP_JWT_AUDIENCES | Comma-separated accepted aud values | (unset = any) |
PEP_JWT_JWKS_CACHE_SECONDS | How long to cache JWKS keys locally | 300 |
PEP_JWT_REQUIRE | false = forward verified claims to OPA and let your controls decide; true = hard-reject any request with a missing or invalid token at the listener | false |
PEP_JWT_FORWARD_PAYLOAD_HEADER | Header name for Envoy-verified JWT claims forwarded to OPA and the upstream | x-controlcore-verified-jwt |
Envoy handles JWKS fetch, caching, and key rotation automatically. The iss, aud, exp, and nbf claims are all verified before claims reach OPA.
Default posture: "policy decides"
With PEP_JWT_REQUIRE=false, a request with a missing or invalid token is not rejected at the listener. Any verified claims are forwarded in x-controlcore-verified-jwt and your controls decide whether to allow or deny the request. This lets you write controls that:
- Require a valid JWT for sensitive paths.
- Allow unauthenticated access to public paths.
- Apply graduated restrictions based on claims in the token.
Strict posture: hard-reject invalid tokens
Set PEP_JWT_REQUIRE=true when every request to the protected resource must carry a valid token regardless of controls. The Bouncer returns HTTP 401 before OPA is consulted.
Verification
With PEP_JWT_REQUIRE=true, send a request signed by an untrusted key:
curl -I -H "Authorization: Bearer <token-signed-by-wrong-key>" \
https://<bouncer-host>:8080/any-path
# Expected: HTTP/1.1 401 Unauthorized
Troubleshooting — all requests return 401 unexpectedly: Verify that
PEP_JWT_JWKS_URIis reachable from the Bouncer network and that the tokenissclaim matchesPEP_JWT_ISSUERexactly (case-sensitive). Check Envoy logs for JWKS fetch errors. See Troubleshooting.
3 — WebSocket inspection (~2 min)
WebSocket connections pass through the Bouncer's standard OPA enforcement path. The upgrade handshake is evaluated by your controls just like any HTTP request.
| Variable | Purpose | Default |
|---|---|---|
BOUNCER_WS_EXT_PROC | Keep WebSocket upgrade handshake in the OPA enforcement path | true |
Setting BOUNCER_WS_EXT_PROC=false bypasses OPA for WebSocket upgrades. This is a documented residual-risk bypass intended only for short-term debugging. Leave it at the default (true) for production.
4 — Upstream protocol and east-west mTLS (~5 min)
Configure how the Bouncer connects to the resource it protects.
| Variable | Purpose | Default |
|---|---|---|
TARGET_UPSTREAM_PROTOCOL | auto, http1, or http2 — use http2 for gRPC-native upstreams | auto |
TARGET_TLS_ENABLED | Enable TLS on the Bouncer → upstream connection | false |
TARGET_TLS_CA_FILE | Trust bundle to verify the upstream certificate | (optional) |
TARGET_TLS_CLIENT_CERT_FILE | Client certificate for mutual TLS to the upstream | (optional) |
TARGET_TLS_CLIENT_KEY_FILE | Client private key for mutual TLS to the upstream | (optional) |
TARGET_TLS_SNI | SNI hostname to send to the upstream | (optional) |
Security notes
BOUNCER_TLS_MIN_VERSIONdefaults toTLSv1_2. Set toTLSv1_3for the strictest posture when all clients support it.- Only JWT claims forwarded in
PEP_JWT_FORWARD_PAYLOAD_HEADERare Envoy-verified and authoritative. Never treat claims from arbitrary upstream headers as verified. - Rotating TLS material does not require a Bouncer restart when SPIRE SDS is enabled.
BOUNCER_WS_EXT_PROC=falseis a residual-risk bypass. Document the reason in your change log if you use it.