Files
certimate/internal/domains/deploy.go
T
2024-11-18 20:03:11 +08:00

214 lines
5.7 KiB
Go

package domains
import (
"context"
"crypto/ecdsa"
"crypto/rsa"
"fmt"
"strings"
"time"
"github.com/pocketbase/pocketbase/models"
"golang.org/x/exp/slices"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/deployer"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/pkg/utils/x509"
)
type Phase string
const (
checkPhase Phase = "check"
applyPhase Phase = "apply"
deployPhase Phase = "deploy"
)
const validityDuration = time.Hour * 24 * 10
func deploy(ctx context.Context, record *models.Record) error {
defer func() {
if r := recover(); r != nil {
app.GetApp().Logger().Error("部署失败", "err", r)
}
}()
var certificate *applicant.Certificate
history := NewHistory(record)
defer history.commit()
// ############1.检查域名配置
history.record(checkPhase, "开始检查", nil)
currRecord, err := app.GetApp().Dao().FindRecordById("domains", record.Id)
if err != nil {
app.GetApp().Logger().Error("获取记录失败", "err", err)
history.record(checkPhase, "获取域名配置失败", &RecordInfo{Err: err})
return err
}
history.record(checkPhase, "获取记录成功", nil)
cert := currRecord.GetString("certificate")
expiredAt := currRecord.GetDateTime("expiredAt").Time()
// 检查证书是否包含设置的所有域名
changed := isCertChanged(cert, currRecord)
if cert != "" && time.Until(expiredAt) > validityDuration && currRecord.GetBool("deployed") && !changed {
app.GetApp().Logger().Info("证书在有效期内")
history.record(checkPhase, "证书在有效期内且已部署,跳过", &RecordInfo{
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
}, true)
// 跳过的情况也算成功
history.setWholeSuccess(true)
return nil
}
history.record(checkPhase, "检查通过", nil, true)
// ############2.申请证书
history.record(applyPhase, "开始申请", nil)
if cert != "" && time.Until(expiredAt) > validityDuration && !changed {
history.record(applyPhase, "证书在有效期内,跳过", &RecordInfo{
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
})
} else {
applicant, err := applicant.Get(currRecord)
if err != nil {
history.record(applyPhase, "获取applicant失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("获取applicant失败", "err", err)
return err
}
certificate, err = applicant.Apply()
if err != nil {
history.record(applyPhase, "申请证书失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("申请证书失败", "err", err)
return err
}
history.record(applyPhase, "申请证书成功", &RecordInfo{
Info: []string{fmt.Sprintf("证书地址: %s", certificate.CertUrl)},
})
history.setCert(certificate)
}
history.record(applyPhase, "保存证书成功", nil, true)
// ############3.部署证书
history.record(deployPhase, "开始部署", nil, false)
deployers, err := deployer.Gets(currRecord, certificate)
if err != nil {
history.record(deployPhase, "获取deployer失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("获取deployer失败", "err", err)
return err
}
// 没有部署配置,也算成功
if len(deployers) == 0 {
history.record(deployPhase, "没有部署配置", &RecordInfo{Info: []string{"没有部署配置"}})
history.setWholeSuccess(true)
return nil
}
for _, deployer := range deployers {
if err = deployer.Deploy(ctx); err != nil {
app.GetApp().Logger().Error("部署失败", "err", err)
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfos()})
return err
}
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
Info: deployer.GetInfos(),
}, false)
}
app.GetApp().Logger().Info("部署成功")
history.record(deployPhase, "部署成功", nil, true)
history.setWholeSuccess(true)
return nil
}
func isCertChanged(certificate string, record *models.Record) bool {
// 如果证书为空,直接返回true
if certificate == "" {
return true
}
// 解析证书
cert, err := x509.ParseCertificateFromPEM(certificate)
if err != nil {
app.GetApp().Logger().Error("解析证书失败", "err", err)
return true
}
// 遍历域名列表,检查是否都在证书中,找到第一个不存在证书中域名时提前返回true
for _, domain := range strings.Split(record.GetString("domain"), ";") {
if !slices.Contains(cert.DNSNames, domain) && !slices.Contains(cert.DNSNames, "*."+removeLastSubdomain(domain)) {
return true
}
}
// 解析applyConfig
applyConfig := &domain.ApplyConfig{}
record.UnmarshalJSONField("applyConfig", applyConfig)
// 检查证书加密算法是否变更
switch pubkey := cert.PublicKey.(type) {
case *rsa.PublicKey:
bitSize := pubkey.N.BitLen()
switch bitSize {
case 2048:
// RSA2048
if applyConfig.KeyAlgorithm != "" && applyConfig.KeyAlgorithm != "RSA2048" {
return true
}
case 3072:
// RSA3072
if applyConfig.KeyAlgorithm != "RSA3072" {
return true
}
case 4096:
// RSA4096
if applyConfig.KeyAlgorithm != "RSA4096" {
return true
}
case 8192:
// RSA8192
if applyConfig.KeyAlgorithm != "RSA8192" {
return true
}
}
case *ecdsa.PublicKey:
bitSize := pubkey.Curve.Params().BitSize
switch bitSize {
case 256:
// EC256
if applyConfig.KeyAlgorithm != "EC256" {
return true
}
case 384:
// EC384
if applyConfig.KeyAlgorithm != "EC384" {
return true
}
}
}
return false
}
func removeLastSubdomain(domain string) string {
parts := strings.Split(domain, ".")
if len(parts) > 1 {
return strings.Join(parts[1:], ".")
}
return domain
}