mirror of
https://github.com/acepanel/panel.git
synced 2026-02-07 18:13:13 +08:00
feat(证书管理): 支持自动续签
This commit is contained in:
74
app/console/commands/cert_renew.go
Normal file
74
app/console/commands/cert_renew.go
Normal 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
|
||||
}
|
||||
@@ -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{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 不存在",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user