166 lines
9.5 KiB
Go
166 lines
9.5 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNonprodEvidencePreflightRequiresExplicitAsteriskConfig(t *testing.T) {
|
|
tools := t.TempDir()
|
|
for name, script := range map[string]string{
|
|
"id": "if [ \"$1\" = -u ]; then echo 0; else exec /usr/bin/id \"$@\"; fi\n",
|
|
"date": "if [ \"${TZ-}\" = Asia/Shanghai ] && [ \"$1\" = +%H%M ]; then echo 2100; else exec /usr/bin/date \"$@\"; fi\n",
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(tools, name), []byte("#!/bin/sh\n"+script), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
currentUser, err := user.Current()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := t.TempDir()
|
|
command := exec.Command("bash", "../../deploys/test/nonprod-call-evidence.sh", "--environment", "mock",
|
|
"--asterisk-scope", "user", "--trunk", "saas-trunk-42", "--target", "15003164745", "--run-as", currentUser.Username,
|
|
"--interface", "lo", "--recording-dir", filepath.Join(root, "recordings"), "--evidence-dir", filepath.Join(root, "evidence"),
|
|
"--preflight-only", "--", "/bin/true")
|
|
command.Env = append(os.Environ(), "PATH="+tools+":"+os.Getenv("PATH"), "ASTERISK_BIN=/bin/true", "ASTERISK_CONFIG=")
|
|
output, err := command.CombinedOutput()
|
|
if err == nil || !strings.Contains(string(output), "explicit user Asterisk configuration required") {
|
|
t.Fatalf("off-hours non-dial preflight must check Asterisk configuration: err=%v output=%s", err, output)
|
|
}
|
|
fromSaaS := exec.Command("bash", "../../deploys/test/nonprod-call-evidence.sh", "--environment", "mock",
|
|
"--asterisk-scope", "user", "--trunk", "provider-primary", "--target", "15803300952", "--run-as", currentUser.Username,
|
|
"--interface", "lo", "--recording-dir", filepath.Join(root, "recordings"), "--evidence-dir", filepath.Join(root, "saas-evidence"),
|
|
"--preflight-only", "--", "/bin/true")
|
|
fromSaaS.Env = command.Env
|
|
output, err = fromSaaS.CombinedOutput()
|
|
if err == nil || !strings.Contains(string(output), "explicit user Asterisk configuration required") {
|
|
t.Fatalf("SaaS supplied digit route did not reach the same evidence gate: err=%v output=%s", err, output)
|
|
}
|
|
withoutPreflight := exec.Command("bash", "../../deploys/test/nonprod-call-evidence.sh", "--environment", "mock",
|
|
"--asterisk-scope", "user", "--trunk", "provider-primary", "--target", "15003164745", "--run-as", currentUser.Username,
|
|
"--interface", "lo", "--recording-dir", filepath.Join(root, "recordings"), "--evidence-dir", filepath.Join(root, "real-attempt"),
|
|
"--", "/bin/true")
|
|
withoutPreflight.Env = command.Env
|
|
output, err = withoutPreflight.CombinedOutput()
|
|
if err == nil || !strings.Contains(string(output), "explicit user Asterisk configuration required") {
|
|
t.Fatalf("SaaS-governed off-hours call still requires host diagnostics: err=%v output=%s", err, output)
|
|
}
|
|
}
|
|
|
|
func TestNonprodUserAsteriskScopeUsesUserServiceAndConfiguredCLI(t *testing.T) {
|
|
tools := t.TempDir()
|
|
write := func(name, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(tools, name)
|
|
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
write("id", "if [ \"$1\" = -u ]; then echo 0; else exec /usr/bin/id \"$@\"; fi\n")
|
|
write("date", "if [ \"${TZ-}\" = Asia/Shanghai ] && [ \"$1\" = +%H%M ]; then echo 1000; else exec /usr/bin/date \"$@\"; fi\n")
|
|
write("runuser", "[ \"$1\" = -u ] && [ \"$3\" = -- ] || exit 99\nshift 3\nexec \"$@\"\n")
|
|
write("systemctl", "[ \"$1\" = --user ] && [ \"$3\" = go-sip-asterisk.service ] || exit 99\nprintf '%s\\n' \"$2\" >>\"$TEST_SERVICE_LOG\"\ncase \"$2\" in is-enabled) echo enabled;; is-active) echo active;; *) exit 99;; esac\n")
|
|
write("ip", "echo 'default via 192.0.2.1 dev eth0'\n")
|
|
write("ss", "echo 'udp 127.0.0.1:5060'\n")
|
|
write("tcpdump", "printf '%s\\n' \"$*\" >>\"$TEST_CAPTURE_ARGS\"\ncase \" $* \" in *' -c 1 '*) exit 124;; esac\nexit 99\n")
|
|
asterisk := write("asterisk", "[ \"$1\" = -C ] && [ \"$2\" = \"$TEST_CONFIG\" ] && [ \"$3\" = -rx ] || exit 98\nprintf '%s\\n' \"$4\" >>\"$TEST_CLI_LOG\"\ncase \"$4\" in 'module show like res_ari.so') echo 'res_ari.so Asterisk REST Interface 0 Running';; 'http show status') echo 'Server Enabled and Bound to 127.0.0.1:8088'; echo '/ari/...';; 'pjsip show endpoint '*) echo 'Endpoint: not-loaded';; *) exit 97;; esac\n")
|
|
root := t.TempDir()
|
|
configFile := filepath.Join(root, "asterisk.conf")
|
|
if err := os.WriteFile(configFile, []byte("[directories]\n"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
currentUser, err := user.Current()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serviceLog := filepath.Join(root, "service-checked")
|
|
cliLog := filepath.Join(root, "cli-checked")
|
|
captureArgs := filepath.Join(root, "capture-args")
|
|
command := exec.Command("bash", "../../deploys/test/nonprod-call-evidence.sh", "--environment", "mock",
|
|
"--asterisk-scope", "user", "--trunk", "provider-primary", "--target", "15003164745", "--run-as", currentUser.Username,
|
|
"--interface", "any", "--recording-dir", filepath.Join(root, "recordings"), "--evidence-dir", filepath.Join(root, "evidence"),
|
|
"--preflight-only", "--", "/bin/true")
|
|
command.Env = append(os.Environ(), "PATH="+tools+":"+os.Getenv("PATH"), "ASTERISK_BIN="+asterisk, "ASTERISK_CONFIG="+configFile,
|
|
"ASTERISK_LIBRARY_PATH="+tools, "TCPDUMP_BIN="+filepath.Join(tools, "tcpdump"), "TEST_CONFIG="+configFile,
|
|
"TEST_SERVICE_LOG="+serviceLog, "TEST_CLI_LOG="+cliLog, "TEST_CAPTURE_ARGS="+captureArgs)
|
|
output, err := command.CombinedOutput()
|
|
if err == nil || !strings.Contains(string(output), "PJSIP endpoint unavailable") {
|
|
t.Fatalf("missing user-service endpoint must block before dialing: err=%v output=%s", err, output)
|
|
}
|
|
serviceChecks, err := os.ReadFile(serviceLog)
|
|
if err != nil || !strings.Contains(string(serviceChecks), "is-enabled\nis-active\n") {
|
|
t.Fatalf("user service status was not verified: %v %q", err, serviceChecks)
|
|
}
|
|
actualCapture, err := os.ReadFile(captureArgs)
|
|
if err != nil || !strings.Contains(string(actualCapture), "-i any ") {
|
|
t.Fatalf("preflight must capture both external SIP and loopback RTP: err=%v args=%q", err, actualCapture)
|
|
}
|
|
cliChecks, err := os.ReadFile(cliLog)
|
|
if err != nil || !strings.Contains(string(cliChecks), "module show like res_ari.so\nhttp show status\npjsip show endpoint provider-primary\n") {
|
|
t.Fatalf("configured user Asterisk CLI was not used: %v %q", err, cliChecks)
|
|
}
|
|
}
|
|
|
|
func TestNonprodPreflightRejectsIncompleteCapturedEvidence(t *testing.T) {
|
|
tools := t.TempDir()
|
|
write := func(name, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(tools, name)
|
|
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
write("id", "if [ \"$1\" = -u ]; then echo 0; else exec /usr/bin/id \"$@\"; fi\n")
|
|
write("date", "if [ \"${TZ-}\" = Asia/Shanghai ] && [ \"$1\" = +%H%M ]; then echo 1000; else exec /usr/bin/date \"$@\"; fi\n")
|
|
write("systemctl", "case \"$1\" in is-enabled) echo enabled;; is-active) echo active;; *) exit 1;; esac\n")
|
|
write("ip", "echo 'lo UNKNOWN 127.0.0.1/8'\n")
|
|
write("ss", "echo 'udp 127.0.0.1:5060'\n")
|
|
write("journalctl", "exit 0\n")
|
|
write("chown", "exit 0\n")
|
|
asterisk := write("asterisk", "case \"$2\" in 'module show like res_ari.so') echo 'res_ari.so Asterisk REST Interface 0 Running';; 'http show status') echo 'Server Enabled and Bound to 127.0.0.1:8088'; echo '/ari/...';; 'pjsip show endpoint '*) echo 'Endpoint: provider-primary';; *) echo 'mock Asterisk status';; esac\n")
|
|
tcpdump := write("tcpdump", "case \" $* \" in *' -c 1 '*) exit 124;; esac\nprev=''\nfor arg in \"$@\"; do if [ \"$prev\" = -w ]; then printf 'synthetic packet bytes' >\"$arg\"; fi; prev=\"$arg\"; done\necho '1 packets captured'\nsleep 3\n")
|
|
write("sha256sum", "if [ \"$1\" = /opt/sip-go-agent/current/sip-go-agent ]; then echo 'synthetic installed hash'; exit 0; fi\nif [ \"$1\" = \"$TEST_EVIDENCE/capture.pcap\" ]; then echo 'synthetic capture hash failure' >&2; exit 1; fi\nexit 0\n")
|
|
marker := filepath.Join(tools, "DIALED")
|
|
write("runuser", "touch \"$TEST_DIAL_MARKER\"; exit 88\n")
|
|
currentUser, err := user.Current()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
evidence := filepath.Join(t.TempDir(), "evidence")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
command := exec.CommandContext(ctx, "bash", "../../deploys/test/nonprod-call-evidence.sh",
|
|
"--environment", "mock", "--trunk", "provider-primary", "--target", "15003164745",
|
|
"--interface", "lo", "--run-as", currentUser.Username, "--recording-dir", filepath.Join(tools, "recordings"),
|
|
"--evidence-dir", evidence, "--preflight-only", "--", "/bin/true")
|
|
command.Env = append(os.Environ(), "PATH="+tools+":"+os.Getenv("PATH"), "ASTERISK_BIN="+asterisk, "TCPDUMP_BIN="+tcpdump,
|
|
"TEST_EVIDENCE="+evidence, "TEST_DIAL_MARKER="+marker)
|
|
output, err := command.CombinedOutput()
|
|
if ctx.Err() != nil {
|
|
t.Fatalf("isolated preflight timed out: %v", ctx.Err())
|
|
}
|
|
if err == nil || !strings.Contains(string(output), "required post-call evidence unavailable") {
|
|
t.Fatalf("missing capture checksum did not fail the preflight: err=%v output=%s", err, output)
|
|
}
|
|
if _, err := os.Stat(marker); !os.IsNotExist(err) {
|
|
t.Fatalf("preflight invoked the call command: marker err=%v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(evidence, "capture.pcap")); err != nil {
|
|
t.Fatalf("synthetic evidence was not preserved after collection failed: %v", err)
|
|
}
|
|
failure, err := os.ReadFile(filepath.Join(evidence, "diagnostic-errors.txt"))
|
|
if err != nil || !strings.Contains(string(failure), "capture SHA-256 unavailable") {
|
|
t.Fatalf("missing checksum cause was not preserved: err=%v fact=%s", err, failure)
|
|
}
|
|
}
|