From 1ddc5367d670031a28e566278df945aed7b3b927 Mon Sep 17 00:00:00 2001 From: Navan Chauhan Date: Wed, 4 Mar 2026 14:53:07 -0800 Subject: [PATCH] network: fall back to blanket port block when cgroup isolation unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LinuxKit (Docker Desktop) and OrbStack kernels lack xt_cgroup / nft cgroupv2 socket matching, causing leash to FATAL on startup. When cgroup-scoped filtering fails, block ALL outbound connections to the control plane port instead. This still prevents the target container from reaching leashd while external access via Docker port publishing is unaffected. Also fix nftables ensure_rule comment quoting — nft requires literal quotes around comment values containing colons. Closes #60 --- internal/assets/apply-ip6tables.sh | 21 ++++++++++++++++----- internal/assets/apply-iptables.sh | 20 ++++++++++++++++---- internal/assets/apply-nftables.sh | 18 +++++++++++++----- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/internal/assets/apply-ip6tables.sh b/internal/assets/apply-ip6tables.sh index 9c51537..a6ae4d0 100644 --- a/internal/assets/apply-ip6tables.sh +++ b/internal/assets/apply-ip6tables.sh @@ -63,13 +63,25 @@ fi # SECURITY: This is a REQUIRED security control - failure is fatal. # Requires --cgroupns=host on the container to see host cgroup paths. 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 + # Preferred: scope the block to the target container via cgroup matching. + if ! ensure_rule -t filter -C OUTPUT -m cgroup --path "$TARGET_CGROUP" -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset; 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 - 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 - exit 1 + # Fallback: some kernels (notably LinuxKit on Docker Desktop) lack xt_cgroup support. + # In that case, block ALL local processes in this network namespace from connecting + # to the control plane port. This preserves the security boundary at the cost of + # disallowing in-namespace clients. + echo "leash: WARNING: cgroup-based control plane isolation unavailable (IPv6); blocking all local access to control plane port $LEASH_PORT" >&2 + if ! ensure_rule -t filter -C OUTPUT -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset; then + if ip6tables_cmd -t filter -A OUTPUT -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset 2>&1; then + echo "leash: blocked local access to control plane port $LEASH_PORT (fallback IPv6)" + else + echo "leash: FATAL: could not apply control plane isolation (IPv6 cgroup and fallback failed)" >&2 + echo "leash: This security control is required to prevent target container from accessing leashd API" >&2 + exit 1 + fi + fi fi fi fi @@ -79,4 +91,3 @@ if [ "$RULE_ERRORS" -gt 0 ]; then echo "leash: WARNING: $RULE_ERRORS ip6tables rule(s) failed to apply (IPv6 network interception may be incomplete)" >&2 fi exit 0 - diff --git a/internal/assets/apply-iptables.sh b/internal/assets/apply-iptables.sh index cf05f44..fe86718 100644 --- a/internal/assets/apply-iptables.sh +++ b/internal/assets/apply-iptables.sh @@ -62,13 +62,25 @@ fi # SECURITY: This is a REQUIRED security control - failure is fatal. # Requires --cgroupns=host on the container to see host cgroup paths. 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 + # Preferred: scope the block to the target container via cgroup matching. + if ! ensure_rule -t filter -C OUTPUT -m cgroup --path "$TARGET_CGROUP" -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset; 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 - 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 - exit 1 + # Fallback: some kernels (notably LinuxKit on Docker Desktop) lack xt_cgroup support. + # In that case, block ALL local processes in this network namespace from connecting + # to the control plane port. This preserves the security boundary at the cost of + # disallowing in-namespace clients. + echo "leash: WARNING: cgroup-based control plane isolation unavailable; blocking all local access to control plane port $LEASH_PORT" >&2 + if ! ensure_rule -t filter -C OUTPUT -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset; then + if iptables_cmd -t filter -A OUTPUT -p tcp --dport "$LEASH_PORT" -j REJECT --reject-with tcp-reset 2>&1; then + echo "leash: blocked local access to control plane port $LEASH_PORT (fallback)" + else + echo "leash: FATAL: could not apply control plane isolation (cgroup and fallback failed)" >&2 + echo "leash: This security control is required to prevent target container from accessing leashd API" >&2 + exit 1 + fi + fi fi fi fi diff --git a/internal/assets/apply-nftables.sh b/internal/assets/apply-nftables.sh index 404c1a4..86d4de6 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 @@ -93,9 +93,18 @@ if [ -n "$TARGET_CGROUP" ] && [ -n "$LEASH_PORT" ]; 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 - 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 - exit 1 + # Fallback: some kernels (notably LinuxKit on Docker Desktop) lack nft_socket cgroupv2 support. + # In that case, block ALL local processes in this network namespace from connecting to + # the control plane port. This preserves the security boundary at the cost of disallowing + # in-namespace clients. + echo "leash: WARNING: cgroup-based control plane isolation unavailable (nftables); blocking all local access to control plane port $LEASH_PORT" >&2 + if ensure_rule inet leash out_filter "leash:block-control-plane-fallback" tcp dport $LEASH_PORT reject with tcp reset; then + echo "leash: blocked local access to control plane port $LEASH_PORT (fallback nftables)" + else + echo "leash: FATAL: could not apply control plane isolation (nftables cgroup and fallback failed)" >&2 + echo "leash: This security control is required to prevent target container from accessing leashd API" >&2 + exit 1 + fi fi fi @@ -104,4 +113,3 @@ if [ "$RULE_ERRORS" -gt 0 ]; then echo "leash: WARNING: $RULE_ERRORS nftables rule(s) failed to apply (network interception may be incomplete)" >&2 fi exit 0 -