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:

VariablePurposeDefault
BOUNCER_INGRESS_PORTListener port8080
BOUNCER_TLS_CERT_FILEPath to PEM server certificate(required for TLS)
BOUNCER_TLS_KEY_FILEPath to PEM private key(required for TLS)
BOUNCER_TLS_CA_FILETrust bundle to verify client certificates (enables mTLS)(optional)
BOUNCER_TLS_REQUIRE_CLIENT_CERTRequire a client certificate on every connectionfalse
BOUNCER_TLS_MIN_VERSIONMinimum TLS protocol versionTLSv1_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.

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=true on 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_INGRESS is not set. Set BOUNCER_TLS_CERT_FILE + BOUNCER_TLS_KEY_FILE (or SPIRE_ENABLED=true), or explicitly set BOUNCER_PLAINTEXT_INGRESS=true for 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.

VariablePurposeDefault
PEP_JWT_ISSUERExpected iss claim — enables JWT validation when set(unset = disabled)
PEP_JWT_JWKS_URIURL of the JWKS endpoint(required when issuer is set)
PEP_JWT_AUDIENCESComma-separated accepted aud values(unset = any)
PEP_JWT_JWKS_CACHE_SECONDSHow long to cache JWKS keys locally300
PEP_JWT_REQUIREfalse = forward verified claims to OPA and let your controls decide; true = hard-reject any request with a missing or invalid token at the listenerfalse
PEP_JWT_FORWARD_PAYLOAD_HEADERHeader name for Envoy-verified JWT claims forwarded to OPA and the upstreamx-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_URI is reachable from the Bouncer network and that the token iss claim matches PEP_JWT_ISSUER exactly (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.

VariablePurposeDefault
BOUNCER_WS_EXT_PROCKeep WebSocket upgrade handshake in the OPA enforcement pathtrue

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.

VariablePurposeDefault
TARGET_UPSTREAM_PROTOCOLauto, http1, or http2 — use http2 for gRPC-native upstreamsauto
TARGET_TLS_ENABLEDEnable TLS on the Bouncer → upstream connectionfalse
TARGET_TLS_CA_FILETrust bundle to verify the upstream certificate(optional)
TARGET_TLS_CLIENT_CERT_FILEClient certificate for mutual TLS to the upstream(optional)
TARGET_TLS_CLIENT_KEY_FILEClient private key for mutual TLS to the upstream(optional)
TARGET_TLS_SNISNI hostname to send to the upstream(optional)

Security notes

  • BOUNCER_TLS_MIN_VERSION defaults to TLSv1_2. Set to TLSv1_3 for the strictest posture when all clients support it.
  • Only JWT claims forwarded in PEP_JWT_FORWARD_PAYLOAD_HEADER are 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=false is a residual-risk bypass. Document the reason in your change log if you use it.

Next steps