fix: 修复配置迁移工具对结构化SSL配置与绝对SQLite路径的兼容支持
Some checks are pending
Security Checks / security (push) Waiting to run
Some checks are pending
Security Checks / security (push) Waiting to run
This commit is contained in:
parent
12475f5565
commit
5672614430
@ -84,6 +84,7 @@ type legacyMigrationConfig struct {
|
||||
LoginPolicy string `json:"loginPolicy"`
|
||||
VerificationLoginPolicy string `json:"verificationLoginPolicy"`
|
||||
PluginAllowList []string `json:"pluginAllowList"`
|
||||
SSL SSLConfig `json:"ssl"`
|
||||
}
|
||||
|
||||
var legacyMigrationKnownFields = map[string]struct{}{
|
||||
@ -94,7 +95,7 @@ var legacyMigrationKnownFields = map[string]struct{}{
|
||||
"tgBotToken": {}, "tgChatId": {}, "isInit": {}, "setupState": {}, "webPushUrl": {}, "webPushToken": {},
|
||||
"smtpHost": {}, "smtpPort": {}, "smtpUsername": {}, "smtpPassword": {}, "smtpSecurity": {}, "smtpFrom": {},
|
||||
"smtpFromDomain": {}, "verificationEnabled": {}, "externalRecoveryEnabled": {}, "loginPolicy": {},
|
||||
"verificationLoginPolicy": {}, "pluginAllowList": {},
|
||||
"verificationLoginPolicy": {}, "pluginAllowList": {}, "ssl": {},
|
||||
}
|
||||
|
||||
var legacyPluginExecutableMap = map[string]string{
|
||||
@ -140,7 +141,7 @@ func PlanLegacyMigration(root string, _ RuntimeValidationOptions) (LegacyMigrati
|
||||
if err := json.Unmarshal(data, &legacy); err != nil {
|
||||
return LegacyMigrationPlan{}, err
|
||||
}
|
||||
plan.ManualFields = legacyManualFields(legacy)
|
||||
plan.ManualFields = legacyManualFields(root, legacy)
|
||||
if len(plan.ManualFields) > 0 {
|
||||
return plan, ErrMigrationUnsafeValue
|
||||
}
|
||||
@ -371,9 +372,9 @@ func equivalentStoredSecret(expected, existing StoredSecret, vault *credential.V
|
||||
return expectedErr == nil && existingErr == nil && expectedPlaintext == existingPlaintext
|
||||
}
|
||||
|
||||
func legacyManualFields(legacy legacyMigrationConfig) []string {
|
||||
func legacyManualFields(root string, legacy legacyMigrationConfig) []string {
|
||||
fields := make([]string, 0)
|
||||
_, _, databaseManualFields := legacyDatabasePlan(legacy)
|
||||
_, _, databaseManualFields := legacyDatabasePlan(root, legacy)
|
||||
fields = append(fields, databaseManualFields...)
|
||||
if _, ok := legacyControlledReference(legacy.SSLPublicKeyPath); legacy.SSLPublicKeyPath != "" && !ok {
|
||||
fields = append(fields, "SSLPublicKeyPath")
|
||||
@ -381,6 +382,12 @@ func legacyManualFields(legacy legacyMigrationConfig) []string {
|
||||
if _, ok := legacyControlledReference(legacy.SSLPrivateKeyPath); legacy.SSLPrivateKeyPath != "" && !ok {
|
||||
fields = append(fields, "SSLPrivateKeyPath")
|
||||
}
|
||||
if _, ok := legacyControlledReference(legacy.SSL.CertificateRef); legacy.SSL.CertificateRef != "" && !ok {
|
||||
fields = append(fields, "ssl.certificateRef")
|
||||
}
|
||||
if _, ok := legacyControlledReference(legacy.SSL.PrivateKeyRef); legacy.SSL.PrivateKeyRef != "" && !ok {
|
||||
fields = append(fields, "ssl.privateKeyRef")
|
||||
}
|
||||
if legacy.SMTPUsername == "" && legacy.SMTPPassword != "" {
|
||||
fields = append(fields, "smtpPassword")
|
||||
}
|
||||
@ -568,7 +575,7 @@ func prepareLegacyRuntime(runtime RuntimeConfig) (RuntimeConfig, error) {
|
||||
}
|
||||
|
||||
func legacyDatabaseConfig(root string, vault *credential.Vault, legacy legacyMigrationConfig) (DatabaseConfig, []string, error) {
|
||||
database, password, manualFields := legacyDatabasePlan(legacy)
|
||||
database, password, manualFields := legacyDatabasePlan(root, legacy)
|
||||
if len(manualFields) > 0 {
|
||||
return DatabaseConfig{}, manualFields, nil
|
||||
}
|
||||
@ -583,10 +590,10 @@ func legacyDatabaseConfig(root string, vault *credential.Vault, legacy legacyMig
|
||||
return database, nil, nil
|
||||
}
|
||||
|
||||
func legacyDatabasePlan(legacy legacyMigrationConfig) (DatabaseConfig, string, []string) {
|
||||
func legacyDatabasePlan(root string, legacy legacyMigrationConfig) (DatabaseConfig, string, []string) {
|
||||
switch legacy.DbType {
|
||||
case "", DBTypeSQLite:
|
||||
name, ok := legacySQLiteLocalRef(legacy.DbDSN)
|
||||
name, ok := legacySQLiteLocalRef(root, legacy.DbDSN)
|
||||
if !ok {
|
||||
return DatabaseConfig{}, "", []string{"dbDSN"}
|
||||
}
|
||||
@ -608,15 +615,28 @@ func legacyDatabasePlan(legacy legacyMigrationConfig) (DatabaseConfig, string, [
|
||||
}
|
||||
}
|
||||
|
||||
func legacySQLiteLocalRef(value string) (string, bool) {
|
||||
func legacySQLiteLocalRef(root, value string) (string, bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
value = "smail.db"
|
||||
}
|
||||
value = filepath.ToSlash(value)
|
||||
if root != "" {
|
||||
rootSlash := strings.TrimRight(filepath.ToSlash(root), "/")
|
||||
if strings.HasPrefix(value, rootSlash+"/") {
|
||||
value = strings.TrimPrefix(value, rootSlash+"/")
|
||||
} else if filepath.IsAbs(value) || isWindowsAbsolute(value) {
|
||||
if rel, err := filepath.Rel(root, value); err == nil {
|
||||
rel = filepath.ToSlash(rel)
|
||||
if !strings.HasPrefix(rel, "../") && rel != ".." {
|
||||
value = rel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if filepath.IsAbs(value) || isWindowsAbsolute(value) {
|
||||
return "", false
|
||||
}
|
||||
value = filepath.ToSlash(value)
|
||||
value = strings.TrimPrefix(value, "./")
|
||||
value = strings.TrimPrefix(value, "config/")
|
||||
value = strings.TrimPrefix(value, "data/")
|
||||
@ -740,6 +760,28 @@ func legacySMTPConfig(root string, vault *credential.Vault, legacy legacyMigrati
|
||||
}
|
||||
|
||||
func legacySSLConfig(legacy legacyMigrationConfig) (SSLConfig, []string) {
|
||||
if legacy.SSL.CertificateRef != "" || legacy.SSL.PrivateKeyRef != "" || legacy.SSL.Mode != "" || legacy.SSL.Management != "" {
|
||||
ssl := legacy.SSL
|
||||
if ssl.DefaultDirectory == "" {
|
||||
ssl.DefaultDirectory = "ssl"
|
||||
}
|
||||
var manual []string
|
||||
if ssl.CertificateRef != "" {
|
||||
if ref, ok := legacyControlledReference(ssl.CertificateRef); ok {
|
||||
ssl.CertificateRef = ref
|
||||
} else {
|
||||
manual = append(manual, "ssl.certificateRef")
|
||||
}
|
||||
}
|
||||
if ssl.PrivateKeyRef != "" {
|
||||
if ref, ok := legacyControlledReference(ssl.PrivateKeyRef); ok {
|
||||
ssl.PrivateKeyRef = ref
|
||||
} else {
|
||||
manual = append(manual, "ssl.privateKeyRef")
|
||||
}
|
||||
}
|
||||
return ssl, manual
|
||||
}
|
||||
certificateRef, certificateOK := legacyControlledReference(legacy.SSLPublicKeyPath)
|
||||
privateKeyRef, privateKeyOK := legacyControlledReference(legacy.SSLPrivateKeyPath)
|
||||
certificateRef = runtimepath.LegacyReference(certificateRef)
|
||||
|
||||
@ -724,3 +724,58 @@ func TestMigrateLegacyConfigDerivesSetupStateFromFullPreflight(t *testing.T) {
|
||||
t.Fatalf("setup state = %q / %q", runtime.SetupState, runtime.Root.SetupState)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateLegacyConfigAcceptsStructuredSSLObject(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
legacy := map[string]any{
|
||||
"domain": "mail.example.com",
|
||||
"domains": []string{"mail.example.com"},
|
||||
"webDomain": "mail.example.com",
|
||||
"dbDSN": "./config/smail.db",
|
||||
"dbType": "sqlite",
|
||||
"isInit": true,
|
||||
"ssl": map[string]any{
|
||||
"management": "project",
|
||||
"mode": "managed_default",
|
||||
"defaultDirectory": "ssl",
|
||||
"challenge": "dns",
|
||||
"certificateRef": "ssl/custom.crt",
|
||||
"privateKeyRef": "ssl/custom.key",
|
||||
},
|
||||
}
|
||||
writeLegacyMigrationFixture(t, root, legacy)
|
||||
|
||||
result, err := MigrateLegacyConfig(root, RuntimeValidationOptions{HasSuperAdmin: true, Production: true})
|
||||
if err != nil {
|
||||
t.Fatalf("MigrateLegacyConfig failed: %v", err)
|
||||
}
|
||||
if result.Generation != 1 {
|
||||
t.Fatalf("unexpected generation: %d", result.Generation)
|
||||
}
|
||||
|
||||
runtime, err := NewConfigLoader(root, ConfigLoaderOptions{Validation: RuntimeValidationOptions{HasSuperAdmin: true, Production: true}}).Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
if runtime.SSL.TLS.CertificateRef != "ssl/custom.crt" || runtime.SSL.TLS.PrivateKeyRef != "ssl/custom.key" {
|
||||
t.Fatalf("unexpected SSL refs: %+v", runtime.SSL.TLS)
|
||||
}
|
||||
if runtime.SSL.TLS.Mode != "managed_default" || runtime.SSL.TLS.Management != "project" {
|
||||
t.Fatalf("unexpected SSL mode/mgmt: %+v", runtime.SSL.TLS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateLegacyConfigAcceptsDeploymentAbsoluteSQLiteDSN(t *testing.T) {
|
||||
root := "/opt/smail"
|
||||
ref, ok := legacySQLiteLocalRef(root, "/opt/smail/data/smail.db")
|
||||
if !ok || ref != "smail.db" {
|
||||
t.Fatalf("unexpected sqlite local ref: %q, ok: %v", ref, ok)
|
||||
}
|
||||
dbCfg, _, manual := legacyDatabasePlan(root, legacyMigrationConfig{
|
||||
DbType: "sqlite",
|
||||
DbDSN: "/opt/smail/data/smail.db",
|
||||
})
|
||||
if len(manual) > 0 || dbCfg.LocalRef != "smail.db" {
|
||||
t.Fatalf("unexpected db plan: %+v, manual: %v", dbCfg, manual)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user