Merge branch 'next' into feat/new-workflow

This commit is contained in:
Fu Diwei
2025-01-22 20:21:32 +08:00
16 changed files with 589 additions and 5 deletions

View File

@@ -5,9 +5,12 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"strconv"
"strings"
"time"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
@@ -176,6 +179,26 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
}
}
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
info, err := certs.ParseCertificateFromPEM(req.Certificate)
if err != nil {
return nil, err
}
if time.Now().After(info.NotAfter) {
return nil, errors.New("证书已过期")
}
return &dtos.CertificateValidateCertificateResp{
Domains: strings.Join(info.DNSNames, ";"),
}, nil
}
func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error {
_, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey))
return err
}
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
Subject string
Message string

View File

@@ -4,3 +4,28 @@ type CertificateArchiveFileReq struct {
CertificateId string `json:"-"`
Format string `json:"format"`
}
type CertificateArchiveFileResp struct {
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
}
type CertificateValidateCertificateReq struct {
Certificate string `json:"certificate"`
}
type CertificateValidateCertificateResp struct {
Domains string `json:"domains"`
}
type CertificateValidatePrivateKeyReq struct {
PrivateKey string `json:"privateKey"`
}
type CertificateUploadReq struct {
WorkflowId string `json:"workflowId"`
WorkflowNodeId string `json:"workflowNodeId"`
CertificateId string `json:"certificateId"`
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
}

View File

@@ -29,6 +29,7 @@ const (
WorkflowNodeTypeStart = WorkflowNodeType("start")
WorkflowNodeTypeEnd = WorkflowNodeType("end")
WorkflowNodeTypeApply = WorkflowNodeType("apply")
WorkflowNodeTypeUpload = WorkflowNodeType("upload")
WorkflowNodeTypeDeploy = WorkflowNodeType("deploy")
WorkflowNodeTypeNotify = WorkflowNodeType("notify")
WorkflowNodeTypeBranch = WorkflowNodeType("branch")
@@ -75,6 +76,12 @@ type WorkflowNodeConfigForApply struct {
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(零值将使用默认值 30
}
type WorkflowNodeConfigForUpload struct {
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
Domains string `json:"domains"`
}
type WorkflowNodeConfigForDeploy struct {
Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate”
Provider string `json:"provider"` // 主机提供商
@@ -133,6 +140,14 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
}
}
func (n *WorkflowNode) GetConfigForUpload() WorkflowNodeConfigForUpload {
return WorkflowNodeConfigForUpload{
Certificate: n.getConfigValueAsString("certificate"),
PrivateKey: n.getConfigValueAsString("privateKey"),
Domains: n.getConfigValueAsString("domains"),
}
}
func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy {
return WorkflowNodeConfigForDeploy{
Certificate: n.getConfigValueAsString("certificate"),

View File

@@ -12,6 +12,8 @@ import (
type certificateService interface {
ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error)
ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error)
ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error
}
type CertificateHandler struct {
@@ -24,10 +26,12 @@ func NewCertificateHandler(router *router.RouterGroup[*core.RequestEvent], servi
}
group := router.Group("/certificates")
group.POST("/{certificateId}/archive", handler.run)
group.POST("/{certificateId}/archive", handler.archive)
group.POST("/validate/certificate", handler.validateCertificate)
group.POST("/validate/private-key", handler.validatePrivateKey)
}
func (handler *CertificateHandler) run(e *core.RequestEvent) error {
func (handler *CertificateHandler) archive(e *core.RequestEvent) error {
req := &dtos.CertificateArchiveFileReq{}
req.CertificateId = e.Request.PathValue("certificateId")
if err := e.BindBody(req); err != nil {
@@ -40,3 +44,27 @@ func (handler *CertificateHandler) run(e *core.RequestEvent) error {
return resp.Ok(e, bt)
}
}
func (handler *CertificateHandler) validateCertificate(e *core.RequestEvent) error {
req := &dtos.CertificateValidateCertificateReq{}
if err := e.BindBody(req); err != nil {
return resp.Err(e, err)
}
if rs, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil {
return resp.Err(e, err)
} else {
return resp.Ok(e, rs)
}
}
func (handler *CertificateHandler) validatePrivateKey(e *core.RequestEvent) error {
req := &dtos.CertificateValidatePrivateKeyReq{}
if err := e.BindBody(req); err != nil {
return resp.Err(e, err)
}
if err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil {
return resp.Err(e, err)
} else {
return resp.Ok(e, nil)
}
}

View File

@@ -66,6 +66,8 @@ func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) {
return NewConditionNode(node), nil
case domain.WorkflowNodeTypeApply:
return NewApplyNode(node), nil
case domain.WorkflowNodeTypeUpload:
return NewUploadNode(node), nil
case domain.WorkflowNodeTypeDeploy:
return NewDeployNode(node), nil
case domain.WorkflowNodeTypeNotify:

View File

@@ -0,0 +1,101 @@
package nodeprocessor
import (
"context"
"errors"
"strings"
"time"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/utils/certs"
"github.com/usual2970/certimate/internal/repository"
)
type uploadNode struct {
node *domain.WorkflowNode
outputRepo workflowOutputRepository
*nodeLogger
}
func NewUploadNode(node *domain.WorkflowNode) *uploadNode {
return &uploadNode{
node: node,
nodeLogger: NewNodeLogger(node),
outputRepo: repository.NewWorkflowOutputRepository(),
}
}
// Run 上传证书节点执行
// 包含上传证书的工作流,理论上应该手动执行,如果每天定时执行,也只是重新保存一下
func (n *uploadNode) Run(ctx context.Context) error {
n.AddOutput(ctx,
n.node.Name,
"进入上传证书节点",
)
config := n.node.GetConfigForUpload()
// 检查证书是否过期
// 如果证书过期,则直接返回错误
certX509, err := certs.ParseCertificateFromPEM(config.Certificate)
if err != nil {
n.AddOutput(ctx,
n.node.Name,
"解析证书失败",
)
return err
}
if time.Now().After(certX509.NotAfter) {
n.AddOutput(ctx,
n.node.Name,
"证书已过期",
)
return errors.New("certificate is expired")
}
certificate := &domain.Certificate{
Source: domain.CertificateSourceTypeUpload,
SubjectAltNames: strings.Join(certX509.DNSNames, ";"),
Certificate: config.Certificate,
PrivateKey: config.PrivateKey,
EffectAt: certX509.NotBefore,
ExpireAt: certX509.NotAfter,
WorkflowId: getContextWorkflowId(ctx),
WorkflowNodeId: n.node.Id,
}
// 保存执行结果
// TODO: 先保持一个节点始终只有一个输出,后续增加版本控制
currentOutput := &domain.WorkflowOutput{
WorkflowId: getContextWorkflowId(ctx),
NodeId: n.node.Id,
Node: n.node,
Succeeded: true,
Outputs: n.node.Outputs,
}
// 查询上次执行结果
lastOutput, err := n.outputRepo.GetByNodeId(ctx, n.node.Id)
if err != nil && !domain.IsRecordNotFoundError(err) {
n.AddOutput(ctx, n.node.Name, "查询上传记录失败", err.Error())
return err
}
if lastOutput != nil {
currentOutput.Id = lastOutput.Id
}
if err := n.outputRepo.Save(ctx, currentOutput, certificate, func(id string) error {
if certificate != nil {
certificate.WorkflowOutputId = id
}
return nil
}); err != nil {
n.AddOutput(ctx, n.node.Name, "保存上传记录失败", err.Error())
return err
}
n.AddOutput(ctx, n.node.Name, "保存上传记录成功")
return nil
}