Fix: Select iptables backend by kernel module, fail loud on misprogram#518
Conversation
detect_iptables_cmd() preferred iptables-legacy whenever the legacy nat table was merely listable (iptables-legacy -t nat -L -n). On nft-only nodes (OpenShift/ROSA, EKS-nft) the empty legacy table still lists fine, so legacy got chosen and our chains landed in a backend the live datapath never consults — a silent fail-open for a control whose whole job is to enforce egress. A write-probe is no better: individual legacy ops succeed via nft_compat even where the full nat pipeline later fails. Detect the backend that is actually functional instead, by reading the kernel module table: iptable_nat present in /proc/modules -> iptables-legacy (Kind, kubeadm) absent -> iptables (nft) (OpenShift/ROSA) /proc/modules is host-kernel-wide, readable from the pod netns, timing-independent (it does not depend on Istio's not-yet-installed rules), and not foolable by nft_compat. IPTABLES_CMD still overrides; the operator can set it per-platform. PROC_MODULES is overridable for tests. Also add require_jump(): after each chain is hooked into OUTPUT/PREROUTING (both redirect and enforce-redirect modes), assert the jump actually landed in the chosen backend and exit non-zero with an actionable message if not. set -e already aborts on hard errors; this turns the subtler "command returned 0 but the rule isn't there" case from a silent bypass into a visible crash. Note: this is not a ROSA enablement. On ROSA HCP it makes proxy-init select nft and then fail loud on the container_t -> nf_tables SELinux denial, instead of silently mis-selecting legacy. Making nft actually program on ROSA (SELinux/SCC, or node-level/ambient redirection) is a separate track. Extend test-enforce-redirect.sh with a detection unit test driven by the PROC_MODULES seam (no real kernel needed). Refs rossoctl#502 Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
|
Warning Review limit reached
More reviews will be available in 44 minutes and 33 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
Changesiptables Backend Detection and Rule Verification
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@authbridge/proxy-init/init-iptables.sh`:
- Around line 121-125: The debugging tip comment that states both components use
iptables-legacy contradicts the newly documented backend auto-detection
behavior. Update the outdated comment to reflect that the script now
intelligently auto-detects and supports both the legacy nat backend (using
iptable_nat kernel module on legacy clusters like Kind and kubeadm) and the
nft-only backend (on platforms like OpenShift/ROSA), as described in the
adjacent documentation. This ensures the debugging guidance aligns with the
actual auto-detection capability explained in the kernel module detection logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c1ff3af-61b3-40dd-8c0c-0f682cf80abf
📒 Files selected for processing (3)
authbridge/proxy-init/Dockerfile.initauthbridge/proxy-init/init-iptables.shauthbridge/proxy-init/test-enforce-redirect.sh
| # This script auto-detects the correct backend by reading the kernel module | ||
| # table (/proc/modules): the legacy nat path needs the `iptable_nat` module, | ||
| # which is loaded on legacy clusters (Kind, kubeadm — kube-proxy uses it) and | ||
| # ABSENT on nft-only platforms (OpenShift/ROSA expose only nf_tables + | ||
| # nft_compat). Override with IPTABLES_CMD env var if needed. |
There was a problem hiding this comment.
Align the debugging tip with backend auto-detection behavior.
Line 109 still says both components use iptables-legacy, but this file now supports nft backend selection. That contradiction can mislead runtime triage on nft-only clusters.
📝 Proposed doc fix
-# iptables-legacy-save — both Istio and AuthProxy use iptables-legacy
+# iptables-save / iptables-legacy-save
+# — inspect whichever backend was selected at startup🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@authbridge/proxy-init/init-iptables.sh` around lines 121 - 125, The debugging
tip comment that states both components use iptables-legacy contradicts the
newly documented backend auto-detection behavior. Update the outdated comment to
reflect that the script now intelligently auto-detects and supports both the
legacy nat backend (using iptable_nat kernel module on legacy clusters like Kind
and kubeadm) and the nft-only backend (on platforms like OpenShift/ROSA), as
described in the adjacent documentation. This ensures the debugging guidance
aligns with the actual auto-detection capability explained in the kernel module
detection logic.
…octl#518) The IPv6 nat OUTPUT (AB_REDIRECT) and mangle OUTPUT (AB_NOTCP) jumps were hooked without require_jump, so the loud-fail-on-misprogram guarantee was IPv4-only — a silent v6 jump misprogram (exactly what require_jump exists to catch) would still fail open on a real egress/exfil path. Add the two missing require_jump calls so v6 capture, when attempted (it stays gated on ip6tables availability), succeeds or fails loud like v4. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
akram
left a comment
There was a problem hiding this comment.
Looks good — the /proc/modules approach is the right call. I independently hit this exact problem on ROSA HCP and tried three increasingly aggressive write-probes (list, chain creation, REDIRECT rule creation) — all passed via nft_compat even though the full nat pipeline fails at runtime. /proc/modules is the only reliable signal.
require_jump() is a great addition — we had silent fail-open on our cluster for hours before realizing legacy was selected but non-functional.
This pairs well with the operator-side egressEnforcement: none opt-out in kagenti-operator#436 — on ROSA HCP where neither backend works (SELinux blocks nf_tables too), the admin sets egressEnforcement: none and proxy-init is never injected. If it is injected by mistake, this PR ensures it crashes visibly instead of silently bypassing.
One minor note: the PR body says "does nothing about SELinux" — worth linking to the Red Hat KB 5990311 and kagenti-extensions#502 for the full picture, since someone debugging the nft Permission denied on ROSA will land here first.
LGTM.
mrsabath
left a comment
There was a problem hiding this comment.
Solid, well-reasoned fix. The /proc/modules signal is the right call — host-kernel-wide, timing-independent, and not foolable by nft_compat the way a write-probe is. require_jump is cleanly wired into every insert site: the -C args match each -I exactly, across both redirect and enforce-redirect paths and IPv4+IPv6. The comments are genuinely excellent.
One real defect holds it back: the new detection unit test is neutralized by the harness's own inherited IPTABLES_CMD, so it doesn't actually validate detection — see the inline comment. It's green in CI only because no workflow runs this harness (CI runs sh -n/shellcheck + the proxy-init Docker build, not the runtime test). 一字之差 (a single var makes all the difference) — clear the inherited override in the detection subshells and the test will exercise what it's meant to.
Areas reviewed: Shell (init-iptables.sh, test harness), Dockerfile, commit/PR conventions, CI wiring, security.
Commits: 2, both signed-off (DCO passing).
CI: all checks green — but note the runtime test harness is not part of CI.
| # exercise it against fixture module tables — no real kernel needed. The legacy | ||
| # branch also requires the iptables-legacy binary, so skip the legacy-positive | ||
| # case when it is not installed on the host. | ||
| eval "$(sed -n '/^PROC_MODULES=/,/^}/p' "${INIT}")" |
There was a problem hiding this comment.
must-fix — this detection unit test is short-circuited by the harness's own IPTABLES_CMD, so it doesn't test what it claims to.
The re-exec at L39–40 injects IPTABLES_CMD="${IPT}" (= iptables-nft) into the environment for the entire netns run, and detect_iptables_cmd checks IPTABLES_CMD first and returns it immediately. So:
- L172 (
mods_legacy) returnsiptables-nft, expectediptables-legacy→ FAIL - L178 (
mods_nft) returnsiptables-nft, expectediptables→ FAIL
Reproduced by extracting the function and exporting IPTABLES_CMD=iptables-nft: both cases return iptables-nft. This stays green in CI only because no workflow invokes this harness (CI = sh -n/shellcheck + the proxy-init Docker build). The override case (L181) passes for the wrong reason — it sets its own override, so it'd pass even if detection were completely broken.
Fix: clear the inherited var for the non-override subshells, e.g. wrap the block with env -u IPTABLES_CMD or unset IPTABLES_CMD before L172/L178 (the L181 override case re-sets it explicitly, so it's unaffected).
| # Fail loud if a jump rule we just installed is not actually present in the | ||
| # chosen backend. `set -e` already aborts on hard programming errors; this also | ||
| # catches the subtler case where a command returned 0 but the rule did not land | ||
| # (e.g. rules written into a backend that is not the live datapath), so a |
There was a problem hiding this comment.
nit — the parenthetical “(e.g. rules written into a backend that is not the live datapath)” slightly overstates what require_jump catches. The -C check runs against the chosen backend (${_rj_cmd}), so if detection mis-selects a backend, the rule is both written to and found in that same backend and require_jump passes — it can't detect mis-selection. What it genuinely guards is the “insert returned 0 but the rule didn't persist” case. Consider trimming the e.g. so the comment doesn't read as datapath-vs-backend validation. Mechanism is sound and strictly additive — no functional concern.
Problem
detect_iptables_cmd()inproxy-init/init-iptables.shprefersiptables-legacywhenever the legacynattable is merely listable(
iptables-legacy -t nat -L -n). On nft-only nodes (OpenShift/ROSA, EKS-nft)the empty legacy table still lists fine, so legacy gets chosen and our chains
land in a backend the live datapath never consults — a silent fail-open for
a control whose entire job is to enforce egress. A write-probe is no better:
individual legacy ops succeed via
nft_compateven where the full nat pipelinelater fails, so probing legacy can't distinguish "works" from "works until the
real pipeline."
Fixes the detection half of #502.
Change
Detect the functional backend via
/proc/modulesinstead of a readabilityprobe:
/proc/modulesiptable_natpresentiptables-legacyiptables(nft)/proc/modulesis host-kernel-wide, readable from the pod netns(verified from a pod running
proxy-init), timing-independent (unlikematching Istio's not-yet-installed rules), and not foolable by
nft_compat(unlike a write-probe).
IPTABLES_CMDstill overrides — the operator can set itper-platform.
PROC_MODULESis overridable for tests.Fail loud, never silent — new
require_jump()helper: after each chain ishooked into
OUTPUT/PREROUTING(bothredirectandenforce-redirectmodes), assert the jump actually landed in the chosen backend and
exit 1withan actionable message otherwise.
set -ealready aborts on hard errors; thisturns the subtler "command returned 0 but the rule isn't there" case from a
silent bypass into a visible crash.
Not a ROSA enablement (important)
This does not make egress enforcement work on ROSA HCP and does nothing
about SELinux. On ROSA it changes the failure: proxy-init now correctly
selects nft and fails loud on the real
container_t→ nf_tables SELinuxdenial (
Could not fetch rule set generation id: Permission denied), instead ofsilently mis-selecting legacy. Making nft actually program on ROSA
(SELinux/SCC
seLinuxOptions, or node-level/ambient redirection) is a separatetrack tracked in #502.
Testing
sh -n/bash -nclean; detection function exercised against fixture moduletables (nft →
iptables, override →iptables-legacy).test-enforce-redirect.shextended with a/proc/modules-seam detection unittest (no real kernel needed); existing nft structural/capture/drop assertions
unchanged.
iptable_natloaded) selectsiptables-legacyas before — noregression for the local/dev path.
Reviewer note
The module check picks legacy whenever
iptable_natis loaded. The rare edge —a node with
iptable_natloaded but Istio/kube-proxy actually on nft — wouldsplit backends;
IPTABLES_CMDcovers it, and it doesn't occur on Kind (bothlegacy) or nft-only nodes (
iptable_natabsent).Assisted-By: Claude (Anthropic AI) noreply@anthropic.com
Summary by CodeRabbit
Bug Fixes
Tests