The proxy-init container programs iptables rules for an
AuthBridge-injected pod. It runs once at pod startup as a Kubernetes
init container, then exits. It has two modes, selected by the MODE
env var:
MODE |
Used by | What it does |
|---|---|---|
redirect (default) |
envoy-sidecar |
Transparently REDIRECTs pod traffic to the Envoy listeners. |
enforce-redirect |
proxy-sidecar, lite |
Fail-closed egress guard that captures: REDIRECTs bypassing TCP — external and in-cluster (except in-cluster DNS) — to AuthBridge's transparent listener; DROPs non-TCP external egress; leaves in-cluster non-TCP (DNS/UDP) direct. |
init-iptables.sh writes iptables rules that:
- Outbound — Redirect traffic leaving the workload container to AuthBridge's outbound listener (port 15123). Adds an exclusion for the AuthBridge sidecar's own UID (1337) so its traffic doesn't loop back into itself.
- Inbound — Redirect traffic arriving at the workload container's service port to AuthBridge's inbound listener (port 15124).
- Istio ambient coexistence — Cooperates with ztunnel by
preserving the Istio fwmark (0x539) and respecting the HBONE port
(15008). Designed to work alongside
istio.io/dataplane-mode: ambient. - Configurable exclusions — Honors
OUTBOUND_PORTS_EXCLUDEandINBOUND_PORTS_EXCLUDEenv vars (commonly used to exclude Keycloak's port 8080 to avoid token-exchange loops).
In proxy-sidecar mode the workload is configured with HTTP_PROXY
pointing at AuthBridge's forward proxy. On its own that is purely
cooperative — an app that ignores HTTP_PROXY (or sets NO_PROXY)
egresses directly and bypasses AuthBridge. enforce-redirect closes
that gap by capturing the bypass traffic instead of dropping it:
external TCP that did not go through the forward proxy is transparently
REDIRECTed to AuthBridge's transparent listener (TRANSPARENT_PORT,
default 8082), which recovers the original destination via
SO_ORIGINAL_DST and tunnels it through the same outbound pipeline.
Because nothing is dropped, agents that ignore HTTP_PROXY keep working
— which is what lets enforcement be always-on.
init-iptables.sh installs two chains, because REDIRECT is a
nat-table target but the nat table forbids DROP (iptables errors with
"the use of DROP is therefore inhibited"):
natOUTPUT /AB_REDIRECT(position 1):RETURNztunnel mark0x539, the proxy UID (--uid-owner $PROXY_UID, avoids the loop), loopback, and DNS-over-TCP (-p tcp --dport 53) to each/etc/resolv.confnameserver (so cluster name resolution stays direct); thenREDIRECTall remaining TCP — external and in-cluster — toTRANSPARENT_PORT, so agent→in-cluster calls (e.g. agent→tool) are captured by the egress pipeline too.mangleOUTPUT /AB_NOTCP(position 1): the same UID/mark/loopback exemptions (plusESTABLISHED,RELATEDfirst, so UDP conntrack replies like DNS pass), then DNS-over-UDP (-p udp --dport 53) to each resolv.conf nameserver so cluster DNS keeps working; then-p tcp -j RETURN(TCP is handled by the nat REDIRECT) and a terminalDROPfor all other non-TCP (UDP/QUIC), so HTTP/3 cannot bypass and non-DNS in-cluster UDP is dropped too — well-behaved clients fall back to TCP and get captured.
Because the OUTPUT hook order is raw → mangle → nat → filter, the
mangle chain drops non-TCP on its original destination while TCP falls
through to the nat REDIRECT. Both chains are inserted at position 1,
ahead of Istio's appended (-A) chains. The proxy's own re-originated
egress (--uid-owner $PROXY_UID, RETURNed) falls through to
ISTIO_OUTPUT → ztunnel for transport mTLS under Istio ambient, and goes
out plain without a mesh — so capturing in-cluster TCP composes with
the mesh (AuthBridge does L7, ztunnel does transport) rather than
bypassing it. IPv6 mirrors apply the same rules. See
test-enforce-redirect.sh, which proves
the capture, the preemption, and the non-TCP drop via packet counters.
DNS stays direct by following the pod's actual resolvers — no CIDR guessing. The only thing left direct is DNS (
tcp/53+udp/53) to thenameserverIPs in/etc/resolv.conf, which kubelet writes per the pod'sdnsPolicy. This is cluster-agnostic by construction: it works whether the resolver is a Kind/OpenShift/EKS service ClusterIP (any service CIDR — incl. OpenShift's172.30.0.0/16, which is outside10/8) or a NodeLocal DNSCache at a link-local169.254.xaddress. The script logs the resolved nameservers at startup; override the file path withRESOLV_CONF(mainly for tests). There is no in-cluster CIDR knob —enforce-redirectno longer needs one. (A priorCLUSTER_CIDRSenv was removed; its10.0.0.0/8default silently dropped DNS on OpenShift, where the resolver sits outside10/8.)
enforce-redirectintentionally ignoresOUTBOUND_PORTS_EXCLUDE(aredirect-mode knob). Any destination previously bypassed that way — e.g. a direct LLM endpoint athost.docker.internal:11434— is now captured (external TCP) or dropped (external non-TCP). In-cluster TCP is captured as well (only DNS to the resolvers stays direct). That is the point:enforce-redirectcloses direct-egress holes, and the DNS exemption is scoped toport 53to the resolver IPs — not a TCP bypass.
The script auto-detects iptables-legacy vs iptables-nft and uses
whichever the host kernel exposes. Override with IPTABLES_CMD (and
IP6TABLES_CMD) if needed.
| Variable | Default | Mode | Purpose |
|---|---|---|---|
MODE |
redirect |
all | redirect (envoy-sidecar) or enforce-redirect (proxy-sidecar / lite) |
PROXY_UID |
1337 |
all | UID of the AuthBridge sidecar process; exempted from redirect |
PROXY_PORT |
15123 |
redirect | AuthBridge outbound listener port |
INBOUND_PROXY_PORT |
15124 |
redirect | AuthBridge inbound listener port |
TRANSPARENT_PORT |
8082 |
enforce-redirect | AuthBridge transparent listener port; REDIRECT target for captured external TCP egress |
OUTBOUND_PORTS_EXCLUDE |
(empty) | redirect | Comma-separated outbound port list to skip (e.g. 8080) |
INBOUND_PORTS_EXCLUDE |
(empty) | redirect | Comma-separated inbound port list to skip |
POD_IP |
(required in redirect) |
redirect | Set via Downward API; DNAT target for ambient-mesh inbound. Not used by enforce-redirect. |
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. |
IPTABLES_CMD |
auto-detected | all | Override iptables binary (iptables-legacy / iptables-nft) |
IP6TABLES_CMD |
derived from IPTABLES_CMD |
enforce-redirect | Override ip6tables binary |
The container needs NET_ADMIN and NET_RAW capabilities and runs as
UID 0 — but not privileged mode. The kagenti-operator's webhook
sets up the SecurityContext correctly when injecting the init
container.
make docker-build-init
make load-image # load into a kind clusterThe image is published from CI as
ghcr.io/kagenti/kagenti-extensions/proxy-init:<tag> (build defined
in .github/workflows/build.yaml).
test-enforce-redirect.sh validates
enforce-redirect mode in a private network namespace (unshare --net):
it asserts the AB_REDIRECT / AB_NOTCP rule structure, proves external
TCP is captured to TRANSPARENT_PORT while preempting a simulated Istio
ambient nat OUTPUT REDIRECT, and proves external UDP is dropped — all via
packet counters. Requires root + iptables-nft on Linux (runs on CI; not
macOS):
sudo ./test-enforce-redirect.shThe kagenti-operator's mutating webhook injects the proxy-init container automatically:
redirectmode (MODEunset) when the resolved AuthBridge mode isenvoy-sidecar.enforce-redirectmode (MODE=enforce-redirect) when the resolved AuthBridge mode isproxy-sidecar/lite— the transparent listener in those images receives the captured egress. This is always-on for those modes (the operator injects it unconditionally).
See
authbridge/demos/weather-agent/demo-ui-advanced.md
for an end-to-end demo and
authbridge/demos/token-exchange-routes/README.md
for the route-config reference.