fix: use nft instead of ip6tables/sysctl for IPv6 blocking in vlan50 guard
Some checks failed
validate / lint (push) Failing after 1s
Some checks failed
validate / lint (push) Failing after 1s
The previous ip6tables->sysctl fix (26894d1) was itself wrong: writing /proc/sys/net/ipv6/conf/all/disable_ipv6 failed live with "Read-only file system" despite NET_ADMIN and a passing [ -w ] check - the container runtime mounts /proc/sys read-only by default regardless of capabilities, independent of file permission bits. Making it writable needs either kubelet's securityContext.sysctls (and net.ipv6.conf.*.disable_ipv6 isn't on its default safe-sysctls allowlist, so that means a node-level --allowed-unsafe-sysctls flag) or securityContext.procMount: Unmasked (which needs pod-level user namespaces) - too much blast radius for one pod's IPv6 kill switch. nft (nftables) is genuinely present in the same image (the "nftables" apk package, installed alongside "iptables" but not "ip6tables") and handles the ip6 address family without a separate binary, so it needs no image or capability change. Verified by actually running the exact commands against the real pinned nicolaka/netshoot:v0.11 image (digest-matched to what's on nik-debian), not just against docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7ce0dee711
commit
28d062801a
@ -190,40 +190,48 @@ data:
|
|||||||
iptables -A VLAN50-GUARD -o eth0 -j DROP
|
iptables -A VLAN50-GUARD -o eth0 -j DROP
|
||||||
|
|
||||||
echo "[vlan50-egress-guard] blocking IPv6 entirely (both interfaces) — IPv4-only design"
|
echo "[vlan50-egress-guard] blocking IPv6 entirely (both interfaces) — IPv4-only design"
|
||||||
# sysctl, not an ip6tables DROP policy: nicolaka/netshoot:v0.11 (the
|
# nft (nftables), not ip6tables and not a /proc/sys sysctl write —
|
||||||
# pinned tag actually in use) is built on Alpine 3.18.0 per its real,
|
# both tried first and both failed live, 2026-08-24:
|
||||||
# tag-pinned Dockerfile — confirmed live, 2026-08-24, "ip6tables: not
|
# - ip6tables: genuinely absent from this image. Alpine 3.18.0
|
||||||
# found". Alpine 3.18 packages ip6tables SEPARATELY from iptables
|
# (nicolaka/netshoot:v0.11's real, tag-pinned base — confirmed
|
||||||
# (confirmed against Alpine's own v3.18 package index), and this
|
# against that exact Dockerfile, not master's) packages ip6tables
|
||||||
# image's Dockerfile only installs the latter, so ip6tables genuinely
|
# SEPARATELY from iptables, and this image's Dockerfile only
|
||||||
# does not exist in this container at all — not a PATH issue (the
|
# installs the latter (confirmed against Alpine's own v3.18
|
||||||
# iptables calls just above this ran fine from the same image).
|
# package index) — "ip6tables: not found", not a PATH issue (the
|
||||||
# Disabling IPv6 at the netns level is also strictly stronger than a
|
# iptables calls just above this ran fine from the same image).
|
||||||
# DROP policy would have been anyway: no IPv6 address, neighbor
|
# - `echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6`: failed
|
||||||
# discovery, or routing activity happens on any interface here at
|
# with "Read-only file system" despite the [ -w ] check on that
|
||||||
# all, not just filtered OUTPUT/FORWARD traffic. A single write to
|
# path reporting it writable and NET_ADMIN already being granted
|
||||||
# conf/all/disable_ipv6 is sufficient by itself — the kernel's own
|
# — permission bits and the container runtime's own read-only
|
||||||
# ip-sysctl documentation defines it as equivalent to writing
|
# mount of /proc/sys are two independent gates, and [ -w ] only
|
||||||
# conf/default/disable_ipv6 (for any interface created afterward)
|
# tests the first. Kubernetes' two supported ways to actually get
|
||||||
# *and* every existing per-interface disable_ipv6 (lo/eth0/net1) all
|
# a writable /proc/sys/net/* here are pod-spec
|
||||||
# at once, not merely an aggregate read. Namespaced net.ipv6.*
|
# securityContext.sysctls (kubelet-mediated, pre-container-start
|
||||||
# sysctls are writable directly by a process with CAP_NET_ADMIN in
|
# — but net.ipv6.conf.*.disable_ipv6 isn't on kubelet's own
|
||||||
# its own netns — no pod-spec sysctls: field is needed for this.
|
# default safe-sysctls allowlist, so this would need a
|
||||||
DISABLE_IPV6=/proc/sys/net/ipv6/conf/all/disable_ipv6
|
# node-level --allowed-unsafe-sysctls kubelet flag) or
|
||||||
if [ ! -w "${DISABLE_IPV6}" ]; then
|
# securityContext.procMount: Unmasked (which itself requires
|
||||||
echo "[vlan50-egress-guard] FATAL: ${DISABLE_IPV6} is not writable — refusing to continue without confirmed IPv6 disablement" >&2
|
# pod-level user namespaces, spec.hostUsers: false) — both far
|
||||||
print_diagnostics
|
# more cluster-wide blast radius than an IPv6 kill switch on one
|
||||||
exit 1
|
# pod justifies.
|
||||||
fi
|
# - nft is genuinely present: the same Dockerfile that omits
|
||||||
echo 1 > "${DISABLE_IPV6}"
|
# ip6tables installs the separate "nftables" apk package
|
||||||
if [ "$(cat "${DISABLE_IPV6}")" != "1" ]; then
|
# (confirmed against Alpine's own v3.18 package index, same way
|
||||||
echo "[vlan50-egress-guard] FATAL: wrote 1 to ${DISABLE_IPV6} but it did not stick — refusing to continue without confirmed IPv6 disablement" >&2
|
# ip6tables' absence was). nftables handles the ip6 address
|
||||||
print_diagnostics
|
# family as an ordinary table — no separate ip6-specific binary
|
||||||
exit 1
|
# needed — so this needs no image or capability change beyond
|
||||||
fi
|
# what's already granted (CAP_NET_ADMIN, already present for the
|
||||||
echo "[vlan50-egress-guard] IPv6 disabled (conf/all/disable_ipv6=1)"
|
# iptables/ip rules above). Syntax verified against nftables'
|
||||||
|
# own wiki (base chain + policy, and the meta oifname interface
|
||||||
|
# matcher), not guessed.
|
||||||
|
nft add table ip6 vlan50guard6
|
||||||
|
nft 'add chain ip6 vlan50guard6 output { type filter hook output priority 0; policy drop; }'
|
||||||
|
nft 'add chain ip6 vlan50guard6 forward { type filter hook forward priority 0; policy drop; }'
|
||||||
|
nft add rule ip6 vlan50guard6 output meta oifname lo accept
|
||||||
|
echo "[vlan50-egress-guard] IPv6 blocked (nft table ip6 vlan50guard6, output+forward policy drop, lo excepted)"
|
||||||
|
|
||||||
echo "[vlan50-egress-guard] final state:"
|
echo "[vlan50-egress-guard] final state:"
|
||||||
ip route show
|
ip route show
|
||||||
iptables -S OUTPUT
|
iptables -S OUTPUT
|
||||||
iptables -S VLAN50-GUARD
|
iptables -S VLAN50-GUARD
|
||||||
|
nft list table ip6 vlan50guard6
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user