2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 12:40:25 +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
}

View File

@@ -4,9 +4,9 @@ CREATE TABLE certs
user_id integer NOT NULL,
website_id integer DEFAULT NULL,
dns_id integer DEFAULT NULL,
cron_id integer DEFAULT NULL,
type varchar(255) NOT NULL,
domains text NOT NULL,
auto_renew integer DEFAULT 1,
cert_url varchar(255) DEFAULT NULL,
cert text DEFAULT NULL,
key text DEFAULT NULL,

View File

@@ -831,6 +831,10 @@ const docTemplate = `{
"models.Cert": {
"type": "object",
"properties": {
"auto_renew": {
"description": "自动续签",
"type": "boolean"
},
"cert": {
"description": "证书内容",
"type": "string"
@@ -842,13 +846,6 @@ const docTemplate = `{
"created_at": {
"type": "string"
},
"cron": {
"$ref": "#/definitions/models.Cron"
},
"cron_id": {
"description": "关联的计划任务 ID",
"type": "integer"
},
"dns": {
"$ref": "#/definitions/models.CertDNS"
},
@@ -958,38 +955,6 @@ const docTemplate = `{
}
}
},
"models.Cron": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"log": {
"type": "string"
},
"name": {
"type": "string"
},
"shell": {
"type": "string"
},
"status": {
"type": "boolean"
},
"time": {
"type": "string"
},
"type": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"models.Website": {
"type": "object",
"properties": {
@@ -1025,6 +990,9 @@ const docTemplate = `{
"requests.CertAdd": {
"type": "object",
"properties": {
"auto_renew": {
"type": "boolean"
},
"dns_id": {
"type": "integer"
},

View File

@@ -824,6 +824,10 @@
"models.Cert": {
"type": "object",
"properties": {
"auto_renew": {
"description": "自动续签",
"type": "boolean"
},
"cert": {
"description": "证书内容",
"type": "string"
@@ -835,13 +839,6 @@
"created_at": {
"type": "string"
},
"cron": {
"$ref": "#/definitions/models.Cron"
},
"cron_id": {
"description": "关联的计划任务 ID",
"type": "integer"
},
"dns": {
"$ref": "#/definitions/models.CertDNS"
},
@@ -951,38 +948,6 @@
}
}
},
"models.Cron": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"log": {
"type": "string"
},
"name": {
"type": "string"
},
"shell": {
"type": "string"
},
"status": {
"type": "boolean"
},
"time": {
"type": "string"
},
"type": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"models.Website": {
"type": "object",
"properties": {
@@ -1018,6 +983,9 @@
"requests.CertAdd": {
"type": "object",
"properties": {
"auto_renew": {
"type": "boolean"
},
"dns_id": {
"type": "integer"
},

View File

@@ -41,6 +41,9 @@ definitions:
type: object
models.Cert:
properties:
auto_renew:
description: 自动续签
type: boolean
cert:
description: 证书内容
type: string
@@ -49,11 +52,6 @@ definitions:
type: string
created_at:
type: string
cron:
$ref: '#/definitions/models.Cron'
cron_id:
description: 关联的计划任务 ID
type: integer
dns:
$ref: '#/definitions/models.CertDNS'
dns_id:
@@ -128,27 +126,6 @@ definitions:
updated_at:
type: string
type: object
models.Cron:
properties:
created_at:
type: string
id:
type: integer
log:
type: string
name:
type: string
shell:
type: string
status:
type: boolean
time:
type: string
type:
type: string
updated_at:
type: string
type: object
models.Website:
properties:
created_at:
@@ -172,6 +149,8 @@ definitions:
type: object
requests.CertAdd:
properties:
auto_renew:
type: boolean
dns_id:
type: integer
domains: