2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-07 18:13:13 +08:00

feat(证书管理): 支持自动续签

This commit is contained in:
耗子
2023-11-02 02:53:03 +08:00
parent c46656ed2d
commit 1fa96e828e
9 changed files with 119 additions and 129 deletions

View File

@@ -0,0 +1,74 @@
package commands
import (
"crypto/x509"
"encoding/pem"
"github.com/goravel/framework/contracts/console"
"github.com/goravel/framework/contracts/console/command"
"github.com/goravel/framework/facades"
"github.com/goravel/framework/support/carbon"
"panel/app/models"
"panel/app/services"
)
type CertRenew struct {
}
// Signature The name and signature of the console command.
func (receiver *CertRenew) Signature() string {
return "panel:cert-renew"
}
// Description The console command description.
func (receiver *CertRenew) Description() string {
return "[面板] 证书续签"
}
// Extend The console command extend.
func (receiver *CertRenew) Extend() command.Extend {
return command.Extend{
Category: "panel",
}
}
// Handle Execute the console command.
func (receiver *CertRenew) Handle(ctx console.Context) error {
var certs []models.Cert
err := facades.Orm().Query().With("Website").With("User").With("DNS").Find(&certs)
if err != nil {
return err
}
for _, cert := range certs {
if !cert.AutoRenew {
continue
}
block, _ := pem.Decode([]byte(cert.Cert))
if block != nil {
data, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
// 结束时间大于 7 天的证书不续签
endTime := carbon.FromStdTime(data.NotAfter)
if endTime.Gt(carbon.Now().AddDays(7)) {
continue
}
}
certService := services.NewCertImpl()
_, err = certService.Renew(cert.ID)
if err != nil {
facades.Log().Tags("面板", "证书管理").With(map[string]any{
"cert_id": cert.ID,
"error": err.Error(),
}).Errorf("证书续签失败")
}
}
return nil
}

View File

@@ -14,6 +14,7 @@ type Kernel struct {
func (kernel *Kernel) Schedule() []schedule.Event {
return []schedule.Event{
facades.Schedule().Command("panel:monitoring").EveryMinute().SkipIfStillRunning(),
facades.Schedule().Command("panel:cert-renew").Daily().SkipIfStillRunning(),
}
}
@@ -21,5 +22,6 @@ func (kernel *Kernel) Commands() []console.Command {
return []console.Command{
&commands.Panel{},
&commands.Monitoring{},
&commands.CertRenew{},
}
}

View File

@@ -6,10 +6,11 @@ import (
)
type CertAdd struct {
Type string `form:"type" json:"type"`
Domains []string `form:"domains" json:"domains"`
UserID uint `form:"user_id" json:"user_id"`
DNSID *uint `form:"dns_id" json:"dns_id"`
Type string `form:"type" json:"type"`
Domains []string `form:"domains" json:"domains"`
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
UserID uint `form:"user_id" json:"user_id"`
DNSID *uint `form:"dns_id" json:"dns_id"`
}
func (r *CertAdd) Authorize(ctx http.Context) error {
@@ -18,20 +19,23 @@ func (r *CertAdd) Authorize(ctx http.Context) error {
func (r *CertAdd) Rules(ctx http.Context) map[string]string {
return map[string]string{
"type": "required|in:P256,P384,2048,4096",
"domains": "required|array",
"user_id": "required|exists:cert_users,id",
"type": "required|in:P256,P384,2048,4096",
"domains": "required|array",
"auto_renew": "required|bool",
"user_id": "required|exists:cert_users,id",
}
}
func (r *CertAdd) Messages(ctx http.Context) map[string]string {
return map[string]string{
"type.required": "类型不能为空",
"type.in": "类型必须为 P256, P384, 2048, 4096 中的一个",
"domains.required": "域名不能为空",
"domains.slice": "域名必须为数组",
"user_id.required": "ACME 用户 ID 不能为空",
"user_id.exists": "ACME 用户 ID 不存在",
"type.required": "类型不能为空",
"type.in": "类型必须为 P256, P384, 2048, 4096 中的一个",
"domains.required": "域名不能为空",
"domains.array": "域名必须为数组",
"auto_renew.required": "自动续签不能为空",
"auto_renew.bool": "自动续签必须为布尔值",
"user_id.required": "ACME 用户 ID 不能为空",
"user_id.exists": "ACME 用户 ID 不存在",
}
}

View File

@@ -9,17 +9,16 @@ type Cert struct {
UserID uint `gorm:"default:null" json:"user_id"` // 关联的 ACME 用户 ID
WebsiteID *uint `gorm:"default:null" json:"website_id"` // 关联的网站 ID
DNSID *uint `gorm:"column:dns_id;default:null" json:"dns_id"` // 关联的 DNS ID
CronID *uint `gorm:"default:null" json:"cron_id"` // 关联的计划任务 ID
Type string `gorm:"not null" json:"type"` // 证书类型 (P256, P384, 2048, 4096)
Domains []string `gorm:"type:json;serializer:json" json:"domains"`
CertURL *string `gorm:"default:null" json:"cert_url"` // 证书 URL (续签时使用)
Cert string `gorm:"default:null" json:"cert"` // 证书内容
Key string `gorm:"default:null" json:"key"` // 私钥内容
AutoRenew bool `gorm:"default:true" json:"auto_renew"` // 自动续签
CertURL *string `gorm:"default:null" json:"cert_url"` // 证书 URL (续签时使用)
Cert string `gorm:"default:null" json:"cert"` // 证书内容
Key string `gorm:"default:null" json:"key"` // 私钥内容
CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"`
UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"`
Website *Website `gorm:"foreignKey:WebsiteID" json:"website"`
User *CertUser `gorm:"foreignKey:UserID" json:"user"`
DNS *CertDNS `gorm:"foreignKey:DNSID" json:"dns"`
Cron *Cron `gorm:"foreignKey:CronID" json:"cron"`
}

View File

@@ -7,11 +7,11 @@ import (
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/goravel/framework/facades"
"panel/pkg/tools"
requests "panel/app/http/requests/cert"
"panel/app/models"
"panel/pkg/acme"
"panel/pkg/tools"
)
type Cert interface {
@@ -127,11 +127,11 @@ func (s *CertImpl) CertAdd(request requests.CertAdd) error {
var cert models.Cert
cert.Type = request.Type
cert.Domains = request.Domains
cert.AutoRenew = request.AutoRenew
cert.UserID = request.UserID
if request.DNSID != nil {
cert.DNSID = request.DNSID
// TODO 生成计划任务
}
return facades.Orm().Query().Create(&cert)
@@ -145,10 +145,6 @@ func (s *CertImpl) CertDelete(ID uint) error {
return err
}
if cert.CronID != nil {
// TODO 删除计划任务
}
_, err = facades.Orm().Query().Delete(&models.Cert{}, ID)
return err
}