fix(deploy): fail closed before non-production call diagnostics

This commit is contained in:
2026-09-30 18:35:13 +08:00
parent 66062b0c6c
commit 42139193ef
4 changed files with 133 additions and 6 deletions
+7 -1
View File
@@ -21,7 +21,13 @@ non-production `mock`, `mixed` and `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
and fails closed when its prerequisites are missing.
and fails closed when its prerequisites are missing. Before any dial attempt it
checks the Asia/Shanghai 09:00–20:00 window twice (09:00 included, 20:00
excluded), the exact `enabled` + `active` Asterisk systemd state, the running
ARI module and HTTP `/ari/` route, the selected PJSIP endpoint, and SHA-256
of the installed binary and configuration. Missing facts fail the validation;
`--preflight-only` never authorizes a call. Isolated tests replace host tools
with fakes: they do not prove a real host or supplier is ready.
The offline OSS environment file is a fixture for isolated tests only. It
contains no real credentials or production approval. The former
+42 -5
View File
@@ -60,7 +60,11 @@ while (($#)); do
esac
done
[[ "$environment" != production ]] || { echo 'production requires the separate production gate' >&2; exit 1; }
case "$environment" in
development|mock|mixed|real) ;;
production) echo 'production requires the separate production gate' >&2; exit 1 ;;
*) echo 'invalid non-production environment' >&2; exit 1 ;;
esac
[[ "$call_id" =~ ^[A-Za-z0-9._-]+$ ]] || { echo 'invalid call id' >&2; exit 1; }
[[ "$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; }
@@ -68,6 +72,15 @@ done
[[ ${#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; }
require_call_window() {
local shanghai_hm
shanghai_hm="$(TZ=Asia/Shanghai date +%H%M)" || { echo 'Asia/Shanghai clock unavailable; fail-closed' >&2; exit 1; }
if [[ ! "$shanghai_hm" =~ ^[0-9]{4}$ || "$shanghai_hm" < "0900" || "$shanghai_hm" > "1959" ]]; then
echo 'outside Asia/Shanghai 09:00-20:00; fail-closed' >&2
exit 1
fi
}
require_call_window
if [[ "$interface" == any ]]; then
default_interface="$(ip route show default 2>/dev/null | awk 'NR == 1 {for (i = 1; i <= NF; i++) if ($i == "dev") {print $(i + 1); exit}}')"
[[ -n "$default_interface" ]] && interface="$default_interface"
@@ -107,16 +120,39 @@ redact() {
sed -E 's/(password|secret|token|authorization|api[_-]?key)[^[:space:]]*/\1=<redacted>/Ig'
}
systemctl is-enabled asterisk.service >"$evidence_dir/asterisk-enabled.txt" 2>&1 || true
systemctl is-active asterisk.service >"$evidence_dir/asterisk-active.txt" 2>&1 || true
if ! systemctl is-enabled asterisk.service >"$evidence_dir/asterisk-enabled.txt" 2>&1 ||
! grep -qx enabled "$evidence_dir/asterisk-enabled.txt"; then
echo 'Asterisk service must be enabled and active; fail-closed' >&2
exit 1
fi
if ! systemctl is-active asterisk.service >"$evidence_dir/asterisk-active.txt" 2>&1 ||
! grep -qx active "$evidence_dir/asterisk-active.txt"; then
echo 'Asterisk service must be enabled and active; fail-closed' >&2
exit 1
fi
uname -a >"$evidence_dir/uname.txt"
cat /etc/os-release >"$evidence_dir/os-release.txt"
ip -brief address >"$evidence_dir/ip-address.txt"
ss -lunp >"$evidence_dir/udp-listeners.txt" 2>&1 || ss -lun >"$evidence_dir/udp-listeners.txt"
"$asterisk_bin" -rx "module show like res_ari.so" 2>&1 | redact >"$evidence_dir/ari-module-status.txt"
"$asterisk_bin" -rx "http show status" 2>&1 | redact >"$evidence_dir/ari-http-status.txt"
if ! grep -Eq 'res_ari\.so.*Running' "$evidence_dir/ari-module-status.txt" ||
! grep -Fq 'Server Enabled and Bound' "$evidence_dir/ari-http-status.txt" ||
! grep -Fq '/ari/' "$evidence_dir/ari-http-status.txt"; then
echo 'ARI module or HTTP route unavailable; fail-closed' >&2
exit 1
fi
"$asterisk_bin" -rx "pjsip show endpoint $trunk" 2>&1 | redact >"$evidence_dir/pjsip-endpoint.txt"
if ! grep -Eq 'Endpoint:[[:space:]]*' "$evidence_dir/pjsip-endpoint.txt" || ! grep -Fq "$trunk" "$evidence_dir/pjsip-endpoint.txt"; then
echo 'PJSIP endpoint unavailable; fail-closed' >&2
exit 1
fi
"$asterisk_bin" -rx "pjsip show contacts" 2>&1 | redact >"$evidence_dir/pjsip-contacts-before.txt"
"$asterisk_bin" -rx "core show channels verbose" 2>&1 | redact >"$evidence_dir/channels-before.txt"
sha256sum /opt/sip-go-agent/current/sip-go-agent /etc/sip-go-agent/artifacts/*.json /etc/sip-go-agent/ai/*.json >"$evidence_dir/installed-sha256.txt" 2>&1 || true
if ! sha256sum /opt/sip-go-agent/current/sip-go-agent /etc/sip-go-agent/artifacts/*.json /etc/sip-go-agent/ai/*.json >"$evidence_dir/installed-sha256.txt" 2>&1; then
echo 'installed package/config SHA-256 unavailable; fail-closed' >&2
exit 1
fi
logger_enabled=0
capture_pid=""
@@ -243,7 +279,7 @@ reserve_attempt() {
return
fi
local today count legacy_count metadata
today="$(date -u +%F)"
today="$(TZ=Asia/Shanghai date +%F)"
install -d -m 0700 "$(dirname "$attempt_ledger")"
touch "$attempt_ledger"
exec 9>>"$attempt_ledger.lock"
@@ -295,6 +331,7 @@ if ((preflight_only)); then
exit 0
fi
require_call_window
call_status=0
set +e
runuser -u "$run_as" -- "${call_command[@]}" >"$evidence_dir/call-output.private" 2>&1