Skip to content

Commit 91708b7

Browse files
committed
feat(proxy-init): add enforce-drop mode for proxy-sidecar egress enforcement
proxy-sidecar mode routes outbound traffic through AuthBridge via the HTTP_PROXY env var, which is purely cooperative: a workload that ignores HTTP_PROXY (or sets NO_PROXY) egresses directly and bypasses AuthBridge entirely. Add a fail-closed egress guard so all external traffic is forced through the forward proxy regardless of whether the workload honors HTTP_PROXY. init-iptables.sh gains a MODE switch. Default `redirect` preserves the existing envoy-sidecar behavior byte-for-byte. MODE=enforce-drop builds an AB_EGRESS chain hooked from mangle OUTPUT at position 1: - RETURN ztunnel sockets (fwmark 0x539), the proxy UID, loopback, and the in-cluster CIDRs (pods/services/DNS) - DROP everything else (direct external egress, including UDP/QUIC) plus an IPv6 mirror that drops external v6 egress. Placement is the mangle table, not filter: when Istio ambient is active it installs an in-pod `nat OUTPUT` REDIRECT (ISTIO_OUTPUT -> ztunnel :15001). The netfilter OUTPUT hook order is raw -> mangle -> nat -> filter, so a DROP in mangle evaluates the original destination and fires before ambient's nat redirect can rewrite it; a DROP in filter would run after nat and be defeated. -I 1 also keeps us ahead of Istio's appended mangle chain. This makes the guard robust with no ambient, in-pod ambient, or node-level ambient. test-enforce-drop.sh validates the rule structure and proves the mangle DROP preempts a simulated ISTIO_OUTPUT nat REDIRECT via packet counters, in an unshare --net namespace. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
1 parent a7ebd87 commit 91708b7

3 files changed

Lines changed: 289 additions & 24 deletions

File tree

authbridge/proxy-init/README.md

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# proxy-init
22

3-
The `proxy-init` container sets up iptables rules so that traffic in
4-
and out of an AuthBridge-injected pod is transparently redirected to
5-
the AuthBridge sidecar's listeners. It runs once at pod startup as a
6-
Kubernetes init container, then exits.
3+
The `proxy-init` container programs iptables rules for an
4+
AuthBridge-injected pod. It runs once at pod startup as a Kubernetes
5+
init container, then exits. It has two modes, selected by the `MODE`
6+
env var:
77

