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
Fix: Exempt DNS by resolv.conf nameservers, not CLUSTER_CIDRS
enforce-redirect left in-cluster DNS direct only for CLUSTER_CIDRS, whose
default was Kind-shaped 10.0.0.0/8. On OpenShift/HyperShift the DNS
resolver and Services live in the service network (172.30/172.31, outside
10/8), so the agent's UDP/53 to the resolver hit the mangle terminal DROP
-> name resolution failed -> all in-cluster egress (incl. agent->MCP)
failed. The resolver address is also unpredictable in general: a service
ClusterIP in any service CIDR, or a NodeLocal DNSCache at link-local
169.254.x -- no CIDR guess covers all cases.
Read the pod's actual resolvers from /etc/resolv.conf (kubelet-written per
dnsPolicy; the authoritative, cluster-agnostic source) and leave only DNS
(UDP/53 + TCP/53) to those IPs direct -- IPv4 via iptables, IPv6 via
ip6tables. Remove CLUSTER_CIDRS / CLUSTER_CIDRS6 entirely; their only
remaining job post in-cluster-capture was the DNS carve-out, and a
hard-coded CIDR default is both fragile and a footgun. Non-DNS in-cluster
UDP is now dropped too (nothing legitimately needs it), tightening the
guard.
The script ignores CLUSTER_CIDRS even if an older operator still injects
it, so this is backward-compatible and needs no lockstep operator change.
test-enforce-redirect.sh: drive with a mock resolv.conf whose v4 resolver
(172.31.0.10) is deliberately OUTSIDE 10/8 -- encoding the OCP regression
-- plus a v6 resolver; assert tcp/53 + udp/53 RETURN to the resolvers and
that no 10/8 / CLUSTER_CIDRS remnant exists. README updated.
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Copy file name to clipboardExpand all lines: authbridge/proxy-init/README.md
+27-26Lines changed: 27 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,18 +48,19 @@ nat-table target but the nat table forbids `DROP` (`iptables` errors with
48
48
49
49
-**`nat` OUTPUT / `AB_REDIRECT`** (position 1): `RETURN` ztunnel mark
50
50
`0x539`, the proxy UID (`--uid-owner $PROXY_UID`, avoids the loop),
51
-
loopback, and in-cluster **DNS-over-TCP** (`-p tcp --dport 53` to
52
-
`CLUSTER_CIDRS`, so cluster name resolution stays direct); then
53
-
`REDIRECT` all remaining **TCP** — external **and** in-cluster — to
51
+
loopback, and **DNS-over-TCP** (`-p tcp --dport 53`) to each
52
+
`/etc/resolv.conf` nameserver (so cluster name resolution stays direct);
53
+
then `REDIRECT` all remaining **TCP** — external **and** in-cluster — to
54
54
`TRANSPARENT_PORT`, so agent→in-cluster calls (e.g. agent→tool) are
55
55
captured by the egress pipeline too.
56
-
-**`mangle` OUTPUT / `AB_NOTCP`** (position 1): the same exemptions
57
-
(plus `ESTABLISHED,RELATED` first, so UDP conntrack replies like DNS
58
-
pass), **including the full `CLUSTER_CIDRS``RETURN`** so in-cluster
59
-
non-TCP (DNS-over-UDP and any other in-cluster UDP) stays direct; then
60
-
`-p tcp -j RETURN` (TCP is handled by the nat REDIRECT) and a terminal
61
-
`DROP` for external **non-TCP** (UDP/QUIC), so HTTP/3 cannot bypass —
62
-
well-behaved clients fall back to TCP and get captured.
56
+
-**`mangle` OUTPUT / `AB_NOTCP`** (position 1): the same UID/mark/loopback
57
+
exemptions (plus `ESTABLISHED,RELATED` first, so UDP conntrack replies
58
+
like DNS pass), then **DNS-over-UDP** (`-p udp --dport 53`) to each
59
+
resolv.conf nameserver so cluster DNS keeps working; then `-p tcp -j
60
+
RETURN` (TCP is handled by the nat REDIRECT) and a terminal `DROP` for
61
+
all other **non-TCP** (UDP/QUIC), so HTTP/3 cannot bypass and non-DNS
62
+
in-cluster UDP is dropped too — well-behaved clients fall back to TCP and
63
+
get captured.
63
64
64
65
Because the OUTPUT hook order is `raw → mangle → nat → filter`, the
65
66
mangle chain drops non-TCP on its original destination while TCP falls
@@ -73,25 +74,26 @@ bypassing it. IPv6 mirrors apply the same rules. See
73
74
[`test-enforce-redirect.sh`](./test-enforce-redirect.sh), which proves
74
75
the capture, the preemption, and the non-TCP drop via packet counters.
75
76
76
-
> **`CLUSTER_CIDRS` is Kind-shaped by default.** It now governs only what
77
-
> stays **direct**: in-cluster DNS-over-TCP (`tcp/53`) and in-cluster
78
-
> non-TCP (so cluster DNS keeps working). The `10.0.0.0/8` default covers
79
-
> Kind (pods `10.244.0.0/16` + services `10.96.0.0/16`). Other distros
80
-
> differ — **OpenShift** uses services `172.30.0.0/16` and pods
81
-
> `10.128.0.0/14`, and `172.30.0.0/16` is **outside**`10/8`, so with the
82
-
> default, cluster DNS (CoreDNS on `172.30.x`) is no longer left direct and
83
-
> **resolution breaks**. On OCP/EKS/etc. you **must** override
84
-
> `CLUSTER_CIDRS` with the cluster's real pod+service ranges. The script
85
-
> logs the resolved value at startup, and the operator sets it from the
86
-
> cluster's CIDRs.
77
+
> **DNS stays direct by following the pod's actual resolvers — no CIDR
78
+
> guessing.** The only thing left direct is DNS (`tcp/53` + `udp/53`) to the
79
+
> `nameserver` IPs in `/etc/resolv.conf`, which kubelet writes per the pod's
80
+
> `dnsPolicy`. This is cluster-agnostic by construction: it works whether the
81
+
> resolver is a Kind/OpenShift/EKS service ClusterIP (any service CIDR — incl.
82
+
> OpenShift's `172.30.0.0/16`, which is **outside**`10/8`) or a NodeLocal
83
+
> DNSCache at a link-local `169.254.x` address. The script logs the resolved
84
+
> nameservers at startup; override the file path with `RESOLV_CONF` (mainly
85
+
> for tests). There is **no** in-cluster CIDR knob — `enforce-redirect` no
86
+
> longer needs one. (A prior `CLUSTER_CIDRS` env was removed; its `10.0.0.0/8`
87
+
> default silently dropped DNS on OpenShift, where the resolver sits outside
88
+
> `10/8`.)
87
89
88
90
> **`enforce-redirect` intentionally ignores `OUTBOUND_PORTS_EXCLUDE`** (a
89
91
> `redirect`-mode knob). Any destination previously bypassed that way —
90
92
> e.g. a direct LLM endpoint at `host.docker.internal:11434` — is now
91
93
> captured (external TCP) or dropped (external non-TCP). In-cluster TCP is
92
-
> captured as well (only in-cluster DNS stays direct), so `CLUSTER_CIDRS`
93
-
> is **not** a TCP bypass — it only keeps cluster DNS and in-cluster UDP
94
-
> direct. That is the point: `enforce-redirect` closes direct-egress holes.
94
+
> captured as well (only DNS to the resolvers stays direct). That is the
95
+
> point: `enforce-redirect` closes direct-egress holes, and the DNS
96
+
> exemption is scoped to `port 53` to the resolver IPs — not a TCP bypass.
95
97
96
98
## iptables backend
97
99
@@ -111,8 +113,7 @@ whichever the host kernel exposes. Override with `IPTABLES_CMD` (and
111
113
|`OUTBOUND_PORTS_EXCLUDE`| (empty) | redirect | Comma-separated outbound port list to skip (e.g. `8080`) |
112
114
|`INBOUND_PORTS_EXCLUDE`| (empty) | redirect | Comma-separated inbound port list to skip |
113
115
|`POD_IP`| (required in `redirect`) | redirect | Set via Downward API; DNAT target for ambient-mesh inbound. Not used by `enforce-redirect`. |
114
-
|`CLUSTER_CIDRS`|`10.0.0.0/8`| enforce-redirect | Comma-separated in-cluster CIDRs kept direct **for DNS only**: DNS-over-TCP (`tcp/53`) + all in-cluster non-TCP (UDP). Other in-cluster TCP is captured. |
|`RESOLV_CONF`|`/etc/resolv.conf`| enforce-redirect | Path read at init for `nameserver` IPs; DNS (`tcp/53` + `udp/53`) to those IPs is left direct (IPv4→`iptables`, IPv6→`ip6tables`). Override mainly for tests. |
0 commit comments