feat(ssf): 实现安全事件一键连接与凭据托管
新增数据库驱动的 SecurityEventConnectionManager,复用 RFC 7662 机器 Client,自动完成 Discovery、Push Bearer 生成、Stream 创建、Verification、首次启用、零停机轮换和安全退役。 增加文件与 Kubernetes SecretStore、最小权限 RBAC、动态 Receiver/撤销水位/内省降级,以及系统设置管理页面。已通过全量 Go 测试、go vet、关键包竞态测试、27 个 Web 测试、类型检查、生产构建、Compose 和 Kubernetes dry-run。
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package securityevents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKubernetesSecretStoreUsesOnePreprovisionedSecret(t *testing.T) {
|
||||
var mutex sync.Mutex
|
||||
data := map[string]string{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/namespaces/easyai/secrets/easyai-gateway-security-events" || r.Header.Get("Authorization") != "Bearer service-account-token" {
|
||||
t.Fatalf("unexpected Kubernetes request: %s authorization=%t", r.URL.Path, r.Header.Get("Authorization") != "")
|
||||
}
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": data})
|
||||
case http.MethodPatch:
|
||||
var patch struct {
|
||||
Data map[string]*string `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for key, value := range patch.Data {
|
||||
if value == nil {
|
||||
delete(data, key)
|
||||
} else {
|
||||
data[key] = *value
|
||||
}
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": data})
|
||||
default:
|
||||
http.Error(w, "unsupported", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
tokenFile := filepath.Join(t.TempDir(), "token")
|
||||
if err := os.WriteFile(tokenFile, []byte("service-account-token\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secrets, err := NewKubernetesSecretStore(KubernetesSecretStoreConfig{
|
||||
Namespace: "easyai", SecretName: "easyai-gateway-security-events", APIServer: server.URL,
|
||||
TokenFile: tokenFile, HTTPClient: server.Client(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value := []byte("0123456789abcdef0123456789abcdef")
|
||||
if err := secrets.Put(context.Background(), "ssf-push-connection", value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data["ssf-push-connection"] != base64.StdEncoding.EncodeToString(value) {
|
||||
t.Fatal("Push Bearer was not stored in Kubernetes Secret data")
|
||||
}
|
||||
loaded, err := secrets.Get(context.Background(), "ssf-push-connection")
|
||||
if err != nil || string(loaded) != string(value) {
|
||||
t.Fatalf("loaded secret mismatch: err=%v", err)
|
||||
}
|
||||
clear(loaded)
|
||||
if err := secrets.Delete(context.Background(), "ssf-push-connection"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := data["ssf-push-connection"]; ok {
|
||||
t.Fatal("Push Bearer key was not removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesSecretStoreRejectsUnprovisionedSecret(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { http.NotFound(w, nil) }))
|
||||
defer server.Close()
|
||||
tokenFile := filepath.Join(t.TempDir(), "token")
|
||||
if err := os.WriteFile(tokenFile, []byte("token"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secrets, err := NewKubernetesSecretStore(KubernetesSecretStoreConfig{
|
||||
Namespace: "easyai", SecretName: "easyai-gateway-security-events", APIServer: server.URL,
|
||||
TokenFile: tokenFile, HTTPClient: server.Client(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := secrets.Put(context.Background(), "ssf-push-connection", []byte("0123456789abcdef0123456789abcdef")); err == nil {
|
||||
t.Fatal("missing pre-provisioned Kubernetes Secret was accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user