Skip to content

Commit bbffe8f

Browse files
committed
refactor(proxy-init): remove obsolete enforce-drop mode
enforce-drop existed only because, before the transparent listener, a REDIRECT had no receiver so DROP was the only fail-closed option. Now that every proxy-sidecar/lite image ships the transparent listener, enforce-redirect captures bypass egress instead of dropping it, and the operator only emits enforce-redirect. enforce-drop is dead. Remove the enforce-drop MODE (setup_enforce_drop + AB_EGRESS chain + the dispatch + the mode-table/env-var/injection docs) and test-enforce-drop.sh. The init script now accepts only redirect | enforce-redirect. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
1 parent 66f82e9 commit bbffe8f

3 files changed

Lines changed: 35 additions & 276 deletions

File tree

authbridge/proxy-init/README.md

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
The `proxy-init` container programs iptables rules for an
44
AuthBridge-injected pod. It runs once at pod startup as a Kubernetes
5-
init container, then exits. It has three modes, selected by the `MODE`
5+
init container, then exits. It has two modes, selected by the `MODE`
66
env var:
77

88
| `MODE` | Used by | What it does |
99
|---|---|---|
1010
| `redirect` (default) | `envoy-sidecar` | Transparently **REDIRECT**s pod traffic to the Envoy listeners. |
1111
| `enforce-redirect` | `proxy-sidecar`, `lite` | Fail-closed egress guard that **captures**: REDIRECTs external TCP that bypasses the forward proxy to AuthBridge's transparent listener; DROPs non-TCP external egress. |
12-
| `enforce-drop` | `proxy-sidecar`, `lite` | Fail-closed egress guard that **DROP**s any egress that bypasses the forward proxy. Predates `enforce-redirect`; retained as a no-transparent-listener fallback. |
1312

1413
## `redirect` mode (envoy-sidecar)
1514

@@ -66,58 +65,22 @@ the Envoy path. IPv6 mirrors apply the same rules. See
6665
[`test-enforce-redirect.sh`](./test-enforce-redirect.sh), which proves
6766
the capture, the preemption, and the non-TCP drop via packet counters.
6867

