feat(nonprod): bind native Agent to signed calls and live capture

This commit is contained in:
2026-10-04 15:08:02 +08:00
parent 5f052c1db0
commit 0e9e6411be
22 changed files with 514 additions and 45 deletions
+10 -2
View File
@@ -17,7 +17,7 @@ systemd service or add it to a production host.
## Native Asterisk validation
`nonprod-call-evidence.sh` is the mandatory capture-first wrapper for
non-production `mock`, `mixed` and `real` call checks. It runs on a validation
non-production `mock`, `mixed` and explicit `nonprod-real` call checks. It runs on a validation
host with native Asterisk and required diagnostics; it is not an Asterisk or
Agent replacement and is not containerized. Run it explicitly with the current
call authorization and the approved target/trunk. It refuses production mode
@@ -29,7 +29,15 @@ of the installed binary and configuration. Missing facts fail the validation;
`--preflight-only` never authorizes a call. After capture, missing capture or
recording SHA-256, Asterisk journal, SIP summary, logger shutdown, timestamp, or
restricted evidence ownership is recorded in `diagnostic-errors.txt` and fails
the check; an already failed call keeps its nonzero result. Isolated tests
the check; an already failed call keeps its nonzero result. Once live tcpdump
and the PJSIP logger are running, the script creates a root-owned, group-readable
`<call-id>.active` capture arm under `--proof-root` (default
`/run/sip-go-agent/nonprod-armed`). The real Agent must set `AGENT_EVIDENCE_ROOT`
to this directory; it checks the exact approved event ID, trunk, raw callee,
recent arm and live capture PID before origination. The script removes the arm
before stopping capture. Run one approved call per invocation, with
`--call-id` equal to that call's MQ `event_id`; never reuse a stale arm.
Isolated tests
replace host tools with fakes: they do not prove a real host or supplier is ready.
For the **nonproduction user-level Asterisk service only**, pass
+21
View File
@@ -39,6 +39,9 @@ target=""
call_command=()
preflight_only=0
attempt_ledger="/var/lib/sip-go-agent/state/real-call-attempts.tsv"
proof_root="/run/sip-go-agent/nonprod-armed"
proof_file=""
proof_created=0
attempt_number=0
while (($#)); do
@@ -55,6 +58,7 @@ while (($#)); do
--rtp-end) [[ $# -ge 2 ]] || usage; rtp_end=$2; shift 2 ;;
--preflight-only) preflight_only=1; shift ;;
--attempt-ledger) [[ $# -ge 2 ]] || usage; attempt_ledger=$2; shift 2 ;;
--proof-root) [[ $# -ge 2 ]] || usage; proof_root=$2; shift 2 ;;
--trunk) [[ $# -ge 2 ]] || usage; trunk=$2; shift 2 ;;
--target) [[ $# -ge 2 ]] || usage; target=$2; shift 2 ;;
--) shift; call_command=("$@"); break ;;
@@ -73,6 +77,7 @@ esac
[[ "$trunk" =~ ^(provider-primary|provider-second|provider-third|trunk-[A-Za-z0-9._-]+)$ ]] || { echo 'trunk is not an approved non-production trunk id' >&2; exit 1; }
[[ "$target" =~ ^(15003164745|15830461047)$ ]] || { echo 'target is outside the approved outbound whitelist' >&2; exit 1; }
[[ "$attempt_ledger" =~ ^/[A-Za-z0-9._/-]+$ ]] || { echo 'invalid attempt ledger path' >&2; exit 1; }
[[ "$proof_root" =~ ^/[A-Za-z0-9._/-]+$ ]] || { echo 'invalid proof root path' >&2; exit 1; }
[[ ${#call_command[@]} -gt 0 ]] || { echo 'call command is required after --' >&2; exit 1; }
[[ "$interface" =~ ^[A-Za-z0-9_.:-]+$ ]] || { echo 'invalid capture interface' >&2; exit 1; }
[[ "$sip_port" =~ ^[0-9]+$ && "$rtp_start" =~ ^[0-9]+$ && "$rtp_end" =~ ^[0-9]+$ ]] || { echo 'invalid port' >&2; exit 1; }
@@ -294,6 +299,10 @@ cleanup() {
local call_exit=$? evidence_failed=0
trap - EXIT
set +e
if ((proof_created)) && ! rm -f -- "$proof_file"; then
printf 'capture arm removal failed\n' >>"$evidence_dir/diagnostic-errors.txt"
evidence_failed=1
fi
stop_capture
if ((logger_enabled)) && ! asterisk_cli "pjsip set logger off" >"$evidence_dir/pjsip-logger-off.txt" 2>&1; then
printf 'PJSIP logger stop failed\n' >>"$evidence_dir/diagnostic-errors.txt"
@@ -394,6 +403,18 @@ if ((preflight_only)); then
fi
require_call_window
# The Agent checks this root-owned, call-specific live capture arm before any
# originate. A stale arm is never overwritten; the trap removes it first.
install -d -o root -g "$run_as" -m 0750 -- "$proof_root"
proof_file="$proof_root/$call_id.active"
[[ ! -e "$proof_file" ]] || { echo 'stale or concurrent capture arm; fail-closed' >&2; exit 1; }
proof_tmp="$(mktemp --tmpdir="$proof_root" ".$call_id.XXXXXX")"
printf '%s\t%s\t%s\n' "$capture_pid" "$trunk" "$target" >"$proof_tmp"
chown "root:$run_as" "$proof_tmp"
chmod 0640 "$proof_tmp"
ln -- "$proof_tmp" "$proof_file" || { rm -f -- "$proof_tmp"; echo 'capture arm already exists; fail-closed' >&2; exit 1; }
proof_created=1
rm -f -- "$proof_tmp"
call_status=0
set +e
runuser -u "$run_as" -- "${call_command[@]}" >"$evidence_dir/call-output.private" 2>&1