mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
b76f707c8b
Result: {"status":"keep","total_issues":74,"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":0,"golint_intrange":3,"golint_modernize":5,"golint_nilnil":3,"golint_perfsprint":18,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":73,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":38}
151 lines
4.6 KiB
Go
151 lines
4.6 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package tls
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/openflare/tls/acme"
|
|
"github.com/Rain-kl/Wavelet/internal/infra/task"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
)
|
|
|
|
const (
|
|
acmeRenewLeadTime = 7 * 24 * time.Hour
|
|
tlsProviderACME = "acme"
|
|
tlsApplyStatusApplying = "applying"
|
|
tlsApplyStatusReady = "ready"
|
|
)
|
|
|
|
var obtainTLSCertificate = obtainCertificate
|
|
|
|
// SetObtainCertificateFuncForTest swaps the async obtain implementation for tests.
|
|
func SetObtainCertificateFuncForTest(fn func(context.Context, *model.TLSCertificate) error) func() {
|
|
previous := obtainTLSCertificate
|
|
obtainTLSCertificate = fn
|
|
return func() {
|
|
obtainTLSCertificate = previous
|
|
}
|
|
}
|
|
|
|
func obtainCertificate(ctx context.Context, cert *model.TLSCertificate) error {
|
|
task.AppendLog(ctx, "【续签任务】开始续签,设置申请状态为 applying...")
|
|
cert.ApplyStatus = tlsApplyStatusApplying
|
|
if err := repository.SaveTLSCertificate(ctx, cert); err != nil {
|
|
return err
|
|
}
|
|
|
|
task.AppendLog(ctx, "【续签任务】正在解析 ACME 账户...")
|
|
acmeAccount, err := resolveAcmeAccount(ctx, cert)
|
|
if err != nil {
|
|
return updateCertError(ctx, cert, fmt.Sprintf("Failed to get ACME account: %v", err))
|
|
}
|
|
|
|
task.AppendLog(ctx, "【续签任务】正在解析 DNS 账户信息 (ID=%d)...", cert.DNSAccountID)
|
|
dnsAccount, err := repository.GetDNSAccountByID(ctx, cert.DNSAccountID)
|
|
if err != nil {
|
|
return updateCertError(ctx, cert, fmt.Sprintf("Failed to get DNS account: %v", err))
|
|
}
|
|
|
|
task.AppendLog(ctx, "【续签任务】正在解密 DNS 账号凭据及 ACME 账户私钥...")
|
|
dnsAuth, err := openSensitive(dnsAccount.Authorization)
|
|
if err != nil {
|
|
return updateCertError(ctx, cert, fmt.Sprintf("Failed to decrypt DNS credentials: %v", err))
|
|
}
|
|
|
|
acmePrivateKey, err := openSensitive(acmeAccount.PrivateKey)
|
|
if err != nil {
|
|
return updateCertError(ctx, cert, fmt.Sprintf("Failed to decrypt ACME account key: %v", err))
|
|
}
|
|
|
|
domains := splitAcmeDomains(cert.PrimaryDomain, cert.OtherDomains)
|
|
task.AppendLog(ctx, "【续签任务】待申请的域名列表: %v", domains)
|
|
|
|
task.AppendLog(ctx, "【续签任务】正在调用 ACME 客户端(通过 DNS-01 挑战)发起 SSL 证书签发请求,请稍候...")
|
|
newAccountURL, newPrivateKeyPEM, result, err := acme.ObtainSSL(
|
|
acmeAccount.Email,
|
|
acmePrivateKey,
|
|
acmeAccount.URL,
|
|
dnsAccount.Type,
|
|
dnsAuth,
|
|
cert.DNS1,
|
|
cert.DNS2,
|
|
cert.DisableCNAME,
|
|
cert.SkipDNS,
|
|
cert.KeyAlgorithm,
|
|
domains,
|
|
)
|
|
|
|
task.AppendLog(ctx, "【续签任务】正在保存 ACME 账户可能的变更...")
|
|
if err := persistAcmeAccountUpdates(ctx, cert, acmeAccount, newAccountURL, newPrivateKeyPEM, acmePrivateKey); err != nil {
|
|
return updateCertError(ctx, cert, err.Error())
|
|
}
|
|
|
|
if err != nil {
|
|
return updateCertError(ctx, cert, err.Error())
|
|
}
|
|
|
|
task.AppendLog(ctx, "【续签任务】证书签发成功,正在将证书内容与私钥安全写入数据库...")
|
|
if err := saveObtainedCertificate(ctx, cert, result); err != nil {
|
|
return updateCertError(ctx, cert, err.Error())
|
|
}
|
|
task.AppendLog(ctx, "【续签任务】证书数据存储完成!")
|
|
return nil
|
|
}
|
|
|
|
func updateCertError(ctx context.Context, cert *model.TLSCertificate, message string) error {
|
|
cert.ApplyStatus = "error"
|
|
cert.ApplyMessage = message
|
|
if err := repository.SaveTLSCertificate(ctx, cert); err != nil {
|
|
return err
|
|
}
|
|
return fmt.Errorf("%s", message)
|
|
}
|
|
|
|
func splitAcmeDomains(primaryDomain, otherDomains string) []string {
|
|
primaryDomain = strings.TrimSpace(primaryDomain)
|
|
domains := []string{}
|
|
if primaryDomain != "" {
|
|
domains = append(domains, primaryDomain)
|
|
}
|
|
otherDomains = strings.TrimSpace(otherDomains)
|
|
if otherDomains == "" {
|
|
return domains
|
|
}
|
|
|
|
separator := "\n"
|
|
if !strings.Contains(otherDomains, "\n") && strings.Contains(otherDomains, ",") {
|
|
separator = ","
|
|
}
|
|
for domain := range strings.SplitSeq(otherDomains, separator) {
|
|
domain = strings.TrimSpace(domain)
|
|
if domain != "" {
|
|
domains = append(domains, domain)
|
|
}
|
|
}
|
|
return domains
|
|
}
|
|
|
|
// CertificatesDueForRenewal returns ACME certificates that should be renewed at the given time.
|
|
func CertificatesDueForRenewal(certificates []model.TLSCertificate, now time.Time) []model.TLSCertificate {
|
|
due := make([]model.TLSCertificate, 0)
|
|
for _, cert := range certificates {
|
|
if !cert.AutoRenew || cert.Provider != tlsProviderACME || cert.ApplyStatus == tlsApplyStatusApplying {
|
|
continue
|
|
}
|
|
if cert.NotAfter.IsZero() {
|
|
continue
|
|
}
|
|
if cert.NotAfter.Sub(now) < acmeRenewLeadTime {
|
|
due = append(due, cert)
|
|
}
|
|
}
|
|
return due
|
|
}
|