69-
`CLUSTER_CIDRS` has the same Kind-shaped default and OCP/EKS override
70-
requirement as documented under `enforce-drop` below.
71-
72-
## `enforce-drop` mode (proxy-sidecar)
73-
74-
In `proxy-sidecar` mode the workload is configured with `HTTP_PROXY`
75-
pointing at AuthBridge's forward proxy. On its own that is purely
76-
cooperative — an app that ignores `HTTP_PROXY` (or sets `NO_PROXY`)
77-
egresses directly and bypasses AuthBridge. `enforce-drop` closes that
78-
gap **without** transparently redirecting (you cannot REDIRECT raw
79-
traffic into a CONNECT forward proxy): it installs a fail-closed guard
80-
that DROPs any direct egress, forcing all external traffic through the
81-
proxy regardless of whether the app honors `HTTP_PROXY`.
82-
83-
`init-iptables.sh` builds a dedicated `AB_EGRESS` chain hooked from
84-
**`mangle` OUTPUT at position 1**, with this order:
85-
86-
1. `RETURN` ztunnel's own sockets (fwmark `0x539`) — keeps the mesh path working; a no-op when ambient is absent.
87-
2. `RETURN` the proxy's own re-originated egress (`--uid-owner $PROXY_UID`, default 1337).
88-
3. `RETURN` loopback (the app → proxy hop) and in-cluster CIDRs (`CLUSTER_CIDRS`, mesh/DNS).
89-
4. `DROP` everything else — direct external egress, including UDP (QUIC/HTTP-3).
90-
91-
An IPv6 mirror drops external v6 egress (allowing loopback, link-local,
92-
the proxy UID, and `CLUSTER_CIDRS6`).
93-
9468
> **`CLUSTER_CIDRS` is Kind-shaped by default.** The `10.0.0.0/8` default
9569
> covers Kind (pods `10.244.0.0/16` + services `10.96.0.0/16`). Other
9670
> distros differ — **OpenShift** uses services `172.30.0.0/16` and pods
9771
> `10.128.0.0/14`, and `172.30.0.0/16` is **outside** `10/8`, so the
9872
> default would drop in-cluster service traffic. On OCP/EKS/etc. you
9973
> **must** override `CLUSTER_CIDRS` with the cluster's real pod+service
10074
> ranges. The script logs the resolved value at startup, and the
101-
> operator wiring (follow-up PR) sets it from the cluster's CIDRs.
75+
> operator sets it from the cluster's CIDRs.
10276
103-
> **`enforce-drop` intentionally ignores `OUTBOUND_PORTS_EXCLUDE`** (a
77+
> **`enforce-redirect` intentionally ignores `OUTBOUND_PORTS_EXCLUDE`** (a
10478
> `redirect`-mode knob). Any destination previously bypassed that way —
10579
> e.g. a direct LLM endpoint at `host.docker.internal:11434` — is now
106-
> dropped unless it goes through the forward proxy or falls within
107-
> `CLUSTER_CIDRS`. That is the point: `enforce-drop` closes direct-egress
108-
> holes. Operators relying on a bypass must route it through the proxy
109-
> (or, for in-cluster targets, include it in `CLUSTER_CIDRS`).
110-
111-
**Why `mangle` OUTPUT, not `filter`:** when Istio ambient is active it
112-
installs an in-pod `nat OUTPUT` REDIRECT (`ISTIO_OUTPUT` → ztunnel
113-
`:15001`). The netfilter OUTPUT hook order is `raw → mangle → nat →
114-
filter`, so a DROP in `mangle` evaluates the original destination and
115-
fires **before** ambient's nat redirect can rewrite it; a DROP in
116-
`filter` would run after nat and be defeated. `-I 1` also places the
117-
chain ahead of Istio's appended (`-A`) mangle chain. This makes the
118-
guard robust with no ambient, in-pod ambient, or node-level ambient.
119-
See [`test-enforce-drop.sh`](./test-enforce-drop.sh), which proves the
120-
preemption via packet counters.
80+
> captured (external TCP) or dropped (external non-TCP) unless it falls
81+
> within `CLUSTER_CIDRS`. That is the point: `enforce-redirect` closes
82+
> direct-egress holes. Operators relying on a bypass for an in-cluster
83+
> target must include it in `CLUSTER_CIDRS`.
12184
12285
## iptables backend
12386

@@ -129,18 +92,18 @@ whichever the host kernel exposes. Override with `IPTABLES_CMD` (and
12992

13093
| Variable | Default | Mode | Purpose |
13194
|---|---|---|---|
132-
| `MODE` | `redirect` | all | `redirect` (envoy-sidecar), `enforce-redirect` or `enforce-drop` (proxy-sidecar) |
133-
| `PROXY_UID` | `1337` | all | UID of the AuthBridge sidecar process; exempted from redirect / drop |
95+
| `MODE` | `redirect` | all | `redirect` (envoy-sidecar) or `enforce-redirect` (proxy-sidecar / lite) |
96+
| `PROXY_UID` | `1337` | all | UID of the AuthBridge sidecar process; exempted from redirect |
13497
| `PROXY_PORT` | `15123` | redirect | AuthBridge outbound listener port |
13598
| `INBOUND_PROXY_PORT` | `15124` | redirect | AuthBridge inbound listener port |
13699
| `TRANSPARENT_PORT` | `8082` | enforce-redirect | AuthBridge transparent listener port; REDIRECT target for captured external TCP egress |
137100
| `OUTBOUND_PORTS_EXCLUDE` | (empty) | redirect | Comma-separated outbound port list to skip (e.g. `8080`) |
138101
| `INBOUND_PORTS_EXCLUDE` | (empty) | redirect | Comma-separated inbound port list to skip |
139-
| `POD_IP` | (required in `redirect`) | redirect | Set via Downward API; DNAT target for ambient-mesh inbound. Not used by `enforce-*`. |
140-
| `CLUSTER_CIDRS` | `10.0.0.0/8` | enforce-redirect, enforce-drop | Comma-separated in-cluster CIDRs allowed direct (pods/services/DNS) |
141-
| `CLUSTER_CIDRS6` | (empty) | enforce-redirect, enforce-drop | IPv6 in-cluster CIDRs (dual-stack); empty drops all external v6 egress |
102+
| `POD_IP` | (required in `redirect`) | redirect | Set via Downward API; DNAT target for ambient-mesh inbound. Not used by `enforce-redirect`. |
103+
| `CLUSTER_CIDRS` | `10.0.0.0/8` | enforce-redirect | Comma-separated in-cluster CIDRs allowed direct (pods/services/DNS) |
104+
| `CLUSTER_CIDRS6` | (empty) | enforce-redirect | IPv6 in-cluster CIDRs (dual-stack); empty drops all external v6 egress |
142105
| `IPTABLES_CMD` | auto-detected | all | Override iptables binary (`iptables-legacy` / `iptables-nft`) |
143-
| `IP6TABLES_CMD` | derived from `IPTABLES_CMD` | enforce-redirect, enforce-drop | Override ip6tables binary |
106+
| `IP6TABLES_CMD` | derived from `IPTABLES_CMD` | enforce-redirect | Override ip6tables binary |
144107

145108
## Required Kubernetes capabilities
146109

@@ -164,17 +127,14 @@ in [`.github/workflows/build.yaml`](../../.github/workflows/build.yaml)).
164127

165128
[`test-enforce-redirect.sh`](./test-enforce-redirect.sh) validates
166129
`enforce-redirect` mode in a private network namespace (`unshare --net`):
167-
it asserts the `AB_REDIRECT` rule structure, proves external TCP is
168-
captured to `TRANSPARENT_PORT` while preempting a simulated Istio ambient
169-
`nat OUTPUT` REDIRECT, and proves external UDP is dropped — all via packet
170-
counters. [`test-enforce-drop.sh`](./test-enforce-drop.sh) does the same
171-
for `enforce-drop` (asserts `AB_EGRESS` structure and the `mangle` DROP
172-
preemption). Both require root + iptables-nft on Linux (run on CI; not
130+
it asserts the `AB_REDIRECT` / `AB_NOTCP` rule structure, proves external
131+
TCP is captured to `TRANSPARENT_PORT` while preempting a simulated Istio
132+
ambient `nat OUTPUT` REDIRECT, and proves external UDP is dropped — all via
133+
packet counters. Requires root + iptables-nft on Linux (runs on CI; not
173134
macOS):
174135

175136
```sh
176137
sudo ./test-enforce-redirect.sh
177-
sudo ./test-enforce-drop.sh
178138
```
179139

180140
## Where it gets injected
@@ -186,11 +146,8 @@ container automatically:
186146
`envoy-sidecar`.
187147
- `enforce-redirect` mode (`MODE=enforce-redirect`) when the resolved
188148
AuthBridge mode is `proxy-sidecar` / `lite` — the transparent listener
189-
in those images receives the captured egress. _The operator wiring that
190-
sets this lands in the companion kagenti-operator PR; this PR adds the
191-
mode to the image and the transparent listener to the proxy._
192-
- `enforce-drop` mode (`MODE=enforce-drop`) — the drop-based fallback,
193-
retained for environments without the transparent listener.
149+
in those images receives the captured egress. This is always-on for
150+
those modes (the operator injects it unconditionally).
194151

195152
See
196153
[`authbridge/demos/weather-agent/demo-ui-advanced.md`](../demos/weather-agent/demo-ui-advanced.md)

authbridge/proxy-init/init-iptables.sh

Lines changed: 16 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,10 @@ set -e
141141
# In-cluster + loopback + proxy-UID traffic is left direct.
142142
# Nothing breaks for agents that ignore HTTP_PROXY — their
143143
# traffic is captured, not dropped. See setup_enforce_redirect().
144-
# enforce-drop — proxy-sidecar: a fail-closed egress guard that DROPs any
145-
# egress bypassing the proxy. Predates enforce-redirect;
146-
# retained for the no-transparent-listener fallback. The app
147-
# is configured with HTTP_PROXY pointing at AuthBridge's
148-
# forward proxy. Installs no REDIRECT and no inbound rules.
149-
# See setup_enforce_drop() below.
150144
MODE="${MODE:-redirect}"
151145
case "${MODE}" in
152-
redirect|enforce-redirect|enforce-drop) ;;
153-
*) echo "ERROR: unknown MODE='${MODE}' (expected: redirect | enforce-redirect | enforce-drop)" >&2; exit 1 ;;
146+
redirect|enforce-redirect) ;;
147+
*) echo "ERROR: unknown MODE='${MODE}' (expected: redirect | enforce-redirect)" >&2; exit 1 ;;
154148
esac
155149

