Smail/backend/utils/runtimepath/deployment.go
smail a045a0cd17
Some checks are pending
Security Checks / security (push) Waiting to run
实现部署启动自动归档密钥与证书双版本保留
2026-09-08 20:34:38 +08:00

97 lines
2.8 KiB
Go

package runtimepath
import (
"errors"
"os"
"path/filepath"
"strings"
)
// CheckKeyLayout 拒绝同时存在新旧密钥,避免误用另一套加密材料。
func CheckKeyLayout(root string) error {
legacy, modern := false, false
for _, name := range BusinessKeyNames() {
if _, err := os.Lstat(filepath.Join(root, "data", name)); err == nil {
legacy = true
} else if !os.IsNotExist(err) {
return err
}
if _, err := os.Lstat(groupedKeyPath(root, name)); err == nil {
modern = true
if _, err := os.Lstat(filepath.Join(root, "ssl", name)); !os.IsNotExist(err) {
return errors.New("密钥同时存在于归档目录和旧目录,拒绝自动选择或覆盖")
}
} else if !os.IsNotExist(err) {
return err
}
if _, err := os.Lstat(filepath.Join(root, "ssl", name)); err == nil {
modern = true
} else if !os.IsNotExist(err) {
return err
}
}
if legacy && modern {
return errors.New("新旧目录同时存在密钥,必须先人工确认迁移,不能自动选择")
}
return nil
}
// DeploymentLayout 识别已迁移部署,旧部署继续只读原路径,不能静默生成替代密钥。
func DeploymentLayout(root string) bool {
if _, err := os.Lstat(filepath.Join(root, "data", "credential.key")); err == nil {
return false
}
for _, name := range []string{"data/smail-state.db", "ssl/credential.key", "ssl/credentials/credential.key"} {
info, err := os.Lstat(filepath.Join(root, filepath.FromSlash(name)))
if err == nil && info.Mode().IsRegular() {
return true
}
}
return false
}
func SecureDirectory(root, name string) (string, error) {
if !filepath.IsLocal(name) || strings.ContainsAny(name, `/\`) || name == "." {
return "", errors.New("受保护目录名称无效")
}
if err := safeDirectory(root); err != nil {
return "", err
}
directory := filepath.Join(root, name)
if err := os.Mkdir(directory, 0700); err != nil && !os.IsExist(err) {
return "", err
}
if err := safeDirectory(directory); err != nil {
return "", err
}
return directory, os.Chmod(directory, 0700)
}
func KeyDirectory(root string) string {
return filepath.Dir(KeyPath(root, "credential.key"))
}
func SSLReference(root, value string) string {
value = LegacyReference(value)
if !DeploymentLayout(root) {
return value
}
for _, prefix := range []string{"data/ssl", "data/dkim"} {
if value == prefix || len(value) > len(prefix) && value[:len(prefix)+1] == prefix+"/" {
if prefix == "data/dkim" {
return "ssl/dkim" + value[len(prefix):]
}
return "ssl" + value[len(prefix):]
}
}
return value
}
func SSLDirectory(root string) string {
return filepath.Join(root, filepath.FromSlash(SSLReference(root, "data/ssl")))
}
func DKIMDirectory(root string) string {
return filepath.Join(root, filepath.FromSlash(SSLReference(root, "data/dkim")))
}