8-
**`proxy-init` is only used in `envoy-sidecar` mode.** In
9-
`proxy-sidecar` mode (the AuthBridge cluster default after
10-
kagenti-operator#361) traffic interception is done via `HTTP_PROXY`
11-
env vars on the workload container — no iptables, no init container.
8+
| `MODE` | Used by | What it does |
9+
|---|---|---|
10+
| `redirect` (default) | `envoy-sidecar` | Transparently **REDIRECT**s pod traffic to the Envoy listeners. |
11+
| `enforce-drop` | `proxy-sidecar` | Fail-closed egress guard — **DROP**s any egress that bypasses the forward proxy. |
1212

13-
## What it does
13+
## `redirect` mode (envoy-sidecar)
1414

1515
`init-iptables.sh` writes iptables rules that:
1616

@@ -28,21 +28,60 @@ env vars on the workload container — no iptables, no init container.
2828
`INBOUND_PORTS_EXCLUDE` env vars (commonly used to exclude
2929
Keycloak's port 8080 to avoid token-exchange loops).
3030

31+
## `enforce-drop` mode (proxy-sidecar)
32+
33+
In `proxy-sidecar` mode the workload is configured with `HTTP_PROXY`
34+
pointing at AuthBridge's forward proxy. On its own that is purely
35+
cooperative — an app that ignores `HTTP_PROXY` (or sets `NO_PROXY`)
36+
egresses directly and bypasses AuthBridge. `enforce-drop` closes that
37+
gap **without** transparently redirecting (you cannot REDIRECT raw
38+
traffic into a CONNECT forward proxy): it installs a fail-closed guard
39+
that DROPs any direct egress, forcing all external traffic through the
40+
proxy regardless of whether the app honors `HTTP_PROXY`.
41+
42+
`init-iptables.sh` builds a dedicated `AB_EGRESS` chain hooked from
43+
**`mangle` OUTPUT at position 1**, with this order:
44+
45+
1. `RETURN` ztunnel's own sockets (fwmark `0x539`) — keeps the mesh path working; a no-op when ambient is absent.
46+
2. `RETURN` the proxy's own re-originated egress (`--uid-owner $PROXY_UID`, default 1337).
47+
3. `RETURN` loopback (the app → proxy hop) and in-cluster CIDRs (`CLUSTER_CIDRS`, mesh/DNS).
48+
4. `DROP` everything else — direct external egress, including UDP (QUIC/HTTP-3).
49+
50+
An IPv6 mirror drops external v6 egress (allowing loopback, link-local,
51+
the proxy UID, and `CLUSTER_CIDRS6`).
52+
53+
**Why `mangle` OUTPUT, not `filter`:** when Istio ambient is active it
54+
installs an in-pod `nat OUTPUT` REDIRECT (`ISTIO_OUTPUT` → ztunnel
55+
`:15001`). The netfilter OUTPUT hook order is `raw → mangle → nat →
56+
filter`, so a DROP in `mangle` evaluates the original destination and
57+
fires **before** ambient's nat redirect can rewrite it; a DROP in
58+
`filter` would run after nat and be defeated. `-I 1` also places the
59+
chain ahead of Istio's appended (`-A`) mangle chain. This makes the
60+
guard robust with no ambient, in-pod ambient, or node-level ambient.
61+
See [`test-enforce-drop.sh`](./test-enforce-drop.sh), which proves the
62+
preemption via packet counters.
63+
64+
## iptables backend
65+
3166
The script auto-detects `iptables-legacy` vs `iptables-nft` and uses
32-
whichever the host kernel exposes. Override with `IPTABLES_CMD` if
33-
needed.
67+
whichever the host kernel exposes. Override with `IPTABLES_CMD` (and
68+
`IP6TABLES_CMD`) if needed.
3469

3570
## Environment variables
3671

37-
| Variable | Default | Purpose |
38-
|---|---|---|
39-
| `PROXY_PORT` | `15123` | AuthBridge outbound listener port |
40-
| `INBOUND_PROXY_PORT` | `15124` | AuthBridge inbound listener port |
41-
| `PROXY_UID` | `1337` | UID of the AuthBridge sidecar process; excluded from outbound redirect |
42-
| `OUTBOUND_PORTS_EXCLUDE` | (empty) | Comma-separated outbound port list to skip (e.g. `8080`) |
43-
| `INBOUND_PORTS_EXCLUDE` | (empty) | Comma-separated inbound port list to skip |
44-
| `POD_IP` | (required) | Set via Downward API; used as the DNAT target for ambient-mesh inbound |
45-
| `IPTABLES_CMD` | auto-detected | Override iptables binary (`iptables-legacy` / `iptables-nft`) |
72+
| Variable | Default | Mode | Purpose |
73+
|---|---|---|---|
74+
| `MODE` | `redirect` | both | `redirect` (envoy-sidecar) or `enforce-drop` (proxy-sidecar) |
75+
| `PROXY_UID` | `1337` | both | UID of the AuthBridge sidecar process; exempted from redirect / drop |
76+
| `PROXY_PORT` | `15123` | redirect | AuthBridge outbound listener port |
77+
| `INBOUND_PROXY_PORT` | `15124` | redirect | AuthBridge inbound listener port |
78+
| `OUTBOUND_PORTS_EXCLUDE` | (empty) | redirect | Comma-separated outbound port list to skip (e.g. `8080`) |
79+
| `INBOUND_PORTS_EXCLUDE` | (empty) | redirect | Comma-separated inbound port list to skip |
80+
| `POD_IP` | (required in `redirect`) | redirect | Set via Downward API; DNAT target for ambient-mesh inbound. Not used by `enforce-drop`. |
81+
| `CLUSTER_CIDRS` | `10.0.0.0/8` | enforce-drop | Comma-separated in-cluster CIDRs allowed direct (pods/services/DNS) |
82+
| `CLUSTER_CIDRS6` | (empty) | enforce-drop | IPv6 in-cluster CIDRs (dual-stack); empty drops all external v6 egress |
83+
| `IPTABLES_CMD` | auto-detected | both | Override iptables binary (`iptables-legacy` / `iptables-nft`) |
84+
| `IP6TABLES_CMD` | derived from `IPTABLES_CMD` | enforce-drop | Override ip6tables binary |
4685

4786
## Required Kubernetes capabilities
4887

@@ -62,11 +101,30 @@ The image is published from CI as
62101
`ghcr.io/kagenti/kagenti-extensions/proxy-init:<tag>` (build defined
63102
in [`.github/workflows/build.yaml`](../../.github/workflows/build.yaml)).
64103

104+
## Testing
105+
106+
[`test-enforce-drop.sh`](./test-enforce-drop.sh) validates `enforce-drop`
107+
mode in a private network namespace (`unshare --net`): it asserts the
108+
`AB_EGRESS` rule structure and proves the `mangle` DROP preempts a
109+
simulated Istio ambient `nat OUTPUT` REDIRECT via packet counters.
110+
Requires root + iptables-nft on Linux (runs on CI; not macOS):
111+
112+
```sh
113+
sudo ./test-enforce-drop.sh
114+
```
115+
65116
## Where it gets injected
66117

67118
The kagenti-operator's mutating webhook injects the proxy-init
68-
container automatically when the resolved AuthBridge mode is
69-
`envoy-sidecar`. See
119+
container automatically:
120+
121+
- `redirect` mode (`MODE` unset) when the resolved AuthBridge mode is
122+
`envoy-sidecar`.
123+
- `enforce-drop` mode (`MODE=enforce-drop`) when `proxy-sidecar`
124+
egress enforcement is enabled (opt-in; see the kagenti-operator
125+
injector wiring).
126+
127+
See
70128
[`authbridge/demos/weather-agent/demo-ui-advanced.md`](../demos/weather-agent/demo-ui-advanced.md)
71129
for an end-to-end demo and
72130
[`authbridge/demos/token-exchange-routes/README.md`](../demos/token-exchange-routes/README.md)

authbridge/proxy-init/init-iptables.sh

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@
126126

127127
set -e
128128

129+
# --- Mode selection ---
130+
# MODE selects the interception strategy:
131+
# redirect (default) — envoy-sidecar: transparently REDIRECT pod traffic
132+
# to the Envoy listeners (the behavior documented above).
133+
# enforce-drop — proxy-sidecar: a fail-closed egress guard. The app is
134+
# configured with HTTP_PROXY pointing at AuthBridge's forward
135+
# proxy; this mode DROPs any egress that bypasses the proxy,
136+
# forcing all external traffic through AuthBridge regardless of
137+
# whether the app honors HTTP_PROXY. It installs no REDIRECT and
138+
# no PREROUTING/inbound rules. See setup_enforce_drop() below.
139+
MODE="${MODE:-redirect}"
140+
case "${MODE}" in
141+
redirect|enforce-drop) ;;
142+
*) echo "ERROR: unknown MODE='${MODE}' (expected: redirect | enforce-drop)" >&2; exit 1 ;;
143+
esac
144+
129145
# --- Auto-detect iptables backend ---
130146
# Prefer iptables-legacy for maximum compatibility with Kubernetes networking.
131147
# The nft backend sets rules in a different netfilter table that may not be
@@ -153,6 +169,17 @@ SSH_PORT="${SSH_PORT:-22}"
153169
OUTBOUND_PORTS_EXCLUDE="${OUTBOUND_PORTS_EXCLUDE:-}"
154170
INBOUND_PORTS_EXCLUDE="${INBOUND_PORTS_EXCLUDE:-}"
155171

