You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Per-Actor TLS interception requires choosing between MITM and TLS passthrough before the gateway completes the inner TLS handshake.
The approach explored in #1535 passes policy through the CONNECT boundary and evaluates it in a custom gateway listener filter. Another option is to make this decision in atunnel, which already handles each Actor’s outbound TCP connections and establishes the authenticated CONNECT tunnel.
Proposed solution
Add ClientHello sniffing to atunnel. After extracting the SNI, atunnel evaluates the Actor’s MITM configuration and attaches the resulting TLS action to the outer CONNECT request. The gateway maps that action into filter state and uses it to select the appropriate filter chain.
The connection flow would be:
Inspect the initial client bytes.
Before sending CONNECT, atunnel reads enough bytes to identify a TLS ClientHello and extract its SNI. This does not terminate TLS or send a ServerHello. All consumed bytes are buffered, with explicit time and size limits.
Evaluate the Actor’s configuration. atunnel uses the SNI and original destination port to select terminate or passthrough. The configuration should come from the Actor’s EgressPolicy, preserving the ordering and matching semantics being discussed in egress policy enhacements for GA #1751, rather than introducing a separately authored policy.
Carry the decision in CONNECT metadata.
For example, with an illustrative header name and format:
The CONNECT authority remains the original destination IP:port. The header describes the TLS connection inside the tunnel, not the outer mTLS connection to the gateway.
Select the gateway filter chain.
After authenticating and authorizing the CONNECT request, the gateway copies the validated action into read-only filter state, for example dev.ate.egress.tls_action, and shares it with the internal listener.
terminate selects the MITM chain, using the existing sdsmint certificate flow.
passthrough selects the opaque TLS forwarding chain.
Forward the original stream.
Once CONNECT succeeds, atunnel forwards the buffered bytes exactly once, followed by normal bidirectional forwarding.
Policy and trust semantics
The header is generated by trusted atunnel code and accepted only on the authenticated outer CONNECT path. It must never be copied from Actor-provided application headers or overwritten by requests inside the tunnel.
The TLS action controls interception; it does not independently authorize egress. The gateway continues to enforce the Actor’s policy, including authorization of passthrough traffic. Missing policy or an unmatched destination must not become an implicit allow.
For inspected HTTPS, the gateway retains per-request authorization, SNI/authority consistency checks, credential injection, and upstream certificate verification. Hostname-based authorization must route to the authorized hostname, as required by the egress contract.
Sniffing errors, exceeded limits, or unavailable required configuration must not silently downgrade an inspection requirement to passthrough. Plaintext HTTP should continue through the HTTP policy path; TLS without a usable SNI cannot match an SNI-based rule.
Tradeoffs and alternative
This approach lets the gateway consume a small, explicit connection decision using existing Envoy configuration primitives. It avoids needing a custom listener filter solely to evaluate per-Actor MITM rules at the gateway.
The tradeoff is that atunnel gains ClientHello parsing and access to Actor-specific configuration. Sniffing also happens before CONNECT establishment, so buffering and timeout behavior need to be bounded.
Keeping the decision entirely in the gateway, as explored in #1535, remains an alternative. It keeps policy evaluation centralized but requires the gateway to evaluate the Actor’s configuration during internal listener selection.
I have already validated the SNI sniffing → CONNECT header → filter state → filter-chain selection path in a PoC. Integrating it into Substrate would require policy delivery, lifecycle handling, and end-to-end tests.
Problem
Per-Actor TLS interception requires choosing between MITM and TLS passthrough before the gateway completes the inner TLS handshake.
The approach explored in #1535 passes policy through the CONNECT boundary and evaluates it in a custom gateway listener filter. Another option is to make this decision in
atunnel, which already handles each Actor’s outbound TCP connections and establishes the authenticated CONNECT tunnel.Proposed solution
Add ClientHello sniffing to
atunnel. After extracting the SNI,atunnelevaluates the Actor’s MITM configuration and attaches the resulting TLS action to the outer CONNECT request. The gateway maps that action into filter state and uses it to select the appropriate filter chain.The connection flow would be:
Inspect the initial client bytes.
Before sending CONNECT,
atunnelreads enough bytes to identify a TLS ClientHello and extract its SNI. This does not terminate TLS or send a ServerHello. All consumed bytes are buffered, with explicit time and size limits.Evaluate the Actor’s configuration.
atunneluses the SNI and original destination port to selectterminateorpassthrough. The configuration should come from the Actor’sEgressPolicy, preserving the ordering and matching semantics being discussed in egress policy enhacements for GA #1751, rather than introducing a separately authored policy.Carry the decision in CONNECT metadata.
For example, with an illustrative header name and format:
The CONNECT authority remains the original destination
IP:port. The header describes the TLS connection inside the tunnel, not the outer mTLS connection to the gateway.Select the gateway filter chain.
After authenticating and authorizing the CONNECT request, the gateway copies the validated action into read-only filter state, for example
dev.ate.egress.tls_action, and shares it with the internal listener.Envoy’s HTTP set-filter-state filter and filter-chain matcher provide the required configuration primitives:
terminateselects the MITM chain, using the existingsdsmintcertificate flow.passthroughselects the opaque TLS forwarding chain.Forward the original stream.
Once CONNECT succeeds,
atunnelforwards the buffered bytes exactly once, followed by normal bidirectional forwarding.Policy and trust semantics
The header is generated by trusted
atunnelcode and accepted only on the authenticated outer CONNECT path. It must never be copied from Actor-provided application headers or overwritten by requests inside the tunnel.The TLS action controls interception; it does not independently authorize egress. The gateway continues to enforce the Actor’s policy, including authorization of passthrough traffic. Missing policy or an unmatched destination must not become an implicit allow.
For inspected HTTPS, the gateway retains per-request authorization, SNI/authority consistency checks, credential injection, and upstream certificate verification. Hostname-based authorization must route to the authorized hostname, as required by the egress contract.
Sniffing errors, exceeded limits, or unavailable required configuration must not silently downgrade an inspection requirement to passthrough. Plaintext HTTP should continue through the HTTP policy path; TLS without a usable SNI cannot match an SNI-based rule.
Tradeoffs and alternative
This approach lets the gateway consume a small, explicit connection decision using existing Envoy configuration primitives. It avoids needing a custom listener filter solely to evaluate per-Actor MITM rules at the gateway.
The tradeoff is that
atunnelgains ClientHello parsing and access to Actor-specific configuration. Sniffing also happens before CONNECT establishment, so buffering and timeout behavior need to be bounded.Keeping the decision entirely in the gateway, as explored in #1535, remains an alternative. It keeps policy evaluation centralized but requires the gateway to evaluate the Actor’s configuration during internal listener selection.
I have already validated the SNI sniffing → CONNECT header → filter state → filter-chain selection path in a PoC. Integrating it into Substrate would require policy delivery, lifecycle handling, and end-to-end tests.