152 lines
5.2 KiB
Go
152 lines
5.2 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.pcux.cn/xuans/Smail/utils/runtimepath"
|
|
)
|
|
|
|
// MigrateLegacySSLStorage 在监听器启动前迁移旧引用;提交失败时不删除旧材料。
|
|
// 自定义模式优先复用 ssl 根目录中完全相同的原件,无法匹配时才迁出旧版本。
|
|
func MigrateLegacySSLStorage(root string) error {
|
|
if !runtimepath.DeploymentLayout(root) {
|
|
return nil
|
|
}
|
|
configRoot, err := os.OpenRoot(filepath.Join(root, "config"))
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
data, err := configRoot.ReadFile("config.json")
|
|
_ = configRoot.Close()
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
partitioned, err := isPartitionedConfig(data)
|
|
if err != nil || !partitioned {
|
|
return err
|
|
}
|
|
options := RuntimeValidationOptions{HasSuperAdmin: true}
|
|
store := NewTransactionStore(root, options)
|
|
if err := store.Recover(); err != nil {
|
|
return err
|
|
}
|
|
current, err := NewConfigLoader(root, ConfigLoaderOptions{Validation: options}).Load()
|
|
if err != nil || !legacyManagedSSLReference(current.SSL.TLS.CertificateRef) {
|
|
return err
|
|
}
|
|
ref, err := resolveManagedSSLReferencesAtRoot(root, current.SSL.TLS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key, err := readExternalSSLFile(ref.PrivateKeyPath, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
certificate, err := readExternalSSLFile(ref.CertificatePath, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
candidate := cloneRuntimeConfig(current)
|
|
next := &candidate.SSL.TLS
|
|
if EffectiveSSLManagement(*next) != SSLManagementProject {
|
|
ref, err = matchingDirectSSL(root, key, certificate)
|
|
} else {
|
|
ref = ManagedSSLReference{}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ref.CertificateRef == "" {
|
|
ref, err = stageFlatSSLAtRoot(root, next.Mode, key, certificate, nil, validationCertificateDomains(&candidate))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer releaseManagedSSL(root, SSLConfig{CertificateRef: ref.CertificateRef})
|
|
}
|
|
next.CertificateRef, next.PrivateKeyRef = ref.CertificateRef, ref.PrivateKeyRef
|
|
next.DefaultDirectory = "ssl"
|
|
if next.Mode == ManagedSSLModeUpload {
|
|
next.CustomDirectory = "ssl"
|
|
}
|
|
previous, err := previousLegacySSL(root, current.SSL.TLS, certificate, validationCertificateDomains(&candidate))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if previous.CertificateRef != "" {
|
|
defer releaseManagedSSL(root, SSLConfig{CertificateRef: previous.CertificateRef})
|
|
next.PreviousCertificateRef, next.PreviousPrivateKeyRef = previous.CertificateRef, previous.PrivateKeyRef
|
|
}
|
|
if err := validateCustomSSLReferences(root, candidate); err != nil {
|
|
return err
|
|
}
|
|
setCandidateGeneration(&candidate, current.Generation+1)
|
|
return store.Commit(current, candidate)
|
|
}
|
|
|
|
func matchingDirectSSL(root string, key, certificate []byte) (ManagedSSLReference, error) {
|
|
directory := filepath.Join(root, "ssl")
|
|
entries, err := os.ReadDir(directory)
|
|
if err != nil {
|
|
return ManagedSSLReference{}, err
|
|
}
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".crt") || strings.HasPrefix(entry.Name(), flatSSLPrefix) {
|
|
continue
|
|
}
|
|
publicPath := filepath.Join(directory, entry.Name())
|
|
privatePath := strings.TrimSuffix(publicPath, ".crt") + ".key"
|
|
publicBytes, publicErr := readExternalSSLFile(publicPath, false)
|
|
privateBytes, privateErr := readExternalSSLFile(privatePath, true)
|
|
if publicErr == nil && privateErr == nil && bytes.Equal(certificate, publicBytes) && bytes.Equal(key, privateBytes) {
|
|
return ManagedSSLReference{Mode: ManagedSSLModeUpload, CertificateRef: directSSLReference(root, publicPath), PrivateKeyRef: directSSLReference(root, privatePath)}, nil
|
|
}
|
|
}
|
|
return ManagedSSLReference{}, nil
|
|
}
|
|
|
|
// 重复副本不算上一套证书;只迁移最近一套仍有效且覆盖当前服务域名的不同材料。
|
|
func previousLegacySSL(root string, active SSLConfig, certificate []byte, domains []string) (ManagedSSLReference, error) {
|
|
var candidates []retainedSSLVersion
|
|
for _, mode := range []string{ManagedSSLModeDefault, ManagedSSLModeUpload} {
|
|
directory, err := managedSSLRootPathAtRoot(root, mode)
|
|
if err != nil {
|
|
return ManagedSSLReference{}, err
|
|
}
|
|
entries, err := os.ReadDir(directory)
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return ManagedSSLReference{}, err
|
|
}
|
|
for _, entry := range entries {
|
|
if !validSSLVersion(entry.Name()) && entry.Name() != "current" {
|
|
continue
|
|
}
|
|
if info, err := entry.Info(); err == nil && info.IsDir() && info.Mode()&os.ModeSymlink == 0 {
|
|
candidates = append(candidates, retainedSSLVersion{directory: filepath.Join(directory, entry.Name()), modified: info.ModTime()})
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(candidates, func(i, j int) bool { return candidates[i].modified.After(candidates[j].modified) })
|
|
for _, candidate := range candidates {
|
|
publicBytes, publicErr := readExternalSSLFile(filepath.Join(candidate.directory, "public.crt"), false)
|
|
privateBytes, privateErr := readExternalSSLFile(filepath.Join(candidate.directory, "private.key"), true)
|
|
if publicErr != nil || privateErr != nil || bytes.Equal(certificate, publicBytes) || validateSSLMaterial(privateBytes, publicBytes, nil, domains) != nil {
|
|
continue
|
|
}
|
|
return stageFlatSSLAtRoot(root, active.Mode, privateBytes, publicBytes, nil, domains)
|
|
}
|
|
return ManagedSSLReference{}, nil
|
|
}
|