TLS Intercept for CONNECT-based L7 Filtering and Credential Injection #650
lukehinds
started this conversation in
Feature Requests
Replies: 1 comment 1 reply
-
|
@JimBugwadia , how well will this play with a k8s deployment? I am averse to MITM, so kept this as ephemeral and sanitized as I could. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
The proxy has three modes with fundamentally different visibility:
/openai/...)*_BASE_URLenv vars)The reverse proxy gives full L7 control but requires SDK cooperation via base URL env vars. Both CONNECT modes (direct tunnel and external proxy passthrough) work universally but are opaque byte streams. SDKs and tools that don't honour
*_BASE_URLgo through CONNECT, bypassing credential injection and endpoint filtering entirely. The external proxy path additionally delegates allow/deny decisions to the enterprise proxy, but nono has no visibility into the traffic itself.Solution
Selectively terminate TLS on CONNECT requests using an ephemeral, per-session CA. Only intercept connections to hosts that match a configured route with
endpoint_rulesorcredential_key. All other CONNECT traffic remains a transparent tunnel.Design Constraints
Selective interception only -- Only CONNECT requests to hosts matching a route with
endpoint_rulesorcredential_keyget TLS-terminated. Everything else stays as a transparent tunnel (current behaviour unchanged).Hard fail on cert pinning -- If TLS interception fails (pinned cert, custom TLS config in the agent), the proxy returns a 502 with a clear diagnostic. It does NOT fall back to a transparent tunnel, because the route requires credential injection or filtering to function correctly.
Shared L7 processing with reverse proxy -- The intercepted CONNECT path reuses the same request-forwarding logic as the reverse proxy (credential injection, endpoint rules, audit logging, upstream TLS). See L7 Processing below.
Ephemeral CA Lifecycle
The CA exists only for the lifetime of a single proxy session. No long-lived key material.
Generation
rcgenCA:TRUEbasic constraintCN=nono-session-caStorage
Zeroizing<Vec<u8>>)0o400(owner-read only)Cleanup
ProxyHandle::shutdown()zeroizes the in-memory key and deletes the CA cert tempfileDropimpl as a safety net if shutdown isn't called explicitlyLeaf Certificate Minting
On each intercepted CONNECT request, the proxy mints a leaf certificate for the requested hostname.
Certificate Properties
Caching
HashMap<String, CertifiedKey>keyed by hostnameTrust Injection
The ephemeral CA cert file path is injected into the child process environment before sandbox application:
SSL_CERT_FILErequests,httpx, Ruby, many CLI tools)REQUESTS_CA_BUNDLErequestsspecificallyNODE_EXTRA_CA_CERTSCURL_CA_BUNDLEThese are additive (agent can still reach non-intercepted hosts with system roots). The env vars point to a PEM file containing both the ephemeral CA cert and the system roots, so non-intercepted TLS connections continue to work.
External Proxy Composition
When an external (enterprise) proxy is configured (
--upstream-proxy), the TLS intercept path must compose with it. The decision tree becomes a three-way branch:Upstream Connection Strategy
forward_request()needs anUpstreamStrategyparameter that abstracts how the upstream TLS connection is established:For the
ExternalProxyvariant, the upstream leg:CONNECT host:port HTTP/1.1(with optional proxy auth)200 Connection EstablishedThis reuses the CONNECT-to-enterprise-proxy logic already in
external.rs(connecting, sending CONNECT, reading 200), but wraps the resulting tunnel in a TLS connection to the upstream rather than relaying raw bytes.Bypass Hosts
The existing
BypassMatcherinexternal.rsdetermines whether a host should skip the enterprise proxy. When bypass matches, the intercept path connects directly to the upstream even if an external proxy is configured. This is consistent with current transparent-tunnel behaviour where bypass hosts go direct.Extracting Shared Enterprise Proxy Helpers
external.rscurrently has inline logic for connecting to the enterprise proxy and reading the CONNECT response. This should be extracted into reusable helpers that both the transparent external proxy path and the intercept upstream path can call:The transparent external proxy path calls this then relays bytes. The intercept path calls this then wraps the stream in a TLS connection for the upstream handshake.
L7 Processing
Shared Request Forwarding
The core L7 logic is extracted into a shared function used by both the reverse proxy and the intercept path:
The shared
forward_requestfunction handles:CredentialStore(header, url_path, query_param, basic_auth)CompiledEndpointRulesTlsConnectorfromRouteStore)Differences Between Callers
/openai/...)api.openai.com:443) viaRouteStore::is_route_upstream()Architecture
New Modules
Modified Modules
CONNECT Handler Branching
Route Matching for Intercept
RouteStorealready hasis_route_upstream()which mapshost:portto routes. A new method determines if a route requires interception:Dependencies
Existing (already in
nono-proxy/Cargo.toml)rustls,tokio-rustls,rustls-pemfile-- TLS termination and upstream connectionszeroize-- Key material cleanupNew
rcgen-- CA and leaf certificate generation (well-maintained, used by rustls test infra)Implementation Phases
Phase 1: Ephemeral CA Infrastructure
tls_intercept/ca.rsandcert_cache.rs. CA generation, leaf cert minting, zeroization. Unit-testable in isolation without network.Phase 2: CONNECT Intercept Path
Branch in
connect.rs. TLS acceptor using minted certs. Wire up to accept client TLS and read the inner HTTP request.Phase 3: Shared L7 Forwarding
Extract
forward_request()fromreverse.rs. Wire both reverse proxy and intercept callers to it.Phase 4: Trust Injection
Generate the combined CA cert + system roots PEM file. Set env vars in child environment. Test across Node.js, Python, curl, Go.
Phase 5: Audit and Diagnostics
Audit log entries distinguishing
Connect(tunnel),ConnectIntercept(terminated), andReversemodes. Diagnostic banner showing which routes are intercepted.Security Considerations
Zeroizing<Vec<u8>>in memory. Only the public certificate is written to a tempfile.0o400. The sandboxed child needs read access, so the file is created before sandbox application.Dropimpl on the CA struct ensures key material is zeroized even on panic or unexpected shutdown.bypass_hostslist, the intercept path connects directly to the upstream, consistent with transparent-tunnel behaviour. This prevents routing internal traffic through the enterprise proxy unnecessarily.Beta Was this translation helpful? Give feedback.
All reactions