Smail/server/controllers/config_public_response_test.go
smail dd04bdb49e
Some checks are pending
Security Checks / security (push) Waiting to run
完成安全整改、配置重构与邮件认证优化
2026-08-28 14:33:13 +08:00

137 lines
4.7 KiB
Go

package controllers
import (
"encoding/json"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/utils/credential"
)
func TestPublicConfigSectionsDoNotExposeSecretsAndRemainStrictlyDecodable(t *testing.T) {
runtime := config.RuntimeConfig{
Generation: 7,
SetupState: config.SetupStateReady,
Root: config.RootConfig{
ConfigVersion: config.CurrentConfigVersion,
SchemaVersion: config.CurrentSchemaVersion,
Generation: 7,
SetupState: config.SetupStateReady,
Database: config.DatabaseConfig{
Type: config.DatabaseTypeMySQL,
Host: "127.0.0.1",
Port: config.DefaultMySQLPort,
Username: "smail",
Name: "smail",
Password: config.StoredSecret{Ciphertext: "enc:v1:database-secret", Version: credential.CurrentVersion, UpdatedAt: time.Now().UTC()},
},
},
SMTP: config.SMTPSectionConfig{
Meta: config.SectionMeta{Schema: config.CurrentSectionSchema, Generation: 7},
Relay: config.SMTPConfig{
RelayHost: "smtp.example.com",
RelayPort: 587,
Username: "relay-user",
Password: config.StoredSecret{Ciphertext: "enc:v1:relay-secret", Version: credential.CurrentVersion, UpdatedAt: time.Now().UTC()},
Security: "starttls",
},
},
Email: config.EmailSectionConfig{Meta: config.SectionMeta{Schema: config.CurrentSectionSchema, Generation: 7}},
SSL: config.SSLSectionConfig{Meta: config.SectionMeta{Schema: config.CurrentSectionSchema, Generation: 7}},
Plugins: config.PluginsSectionConfig{
Meta: config.SectionMeta{Schema: config.CurrentSectionSchema, Generation: 7},
Plugins: map[string]config.PluginInstance{
"sample": {
Enabled: true, Executable: "sample.exe", Permissions: config.PluginPermissions{},
Secret: config.StoredSecret{Ciphertext: "enc:v1:plugin-secret", Version: credential.CurrentVersion, UpdatedAt: time.Now().UTC()},
},
},
},
}
tests := []struct {
section config.Section
decode func([]byte) error
}{
{config.SectionRoot, func(data []byte) error {
var value config.RootConfig
return config.ParseStrict(data, &value)
}},
{config.SectionSMTP, func(data []byte) error {
var value config.SMTPSectionConfig
return config.ParseStrict(data, &value)
}},
{config.SectionEmail, func(data []byte) error {
var value config.EmailSectionConfig
return config.ParseStrict(data, &value)
}},
{config.SectionSSL, func(data []byte) error {
var value config.SSLSectionConfig
return config.ParseStrict(data, &value)
}},
{config.SectionPlugins, func(data []byte) error {
var value config.PluginsSectionConfig
return config.ParseStrict(data, &value)
}},
}
for _, test := range tests {
t.Run(string(test.section), func(t *testing.T) {
public, err := publicConfigSection(runtime, test.section)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(public)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(data), "database-secret") || strings.Contains(string(data), "relay-secret") {
t.Fatalf("public configuration contains secret data: %s", data)
}
writableData := make(map[string]json.RawMessage)
if err := json.Unmarshal(data, &writableData); err != nil {
t.Fatalf("public configuration is not a JSON object: %v", err)
}
delete(writableData, "databasePasswordStatus")
delete(writableData, "relayPasswordStatus")
if test.section == config.SectionPlugins {
var plugins map[string]json.RawMessage
if err := json.Unmarshal(writableData["plugins"], &plugins); err != nil {
t.Fatalf("plugin public configuration is not an object: %v", err)
}
for id, raw := range plugins {
var plugin map[string]json.RawMessage
if err := json.Unmarshal(raw, &plugin); err != nil {
t.Fatalf("plugin %q public configuration is not an object: %v", id, err)
}
status, ok := plugin["secretStatus"]
if !ok || !strings.Contains(string(status), `"configured":true`) || !strings.Contains(string(status), `"hint":"******"`) {
t.Fatalf("plugin %q secret status is incomplete: %s", id, status)
}
if strings.Contains(string(status), "plugin-secret") || strings.Contains(string(status), "enc:v1:") {
t.Fatalf("plugin %q secret status exposed secret material: %s", id, status)
}
delete(plugin, "secretStatus")
plugins[id], err = json.Marshal(plugin)
if err != nil {
t.Fatal(err)
}
}
writableData["plugins"], err = json.Marshal(plugins)
if err != nil {
t.Fatal(err)
}
}
data, err = json.Marshal(writableData)
if err != nil {
t.Fatal(err)
}
if err := test.decode(data); err != nil {
t.Fatalf("public configuration is not strictly decodable: %v; data=%s", err, data)
}
})
}
}