172+
# enforce-drop mode: in-cluster destinations the agent may reach directly
173+
# (pods / services / DNS) — everything else egressing the pod is dropped.
174+
# Defaults to the RFC1918 10/8 block which covers typical Kind pod (10.244/16)
175+
# and service (10.96/16) CIDRs; override with the cluster's actual ranges.
176+
CLUSTER_CIDRS="${CLUSTER_CIDRS:-10.0.0.0/8}"
177+
CLUSTER_CIDRS6="${CLUSTER_CIDRS6:-}" # IPv6 in-cluster CIDRs (dual-stack); empty = none
178+
179+
# IPv6 counterpart of the detected iptables backend (iptables-legacy ->
180+
# ip6tables-legacy, iptables -> ip6tables). Override with IP6TABLES_CMD.
181+
IP6T="${IP6TABLES_CMD:-$(echo "${IPT}" | sed 's/iptables/ip6tables/')}"
182+
156183
# Istio ztunnel defaults
157184
ZTUNNEL_HBONE_PORT="${ZTUNNEL_HBONE_PORT:-15008}"
158185
ZTUNNEL_MARK="${ZTUNNEL_MARK:-0x539/0xfff}" # 0x539 = 1337 decimal, ztunnel's socket fwmark
@@ -162,12 +189,105 @@ ISTIO_HEALTH_PROBE_SRC="${ISTIO_HEALTH_PROBE_SRC:-169.254.7.127}"
162189
# It must be passed via the Kubernetes Downward API (status.podIP) or set manually.
163190
# We use DNAT to the pod IP instead of REDIRECT to avoid needing route_localnet=1,
164191
# which would require a privileged init container (to write to read-only /proc/sys).
165-
if [ -z "${POD_IP}" ]; then
166-
echo "ERROR: POD_IP environment variable is not set." >&2
192+
# POD_IP is only needed by redirect mode (DNAT target for the ambient inbound
193+
# rule). enforce-drop does no DNAT, so it does not require it.
194+
if [ "${MODE}" = "redirect" ] && [ -z "${POD_IP}" ]; then
195+
echo "ERROR: POD_IP environment variable is not set (required for redirect mode)." >&2
167196
echo "Set it via the Kubernetes Downward API (status.podIP) or manually." >&2
168197
exit 1
169198
fi
170199

