Summary
init-iptables.sh's detect_iptables_cmd() selects the wrong iptables backend on nft-only nodes (ROSA, recent RHEL/OpenShift, EKS, recent Ubuntu). It decides which backend to use by checking whether the legacy nat table can be listed, treats that read as proof the legacy backend is in use, and prefers legacy whenever the read succeeds. On nft-only hosts the legacy nat table is still readable (just empty), so the script picks iptables-legacy and programs our chains into a table the live datapath never consults. The result is a silent no-op: enforce-redirect (and redirect) rules insert successfully but never take effect.
File: authbridge/authproxy/init-iptables.sh (detect_iptables_cmd).
Current logic
detect_iptables_cmd() {
if [ -n "${IPTABLES_CMD:-}" ]; then
echo "${IPTABLES_CMD}"; return
fi
if command -v iptables-legacy >/dev/null 2>&1 && \
iptables-legacy -t nat -L -n >/dev/null 2>&1; then # readability probe
echo "iptables-legacy" # legacy-first bias
else
echo "iptables" # nft backend
fi
}
Root cause
Linux exposes two independent packet-filtering subsystems behind the same CLI syntax: legacy x_tables (iptables-legacy) and nf_tables (iptables, the nft backend on Alpine). Rules written to one are not evaluated against the other. Our rules only work if they go into the backend the host (kube-proxy / CNI / Istio) actually uses.
Two problems with the current detection:
- Readability ≠ liveness.
iptables-legacy -t nat -L -n only answers "can I enumerate the legacy nat table?" — a read. It does not answer "is the legacy backend where the live rules live?" On an nft-only node the legacy nat table typically lists fine (empty), so the probe passes and legacy is chosen even though every real NAT rule is in nf_tables.
- Legacy-first bias. The "prefer legacy for compatibility" default reflects an era when K8s/Istio used legacy. Most current clusters are nft-only, so the default is backwards.
Failure mode is silent
Because the rules insert successfully into a real (but dead) table, every command returns success: the init container exits 0, the pod is healthy, and iptables-legacy -t nat -L shows the chains present — yet egress that should be captured/redirected leaks straight out. There is no error to alert on; you only notice that traffic isn't being intercepted.
This is distinct from the capability/SCC failure seen on ROSA (a hard EPERM initializing the nat table that CrashLoops the init container). That one is loud and is about CAP_NET_ADMIN/SCC admission, not backend selection. The detection bug doesn't crash — it leaks. Both can be present on the same platform.
Proposed fix
- Probe liveness, not readability. Pick the backend that already holds the host's rules — e.g. compare
iptables-legacy-save -t nat vs iptables-nft-save -t nat and prefer the backend that contains the existing kube-proxy / Istio chains.
- Write-probe the chosen backend (insert a throwaway rule, then delete it) so failures cleanly distinguish "wrong backend" from "no
CAP_NET_ADMIN", and so a silent no-op can't happen.
- Default to nft when ambiguous, not legacy.
- Keep the existing
IPTABLES_CMD / IP6TABLES_CMD override as the escape hatch, and have the operator set it explicitly on known platforms.
Impact
enforce-redirect (proxy-sidecar / lite) silently fails open on nft-only nodes — egress enforcement is bypassed without any signal.
redirect (envoy-sidecar) interception silently does nothing on the same nodes.
cf. upstream caution that spawn/list success ≠ a functioning backend.
Summary
init-iptables.sh'sdetect_iptables_cmd()selects the wrong iptables backend on nft-only nodes (ROSA, recent RHEL/OpenShift, EKS, recent Ubuntu). It decides which backend to use by checking whether the legacynattable can be listed, treats that read as proof the legacy backend is in use, and prefers legacy whenever the read succeeds. On nft-only hosts the legacynattable is still readable (just empty), so the script picksiptables-legacyand programs our chains into a table the live datapath never consults. The result is a silent no-op:enforce-redirect(andredirect) rules insert successfully but never take effect.File:
authbridge/authproxy/init-iptables.sh(detect_iptables_cmd).Current logic
Root cause
Linux exposes two independent packet-filtering subsystems behind the same CLI syntax: legacy
x_tables(iptables-legacy) andnf_tables(iptables, the nft backend on Alpine). Rules written to one are not evaluated against the other. Our rules only work if they go into the backend the host (kube-proxy / CNI / Istio) actually uses.Two problems with the current detection:
iptables-legacy -t nat -L -nonly answers "can I enumerate the legacy nat table?" — a read. It does not answer "is the legacy backend where the live rules live?" On an nft-only node the legacy nat table typically lists fine (empty), so the probe passes and legacy is chosen even though every real NAT rule is in nf_tables.Failure mode is silent
Because the rules insert successfully into a real (but dead) table, every command returns success: the init container exits 0, the pod is healthy, and
iptables-legacy -t nat -Lshows the chains present — yet egress that should be captured/redirected leaks straight out. There is no error to alert on; you only notice that traffic isn't being intercepted.This is distinct from the capability/SCC failure seen on ROSA (a hard
EPERMinitializing the nat table that CrashLoops the init container). That one is loud and is aboutCAP_NET_ADMIN/SCC admission, not backend selection. The detection bug doesn't crash — it leaks. Both can be present on the same platform.Proposed fix
iptables-legacy-save -t natvsiptables-nft-save -t natand prefer the backend that contains the existing kube-proxy / Istio chains.CAP_NET_ADMIN", and so a silent no-op can't happen.IPTABLES_CMD/IP6TABLES_CMDoverride as the escape hatch, and have the operator set it explicitly on known platforms.Impact
enforce-redirect(proxy-sidecar / lite) silently fails open on nft-only nodes — egress enforcement is bypassed without any signal.redirect(envoy-sidecar) interception silently does nothing on the same nodes.cf. upstream caution that spawn/list success ≠ a functioning backend.