From ef7d38f2afd6cbf43a1bca7db56b853b510d7783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 24 Mar 2024 15:55:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AF=81=E4=B9=A6):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=AD=BE=E5=8F=91=E5=8F=8A=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/http/controllers/cert_controller.go | 30 ++++++++++++++ app/http/requests/cert/cert_deploy.go | 4 +- docs/docs.go | 50 +++++++++++++++++++++++ docs/swagger.json | 50 +++++++++++++++++++++++ docs/swagger.yaml | 31 ++++++++++++++ internal/cert.go | 1 + internal/services/cert.go | 54 ++++++++++++++++++++++--- internal/services/website.go | 4 +- pkg/str/str.go | 9 ----- pkg/str/str_test.go | 5 --- routes/api.go | 1 + 11 files changed, 215 insertions(+), 24 deletions(-) diff --git a/app/http/controllers/cert_controller.go b/app/http/controllers/cert_controller.go index 749bb703..29fc2535 100644 --- a/app/http/controllers/cert_controller.go +++ b/app/http/controllers/cert_controller.go @@ -669,3 +669,33 @@ func (r *CertController) ManualDNS(ctx http.Context) http.Response { return Success(ctx, resolves) } + +// Deploy +// +// @Summary 部署证书 +// @Description 部署面板证书管理的证书 +// @Tags 证书管理 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.CertDeploy true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/cert/deploy [post] +func (r *CertController) Deploy(ctx http.Context) http.Response { + var deployRequest requests.CertDeploy + sanitize := Sanitize(ctx, &deployRequest) + if sanitize != nil { + return sanitize + } + + err := r.cert.Deploy(deployRequest.ID, deployRequest.WebsiteID) + if err != nil { + facades.Log().Request(ctx.Request()).Tags("面板", "证书管理").With(map[string]any{ + "certID": deployRequest.ID, + "error": err.Error(), + }).Info("部署证书失败") + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, nil) +} diff --git a/app/http/requests/cert/cert_deploy.go b/app/http/requests/cert/cert_deploy.go index 473cdc37..22044dd1 100644 --- a/app/http/requests/cert/cert_deploy.go +++ b/app/http/requests/cert/cert_deploy.go @@ -6,8 +6,8 @@ import ( ) type CertDeploy struct { - ID uint `form:"id" json:"id"` - WebsiteID uint `form:"website_id" json:"website_id"` + ID uint `form:"id" json:"id" filter:"uint"` + WebsiteID uint `form:"website_id" json:"website_id" filter:"uint"` } func (r *CertDeploy) Authorize(ctx http.Context) error { diff --git a/docs/docs.go b/docs/docs.go index 9f05b61e..751276df 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -270,6 +270,45 @@ const docTemplate = `{ } } }, + "/panel/cert/deploy": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "部署面板证书管理的证书", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "证书管理" + ], + "summary": "部署证书", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CertDeploy" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, "/panel/cert/dns": { "get": { "security": [ @@ -4068,6 +4107,17 @@ const docTemplate = `{ } } }, + "requests.CertDeploy": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "website_id": { + "type": "integer" + } + } + }, "requests.CertStore": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index e0efe1b9..3d0ed8df 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -263,6 +263,45 @@ } } }, + "/panel/cert/deploy": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "部署面板证书管理的证书", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "证书管理" + ], + "summary": "部署证书", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CertDeploy" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, "/panel/cert/dns": { "get": { "security": [ @@ -4061,6 +4100,17 @@ } } }, + "requests.CertDeploy": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "website_id": { + "type": "integer" + } + } + }, "requests.CertStore": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 15f1233e..7849a358 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -253,6 +253,13 @@ definitions: type: string type: array type: object + requests.CertDeploy: + properties: + id: + type: integer + website_id: + type: integer + type: object requests.CertStore: properties: auto_renew: @@ -793,6 +800,30 @@ paths: summary: 更新证书 tags: - 证书管理 + /panel/cert/deploy: + post: + consumes: + - application/json + description: 部署面板证书管理的证书 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.CertDeploy' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 部署证书 + tags: + - 证书管理 /panel/cert/dns: get: description: 获取面板证书管理的 DNS 接口列表 diff --git a/internal/cert.go b/internal/cert.go index 36c1c67f..93d6f532 100644 --- a/internal/cert.go +++ b/internal/cert.go @@ -23,4 +23,5 @@ type Cert interface { ObtainManual(ID uint) (acme.Certificate, error) ManualDNS(ID uint) ([]acme.DNSRecord, error) Renew(ID uint) (acme.Certificate, error) + Deploy(ID, WebsiteID uint) error } diff --git a/internal/services/cert.go b/internal/services/cert.go index 57f1f565..4555155c 100644 --- a/internal/services/cert.go +++ b/internal/services/cert.go @@ -4,6 +4,7 @@ package services import ( "context" "errors" + "strings" "time" "github.com/goravel/framework/facades" @@ -257,6 +258,11 @@ func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) { if cert.Website == nil { return acme.Certificate{}, errors.New("该证书没有关联网站,无法自动签发") } else { + for _, domain := range cert.Domains { + if strings.Contains(domain, "*") { + return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证") + } + } client.UseHTTP(cert.Website.Path) } } @@ -275,10 +281,10 @@ func (s *CertImpl) ObtainAuto(ID uint) (acme.Certificate, error) { } if cert.Website != nil { - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { return acme.Certificate{}, err } - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { return acme.Certificate{}, err } if err = tools.ServiceReload("openresty"); err != nil { @@ -315,10 +321,10 @@ func (s *CertImpl) ObtainManual(ID uint) (acme.Certificate, error) { } if cert.Website != nil { - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { return acme.Certificate{}, err } - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { return acme.Certificate{}, err } if err = tools.ServiceReload("openresty"); err != nil { @@ -377,6 +383,11 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) { if cert.Website == nil { return acme.Certificate{}, errors.New("该证书没有关联网站,无法续签,可以尝试手动签发") } else { + for _, domain := range cert.Domains { + if strings.Contains(domain, "*") { + return acme.Certificate{}, errors.New("通配符域名无法使用 HTTP 验证") + } + } client.UseHTTP(cert.Website.Path) } } @@ -398,10 +409,10 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) { } if cert.Website != nil { - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".pem", cert.Cert, 0644); err != nil { return acme.Certificate{}, err } - if err := tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+cert.Website.Name+".key", cert.Key, 0644); err != nil { return acme.Certificate{}, err } if err = tools.ServiceReload("openresty"); err != nil { @@ -412,6 +423,37 @@ func (s *CertImpl) Renew(ID uint) (acme.Certificate, error) { return ssl, nil } +// Deploy 部署证书 +func (s *CertImpl) Deploy(ID, WebsiteID uint) error { + var cert models.Cert + err := facades.Orm().Query().Where("id = ?", ID).First(&cert) + if err != nil { + return err + } + + if cert.Cert == "" || cert.Key == "" { + return errors.New("该证书没有签发成功,无法部署") + } + + website := models.Website{} + err = facades.Orm().Query().Where("id = ?", WebsiteID).First(&website) + if err != nil { + return err + } + + if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", cert.Cert, 0644); err != nil { + return err + } + if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".key", cert.Key, 0644); err != nil { + return err + } + if err = tools.ServiceReload("openresty"); err != nil { + return err + } + + return nil +} + func (s *CertImpl) getClient(cert models.Cert) (*acme.Client, error) { var ca string var eab *acme.EAB diff --git a/internal/services/website.go b/internal/services/website.go index 0294cfbd..2c7d36ac 100644 --- a/internal/services/website.go +++ b/internal/services/website.go @@ -393,10 +393,10 @@ func (r *WebsiteImpl) SaveConfig(config requests.SaveConfig) error { // SSL ssl := config.Ssl website.Ssl = ssl - if err := tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", config.SslCertificate, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".pem", config.SslCertificate, 0644); err != nil { return err } - if err := tools.Write("/www/server/vhost/ssl/"+website.Name+".key", config.SslCertificateKey, 0644); err != nil { + if err = tools.Write("/www/server/vhost/ssl/"+website.Name+".key", config.SslCertificateKey, 0644); err != nil { return err } if ssl { diff --git a/pkg/str/str.go b/pkg/str/str.go index c25b7e4c..00b6f86e 100644 --- a/pkg/str/str.go +++ b/pkg/str/str.go @@ -30,12 +30,3 @@ func Camel(s string) string { func LowerCamel(s string) string { return strcase.ToLowerCamel(s) } - -func ContainsString(arr []string, str string) bool { - for _, s := range arr { - if s == str { - return true - } - } - return false -} diff --git a/pkg/str/str_test.go b/pkg/str/str_test.go index de962ac5..95a15b65 100644 --- a/pkg/str/str_test.go +++ b/pkg/str/str_test.go @@ -38,8 +38,3 @@ func (s *StrTestSuite) TestLowerCamel() { s.Equal("topicComment", LowerCamel("topic_comment")) s.Equal("topicComment", LowerCamel("TopicComment")) } - -func (s *StrTestSuite) TestContainsString() { - s.True(ContainsString([]string{"a", "b", "c"}, "a")) - s.False(ContainsString([]string{"a", "b", "c"}, "d")) -} diff --git a/routes/api.go b/routes/api.go index 11c8ac1d..5856b078 100644 --- a/routes/api.go +++ b/routes/api.go @@ -80,6 +80,7 @@ func Api() { r.Post("obtain", certController.Obtain) r.Post("renew", certController.Renew) r.Post("manualDNS", certController.ManualDNS) + r.Post("deploy", certController.Deploy) }) r.Prefix("plugin").Middleware(middleware.Jwt()).Group(func(r route.Router) { pluginController := controllers.NewPluginController()