200+
# =============================================================================
201+
# enforce-drop mode (proxy-sidecar fail-closed egress guard)
202+
# =============================================================================
203+
#
204+
# proxy-sidecar configures the app with HTTP_PROXY=127.0.0.1:<forward-proxy>.
205+
# Unlike redirect mode we do NOT transparently REDIRECT — you cannot redirect
206+
# raw traffic into a CONNECT forward proxy. Instead we DROP any egress that
207+
# leaves the pod without going through the proxy, forcing all external traffic
208+
# through AuthBridge regardless of whether the app honors HTTP_PROXY.
209+
#
210+
# Placement — a dedicated chain hooked from *mangle* OUTPUT at position 1:
211+
# * Istio ambient, when active, installs an in-pod `nat OUTPUT` REDIRECT
212+
# (ISTIO_OUTPUT -> ztunnel :15001). The netfilter OUTPUT hook order is
213+
# raw -> mangle -> nat -> filter, so a DROP in mangle evaluates the
214+
# ORIGINAL destination and fires BEFORE ambient's nat redirect can rewrite
215+
# it. A DROP in `filter` would run after nat and be defeated (dst already
216+
# rewritten to 127.0.0.1). -I 1 also places us ahead of Istio's appended
217+
# (-A) mangle ISTIO_OUTPUT chain.
218+
# * Works identically with no ambient, in-pod ambient, or node-level ambient
219+
# (in the node-level case our pod-netns rule runs before the packet ever
220+
# reaches the host netns).
221+
#
222+
# Rule order in the chain: RETURN ztunnel's own sockets (fwmark 0x539, a no-op
223+
# when ambient is absent) -> RETURN the proxy's own egress (PROXY_UID) ->
224+
# RETURN loopback (app -> proxy) -> RETURN in-cluster CIDRs (mesh/DNS) ->
225+
# DROP everything else (direct external egress, incl. UDP/QUIC).
226+
setup_enforce_drop() {
227+
CHAIN="AB_EGRESS"
228+
229+
echo "enforce-drop: installing fail-closed egress guard (mangle OUTPUT, chain ${CHAIN})"
230+
echo "enforce-drop: exempt proxy UID=${PROXY_UID}; allowed in-cluster CIDRs=${CLUSTER_CIDRS}"
231+
232+
# --- IPv4 ---
233+
${IPT} -t mangle -N "${CHAIN}" 2>/dev/null || true
234+
${IPT} -t mangle -F "${CHAIN}"
235+
# ztunnel's own sockets (ambient) carry fwmark 0x539 — let them through so the
236+
# mesh/HBONE path keeps working. No-op when ambient is not installed.
237+
${IPT} -t mangle -A "${CHAIN}" -m mark --mark "${ZTUNNEL_MARK}" -j RETURN
238+
# the AuthBridge proxy's own re-originated egress (must run as PROXY_UID).
239+
${IPT} -t mangle -A "${CHAIN}" -m owner --uid-owner "${PROXY_UID}" -j RETURN
240+
# app -> proxy over loopback (HTTP_PROXY target), and any loopback traffic.
241+
${IPT} -t mangle -A "${CHAIN}" -o lo -j RETURN
242+
${IPT} -t mangle -A "${CHAIN}" -d 127.0.0.0/8 -j RETURN
243+
# in-cluster traffic (pods / services / DNS) — carried by the mesh, not the proxy.
244+
for cidr in $(echo "${CLUSTER_CIDRS}" | tr ',' ' '); do
245+
[ -n "${cidr}" ] && ${IPT} -t mangle -A "${CHAIN}" -d "${cidr}" -j RETURN
246+
done
247+
# everything else == direct external egress that bypassed the proxy. Drop it.
248+
# No -p filter, so UDP (QUIC/HTTP-3) is dropped as well as TCP.
249+
${IPT} -t mangle -A "${CHAIN}" -j DROP
250+
# Hook at position 1 so we run before any appended Istio mangle chain and
251+
# before nat OUTPUT.
252+
if ! ${IPT} -t mangle -C OUTPUT -j "${CHAIN}" 2>/dev/null; then
253+
${IPT} -t mangle -I OUTPUT 1 -j "${CHAIN}"
254+
fi
255+
echo "enforce-drop: IPv4 egress guard configured"
256+
257+
# --- IPv6 ---
258+
# Cluster is IPv4-only by default; until v6 cluster CIDRs are wired
259+
# (CLUSTER_CIDRS6), drop all external v6 egress while allowing loopback,
260+
# link-local (NDP), the proxy UID, and ztunnel's mark.
261+
if command -v "${IP6T%% *}" >/dev/null 2>&1 && ${IP6T} -t mangle -L >/dev/null 2>&1; then
262+
${IP6T} -t mangle -N "${CHAIN}" 2>/dev/null || true
263+
${IP6T} -t mangle -F "${CHAIN}"
264+
${IP6T} -t mangle -A "${CHAIN}" -m mark --mark "${ZTUNNEL_MARK}" -j RETURN
265+
${IP6T} -t mangle -A "${CHAIN}" -m owner --uid-owner "${PROXY_UID}" -j RETURN
266+
${IP6T} -t mangle -A "${CHAIN}" -o lo -j RETURN
267+
${IP6T} -t mangle -A "${CHAIN}" -d ::1/128 -j RETURN
268+
${IP6T} -t mangle -A "${CHAIN}" -d fe80::/10 -j RETURN
269+
for cidr in $(echo "${CLUSTER_CIDRS6}" | tr ',' ' '); do
270+
[ -n "${cidr}" ] && ${IP6T} -t mangle -A "${CHAIN}" -d "${cidr}" -j RETURN
271+
done
272+
${IP6T} -t mangle -A "${CHAIN}" -j DROP
273+
if ! ${IP6T} -t mangle -C OUTPUT -j "${CHAIN}" 2>/dev/null; then
274+
${IP6T} -t mangle -I OUTPUT 1 -j "${CHAIN}"
275+
fi
276+
echo "enforce-drop: IPv6 egress guard configured"
277+
else
278+
echo "enforce-drop: ip6tables unavailable — skipping IPv6 egress guard"
279+
fi
280+
281+
echo "enforce-drop: fail-closed egress guard active"
282+
}
283+
284+
# Dispatch enforce-drop here and exit; redirect mode falls through to the
285+
# transparent-interception logic below.
286+
if [ "${MODE}" = "enforce-drop" ]; then
287+
setup_enforce_drop
288+
exit 0
289+
fi
290+
171291
# =============================================================================
172292
# OUTBOUND traffic interception (nat OUTPUT)
173293
# =============================================================================
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Test harness for init-iptables.sh "enforce-drop" mode (proxy-sidecar
4+
# fail-closed egress guard).
5+
#
6+
# It validates two things in a private network namespace:
7+
# 1. Rule STRUCTURE — the AB_EGRESS chain is hooked from mangle OUTPUT at
8+
# position 1 with the expected RETURN exemptions and a terminal DROP, and
9+
# that no nat/filter rules are created.
10+
# 2. AMBIENT ROBUSTNESS — a DROP in mangle OUTPUT preempts a simulated Istio
11+
# ambient "nat OUTPUT REDIRECT" (ISTIO_OUTPUT). Proven via packet counters:
12+
# after generating an external SYN, the mangle DROP increments and the nat
13+
# REDIRECT does NOT.
14+
#
15+
# Requirements: root (for unshare --net + iptables), iproute2, iptables-nft,
16+
# bash, the dummy kernel module. Runs on Linux / CI (e.g. ubuntu-latest); not
17+
# on macOS. Uses `unshare --net` (not named `ip netns`) so it also works inside
18+
# nested containers. Exit code 0 = all pass.
19+
set -u
20+
21+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
22+
INIT="${INIT_SCRIPT:-${SCRIPT_DIR}/init-iptables.sh}"
23+
IPT="${IPTABLES_CMD:-iptables-nft}"
24+
EXTERNAL="198.51.100.7" # RFC5737 TEST-NET-2, guaranteed unused
25+
26+
# Re-exec into a private network namespace. unshare avoids the /sys remount that
27+
# named `ip netns exec` performs, so this works inside nested containers too.
28+
if [ -z "${_AB_NETNS_REEXEC:-}" ]; then
29+
exec unshare --net env _AB_NETNS_REEXEC=1 INIT_SCRIPT="${INIT}" \
30+
IPTABLES_CMD="${IPT}" bash "$0" "$@"
31+
fi
32+
33+
fail=0
34+
35+
# Fresh netns: bring up lo and a dummy default route so packets to an external
36+
# destination are actually generated and traverse the OUTPUT chain.
37+
ip link set lo up
38+
if ip link add eth-test type dummy 2>/dev/null; then
39+
ip addr add 10.255.255.2/24 dev eth-test
40+
ip link set eth-test up
41+
ip route add default via 10.255.255.1
42+
else
43+
echo "WARN: dummy interface unavailable; preemption packet may not be generated"
44+
fi
45+
46+
echo "### Installing enforce-drop rules"
47+
env MODE=enforce-drop PROXY_UID=1337 CLUSTER_CIDRS=10.0.0.0/8 \
48+
IPTABLES_CMD="${IPT}" IP6TABLES_CMD=ip6tables-nft \
49+
sh "${INIT}" || { echo "FAIL: init script exited non-zero"; exit 1; }
50+
51+
dump=$("${IPT}" -t mangle -S)
52+
echo "--- mangle ruleset ---"; echo "${dump}"
53+
54+
assert() { if echo "${dump}" | grep -qE "$2"; then echo "PASS: $1"; else echo "FAIL: $1"; fail=1; fi; }
55+
assert "AB_EGRESS hooked from OUTPUT" '^-A OUTPUT -j AB_EGRESS'
56+
assert "ztunnel mark RETURN" 'AB_EGRESS .*mark.*0x539.*-j RETURN'
57+
assert "proxy UID RETURN" 'AB_EGRESS .*--uid-owner 1337 -j RETURN'
58+
assert "loopback iface RETURN" 'AB_EGRESS -o lo -j RETURN'
59+
assert "loopback cidr RETURN" 'AB_EGRESS -d 127.0.0.0/8 -j RETURN'
60+
assert "cluster cidr RETURN" 'AB_EGRESS -d 10.0.0.0/8 -j RETURN'
61+
assert "terminal DROP" 'AB_EGRESS -j DROP'
62+
63+
pos1=$("${IPT}" -t mangle -L OUTPUT --line-numbers -n | awk '$1=="1"{print $2}')
64+
if [ "${pos1}" = "AB_EGRESS" ]; then echo "PASS: AB_EGRESS at OUTPUT position 1"
65+
else echo "FAIL: AB_EGRESS not at OUTPUT position 1 (got '${pos1}')"; fail=1; fi
66+
67+
natcount=$("${IPT}" -t nat -S | grep -cE 'AB_EGRESS|REDIRECT|PROXY_' || true)
68+
if [ "${natcount:-0}" -eq 0 ]; then echo "PASS: no nat-table rules created"
69+
else echo "FAIL: enforce-drop created nat rules"; fail=1; fi
70+
71+
echo "### Ambient-preemption test: append a simulated ISTIO_OUTPUT nat REDIRECT"
72+
"${IPT}" -t nat -A OUTPUT -p tcp -d "${EXTERNAL}" -j REDIRECT --to-ports 19999
73+
# Generate an external SYN (uid 0, like an agent bypass attempt).
74+
timeout 2 bash -c "exec 3<>/dev/tcp/${EXTERNAL}/80" 2>/dev/null || true
75+
76+
dropc=$("${IPT}" -t mangle -L AB_EGRESS -n -v | awk '/DROP/{print $1; exit}')
77+
redirc=$("${IPT}" -t nat -L OUTPUT -n -v | awk '/REDIRECT/{print $1; exit}')
78+
echo "mangle AB_EGRESS DROP pkts=${dropc:-?} | nat REDIRECT pkts=${redirc:-?}"
79+
if [ "${dropc:-0}" -gt 0 ] && [ "${redirc:-0}" -eq 0 ]; then
80+
echo "PASS: mangle DROP preempted nat REDIRECT (ambient-robust)"
81+
else
82+
echo "FAIL: preemption not demonstrated (DROP=${dropc:-?}, REDIRECT=${redirc:-?})"; fail=1
83+
fi
84+
85+
echo
86+
[ "${fail}" -eq 0 ] && echo "ALL TESTS PASSED" || echo "SOME TESTS FAILED"
87+
exit "${fail}"

0 commit comments

Comments
 (0)