Files
EvoFirewall/scripts/ci/linux-netns-smoke.sh
Denozordec a37ffdf061
quality / changes (push) Successful in 6s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 27s
quality / web (push) Successful in 49s
quality / api (push) Successful in 1m13s
quality / linux-agent-smoke (push) Failing after 1m31s
quality / docker-check (push) Failing after 4m11s
CD / quality (push) Failing after 8m19s
CD / publish (push) Skipped
fix(ci): isolate agent smoke in namespace-capable helper
2026-10-26 13:23:24 +07:00

85 lines
4.4 KiB
Bash

#!/usr/bin/env bash
# All firewall mutations run only after unshare creates BOTH a network and mount
# namespace. Never installs/runs the agent against the runner's host firewall.
set -Eeuo pipefail
[[ $(uname -s) == Linux ]] || { echo 'Linux namespace runner required' >&2; exit 1; }
for command in unshare ip nft ipset iptables ip6tables python3 curl mount flock timeout; do command -v "$command" >/dev/null; done
temporary=$(mktemp -d)
trap 'rm -rf -- "$temporary"' EXIT
cp apps/api/src/agent-scripts/evofw-firewall.sh "$temporary/agent.sh"
cp scripts/ci/linux-agent-fixture.py "$temporary/server.py"
parent_net=$(readlink /proc/self/ns/net)
parent_mount=$(readlink /proc/self/ns/mnt)
for backend in nft ipset; do
rm -f "$temporary/port" "$temporary/port6" "$temporary/invalid"
unshare --net --mount --pid --fork --mount-proc env EVOFW_TEST_PARENT_NET="$parent_net" EVOFW_TEST_PARENT_MOUNT="$parent_mount" \
bash -s -- "$temporary" "$backend" <<'CHILD'
set -Eeuo pipefail
root=$1
backend=$2
unset http_proxy HTTP_PROXY https_proxy HTTPS_PROXY ALL_PROXY all_proxy
export NO_PROXY='*'
[[ $(readlink /proc/self/ns/net) != "$EVOFW_TEST_PARENT_NET" ]]
[[ $(readlink /proc/self/ns/mnt) != "$EVOFW_TEST_PARENT_MOUNT" ]]
mount --make-rprivate /
# Agent hardcoded config/state/log paths are private mounts, never host writes.
for directory in /etc /var/lib /var/log /run; do mount -t tmpfs tmpfs "$directory"; done
if [[ -n "${EVOFW_TEST_ETC_DATA:-}" ]]; then
[[ "$EVOFW_TEST_ETC_DATA" == /work/helper-etc ]]
cp "$EVOFW_TEST_ETC_DATA/protocols" "$EVOFW_TEST_ETC_DATA/services" /etc/
fi
mkdir -p /etc/evofw /var/lib/evofw
ip link set lo up
ip netns add client
ip link add server0 type veth peer name client0
ip link set client0 netns client
ip addr add 198.18.0.1/24 dev server0
ip -6 addr add 2001:db8:1::1/64 dev server0 nodad
ip link set server0 up
ip -n client addr add 198.18.0.2/24 dev client0
ip -n client -6 addr add 2001:db8:1::2/64 dev client0 nodad
ip -n client link set client0 up
ip -n client link set lo up
python3 "$root/server.py" "$root" &
server_pid=$!
trap 'kill "$server_pid" 2>/dev/null || true; ip netns del client 2>/dev/null || true' EXIT
for attempt in {1..100}; do [[ ! -f "$root/port" ]] || break; sleep .05; done
port=$(cat "$root/port")
port6=$(cat "$root/port6")
cat > /etc/evofw/agent.conf <<CONF
EVOFW_CP_URL=http://127.0.0.1:$port
CLIENT_TOKEN=namespace-test-only
KERNEL_BACKEND=$backend
EVOFW_SKIP_SELF_UPDATE=1
CONF
nft add table inet unrelated
nft 'add chain inet unrelated input { type filter hook input priority 10; policy accept; }'
# A fresh connection through a veth verifies actual packet enforcement.
ip netns exec client curl --noproxy '*' --max-time 2 -fsS "http://198.18.0.1:$port/echo" >/dev/null
ip netns exec client curl --noproxy '*' --max-time 2 -gfsS "http://[2001:db8:1::1]:$port6/echo" >/dev/null
bash "$root/agent.sh"
python3 -c 'import json; assert json.load(open("/var/lib/evofw/telemetry-state.json"))["pending"] == [], "fixture ACK did not drain durable telemetry"'
[[ $(cat /var/lib/evofw/last_hash) == "sha256:$(printf '1%.0s' {1..64})" ]]
nft list table inet unrelated >/dev/null
if ip netns exec client curl --noproxy '*' --max-time 2 -fsS "http://198.18.0.1:$port/echo" >/dev/null; then echo 'deny did not block packet' >&2; exit 1; fi
if ip netns exec client curl --noproxy '*' --max-time 2 -gfsS "http://[2001:db8:1::1]:$port6/echo" >/dev/null; then echo 'IPv6 deny did not block packet' >&2; exit 1; fi
# Same hash must reconcile missing owned state, retaining unrelated rules.
if [[ "$backend" == nft ]]; then nft delete table inet evofw; else ipset flush evofw_deny_v4; fi
bash "$root/agent.sh"
if [[ "$backend" == nft ]]; then nft list table inet evofw >/dev/null; else ipset test evofw_deny_v4 198.18.0.2; fi
nft list table inet unrelated >/dev/null
touch "$root/invalid"
if bash "$root/agent.sh"; then echo 'invalid candidate unexpectedly applied' >&2; exit 1; fi
[[ $(cat /var/lib/evofw/last_hash) == "sha256:$(printf '1%.0s' {1..64})" ]]
if [[ "$backend" == nft ]]; then
nft list set inet evofw deny_v4 | grep -q '198.18.0.2'
nft list set inet evofw deny_v6 | grep -q '2001:db8:1::2'
else
ipset test evofw_deny_v4 198.18.0.2
ipset test evofw_deny_v6 2001:db8:1::2
fi
nft list table inet unrelated >/dev/null
echo "Linux namespace smoke passed ($backend): IPv4+IPv6 apply, real packet deny, drift repair, invalid-candidate last-good, unrelated table preservation"
CHILD
done