From 1fa96e828e9c4bb0818b3dd740108aafffb96e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Thu, 2 Nov 2023 02:53:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AF=81=E4=B9=A6=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=8A=A8=E7=BB=AD=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/console/commands/cert_renew.go | 74 +++++++++++++++++++ app/console/kernel.go | 2 + app/http/requests/cert/cert_add.go | 30 ++++---- app/models/cert.go | 9 +-- app/services/cert.go | 8 +- .../20231101121929_create_certs_table.up.sql | 2 +- docs/docs.go | 46 ++---------- docs/swagger.json | 46 ++---------- docs/swagger.yaml | 31 ++------ 9 files changed, 119 insertions(+), 129 deletions(-) create mode 100644 app/console/commands/cert_renew.go diff --git a/app/console/commands/cert_renew.go b/app/console/commands/cert_renew.go new file mode 100644 index 00000000..7b99fde2 --- /dev/null +++ b/app/console/commands/cert_renew.go @@ -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 +} diff --git a/app/console/kernel.go b/app/console/kernel.go index a0ebc441..a1430717 100644 --- a/app/console/kernel.go +++ b/app/console/kernel.go @@ -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{}, } } diff --git a/app/http/requests/cert/cert_add.go b/app/http/requests/cert/cert_add.go index 7bb44dc7..598e8169 100644 --- a/app/http/requests/cert/cert_add.go +++ b/app/http/requests/cert/cert_add.go @@ -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 不存在", } } diff --git a/app/models/cert.go b/app/models/cert.go index 22f0e1fc..d013fd50 100644 --- a/app/models/cert.go +++ b/app/models/cert.go @@ -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"` } diff --git a/app/services/cert.go b/app/services/cert.go index 3c563c63..82aa2106 100644 --- a/app/services/cert.go +++ b/app/services/cert.go @@ -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 } diff --git a/database/migrations/20231101121929_create_certs_table.up.sql b/database/migrations/20231101121929_create_certs_table.up.sql index 94421a25..74eb1853 100644 --- a/database/migrations/20231101121929_create_certs_table.up.sql +++ b/database/migrations/20231101121929_create_certs_table.up.sql @@ -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, diff --git a/docs/docs.go b/docs/docs.go index c551ce6e..f3ac3e4b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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" }, diff --git a/docs/swagger.json b/docs/swagger.json index 1768df19..4ef90301 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e3b09b7f..3d759819 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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: