Validate explicit Mock Dispatcher runtime settings

This commit is contained in:
2026-09-30 07:26:26 +08:00
parent 7bcde1812f
commit e37a7371be
3 changed files with 145 additions and 0 deletions
@@ -33,6 +33,7 @@
- `PATH=/tmp/sip-go-agent-tools/bin:$PATH sh scripts/check-proto.sh` 通过(lint/build/generate/新包测试/7 文件清单 hash);`git diff --check`、`go test ./internal/rpc ./internal/agent ./internal/dispatcher ./cmd/sip-go-agent -count=1` 通过。其他旧实现/契约入口、文件名碰撞和迁移数据安全仍待 P02/P07,**不能据此称全仓已无代次或整体完成**。
- 新增无实现代次字段的 Dispatcher 环境预检:必须显式给出规范 UUID v4 身份、只读 HTTP 地址及密钥、RabbitMQ 地址和 SQLite 路径;当前 Mock 仅接受本机 HTTP/MQ 目标,mixed/real 在任何资源操作前拒绝。预检不打开数据库或网络,缺失值不继承旧默认配置,错误不打印凭据。`go test ./internal/config -run '^TestLoadDispatcherEnvironment' -count=1` 通过;此预检尚未接入主 CLI,不能当作 P02/P03 完成。
- Dispatcher 另有纯运行环境预检:唯一 Agent 端点清单和 OSS 配置文件须提供路径,本机 gRPC 监听及双向 TLS 文件/Agent 证书指纹须显式给出;缺失、非法指纹或非本机监听明确拒绝且不回显值。该检查不读取配置文件、不连接 Agent、不打开 SQLite/MQ;`go test ./internal/config -run '^TestLoadDispatcherRuntimeEnvironment' -count=1` 通过。当前仍未由 `dispatcher` 主命令调用,不能当作 Agent 加载或 OSS 签发已验收。
- Agent 增加独立的无代次环境预检:Agent/Cell 与预授权 D 的身份、会话与私有恢复路径、隔离 Mock 场景和已加载 SIP 测试事实、D gRPC 目标及 mTLS 文件/指纹均须显式提供;仅允许本机监听和本机 D 端点,mixed/real 在访问文件或网络前拒绝。缺失项不继承旧 `FromEnv` 默认值,凭据/地址不回显;`go test ./internal/config -run '^TestLoadAgentEnvironment' -count=1` 通过。此检查已接入根命令可达的唯一 Agent Mock 入口;本机双向 TLS 监听可由受信 D 证书探测,另一张同 CA 证书被指纹门禁拒绝。本地测试用批准的 D 身份激活会话,错误 D UUID 即使携带受信证书也在写入会话日志前拒绝;尚未执行呼叫、验证 Agent→D 录音或真实 Asterisk 加载。
+65
View File
@@ -0,0 +1,65 @@
package config
import (
"errors"
"fmt"
"os"
"strings"
)
// DispatcherRuntimeEnvironment contains deployment-owned runtime settings. A
// pure preflight must finish before any SQLite, MQ, HTTP or gRPC resource opens.
type DispatcherRuntimeEnvironment struct {
DispatcherEnvironment
Listen string
AgentEndpointsFile string
OSSConfigFile string
CAFile string
CertFile string
KeyFile string
PeerFingerprints map[string]struct{}
}
func LoadDispatcherRuntimeEnvironment(mode string) (DispatcherRuntimeEnvironment, error) {
base, err := LoadDispatcherEnvironment(mode)
if err != nil {
return DispatcherRuntimeEnvironment{}, err
}
settings := DispatcherRuntimeEnvironment{DispatcherEnvironment: base}
get := func(name string) (string, error) {
value := os.Getenv(name)
if strings.TrimSpace(value) == "" {
return "", fmt.Errorf("%s is required", name)
}
return value, nil
}
for _, field := range []struct {
name string
value *string
}{
{"DISPATCHER_GRPC_LISTEN", &settings.Listen},
{"DISPATCHER_AGENT_ENDPOINTS_FILE", &settings.AgentEndpointsFile},
{"DISPATCHER_OSS_CONFIG_FILE", &settings.OSSConfigFile},
{"MTLS_CA_FILE", &settings.CAFile},
{"MTLS_CERT_FILE", &settings.CertFile},
{"MTLS_KEY_FILE", &settings.KeyFile},
} {
value, err := get(field.name)
if err != nil {
return DispatcherRuntimeEnvironment{}, err
}
*field.value = value
}
if !localGRPCAddress(settings.Listen, true) {
return DispatcherRuntimeEnvironment{}, errors.New("DISPATCHER_GRPC_LISTEN must be an isolated local Mock address")
}
fingerprints, err := get("MTLS_PEER_CERT_FINGERPRINTS")
if err != nil {
return DispatcherRuntimeEnvironment{}, err
}
settings.PeerFingerprints, err = ParseCertificateFingerprints(fingerprints)
if err != nil || len(settings.PeerFingerprints) == 0 {
return DispatcherRuntimeEnvironment{}, errors.New("MTLS_PEER_CERT_FINGERPRINTS is invalid")
}
return settings, nil
}
@@ -0,0 +1,79 @@
package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func setCurrentDispatcherRuntimeEnvironment(t *testing.T) string {
t.Helper()
database := setDispatcherEnvironment(t)
root := t.TempDir()
for name, value := range map[string]string{
"DISPATCHER_GRPC_LISTEN": "127.0.0.1:0",
"DISPATCHER_AGENT_ENDPOINTS_FILE": filepath.Join(root, "agent-endpoints.json"),
"DISPATCHER_OSS_CONFIG_FILE": filepath.Join(root, "oss.json"),
"MTLS_CA_FILE": filepath.Join(root, "ca.pem"),
"MTLS_CERT_FILE": filepath.Join(root, "dispatcher.pem"),
"MTLS_KEY_FILE": filepath.Join(root, "dispatcher.key"),
"MTLS_PEER_CERT_FINGERPRINTS": strings.Repeat("a", 64),
} {
t.Setenv(name, value)
}
return database
}
func TestLoadDispatcherRuntimeEnvironmentRequiresExplicitMockDeployment(t *testing.T) {
database := setCurrentDispatcherRuntimeEnvironment(t)
settings, err := LoadDispatcherRuntimeEnvironment("mock")
if err != nil {
t.Fatal(err)
}
if settings.DispatcherID != dispatcherFixtureID || settings.SQLitePath != database || settings.Listen != "127.0.0.1:0" ||
settings.AgentEndpointsFile == "" || settings.OSSConfigFile == "" || len(settings.PeerFingerprints) != 1 {
t.Fatal("Dispatcher deployment values were lost or replaced by a default")
}
if _, err := os.Stat(database); !os.IsNotExist(err) {
t.Fatalf("preflight opened the business database: %v", err)
}
for _, name := range []string{
"DISPATCHER_GRPC_LISTEN", "DISPATCHER_AGENT_ENDPOINTS_FILE", "DISPATCHER_OSS_CONFIG_FILE",
"MTLS_CA_FILE", "MTLS_CERT_FILE", "MTLS_KEY_FILE", "MTLS_PEER_CERT_FINGERPRINTS",
} {
t.Run(name, func(t *testing.T) {
setCurrentDispatcherRuntimeEnvironment(t)
t.Setenv(name, "")
if _, err := LoadDispatcherRuntimeEnvironment("mock"); err == nil || !strings.Contains(err.Error(), name) {
t.Fatalf("missing Dispatcher setting silently inherited an old default: %v", err)
}
})
}
}
func TestLoadDispatcherRuntimeEnvironmentRejectsExternalOrUnsupportedModeWithoutIO(t *testing.T) {
for _, mode := range []string{"real", "mixed", ""} {
t.Run(mode, func(t *testing.T) {
database := setCurrentDispatcherRuntimeEnvironment(t)
if _, err := LoadDispatcherRuntimeEnvironment(mode); err == nil || !strings.Contains(err.Error(), "Mock") {
t.Fatalf("unapproved mode was admitted: %v", err)
}
if _, err := os.Stat(database); !os.IsNotExist(err) {
t.Fatalf("mode rejection opened SQLite: %v", err)
}
})
}
setCurrentDispatcherRuntimeEnvironment(t)
const external = "0.0.0.0:19443"
t.Setenv("DISPATCHER_GRPC_LISTEN", external)
if _, err := LoadDispatcherRuntimeEnvironment("mock"); err == nil || !strings.Contains(err.Error(), "DISPATCHER_GRPC_LISTEN") || strings.Contains(err.Error(), external) {
t.Fatalf("external Mock listener was admitted or echoed: %v", err)
}
setCurrentDispatcherRuntimeEnvironment(t)
const invalid = "not-a-fingerprint"
t.Setenv("MTLS_PEER_CERT_FINGERPRINTS", invalid)
if _, err := LoadDispatcherRuntimeEnvironment("mock"); err == nil || !strings.Contains(err.Error(), "MTLS_PEER_CERT_FINGERPRINTS") || strings.Contains(err.Error(), invalid) {
t.Fatalf("unapproved peer fingerprint was accepted or echoed: %v", err)
}
}