156150
# --- Auto-detect iptables backend ---
@@ -184,10 +178,11 @@ SSH_PORT="${SSH_PORT:-22}"
184178
OUTBOUND_PORTS_EXCLUDE="${OUTBOUND_PORTS_EXCLUDE:-}"
185179
INBOUND_PORTS_EXCLUDE="${INBOUND_PORTS_EXCLUDE:-}"
186180

187-
# enforce-drop mode: in-cluster destinations the agent may reach directly
188-
# (pods / services / DNS) — everything else egressing the pod is dropped.
189-
# Defaults to the RFC1918 10/8 block which covers typical Kind pod (10.244/16)
190-
# and service (10.96/16) CIDRs; override with the cluster's actual ranges.
181+
# enforce-redirect mode: in-cluster destinations the agent may reach directly
182+
# (pods / services / DNS) — external TCP is REDIRECTed to the transparent
183+
# listener and external non-TCP is dropped. Defaults to the RFC1918 10/8 block
184+
# which covers typical Kind pod (10.244/16) and service (10.96/16) CIDRs;
185+
# override with the cluster's actual ranges.
191186
CLUSTER_CIDRS="${CLUSTER_CIDRS:-10.0.0.0/8}"
192187
CLUSTER_CIDRS6="${CLUSTER_CIDRS6:-}" # IPv6 in-cluster CIDRs (dual-stack); empty = none
193188

