This commit is contained in:
parent
73753ac4e1
commit
9465b4fee9
@ -26,7 +26,7 @@ func run(args []string) error {
|
||||
historySource := flags.String("deployment-history", "", "停服复制完成后,从原部署导入配置历史并转换证书引用")
|
||||
exportTLS := flags.String("export-project-tls", "", "将项目托管证书导出为本机 TLS 入口使用的合并 PEM 文件")
|
||||
publishTLS := flags.String("publish-external-tls", "", "将外部续期证书校验并原子发布为 Nginx 使用的合并 PEM 文件")
|
||||
externalTLSDirectory := flags.String("external-tls-dir", "", "外部续期目录,包含权限受保护的 smail.crt 和 smail.key")
|
||||
externalTLSDirectory := flags.String("external-tls-dir", "", "外部续期目录,包含以邮件主域名命名且权限受保护的证书与私钥")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/utils/atomicfile"
|
||||
)
|
||||
@ -38,7 +39,7 @@ func ExportProjectTLS(root, destination string) error {
|
||||
}
|
||||
|
||||
// PublishExternalTLS 仅由部署管理员显式提供外部续期目录,不从网页配置读取高权限源路径。
|
||||
// 外部工具统一使用 smail 文件名;校验失败时不替换入口的有效证书。
|
||||
// 外部工具使用邮件主域名作为文件名,与续期读取规则一致;校验失败时保留入口证书。
|
||||
func PublishExternalTLS(root, sourceDirectory, destination string) error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return errors.New("本机 Nginx 证书同步仅支持 Linux")
|
||||
@ -57,7 +58,11 @@ func PublishExternalTLS(root, sourceDirectory, destination string) error {
|
||||
if filepath.Dir(destination) == sourceDirectory {
|
||||
return ErrUnsafeSSLFile
|
||||
}
|
||||
return exportTLSBundle(sourceDirectory, "smail.crt", "smail.key", destination, validationCertificateDomains(&runtime))
|
||||
domain := runtime.Root.Domains.Primary
|
||||
if domain == "" || domain == "." || domain == ".." || strings.ContainsAny(domain, `/\:`) {
|
||||
return ErrUnsafeSSLFile
|
||||
}
|
||||
return exportTLSBundle(sourceDirectory, domain+".crt", domain+".key", destination, validationCertificateDomains(&runtime))
|
||||
}
|
||||
|
||||
func exportTLSBundle(sourceDirectory, certificateName, privateKeyName, destination string, domains []string) error {
|
||||
|
||||
@ -35,8 +35,8 @@ func TestPublishExternalTLSValidatesBeforeReplacingBundle(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
write("smail.crt", certificate)
|
||||
write("smail.key", key)
|
||||
write("example.com.crt", certificate)
|
||||
write("example.com.key", key)
|
||||
destination := filepath.Join(output, "tls.pem")
|
||||
if err := PublishExternalTLS(root, source, destination); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -69,22 +69,22 @@ func TestPublishExternalTLSValidatesBeforeReplacingBundle(t *testing.T) {
|
||||
t.Fatal("failed publication replaced the previous bundle")
|
||||
}
|
||||
}
|
||||
write("smail.key", []byte("invalid"))
|
||||
write("example.com.key", []byte("invalid"))
|
||||
assertRejected()
|
||||
wrongKey, wrongCertificate := testManagedCertificate(t, "other.example", time.Now().Add(-time.Hour), time.Now().Add(time.Hour), 2048)
|
||||
write("smail.key", wrongKey)
|
||||
write("smail.crt", wrongCertificate)
|
||||
write("example.com.key", wrongKey)
|
||||
write("example.com.crt", wrongCertificate)
|
||||
assertRejected()
|
||||
write("smail.key", key)
|
||||
write("smail.crt", certificate)
|
||||
if err := os.Chmod(filepath.Join(source, "smail.key"), 0644); err != nil {
|
||||
write("example.com.key", key)
|
||||
write("example.com.crt", certificate)
|
||||
if err := os.Chmod(filepath.Join(source, "example.com.key"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertRejected()
|
||||
if err := os.Chmod(filepath.Join(source, "smail.key"), 0600); err != nil {
|
||||
if err := os.Chmod(filepath.Join(source, "example.com.key"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := PublishExternalTLS(root, source, filepath.Join(source, "smail.crt")); err == nil {
|
||||
if err := PublishExternalTLS(root, source, filepath.Join(source, "example.com.crt")); err == nil {
|
||||
t.Fatal("source overwrite allowed")
|
||||
}
|
||||
candidate.Root.HTTP.TLSMode = HTTPSTLSModeDirect
|
||||
|
||||
@ -8,7 +8,7 @@ Type=oneshot
|
||||
EnvironmentFile=/etc/smail/acme-dns.env
|
||||
EnvironmentFile=/etc/smail/acme-renew.env
|
||||
ExecStartPre=/usr/sbin/nginx -t
|
||||
ExecStart=/usr/local/libexec/smail-acme/lego --path /var/lib/smail-acme-tls --email ${SMAIL_ACME_EMAIL} --accept-tos --key-type ec256 --dns ${SMAIL_ACME_DNS_PROVIDER} --domains ${SMAIL_MAIL_DOMAIN} --domains *.${SMAIL_MAIL_DOMAIN} --filename smail --http-timeout 20 --cert.timeout 120 renew --days 30 --reuse-key --no-random-sleep
|
||||
ExecStart=/usr/local/libexec/smail-acme/lego --path /var/lib/smail-acme-tls --email ${SMAIL_ACME_EMAIL} --accept-tos --key-type ec256 --dns ${SMAIL_ACME_DNS_PROVIDER} --domains ${SMAIL_MAIL_DOMAIN} --domains *.${SMAIL_MAIL_DOMAIN} --http-timeout 20 --cert.timeout 120 renew --days 30 --reuse-key --no-random-sleep
|
||||
ExecStartPost=/opt/smail/smail-config --root /opt/smail --external-tls-dir /var/lib/smail-acme-tls/certificates --publish-external-tls /etc/nginx/smail/tls.pem
|
||||
ExecStartPost=/usr/sbin/nginx -t
|
||||
ExecStartPost=/usr/bin/systemctl reload nginx.service
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
|
||||
1. 安装与项目依赖版本一致的 lego 到 `/usr/local/libexec/smail-acme/lego`,安装最新版 `smail-config` 到 `/opt/smail/smail-config`。
|
||||
2. 创建权限为 `0700` 的 `/var/lib/smail-acme-tls`,以及权限为 `0600`、root 所有的 `/etc/smail/acme-dns.env` 与 `/etc/smail/acme-renew.env`。按对应示例填写,禁止输出凭据或加入仓库。
|
||||
3. 首次使用相同参数执行 lego 的 `run` 完成签发,不能直接用缺少账户和证书状态的 `renew` 初始化。固定使用 `--filename smail`;申请邮件根域及其泛域名,验证方式为 DNS。本模板要求后台域名被该证书覆盖;不要重复加入已被泛域名覆盖的后台子域,否则签发机构可能拒绝请求。独立的后台域名需要管理员额外加入明确的申请参数。
|
||||
3. 首次使用相同参数执行 lego 的 `run` 完成签发,不能直接用缺少账户和证书状态的 `renew` 初始化。以邮件主域名作为第一个域名参数,使用工具默认的域名文件名,不设置已弃用的 `--filename`,否则签发保存和续期读取的文件名可能不一致。申请邮件根域及其泛域名,验证方式为 DNS。本模板要求后台域名被该证书覆盖;不要重复加入已被泛域名覆盖的后台子域,否则签发机构可能拒绝请求。独立的后台域名需要管理员额外加入明确的申请参数。
|
||||
4. 执行下列发布命令。该命令校验证书有效期、域名、密钥匹配及文件权限,拒绝符号链接、不安全路径和源目录内覆盖;失败时保留原有 Nginx 合并证书。
|
||||
|
||||
```sh
|
||||
@ -66,7 +66,7 @@
|
||||
--publish-external-tls /etc/nginx/smail/tls.pem
|
||||
```
|
||||
|
||||
5. Nginx 的 `ssl_certificate` 与 `ssl_certificate_key` 都引用 `/etc/nginx/smail/tls.pem`。通过 Smail 页面将外部证书路径设置为 `/var/lib/smail-acme-tls/certificates/smail.crt`、私钥路径设置为同目录的 `smail.key`;运行账号必须具备读取权限,不能通过开放私钥权限解决访问问题。
|
||||
5. Nginx 的 `ssl_certificate` 与 `ssl_certificate_key` 都引用 `/etc/nginx/smail/tls.pem`。通过 Smail 页面将外部证书路径设置为 `/var/lib/smail-acme-tls/certificates/<邮件主域名>.crt`、私钥路径设置为同目录的 `<邮件主域名>.key`,与本次初始化确认的邮件主域名一致;运行账号必须具备读取权限,不能通过开放私钥权限解决访问问题。
|
||||
6. 检查 Nginx 配置、平滑加载,并分别核验网页及邮件协议的公网证书。确认首次切换成功后,再安装并启用专用续期 service、timer;不要启用同机其他项目的全局续期任务,也不要同时保留旧的项目证书同步监听器。
|
||||
|
||||
专用计时器每天检查一次,并随机延迟;到期阈值为三十天,续期复用当前证书私钥。续期成功后重新校验并原子发布合并证书,再检查和加载 Nginx。Smail 从外部证书文件热加载,保持其无效新证书回退行为。不要在该任务运行期间另外手工调用同一状态目录的 lego。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user