Skip to content

Gateway egress allowlist: hostname entries block all TLS egress, including the allowlisted hosts #345

Closed

Description

@naeemarsalan

Summary

A Gateway whose egress.allowlist.hosts entries are hostnames blocks all TLS egress from the task, including to the hosts it explicitly allows. An allowlisted host and a non-allowlisted host fail identically, so from inside the sandbox there is no way to tell a working allowlist from a broken one.

CIDR entries work correctly. "*" works correctly. Only the hostname form — which is what docs/concepts.md describes ("an egress allowlist of hosts and ports") — is affected.

Tested on AX v0.3.0 (d8ed0fe), Agent Substrate on a 2-node kind cluster, atelet v0.2.0-beta4.

Reproduction

Three gateways, same task image, same cluster, same minute. The probe is curl https://<host>/ from inside the task via ax ssh.

1. Wildcard — works

egress:
  allowlist:
    hosts:
      - host: "*"
litellm  HTTP=200
litellm  HTTP=200
litellm  HTTP=200

2. Hostnames — blocks everything, including the allowlisted hosts

egress:
  allowlist:
    hosts:
      - host: "litellm-agentic.apps.example.com"
        port: 443
      - host: "git.example.com"
        port: 443
litellm-agentic.apps.example.com   curl: (35) TLS connect error: error:0A000126:SSL routines::unexpected eof while reading
git.example.com                    curl: (35) TLS connect error: ... unexpected eof while reading   <- ALSO allowlisted
example.com                        curl: (35) TLS connect error: ... unexpected eof while reading   <- correctly denied

All three identical, consistently ~1.03s.

3. CIDRs — correct

egress:
  allowlist:
    hosts:
      - host: "172.16.1.153/32"   # litellm
      - host: "46.17.95.101/32"   # git
litellm  HTTP=200
git      HTTP=200
example  curl: (35) Recv failure: Connection reset by peer    <- denied, and distinguishable

The remote is healthy throughout: from the cluster host, outside the sandbox, the same URL returns HTTP=200 in ~22ms, 3/3.

Where it breaks

Not DNS and not TCP. Inside the task, under the hostname gateway:

getent hosts litellm-agentic.apps.example.com   ->  172.16.1.153      (resolves)
</dev/tcp/172.16.1.153/443                      ->  TCP-OK            (connects)
openssl s_client -connect ...:443               ->  "SSL handshake has read 0 bytes and written 1588 bytes"

The ClientHello goes out and nothing comes back.

Logs

atenet-egress, envoy container — note the SNI is parsed correctly, but there is no upstream and the flag is UH (no healthy upstream host):

{"actor":".../actor/killtest2","flags":"UH","sni":"litellm-agentic.apps.example.com",
 "destination":null,"upstream":null,"bytes_rcvd":0,"bytes_sent":0,"duration_ms":0}

Against the wildcard gateway, same host, same minute:

{"actor":".../actor/openprobe","flags":"-","sni":"litellm-agentic.apps.example.com",
 "destination":"172.16.1.153:443","upstream":"172.16.1.153:443","bytes_rcvd":1829,"bytes_sent":5611,"duration_ms":36}

atenet-egress, ext-proc container:

{"level":"INFO","msg":"egress tunnel opened: no address rule allows the destination,
  requests inside it are decided one by one","destination":"172.16.1.153:443","rule":-1}

Reading of the cause

AX's own translation looks correct. internal/substrate/client.go, ApplyEgressPolicy routes any host containing / into a CIDRRule and everything else into HostnameRule.Patterns:

if strings.Contains(h.Host, "/") {
    cidrs = append(cidrs, h.Host)
} else if h.Host != "" {
    patterns = append(patterns, h.Host)
}

So a hostname produces a HostnameRule and no address rule. Substrate then opens a tunnel intending to adjudicate requests inside it one at a time — which works for plaintext HTTP, where there are requests to inspect — but a TLS passthrough connection has none, so envoy never gets an upstream cluster and closes with UH. The SNI is matched and then not used to admit the connection.

If that reading is right, the fix is either for the hostname rule to resolve to an address rule for TLS, or for the documentation to say that hostname allowlisting does not cover TLS and CIDRs are required.

Second, smaller issue: port is silently ignored

HostRule.Port is accepted by the API and never read by ApplyEgressPolicy — neither HostnameRule nor CIDRRule carries it. A Gateway declaring port: 443 constrains nothing, and reads as though it does.

Impact

The failure is silent and closed. Everything reports healthy — the Task is Running, all three conditions are True, GatewayReady says "Network policies active" — while no egress works. The first guess from inside is that the remote is down, because that is exactly what it looks like. This cost us about an hour before the envoy UH flag gave it away.

Happy to test a patch on this cluster.

Activity

  1. aakash-env commented on Sep 21, 2026

    @aakash-env

    @naeemarsalan kindly assign this issue to me

  2. arbazkhan971 commented on Sep 22, 2026

    @arbazkhan971

    Fix ready: implemented on my fork (branch fix/345-gateway-hostname-tls-egress, regression test included, make test green) and ready to PR — but this repo's collaborators_only PR creation policy rejects PRs from non-collaborators (see #358), so I cannot open it. If PR creation opens for external contributors, I will open the PR immediately. Happy to adjust scope per maintainer feedback.

  3. rakyll commented on Sep 25, 2026

    @rakyll
    Member

    We removed gateways from AX, networking will be provided directly from the Substrate layer. #395

  4. tosky1125 commented on Sep 30, 2026

    @tosky1125

    Thanks, that context helps. One clarification from our repro, in case it is useful for others landing here:

    We deployed the substrate directly (install-ate-kind.sh path) on HEAD 2026-09-29 and applied the egress allowlist at the substrate level, so the failing dataplane in our case is the substrate's atenet-egress/Envoy passthrough, which still shows the same signature (UH, destination: null, hostname-authority CONNECT with no upstream, plaintext HTTP passing while TLS fails).

    So as of that HEAD the hostname-vs-TLS behavior described in this issue looks alive on the Substrate side even after the Gateway removal on the AX side. Is the hostname/TLS passthrough fix tracked anywhere on the Substrate repo (or planned as part of the native networking work)? Happy to retest against a branch or tag.

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