diff --git a/backend/config/model.go b/backend/config/model.go index d200bfd..e647eb6 100644 --- a/backend/config/model.go +++ b/backend/config/model.go @@ -104,15 +104,15 @@ type EmailConfig struct { } type SSLConfig struct { - Management string `json:"management,omitempty"` - Mode string `json:"mode"` - DefaultDirectory string `json:"defaultDirectory"` - CustomDirectory string `json:"customDirectory,omitempty"` - Challenge string `json:"challenge,omitempty"` - CertificateRef string `json:"certificateRef,omitempty"` - PrivateKeyRef string `json:"privateKeyRef,omitempty"` - PreviousCertificateRef string `json:"previousCertificateRef,omitempty"` - PreviousPrivateKeyRef string `json:"previousPrivateKeyRef,omitempty"` + Management string `json:"management,omitempty"` + Mode string `json:"mode"` + DefaultDirectory string `json:"defaultDirectory"` + CustomDirectory string `json:"customDirectory,omitempty"` + Challenge string `json:"challenge,omitempty"` + CertificateRef string `json:"certificateRef,omitempty"` + PrivateKeyRef string `json:"privateKeyRef,omitempty"` + PreviousCertificateRef string `json:"previousCertificateRef,omitempty"` + PreviousPrivateKeyRef string `json:"previousPrivateKeyRef,omitempty"` DNSProvider string `json:"dnsProvider,omitempty"` DNSZone string `json:"dnsZone,omitempty"` DNSAccessKey StoredSecret `json:"dnsAccessKey,omitempty"` diff --git a/backend/config/ssl_dns_test.go b/backend/config/ssl_dns_test.go index f6bf87d..6bc13f0 100644 --- a/backend/config/ssl_dns_test.go +++ b/backend/config/ssl_dns_test.go @@ -15,7 +15,9 @@ func TestDNSCredentialsEncryptedPreservedAndRedacted(t *testing.T) { ROOT_PATH = root t.Cleanup(func() { ROOT_PATH = previousRoot }) vault, err := credential.NewVault(root) - if err != nil { t.Fatal(err) } + if err != nil { + t.Fatal(err) + } before := validRuntimeConfig() candidate := cloneRuntimeConfig(before) candidate.SSL.TLS.DNSProvider = DNSProviderAliDNS @@ -23,29 +25,51 @@ func TestDNSCredentialsEncryptedPreservedAndRedacted(t *testing.T) { "tls.dnsAccessKey": {Operation: SecretReplace, Value: &SecretInput{Plaintext: "dns-access-fixture"}}, "tls.dnsSecretKey": {Operation: SecretReplace, Value: &SecretInput{Plaintext: "dns-secret-fixture"}}, } - if err := applySecretChanges(&candidate, SectionSSL, changes, vault); err != nil { t.Fatal(err) } - if err := validateDNSProvider(candidate.SSL.TLS); err != nil { t.Fatal(err) } - if err := ValidateRuntimeSecrets(vault, candidate); err != nil { t.Fatal(err) } + if err := applySecretChanges(&candidate, SectionSSL, changes, vault); err != nil { + t.Fatal(err) + } + if err := validateDNSProvider(candidate.SSL.TLS); err != nil { + t.Fatal(err) + } + if err := ValidateRuntimeSecrets(vault, candidate); err != nil { + t.Fatal(err) + } access, secret, err := DNSProviderCredentials(candidate.SSL.TLS) - if err != nil || access != "dns-access-fixture" || secret != "dns-secret-fixture" { t.Fatal("DNS credentials did not round trip through vault") } + if err != nil || access != "dns-access-fixture" || secret != "dns-secret-fixture" { + t.Fatal("DNS credentials did not round trip through vault") + } data, _ := json.Marshal(candidate) - for _, value := range []string{access, secret} { if strings.Contains(string(data), value) { t.Fatal("plaintext persisted") } } + for _, value := range []string{access, secret} { + if strings.Contains(string(data), value) { + t.Fatal("plaintext persisted") + } + } audit, _ := json.Marshal(AuditChanges(before, candidate)) public, _ := json.Marshal(RedactPublicValue(candidate.SSL)) for _, data := range [][]byte{audit, public} { for _, value := range []string{access, secret, candidate.SSL.TLS.DNSAccessKey.Ciphertext, candidate.SSL.TLS.DNSSecretKey.Ciphertext} { - if strings.Contains(string(data), value) { t.Fatal("DNS secret leaked into audit or public view") } + if strings.Contains(string(data), value) { + t.Fatal("DNS secret leaked into audit or public view") + } } } section := candidate.SSL section.TLS.DNSAccessKey, section.TLS.DNSSecretKey = StoredSecret{}, StoredSecret{} payload, _ := json.Marshal(section) kept, err := applySectionPayload(candidate, SectionSSL, payload, nil, vault) - if err != nil || kept.SSL.TLS.DNSAccessKey != candidate.SSL.TLS.DNSAccessKey || kept.SSL.TLS.DNSSecretKey != candidate.SSL.TLS.DNSSecretKey { t.Fatal("public edit lost stored DNS credentials") } + if err != nil || kept.SSL.TLS.DNSAccessKey != candidate.SSL.TLS.DNSAccessKey || kept.SSL.TLS.DNSSecretKey != candidate.SSL.TLS.DNSSecretKey { + t.Fatal("public edit lost stored DNS credentials") + } payload, _ = json.Marshal(candidate.SSL) - if _, err := applySectionPayload(candidate, SectionSSL, payload, nil, vault); !errors.Is(err, ErrInvalidSecretChange) { t.Fatal("direct stored-secret injection accepted") } + if _, err := applySectionPayload(candidate, SectionSSL, payload, nil, vault); !errors.Is(err, ErrInvalidSecretChange) { + t.Fatal("direct stored-secret injection accepted") + } clear := map[string]SecretChange{"tls.dnsSecretKey": {Operation: SecretClear, Confirmed: true}} - if err := applySecretChanges(&candidate, SectionSSL, clear, vault); !errors.Is(err, ErrSecretClearDenied) { t.Fatal("enabled DNS provider allowed clearing its secret") } + if err := applySecretChanges(&candidate, SectionSSL, clear, vault); !errors.Is(err, ErrSecretClearDenied) { + t.Fatal("enabled DNS provider allowed clearing its secret") + } candidate.SSL.TLS.DNSProvider = "" - if err := applySecretChanges(&candidate, SectionSSL, clear, vault); err != nil { t.Fatal(err) } + if err := applySecretChanges(&candidate, SectionSSL, clear, vault); err != nil { + t.Fatal(err) + } } diff --git a/backend/config/ssl_export.go b/backend/config/ssl_export.go index 20b0a01..57b905e 100644 --- a/backend/config/ssl_export.go +++ b/backend/config/ssl_export.go @@ -2,9 +2,11 @@ package config import ( "bytes" + "errors" "io" "os" "path/filepath" + "runtime" "git.pcux.cn/xuans/Smail/utils/atomicfile" ) @@ -12,6 +14,13 @@ import ( // ExportProjectTLS 为本机 TLS 入口导出一个原子更新的证书私钥合并文件。 // 仅接受项目生成的平铺证书;不允许高权限调用者按配置读取任意服务器路径。 func ExportProjectTLS(root, destination string) error { + if runtime.GOOS != "linux" { + return errors.New("本机 Nginx 证书同步仅支持 Linux") + } + if !filepath.IsAbs(root) || !filepath.IsAbs(destination) { + return ErrUnsafeSSLFile + } + root, destination = filepath.Clean(root), filepath.Clean(destination) runtime, err := NewConfigLoader(root, ConfigLoaderOptions{Validation: RuntimeValidationOptions{HasSuperAdmin: true, Production: true}}).Load() if err != nil { return ErrInvalidSSL @@ -26,43 +35,79 @@ func ExportProjectTLS(root, destination string) error { } sourceDirectory := filepath.Join(root, "ssl") resolvedSource, err := filepath.EvalSymlinks(sourceDirectory) - if err != nil || resolvedSource != sourceDirectory { return ErrUnsafeSSLFile } + if err != nil || resolvedSource != sourceDirectory { + return ErrUnsafeSSLFile + } storage, err := os.OpenRoot(sourceDirectory) - if err != nil { return ErrUnsafeSSLFile } + if err != nil { + return ErrUnsafeSSLFile + } defer storage.Close() read := func(name string) ([]byte, error) { info, err := storage.Lstat(name) - if err != nil || !info.Mode().IsRegular() || info.Size() > maxManagedSSLFileSize || info.Mode().Perm() & 0077 != 0 { return nil, ErrUnsafeSSLFile } + if err != nil || !info.Mode().IsRegular() || info.Size() > maxManagedSSLFileSize || info.Mode().Perm()&0077 != 0 { + return nil, ErrUnsafeSSLFile + } file, err := storage.Open(name) - if err != nil { return nil, ErrUnsafeSSLFile } + if err != nil { + return nil, ErrUnsafeSSLFile + } defer file.Close() opened, err := file.Stat() - if err != nil || !os.SameFile(info, opened) { return nil, ErrUnsafeSSLFile } + if err != nil || !os.SameFile(info, opened) || !opened.Mode().IsRegular() || opened.Mode().Perm()&0077 != 0 { + return nil, ErrUnsafeSSLFile + } data, err := io.ReadAll(io.LimitReader(file, maxManagedSSLFileSize+1)) - if err != nil || len(data) > maxManagedSSLFileSize { return nil, ErrUnsafeSSLFile } + if err != nil || len(data) > maxManagedSSLFileSize { + return nil, ErrUnsafeSSLFile + } + after, err := file.Stat() + if err != nil || !os.SameFile(opened, after) || after.Size() != int64(len(data)) || !opened.ModTime().Equal(after.ModTime()) || after.Mode().Perm()&0077 != 0 { + return nil, ErrUnsafeSSLFile + } return data, nil } certificate, err := read(filepath.Base(publicPath)) - if err != nil { return err } + if err != nil { + return err + } privateKey, err := read(filepath.Base(privatePath)) - if err != nil { return err } - if err := validateSSLMaterial(privateKey, certificate, nil, validationCertificateDomains(&runtime)); err != nil { return err } + if err != nil { + return err + } + if err := validateSSLMaterial(privateKey, certificate, nil, validationCertificateDomains(&runtime)); err != nil { + return err + } combined := append(append(append([]byte{}, certificate...), '\n'), privateKey...) // 输出目录由部署管理员创建,拒绝符号链接和其他用户可写目录。 directory := filepath.Dir(destination) resolved, err := filepath.EvalSymlinks(directory) - if err != nil || resolved != directory { return ErrUnsafeSSLFile } + if err != nil || resolved != directory { + return ErrUnsafeSSLFile + } info, err := os.Stat(directory) - if err != nil || !info.IsDir() || info.Mode().Perm() & 0022 != 0 { return ErrUnsafeSSLFile } + if err != nil || !info.IsDir() || info.Mode().Perm()&0022 != 0 { + return ErrUnsafeSSLFile + } output, err := os.OpenRoot(directory) - if err != nil { return ErrUnsafeSSLFile } + if err != nil { + return ErrUnsafeSSLFile + } defer output.Close() name := filepath.Base(destination) if info, err := output.Lstat(name); err == nil { - if !info.Mode().IsRegular() || info.Mode().Perm() & 0077 != 0 { return ErrUnsafeSSLFile } + if !info.Mode().IsRegular() || info.Mode().Perm()&0077 != 0 { + return ErrUnsafeSSLFile + } previous, err := output.ReadFile(name) - if err != nil { return ErrUnsafeSSLFile } - if bytes.Equal(previous, combined) { return nil } - } else if !os.IsNotExist(err) { return ErrUnsafeSSLFile } + if err != nil { + return ErrUnsafeSSLFile + } + if bytes.Equal(previous, combined) { + return nil + } + } else if !os.IsNotExist(err) { + return ErrUnsafeSSLFile + } return atomicfile.WriteRoot(output, name, combined, 0600) } diff --git a/backend/config/ssl_export_test.go b/backend/config/ssl_export_test.go index 729f737..f4aebf1 100644 --- a/backend/config/ssl_export_test.go +++ b/backend/config/ssl_export_test.go @@ -5,41 +5,107 @@ import ( "crypto/tls" "os" "path/filepath" + "runtime" "testing" "time" ) func TestExportProjectTLSKeepsOldBundleOnInvalidInput(t *testing.T) { + if runtime.GOOS != "linux" { + if err := ExportProjectTLS(t.TempDir(), filepath.Join(t.TempDir(), "tls.pem")); err == nil { + t.Fatal("unsupported platform allowed exporting a private key") + } + t.Skip("Linux Nginx export requires POSIX permission checks") + } root := t.TempDir() outputDirectory := t.TempDir() - if err := os.Chmod(outputDirectory, 0700); err != nil { t.Fatal(err) } + if err := os.Chmod(outputDirectory, 0700); err != nil { + t.Fatal(err) + } destination := filepath.Join(outputDirectory, "tls.pem") runtime := validRuntimeConfig() runtime.Root.Domains = DomainConfig{Primary: "example.com", Web: "web.example.com", Accepted: []string{"example.com"}} key, certificate := testManagedCertificate(t, "example.com", time.Now().Add(-time.Hour), time.Now().Add(time.Hour), 2048, "*.example.com") ref, err := stageFlatSSLAtRoot(root, ManagedSSLModeDefault, key, certificate, nil, nil) - if err != nil { t.Fatal(err) } + if err != nil { + t.Fatal(err) + } defer releaseManagedSSL(root, SSLConfig{CertificateRef: ref.CertificateRef}) runtime.SSL.TLS = SSLConfig{Management: SSLManagementProject, Challenge: SSLChallengeDNS, Mode: ManagedSSLModeDefault, DefaultDirectory: "ssl", CertificateRef: ref.CertificateRef, PrivateKeyRef: ref.PrivateKeyRef} writeRuntimeFixture(t, root, runtime) - if err := ExportProjectTLS(root, destination); err != nil { t.Fatal(err) } + if err := ExportProjectTLS(root, destination); err != nil { + t.Fatal(err) + } bundle, err := os.ReadFile(destination) - if err != nil { t.Fatal(err) } - if _, err := tls.X509KeyPair(bundle, bundle); err != nil { t.Fatal(err) } - if err := ExportProjectTLS(root, destination); err != nil { t.Fatal(err) } - if err := os.WriteFile(ref.PrivateKeyPath, []byte("invalid"), 0600); err != nil { t.Fatal(err) } - if err := ExportProjectTLS(root, destination); err == nil { t.Fatal("invalid key was exported") } + if err != nil { + t.Fatal(err) + } + if _, err := tls.X509KeyPair(bundle, bundle); err != nil { + t.Fatal(err) + } + before, err := os.Stat(destination) + if err != nil || before.Mode().Perm() != 0600 { + t.Fatal("export must be private") + } + if err := ExportProjectTLS(root, destination); err != nil { + t.Fatal(err) + } + after, _ := os.Stat(destination) + if !os.SameFile(before, after) { + t.Fatal("unchanged certificate must not be rewritten") + } + for _, target := range []string{ref.PrivateKeyPath, ref.CertificatePath, destination} { + if err := os.Chmod(target, 0644); err != nil { + t.Fatal(err) + } + if err := ExportProjectTLS(root, destination); err == nil { + t.Fatal("unsafe file permissions accepted") + } + if err := os.Chmod(target, 0600); err != nil { + t.Fatal(err) + } + } + if err := os.Chmod(outputDirectory, 0777); err != nil { + t.Fatal(err) + } + if err := ExportProjectTLS(root, destination); err == nil { + t.Fatal("writable output directory accepted") + } + if err := os.Chmod(outputDirectory, 0700); err != nil { + t.Fatal(err) + } + link := filepath.Join(t.TempDir(), "linked") + if err := os.Symlink(outputDirectory, link); err != nil { + t.Fatal(err) + } + if err := ExportProjectTLS(root, filepath.Join(link, "tls.pem")); err == nil { + t.Fatal("linked output directory accepted") + } + if err := os.WriteFile(ref.PrivateKeyPath, []byte("invalid"), 0600); err != nil { + t.Fatal(err) + } + if err := ExportProjectTLS(root, destination); err == nil { + t.Fatal("invalid key was exported") + } preserved, _ := os.ReadFile(destination) - if !bytes.Equal(bundle, preserved) { t.Fatal("failed export replaced valid bundle") } + if !bytes.Equal(bundle, preserved) { + t.Fatal("failed export replaced valid bundle") + } runtime.SSL.TLS.CertificateRef = "ssl/unmanaged.crt" runtime.SSL.TLS.PrivateKeyRef = "ssl/unmanaged.key" writeRuntimeFixture(t, root, runtime) - if err := ExportProjectTLS(root, destination); err == nil { t.Fatal("unmanaged input accepted") } + if err := ExportProjectTLS(root, destination); err == nil { + t.Fatal("unmanaged input accepted") + } for _, mode := range []string{SSLManagementCustom, SSLManagementWebServer} { runtime.SSL.TLS.Management = mode writeRuntimeFixture(t, root, runtime) - if err := ExportProjectTLS(root, destination); err != nil { t.Fatal(err) } + if err := ExportProjectTLS(root, destination); err != nil { + t.Fatal(err) + } preserved, _ := os.ReadFile(destination) - if !bytes.Equal(bundle, preserved) { t.Fatal("external mode changed exported bundle") } + if !bytes.Equal(bundle, preserved) { + t.Fatal("external mode changed exported bundle") + } } } diff --git a/backend/controllers/config_public_response_test.go b/backend/controllers/config_public_response_test.go index 574fc66..c5f6284 100644 --- a/backend/controllers/config_public_response_test.go +++ b/backend/controllers/config_public_response_test.go @@ -42,7 +42,7 @@ func TestPublicConfigSectionsDoNotExposeSecretsAndRemainStrictlyDecodable(t *tes SSL: config.SSLSectionConfig{ Meta: config.SectionMeta{Schema: config.CurrentSectionSchema, Generation: 7}, TLS: config.SSLConfig{ - DNSProvider: config.DNSProviderAliDNS, + DNSProvider: config.DNSProviderAliDNS, DNSAccessKey: config.StoredSecret{Ciphertext: "enc:v1:dns-access", Version: credential.CurrentVersion, UpdatedAt: time.Now().UTC()}, DNSSecretKey: config.StoredSecret{Ciphertext: "enc:v1:dns-secret", Version: credential.CurrentVersion, UpdatedAt: time.Now().UTC()}, }, diff --git a/backend/services/setup/ssl/dns_automatic.go b/backend/services/setup/ssl/dns_automatic.go index 2b43bd6..ded80e8 100644 --- a/backend/services/setup/ssl/dns_automatic.go +++ b/backend/services/setup/ssl/dns_automatic.go @@ -94,7 +94,9 @@ func (provider *aliDNSChallenge) Present(domain, token, keyAuth string) error { if err != nil { return err } - var response struct{ RecordID string `json:"RecordId"` } + var response struct { + RecordID string `json:"RecordId"` + } err = provider.call("AddDomainRecord", url.Values{ "DomainName": {provider.zone}, "RR": {record.Name}, "Type": {"TXT"}, "Value": {record.Value}, "TTL": {"600"}, @@ -174,7 +176,9 @@ func (provider *aliDNSChallenge) call(action string, values url.Values, target a if err != nil || len(data) > 1<<20 { return errors.New("DNS 服务商响应无效") } - var status struct{ Code string `json:"Code"` } + var status struct { + Code string `json:"Code"` + } if json.Unmarshal(data, &status) != nil || response.StatusCode != http.StatusOK || status.Code != "" { // 服务商错误可能包含凭据、签名或请求原文,不能直接交给日志。 return errors.New("DNS 服务商拒绝请求,请检查凭据、区域和记录管理权限") diff --git a/backend/services/setup/ssl/dns_automatic_test.go b/backend/services/setup/ssl/dns_automatic_test.go index b8a90ea..789c202 100644 --- a/backend/services/setup/ssl/dns_automatic_test.go +++ b/backend/services/setup/ssl/dns_automatic_test.go @@ -35,10 +35,16 @@ func TestAutomaticDNSCleansOnlyOwnedUnchangedRecords(t *testing.T) { _ = json.NewEncoder(w).Encode(map[string]string{"RecordId": id}) case "DescribeDomainRecordInfo": record := records[id] - if tamper { record.Value = "changed-by-user" } + if tamper { + record.Value = "changed-by-user" + } _ = json.NewEncoder(w).Encode(map[string]string{"RecordId": id, "DomainName": "example.com", "RR": record.Name, "Type": "TXT", "Value": record.Value}) case "DeleteDomainRecord": - if failDelete { w.WriteHeader(503); _, _ = w.Write([]byte(`{"Code":"Busy"}`)); return } + if failDelete { + w.WriteHeader(503) + _, _ = w.Write([]byte(`{"Code":"Busy"}`)) + return + } deletes++ delete(records, id) _ = json.NewEncoder(w).Encode(map[string]string{"RecordId": id}) @@ -55,23 +61,50 @@ func TestAutomaticDNSCleansOnlyOwnedUnchangedRecords(t *testing.T) { workers.Add(1) go func(domain string) { defer workers.Done() - if err := provider.Present(domain, "token", "key-"+domain); err != nil { t.Error(err) } + if err := provider.Present(domain, "token", "key-"+domain); err != nil { + t.Error(err) + } }(domain) } } workers.Wait() - if adds != 2 || len(records) != 3 { t.Fatalf("same-name TXT records lost or duplicated: adds=%d records=%d", adds, len(records)) } - if err := provider.CleanUp("other.example.com", "token", "unknown"); err != nil || deletes != 0 { t.Fatal("unknown challenge must not touch DNS") } - mu.Lock(); tamper = true; mu.Unlock() - if err := provider.CleanUp("example.com", "token", "key-example.com"); err == nil || deletes != 0 { t.Fatal("modified record must be retained") } - mu.Lock(); tamper = false; failDelete = true; mu.Unlock() - if err := provider.CleanUp("example.com", "token", "key-example.com"); err == nil || len(provider.records) != 2 { t.Fatal("failed deletion must retain ownership for retry") } - mu.Lock(); failDelete = false; mu.Unlock() - if err := provider.CleanUp("example.com", "token", "key-example.com"); err != nil { t.Fatal(err) } - if len(records) != 2 { t.Fatal("cleaning root challenge removed wildcard or unrelated TXT") } - if err := provider.CleanUp("*.example.com", "token", "key-*.example.com"); err != nil { t.Fatal(err) } - if len(records) != 1 || records["existing"].Value != "unrelated-record" { t.Fatal("existing TXT must survive all challenge cleanup") } - if err := provider.CleanUp("*.example.com", "token", "key-*.example.com"); err != nil || deletes != 2 { t.Fatal("cleanup must be idempotent") } + if adds != 2 || len(records) != 3 { + t.Fatalf("same-name TXT records lost or duplicated: adds=%d records=%d", adds, len(records)) + } + if err := provider.CleanUp("other.example.com", "token", "unknown"); err != nil || deletes != 0 { + t.Fatal("unknown challenge must not touch DNS") + } + mu.Lock() + tamper = true + mu.Unlock() + if err := provider.CleanUp("example.com", "token", "key-example.com"); err == nil || deletes != 0 { + t.Fatal("modified record must be retained") + } + mu.Lock() + tamper = false + failDelete = true + mu.Unlock() + if err := provider.CleanUp("example.com", "token", "key-example.com"); err == nil || len(provider.records) != 2 { + t.Fatal("failed deletion must retain ownership for retry") + } + mu.Lock() + failDelete = false + mu.Unlock() + if err := provider.CleanUp("example.com", "token", "key-example.com"); err != nil { + t.Fatal(err) + } + if len(records) != 2 { + t.Fatal("cleaning root challenge removed wildcard or unrelated TXT") + } + if err := provider.CleanUp("*.example.com", "token", "key-*.example.com"); err != nil { + t.Fatal(err) + } + if len(records) != 1 || records["existing"].Value != "unrelated-record" { + t.Fatal("existing TXT must survive all challenge cleanup") + } + if err := provider.CleanUp("*.example.com", "token", "key-*.example.com"); err != nil || deletes != 2 { + t.Fatal("cleanup must be idempotent") + } } func TestAutomaticDNSRejectsOutsideZoneAndRedactsErrors(t *testing.T) { @@ -84,13 +117,21 @@ func TestAutomaticDNSRejectsOutsideZoneAndRedactsErrors(t *testing.T) { })) defer server.Close() provider := &aliDNSChallenge{zone: "example.com", accessKey: "test-access", secretKey: "test-secret", endpoint: server.URL, client: server.Client(), records: map[string]aliDNSRecord{}} - if err := provider.Present("unrelated.com", "token", "key"); err == nil || calls != 0 { t.Fatal("out-of-zone write attempted") } + if err := provider.Present("unrelated.com", "token", "key"); err == nil || calls != 0 { + t.Fatal("out-of-zone write attempted") + } err := provider.Present("example.com", "token", "key") - if err == nil || calls != 1 { t.Fatal("provider refusal not reported") } + if err == nil || calls != 1 { + t.Fatal("provider refusal not reported") + } for _, secret := range []string{"test-access", "test-secret", "signature"} { - if strings.Contains(err.Error(), secret) { t.Fatal("error leaked provider response") } + if strings.Contains(err.Error(), secret) { + t.Fatal("error leaked provider response") + } } for _, management := range []string{config.SSLManagementCustom, config.SSLManagementWebServer} { - if _, err := automaticDNSProvider(&config.Config{SSL: config.SSLConfig{Management: management, Challenge: config.SSLChallengeDNS}}); err == nil { t.Fatal("external certificate mode allowed automatic DNS") } + if _, err := automaticDNSProvider(&config.Config{SSL: config.SSLConfig{Management: management, Challenge: config.SSLChallengeDNS}}); err == nil { + t.Fatal("external certificate mode allowed automatic DNS") + } } } diff --git a/backend/services/setup/ssl/ssl.go b/backend/services/setup/ssl/ssl.go index 9aebb00..e5be554 100644 --- a/backend/services/setup/ssl/ssl.go +++ b/backend/services/setup/ssl/ssl.go @@ -118,10 +118,10 @@ func SetSSL(sslType, priKey, crtKey string) error { Management: config.SSLManagementProject, Mode: config.ManagedSSLModeDefault, DefaultDirectory: runtimepath.SSLReference(config.ROOT_PATH, "data/ssl"), - DNSProvider: previousSSL.DNSProvider, - DNSZone: previousSSL.DNSZone, - DNSAccessKey: previousSSL.DNSAccessKey, - DNSSecretKey: previousSSL.DNSSecretKey, + DNSProvider: previousSSL.DNSProvider, + DNSZone: previousSSL.DNSZone, + DNSAccessKey: previousSSL.DNSAccessKey, + DNSSecretKey: previousSSL.DNSSecretKey, } if cfg.SSLType == config.SSLTypeAutoDNS { cfg.SSL.Challenge = config.SSLChallengeDNS @@ -192,10 +192,10 @@ func applyManagedSSLReference(cfg *config.Config, reference config.ManagedSSLRef DefaultDirectory: "ssl", CertificateRef: reference.CertificateRef, PrivateKeyRef: reference.PrivateKeyRef, - DNSProvider: previousSSL.DNSProvider, - DNSZone: previousSSL.DNSZone, - DNSAccessKey: previousSSL.DNSAccessKey, - DNSSecretKey: previousSSL.DNSSecretKey, + DNSProvider: previousSSL.DNSProvider, + DNSZone: previousSSL.DNSZone, + DNSAccessKey: previousSSL.DNSAccessKey, + DNSSecretKey: previousSSL.DNSSecretKey, } if reference.Mode == config.ManagedSSLModeUpload { cfg.SSL.CustomDirectory = "ssl"