Validate explicit Mock Dispatcher runtime settings
This commit is contained in:
@@ -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 加载。
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user