Skip to content

[Proposal]: Select per-Actor TLS interception in atunnel and carry the decision through CONNECT #1763

Description

@Kuromesi

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, 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:

  1. 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.

  2. 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.

  3. Carry the decision in CONNECT metadata.
    For example, with an illustrative header name and format:

    CONNECT 203.0.113.10:443 HTTP/1.1
    Host: 203.0.113.10:443
    x-ate-egress-tls: action=terminate;sni=api.example.com

    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.

  4. 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:

    • terminate selects the MITM chain, using the existing sdsmint certificate flow.
    • passthrough selects the opaque TLS forwarding chain.
  5. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions