From 2146f79dbc9db852c7f8af254244f233257a20e6 Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 7 Oct 2026 16:58:31 +0800 Subject: [PATCH] Keep tcpdump privileged for private nonprod evidence --- deploys/test/nonprod-call-evidence.sh | 6 ++++-- internal/config/nonprod_call_gate_test.go | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/deploys/test/nonprod-call-evidence.sh b/deploys/test/nonprod-call-evidence.sh index deb96fe..1ef1f33 100755 --- a/deploys/test/nonprod-call-evidence.sh +++ b/deploys/test/nonprod-call-evidence.sh @@ -80,6 +80,8 @@ esac # "any" includes both provider SIP and the local Asterisk ExternalMedia RTP. # Reducing it to the default-route NIC silently omits loopback media. if [[ -z "$evidence_dir" ]]; then + # Debian's tcpdump AppArmor profile denies writes under hidden home dirs. + # Use this root-only system path unless another capture-approved path is known. evidence_dir="/var/lib/sip-go-agent/evidence/$call_id" fi install -d -m 0700 "$evidence_dir" @@ -104,7 +106,7 @@ command -v python3 >/dev/null || { echo 'python3 unavailable for SIP evidence su # A successful one-packet probe or a timeout after opening the capture proves # that the binary can open a raw capture socket; permission errors fail closed. probe_status=0 -timeout 2s "$tcpdump_bin" -i "$interface" -nn -c 1 -w /dev/null >/dev/null 2>"$evidence_dir/tcpdump-preflight.log" || probe_status=$? +timeout 2s "$tcpdump_bin" -Z root -i "$interface" -nn -c 1 -w /dev/null >/dev/null 2>"$evidence_dir/tcpdump-preflight.log" || probe_status=$? if [[ "$probe_status" != 0 && "$probe_status" != 124 ]]; then echo "tcpdump CAP_NET_RAW preflight failed: status=$probe_status" >&2 exit 1 @@ -331,7 +333,7 @@ trap cleanup EXIT asterisk_cli "pjsip set logger on" >"$evidence_dir/pjsip-logger-on.txt" 2>&1 || { echo 'cannot enable PJSIP logger; fail-closed' >&2; exit 1; } logger_enabled=1 -"$tcpdump_bin" -i "$interface" -nn -s0 -U -w "$evidence_dir/capture.pcap" \ +"$tcpdump_bin" -Z root -i "$interface" -nn -s0 -U -w "$evidence_dir/capture.pcap" \ "udp port $sip_port or (udp portrange $rtp_start-$rtp_end)" >"$evidence_dir/tcpdump.log" 2>&1 & capture_pid=$! sleep 1 diff --git a/internal/config/nonprod_call_gate_test.go b/internal/config/nonprod_call_gate_test.go index 2336aa6..38a688d 100644 --- a/internal/config/nonprod_call_gate_test.go +++ b/internal/config/nonprod_call_gate_test.go @@ -11,6 +11,19 @@ import ( "time" ) +func TestNonprodCaptureKeepsWriteAccessToPrivateEvidence(t *testing.T) { + script, err := os.ReadFile("../../deploys/test/nonprod-call-evidence.sh") + if err != nil { + t.Fatal(err) + } + // Debian tcpdump drops to its unprivileged account by default. Both + // capture invocations must retain root to write inside the root-only + // evidence directory; otherwise the preflight succeeds but capture fails. + if count := strings.Count(string(script), `"$tcpdump_bin" -Z root -i "$interface"`); count != 2 { + t.Fatalf("preflight and live capture must both retain write access: found %d root-owned invocations", count) + } +} + func TestNonprodCallEvidenceFailsBeforeDialWithoutRequiredGates(t *testing.T) { cases := []struct { name, hour, environment, enabled, active, ari, endpoint, want string