Some checks are pending
Security Checks / security (push) Waiting to run
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
205 lines
5.4 KiB
Go
205 lines
5.4 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
type Section string
|
|
|
|
const (
|
|
SectionRoot Section = "root"
|
|
SectionSMTP Section = "smtp"
|
|
SectionEmail Section = "email"
|
|
SectionSSL Section = "ssl"
|
|
SectionPlugins Section = "plugins"
|
|
)
|
|
|
|
const CurrentSectionSchema uint32 = 1
|
|
|
|
type SectionRef struct {
|
|
File string `json:"file"`
|
|
Schema uint32 `json:"schema"`
|
|
Generation uint64 `json:"generation"`
|
|
SHA256 string `json:"sha256"`
|
|
}
|
|
|
|
type SectionMeta struct {
|
|
Schema uint32 `json:"schema"`
|
|
Generation uint64 `json:"generation"`
|
|
}
|
|
|
|
type DomainConfig struct {
|
|
Primary string `json:"primary,omitempty"`
|
|
Accepted []string `json:"accepted,omitempty"`
|
|
Web string `json:"web,omitempty"`
|
|
AccessHosts []string `json:"accessHosts,omitempty"`
|
|
}
|
|
|
|
type HTTPConfig struct {
|
|
Port int `json:"port,omitempty"`
|
|
HTTPSPort int `json:"httpsPort,omitempty"`
|
|
HTTPSEnabled bool `json:"httpsEnabled"`
|
|
TLSMode string `json:"tlsMode,omitempty"`
|
|
TrustedProxyCIDRs []string `json:"trustedProxyCidrs,omitempty"`
|
|
}
|
|
|
|
const (
|
|
HTTPSTLSModeDirect = "direct"
|
|
HTTPSTLSModeProxy = "proxy"
|
|
)
|
|
|
|
type SMTPSectionConfig struct {
|
|
Meta SectionMeta `json:"meta"`
|
|
Relay SMTPConfig `json:"relay"`
|
|
}
|
|
|
|
type EmailSectionConfig struct {
|
|
Meta SectionMeta `json:"meta"`
|
|
Policy EmailConfig `json:"policy"`
|
|
}
|
|
|
|
type SSLSectionConfig struct {
|
|
Meta SectionMeta `json:"meta"`
|
|
TLS SSLConfig `json:"tls"`
|
|
}
|
|
|
|
type PluginNetworkTarget struct {
|
|
Scheme string `json:"scheme"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
}
|
|
|
|
type PluginPermissions struct {
|
|
Capabilities []string `json:"capabilities,omitempty"`
|
|
Network []PluginNetworkTarget `json:"network,omitempty"`
|
|
}
|
|
|
|
type PluginInstance struct {
|
|
Enabled bool `json:"enabled"`
|
|
Executable string `json:"executable"`
|
|
Permissions PluginPermissions `json:"permissions"`
|
|
Secret StoredSecret `json:"secret,omitempty"`
|
|
Settings json.RawMessage `json:"settings,omitempty"`
|
|
}
|
|
|
|
type PluginsSectionConfig struct {
|
|
Meta SectionMeta `json:"meta"`
|
|
Plugins map[string]PluginInstance `json:"plugins"`
|
|
}
|
|
|
|
type RuntimeConfig struct {
|
|
Generation uint64
|
|
SetupState SetupState
|
|
Root RootConfig
|
|
SMTP SMTPSectionConfig
|
|
Email EmailSectionConfig
|
|
SSL SSLSectionConfig
|
|
Plugins PluginsSectionConfig
|
|
LoadedAt time.Time
|
|
}
|
|
|
|
type sectionDefinition struct {
|
|
File string
|
|
Schema uint32
|
|
}
|
|
|
|
var fixedSectionDefinitions = map[Section]sectionDefinition{
|
|
SectionRoot: {File: "config.json", Schema: CurrentConfigVersion},
|
|
SectionSMTP: {File: "smtp.json", Schema: CurrentSectionSchema},
|
|
SectionEmail: {File: "email.json", Schema: CurrentSectionSchema},
|
|
SectionSSL: {File: "ssl.json", Schema: CurrentSectionSchema},
|
|
SectionPlugins: {File: "plugins.json", Schema: CurrentSectionSchema},
|
|
}
|
|
|
|
var requiredPartitionSections = []Section{SectionSMTP, SectionEmail, SectionSSL, SectionPlugins}
|
|
|
|
var (
|
|
ErrInvalidSection = errors.New("invalid configuration section")
|
|
ErrInvalidSectionReference = errors.New("invalid configuration section reference")
|
|
ErrSectionGenerationMismatch = errors.New("configuration section generation mismatch")
|
|
)
|
|
|
|
func FixedSectionFile(section Section) (string, bool) {
|
|
definition, ok := fixedSectionDefinitions[section]
|
|
return definition.File, ok
|
|
}
|
|
|
|
func FixedSectionSchema(section Section) (uint32, bool) {
|
|
definition, ok := fixedSectionDefinitions[section]
|
|
return definition.Schema, ok
|
|
}
|
|
|
|
func RequiredPartitionSections() []Section {
|
|
result := append([]Section(nil), requiredPartitionSections...)
|
|
return result
|
|
}
|
|
|
|
func ValidateSectionReferences(references map[Section]SectionRef, generation uint64) error {
|
|
if generation == 0 || len(references) != len(requiredPartitionSections) {
|
|
return ErrInvalidSectionReference
|
|
}
|
|
for section := range references {
|
|
if section == SectionRoot {
|
|
return ErrInvalidSectionReference
|
|
}
|
|
if _, ok := fixedSectionDefinitions[section]; !ok {
|
|
return ErrInvalidSectionReference
|
|
}
|
|
}
|
|
for _, section := range requiredPartitionSections {
|
|
reference, ok := references[section]
|
|
if !ok {
|
|
return ErrInvalidSectionReference
|
|
}
|
|
definition := fixedSectionDefinitions[section]
|
|
if reference.File != definition.File || reference.Schema != definition.Schema {
|
|
return ErrInvalidSectionReference
|
|
}
|
|
if reference.Generation != generation {
|
|
return ErrSectionGenerationMismatch
|
|
}
|
|
if !validSHA256(reference.SHA256) {
|
|
return ErrInvalidSectionReference
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateSectionMeta(section Section, meta SectionMeta, expectedGeneration uint64) error {
|
|
definition, ok := fixedSectionDefinitions[section]
|
|
if !ok || section == SectionRoot || meta.Schema != definition.Schema {
|
|
return ErrInvalidSection
|
|
}
|
|
if meta.Generation == 0 || meta.Generation != expectedGeneration {
|
|
return ErrSectionGenerationMismatch
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SectionDigest(data []byte) string {
|
|
digest := sha256.Sum256(data)
|
|
return hex.EncodeToString(digest[:])
|
|
}
|
|
|
|
func OrderedPartitionSections() []Section {
|
|
result := RequiredPartitionSections()
|
|
sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
|
|
return result
|
|
}
|
|
|
|
func validSHA256(value string) bool {
|
|
if len(value) != sha256.Size*2 {
|
|
return false
|
|
}
|
|
decoded, err := hex.DecodeString(value)
|
|
if err != nil || len(decoded) != sha256.Size {
|
|
return false
|
|
}
|
|
return value == hex.EncodeToString(decoded)
|
|
}
|