Persist agent/secrets settings with masked secrets and runner hot-reload; add maintenance windows, maintpage binary, and zero-downtime listener handoff.
95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
package sysadmin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestMaskSecret(t *testing.T) {
|
|
if got := MaskSecret(""); got != "" {
|
|
t.Fatalf("empty: %q", got)
|
|
}
|
|
if got := MaskSecret("short"); got != "****" {
|
|
t.Fatalf("short: %q", got)
|
|
}
|
|
got := MaskSecret("tvly-prod-ABCDEFGHijklmnop")
|
|
if got != "tvly...mnop" {
|
|
t.Fatalf("single: %q", got)
|
|
}
|
|
got = MaskSecret("tvly-aaaa1111,tvly-bbbb2222")
|
|
if got != "tvly...1111,tvly...2222" {
|
|
t.Fatalf("pool: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSecretKeepAndFingerprint(t *testing.T) {
|
|
keep, err := secretKeepExisting(json.RawMessage(`""`))
|
|
if err != nil || !keep {
|
|
t.Fatalf("empty keep: keep=%v err=%v", keep, err)
|
|
}
|
|
keep, err = secretKeepExisting(json.RawMessage(`"new-key"`))
|
|
if err != nil || keep {
|
|
t.Fatalf("new value should not keep: keep=%v err=%v", keep, err)
|
|
}
|
|
fp := secretFingerprintJSON(json.RawMessage(`"secret-value"`))
|
|
var m map[string]string
|
|
if err := json.Unmarshal(fp, &m); err != nil || m["sha256"] == "" {
|
|
t.Fatalf("fingerprint: %s err=%v", string(fp), err)
|
|
}
|
|
if stringsContains(string(fp), "secret-value") {
|
|
t.Fatal("fingerprint leaked plaintext")
|
|
}
|
|
}
|
|
|
|
func TestSecretSettingMaskedOnList(t *testing.T) {
|
|
store := newMemStore()
|
|
svc := NewService(nil, Options{Store: store})
|
|
raw, _ := json.Marshal("tvly-prod-REALKEYVALUE99")
|
|
if _, err := svc.UpdateSettingEx(context.Background(), SettingUpdate{
|
|
Key: "secrets.tavily_api_keys", Value: raw, UpdatedBy: 1,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
list, err := svc.ListSettings(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var found *Setting
|
|
for i := range list {
|
|
if list[i].Key == "secrets.tavily_api_keys" {
|
|
found = &list[i]
|
|
break
|
|
}
|
|
}
|
|
if found == nil {
|
|
t.Fatal("setting not found")
|
|
}
|
|
if found.Value != "" {
|
|
t.Fatalf("secret value leaked: %#v", found.Value)
|
|
}
|
|
if !found.HasValue || found.Masked == "" || stringsContains(found.Masked, "REALKEY") {
|
|
t.Fatalf("mask broken: %+v", found)
|
|
}
|
|
// audit should be fingerprint
|
|
if len(store.audits) == 0 {
|
|
t.Fatal("expected audit")
|
|
}
|
|
if stringsContains(string(store.audits[len(store.audits)-1].after), "REALKEY") {
|
|
t.Fatalf("audit leaked: %s", store.audits[len(store.audits)-1].after)
|
|
}
|
|
}
|
|
|
|
func stringsContains(s, sub string) bool {
|
|
return len(sub) == 0 || (len(s) >= len(sub) && (s == sub || len(s) > 0 && containsFold(s, sub)))
|
|
}
|
|
|
|
func containsFold(s, sub string) bool {
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|