@@ -205,118 +200,23 @@ ISTIO_HEALTH_PROBE_SRC="${ISTIO_HEALTH_PROBE_SRC:-169.254.7.127}"
205200
# We use DNAT to the pod IP instead of REDIRECT to avoid needing route_localnet=1,
206201
# which would require a privileged init container (to write to read-only /proc/sys).
207202
# POD_IP is only needed by redirect mode (DNAT target for the ambient inbound
208-
# rule). enforce-drop does no DNAT, so it does not require it.
203+
# rule). enforce-redirect does no DNAT, so it does not require it.
209204
if [ "${MODE}" = "redirect" ] && [ -z "${POD_IP}" ]; then
210205
echo "ERROR: POD_IP environment variable is not set (required for redirect mode)." >&2
211206
echo "Set it via the Kubernetes Downward API (status.podIP) or manually." >&2
212207
exit 1
213208
fi
214209

215-
# =============================================================================
216-
# enforce-drop mode (proxy-sidecar fail-closed egress guard)
217-
# =============================================================================
218-
#
219-
# proxy-sidecar configures the app with HTTP_PROXY=127.0.0.1:<forward-proxy>.
220-
# Unlike redirect mode we do NOT transparently REDIRECT — you cannot redirect
221-
# raw traffic into a CONNECT forward proxy. Instead we DROP any egress that
222-
# leaves the pod without going through the proxy, forcing all external traffic
223-
# through AuthBridge regardless of whether the app honors HTTP_PROXY.
224-
#
225-
# Placement — a dedicated chain hooked from *mangle* OUTPUT at position 1:
226-
# * Istio ambient, when active, installs an in-pod `nat OUTPUT` REDIRECT
227-
# (ISTIO_OUTPUT -> ztunnel :15001). The netfilter OUTPUT hook order is
228-
# raw -> mangle -> nat -> filter, so a DROP in mangle evaluates the
229-
# ORIGINAL destination and fires BEFORE ambient's nat redirect can rewrite
230-
# it. A DROP in `filter` would run after nat and be defeated (dst already
231-
# rewritten to 127.0.0.1). -I 1 also places us ahead of Istio's appended
232-
# (-A) mangle ISTIO_OUTPUT chain.
233-
# * Works identically with no ambient, in-pod ambient, or node-level ambient
234-
# (in the node-level case our pod-netns rule runs before the packet ever
235-
# reaches the host netns).
236-
#
237-
# Rule order in the chain: RETURN ztunnel's own sockets (fwmark 0x539, a no-op
238-
# when ambient is absent) -> RETURN the proxy's own egress (PROXY_UID) ->
239-
# RETURN loopback (app -> proxy) -> RETURN in-cluster CIDRs (mesh/DNS) ->
240-
# DROP everything else (direct external egress, incl. UDP/QUIC).
241-
setup_enforce_drop() {
242-
CHAIN="AB_EGRESS"
243-
244-
echo "enforce-drop: installing fail-closed egress guard (mangle OUTPUT, chain ${CHAIN})"
245-
echo "enforce-drop: exempt proxy UID=${PROXY_UID}; allowed in-cluster CIDRs=${CLUSTER_CIDRS}"
246-
247-
# --- IPv4 ---
248-
${IPT} -t mangle -N "${CHAIN}" 2>/dev/null || true
249-
${IPT} -t mangle -F "${CHAIN}"
250-
# Replies to inbound connections (and related flows) are locally generated and
251-
# also traverse OUTPUT — a reply is never a "bypass". Let established/related
252-
# traffic through FIRST, so e.g. kubelet health-probe responses to an
253-
# off-cluster node IP (in Kind the node is 172.18.0.0/16, outside CLUSTER_CIDRS)
254-
# are not caught by the terminal DROP. Only NEW app-initiated flows are gated.
255-
${IPT} -t mangle -A "${CHAIN}" -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
256-
# ztunnel's own sockets (ambient) carry fwmark 0x539 — let them through so the
257-
# mesh/HBONE path keeps working. No-op when ambient is not installed.
258-
${IPT} -t mangle -A "${CHAIN}" -m mark --mark "${ZTUNNEL_MARK}" -j RETURN
259-
# the AuthBridge proxy's own re-originated egress (must run as PROXY_UID).
260-
${IPT} -t mangle -A "${CHAIN}" -m owner --uid-owner "${PROXY_UID}" -j RETURN
261-
# app -> proxy over loopback (HTTP_PROXY target), and any loopback traffic.
262-
${IPT} -t mangle -A "${CHAIN}" -o lo -j RETURN
263-
${IPT} -t mangle -A "${CHAIN}" -d 127.0.0.0/8 -j RETURN
264-
# in-cluster traffic (pods / services / DNS) — carried by the mesh, not the proxy.
265-
for cidr in $(echo "${CLUSTER_CIDRS}" | tr ',' ' '); do
266-
[ -n "${cidr}" ] && ${IPT} -t mangle -A "${CHAIN}" -d "${cidr}" -j RETURN
267-
done
268-
# everything else == direct external egress that bypassed the proxy. Drop it.
269-
# No -p filter, so UDP (QUIC/HTTP-3) is dropped as well as TCP.
270-
${IPT} -t mangle -A "${CHAIN}" -j DROP
271-
# Hook at position 1 so we run before any appended Istio mangle chain and
272-
# before nat OUTPUT.
273-
if ! ${IPT} -t mangle -C OUTPUT -j "${CHAIN}" 2>/dev/null; then
274-
${IPT} -t mangle -I OUTPUT 1 -j "${CHAIN}"
275-
fi
276-
echo "enforce-drop: IPv4 egress guard configured"
277-
278-
# --- IPv6 ---
279-
# Cluster is IPv4-only by default; until v6 cluster CIDRs are wired
280-
# (CLUSTER_CIDRS6), drop external v6 egress while allowing: established/related
281-
# replies, loopback, link-local unicast (fe80::/10) and link-local multicast
282-
# (ff02::/16, which carries NDP neighbor/router solicitations and MLD), the
283-
# proxy UID, and ztunnel's mark.
284-
if command -v "${IP6T%% *}" >/dev/null 2>&1 && ${IP6T} -t mangle -L >/dev/null 2>&1; then
285-
${IP6T} -t mangle -N "${CHAIN}" 2>/dev/null || true
286-
${IP6T} -t mangle -F "${CHAIN}"
287-
${IP6T} -t mangle -A "${CHAIN}" -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
288-
${IP6T} -t mangle -A "${CHAIN}" -m mark --mark "${ZTUNNEL_MARK}" -j RETURN
289-
${IP6T} -t mangle -A "${CHAIN}" -m owner --uid-owner "${PROXY_UID}" -j RETURN
290-
${IP6T} -t mangle -A "${CHAIN}" -o lo -j RETURN
291-
${IP6T} -t mangle -A "${CHAIN}" -d ::1/128 -j RETURN
292-
${IP6T} -t mangle -A "${CHAIN}" -d fe80::/10 -j RETURN
293-
${IP6T} -t mangle -A "${CHAIN}" -d ff02::/16 -j RETURN
294-
for cidr in $(echo "${CLUSTER_CIDRS6}" | tr ',' ' '); do
295-
[ -n "${cidr}" ] && ${IP6T} -t mangle -A "${CHAIN}" -d "${cidr}" -j RETURN
296-
done
297-
${IP6T} -t mangle -A "${CHAIN}" -j DROP
298-
if ! ${IP6T} -t mangle -C OUTPUT -j "${CHAIN}" 2>/dev/null; then
299-
${IP6T} -t mangle -I OUTPUT 1 -j "${CHAIN}"
300-
fi
301-
echo "enforce-drop: IPv6 egress guard configured"
302-
else
303-
echo "enforce-drop: ip6tables unavailable — skipping IPv6 egress guard"
304-
fi
305-
306-
echo "enforce-drop: fail-closed egress guard active"
307-
}
308-
309210
# =============================================================================
310211
# enforce-redirect mode (proxy-sidecar fail-closed egress guard, capture variant)
311212
# =============================================================================
312213
#
313-
# Same intent as enforce-drop — force all external egress through AuthBridge
314-
# regardless of whether the app honors HTTP_PROXY — but instead of DROPping
315-
# bypass traffic, it CAPTURES it: external TCP is transparently REDIRECTed to
316-
# the forward proxy's transparent listener (TRANSPARENT_PORT), which recovers
317-
# the original destination via SO_ORIGINAL_DST and tunnels it through the same
318-
# outbound pipeline. Because nothing is dropped, agents that ignore HTTP_PROXY
319-
# keep working — this is what lets enforcement be always-on.
214+
# Forces all external egress through AuthBridge regardless of whether the app
215+
# honors HTTP_PROXY, by CAPTURING bypass traffic: external TCP is transparently
216+
# REDIRECTed to the forward proxy's transparent listener (TRANSPARENT_PORT),
217+
# which recovers the original destination via SO_ORIGINAL_DST and tunnels it
218+
# through the same outbound pipeline. Because nothing is dropped, agents that
219+
# ignore HTTP_PROXY keep working — this is what lets enforcement be always-on.
320220
#
321221
# Placement — a dedicated chain hooked from *nat* OUTPUT at position 1 (REDIRECT
322222
# is a nat-table target). Inserted before Istio's appended ISTIO_OUTPUT so we
@@ -330,7 +230,7 @@ setup_enforce_drop() {
330230
# TRANSPARENT_PORT -> DROP all other external egress (UDP/QUIC, so HTTP/3 can't
331231
# bypass; well-behaved clients fall back to TCP and get captured).
332232
#
333-
# Unlike enforce-drop there is no conntrack ESTABLISHED rule: nat only evaluates
233+
# The nat REDIRECT chain has no conntrack ESTABLISHED rule: nat only evaluates
334234
# the first packet of a flow, so replies and established connections are not
335235
# re-translated.
336236
# Two chains are needed because REDIRECT is a nat-table target but the nat table
@@ -438,16 +338,12 @@ setup_enforce_redirect() {
438338
echo "enforce-redirect: fail-closed egress capture active"
439339
}
440340

441-
# Dispatch enforce-* modes here and exit; redirect mode falls through to the
341+
# Dispatch enforce-redirect here and exit; redirect mode falls through to the
442342
# transparent-interception logic below.
443343
if [ "${MODE}" = "enforce-redirect" ]; then
444344
setup_enforce_redirect
445345
exit 0
446346
fi
447-
if [ "${MODE}" = "enforce-drop" ]; then
448-
setup_enforce_drop
449-
exit 0
450-
fi
451347

452348
# =============================================================================
453349
# OUTBOUND traffic interception (nat OUTPUT)

0 commit comments

Comments
 (0)