mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 08:57:57 +00:00
feat(secrets): encrypt sensitive store fields (AES-GCM, master.key 0600, api_key_env) + fast models-endpoint probing (fix zen backlog + source status) + UI cleanup (drop redundant parens labels, grid models 4/row)
This commit is contained in:
@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -95,7 +96,7 @@ func TestStoreUpsertRemove(t *testing.T) {
|
||||
if err := s.Load(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Upsert(Source{Name: "a", BaseURL: "http://a", Models: []Model{{ID: "m"}}}); err != nil {
|
||||
if err := s.Upsert(Source{Name: "a", BaseURL: "http://a", APIKey: "sk-a", Models: []Model{{ID: "m"}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Upsert(Source{Name: "b", BaseURL: "http://b", Models: []Model{{ID: "m2"}}}); err != nil {
|
||||
@ -119,4 +120,80 @@ func TestStoreUpsertRemove(t *testing.T) {
|
||||
if len(s2.List()) != 1 {
|
||||
t.Fatalf("reloaded list = %d", len(s2.List()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreSecretEncryption(t *testing.T) {
|
||||
t.Setenv("LLMS_PROXY_MASTER_KEY", "")
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "runtime.json")
|
||||
s := NewStore(path)
|
||||
if s.box == nil {
|
||||
t.Fatal("expected secret box")
|
||||
}
|
||||
if err := s.Load(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
headers := map[string]string{"Authorization": "Bearer sk-hdr", "X-Custom": "plain"}
|
||||
if err := s.Upsert(Source{Name: "a", BaseURL: "http://a", APIKey: "sk-secret-123", Headers: headers}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.SaveKey(GWKey{Key: "gw-secret", Role: "admin"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// file on disk must not contain plaintext secrets
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, plain := range []string{"sk-secret-123", "Bearer sk-hdr", "gw-secret"} {
|
||||
if strings.Contains(string(raw), plain) {
|
||||
t.Fatalf("secret %q stored in plaintext on disk", plain)
|
||||
}
|
||||
}
|
||||
// in-memory stays plaintext after the writes
|
||||
src := s.List()[0]
|
||||
if src.APIKey != "sk-secret-123" {
|
||||
t.Fatalf("in-memory api_key = %q", src.APIKey)
|
||||
}
|
||||
if src.Headers["Authorization"] != "Bearer sk-hdr" {
|
||||
t.Fatal("in-memory header not plaintext")
|
||||
}
|
||||
// reload: decrypted back
|
||||
s2 := NewStore(path)
|
||||
if err := s2.Load(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s2.List()[0].APIKey != "sk-secret-123" {
|
||||
t.Fatalf("reloaded api_key = %q", s2.List()[0].APIKey)
|
||||
}
|
||||
if k, ok := s2.KeyByValue("gw-secret"); !ok || k.Role != "admin" {
|
||||
t.Fatalf("reloaded key lookup failed: %+v %v", k, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretBoxRoundTrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
box, err := NewSecretBox(filepath.Join(dir, "runtime.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, err := box.Encrypt("sk-abc-xyz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.HasPrefix(v, "sk-") || strings.Contains(v, "abc-xyz") {
|
||||
t.Fatalf("ciphertext leaked plaintext: %q", v)
|
||||
}
|
||||
out, err := box.Decrypt(v)
|
||||
if err != nil || out != "sk-abc-xyz" {
|
||||
t.Fatalf("roundtrip: %q %v", out, err)
|
||||
}
|
||||
if plain, err := box.Decrypt("sk-plain"); err != nil || plain != "sk-plain" {
|
||||
t.Fatalf("plain passthrough: %q %v", plain, err)
|
||||
}
|
||||
// wrong key must error
|
||||
bad, _ := NewSecretBox(filepath.Join(t.TempDir(), "runtime.json"))
|
||||
if _, err := bad.Decrypt(v); err == nil {
|
||||
t.Fatal("expected decrypt failure with wrong key")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user