From 11e5d04c7cd6be05dcedc6304e6fac5a80ac1a94 Mon Sep 17 00:00:00 2001 From: Shreyas Sunkad Date: Thu, 26 Feb 2026 11:22:06 -0800 Subject: [PATCH] Fix nftables comment quoting and add cgroup isolation toggle Three bugs prevented leash from starting on Docker Desktop Kubernetes: 1. nftables comment quoting: Comments like "leash:return-mitm" weren't wrapped in nft-level quotes, causing all nftables rules to fail because ':' is a special token in nft's parser. 2. nftables cgroup path quoting: The cgroup hierarchy path passed to "socket cgroupv2 level 1" wasn't quoted, causing '/' to be parsed as a division operator. 3. Docker Desktop's LinuxKit kernel lacks xt_cgroup (iptables) and nft socket cgroupv2 support, making cgroup-based control plane isolation impossible on that platform. Changes: - Fix ensure_rule() comment quoting in apply-nftables.sh - Fix cgroup path quoting in apply-nftables.sh - Add LEASH_CGROUP_ISOLATION env var (default: "required") to all three scripts so callers can set "optional" for environments where the kernel lacks cgroup matching support - Add nftables package to Dockerfile.leash runtime-base so the preferred nftables code path is available Fixes #64 Co-Authored-By: Claude Opus 4.6 --- Dockerfile.leash | 1 + internal/assets/apply-ip6tables.sh | 11 +++++++++-- internal/assets/apply-iptables.sh | 12 ++++++++++-- internal/assets/apply-nftables.sh | 12 +++++++++--- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Dockerfile.leash b/Dockerfile.leash index e5a723c..8dc6550 100644 --- a/Dockerfile.leash +++ b/Dockerfile.leash @@ -97,6 +97,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ dnsutils \ iproute2 \ iptables \ + nftables \ iputils-ping \ less \ libbpf1 \ diff --git a/internal/assets/apply-ip6tables.sh b/internal/assets/apply-ip6tables.sh index 9c51537..541c241 100644 --- a/internal/assets/apply-ip6tables.sh +++ b/internal/assets/apply-ip6tables.sh @@ -60,16 +60,23 @@ fi # Block target container from reaching leashd control plane on any interface (IPv6). # This prevents a compromised agent from accessing the leashd API. -# SECURITY: This is a REQUIRED security control - failure is fatal. +# SECURITY: This is a REQUIRED security control - failure is fatal unless +# LEASH_CGROUP_ISOLATION=optional is set. # Requires --cgroupns=host on the container to see host cgroup paths. +CGROUP_ISOLATION=${LEASH_CGROUP_ISOLATION:-required} + if [ -n "$TARGET_CGROUP" ] && [ -n "$LEASH_PORT" ]; then if ! ensure_rule -t filter -C OUTPUT -m cgroup --path "$TARGET_CGROUP" -p tcp --dport "$LEASH_PORT" -j REJECT; then if ip6tables_cmd -t filter -A OUTPUT -m cgroup --path "$TARGET_CGROUP" -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset 2>&1; then echo "leash: blocked target cgroup $TARGET_CGROUP from reaching control plane port $LEASH_PORT (ip6tables)" - else + elif [ "$CGROUP_ISOLATION" = "required" ]; then echo "leash: FATAL: could not apply cgroup-based control plane isolation (IPv6)" >&2 echo "leash: This security control is required to prevent target container from accessing leashd API" >&2 + echo "leash: Set LEASH_CGROUP_ISOLATION=optional to run without cgroup isolation" >&2 exit 1 + else + echo "leash: WARNING: cgroup-based control plane isolation unavailable (ip6tables); continuing without it" >&2 + RULE_ERRORS=$((RULE_ERRORS + 1)) fi fi fi diff --git a/internal/assets/apply-iptables.sh b/internal/assets/apply-iptables.sh index cf05f44..d7e653b 100644 --- a/internal/assets/apply-iptables.sh +++ b/internal/assets/apply-iptables.sh @@ -59,16 +59,24 @@ fi # Block target container from reaching leashd control plane on any interface. # This prevents a compromised agent from accessing the leashd API. -# SECURITY: This is a REQUIRED security control - failure is fatal. +# SECURITY: This is a REQUIRED security control - failure is fatal unless +# LEASH_CGROUP_ISOLATION=optional is set (e.g. for Docker Desktop where +# the kernel lacks xt_cgroup / nft socket cgroupv2 support). # Requires --cgroupns=host on the container to see host cgroup paths. +CGROUP_ISOLATION=${LEASH_CGROUP_ISOLATION:-required} + if [ -n "$TARGET_CGROUP" ] && [ -n "$LEASH_PORT" ]; then if ! ensure_rule -t filter -C OUTPUT -m cgroup --path "$TARGET_CGROUP" -p tcp --dport "$LEASH_PORT" -j REJECT; then if iptables_cmd -t filter -A OUTPUT -m cgroup --path "$TARGET_CGROUP" -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset 2>&1; then echo "leash: blocked target cgroup $TARGET_CGROUP from reaching control plane port $LEASH_PORT" - else + elif [ "$CGROUP_ISOLATION" = "required" ]; then echo "leash: FATAL: could not apply cgroup-based control plane isolation" >&2 echo "leash: This security control is required to prevent target container from accessing leashd API" >&2 + echo "leash: Set LEASH_CGROUP_ISOLATION=optional to run without cgroup isolation" >&2 exit 1 + else + echo "leash: WARNING: cgroup-based control plane isolation unavailable (iptables); continuing without it" >&2 + RULE_ERRORS=$((RULE_ERRORS + 1)) fi fi fi diff --git a/internal/assets/apply-nftables.sh b/internal/assets/apply-nftables.sh index 404c1a4..a69e447 100644 --- a/internal/assets/apply-nftables.sh +++ b/internal/assets/apply-nftables.sh @@ -53,7 +53,7 @@ ensure_rule() { if nft_cmd list chain "$fam" "$tbl" "$chain" 2>/dev/null | grep -F "comment \"$comment\"" >/dev/null; then return 0 fi - if ! nft_cmd add rule "$fam" "$tbl" "$chain" "$@" comment "$comment" 2>/dev/null; then + if ! nft_cmd add rule "$fam" "$tbl" "$chain" "$@" comment "\"$comment\"" 2>/dev/null; then echo "leash: WARNING: failed to add nftables rule $comment" >&2 RULE_ERRORS=$((RULE_ERRORS + 1)) return 1 @@ -85,17 +85,23 @@ ensure_rule inet leash out_route "leash:drop-quic" udp dport 443 drop # Uses inet family to cover both IPv4 and IPv6. # SECURITY: This is a REQUIRED security control - failure is fatal. # Requires --cgroupns=host on the container to see host cgroup paths. +CGROUP_ISOLATION=${LEASH_CGROUP_ISOLATION:-required} + if [ -n "$TARGET_CGROUP" ] && [ -n "$LEASH_PORT" ]; then ensure_chain inet leash out_filter { type filter hook output priority 0\; } # Check if rule already exists if nft_cmd list chain inet leash out_filter 2>/dev/null | grep -F "leash:block-control-plane" >/dev/null; then : # Rule already exists, nothing to do - elif nft_cmd add rule inet leash out_filter socket cgroupv2 level 1 "$TARGET_CGROUP" tcp dport $LEASH_PORT reject with tcp reset comment "leash:block-control-plane" 2>&1; then + elif nft_cmd add rule inet leash out_filter socket cgroupv2 level 1 "\"$TARGET_CGROUP\"" tcp dport $LEASH_PORT reject with tcp reset comment "\"leash:block-control-plane\"" 2>&1; then echo "leash: blocked target cgroup $TARGET_CGROUP from reaching control plane port $LEASH_PORT (nftables)" - else + elif [ "$CGROUP_ISOLATION" = "required" ]; then echo "leash: FATAL: could not apply cgroup-based control plane isolation (nftables)" >&2 echo "leash: This security control is required to prevent target container from accessing leashd API" >&2 + echo "leash: Set LEASH_CGROUP_ISOLATION=optional to run without cgroup isolation" >&2 exit 1 + else + echo "leash: WARNING: cgroup-based control plane isolation unavailable (nftables); continuing without it" >&2 + RULE_ERRORS=$((RULE_ERRORS + 1)) fi fi