mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 11:27:17 +08:00
feat(证书): 优化签发及部署
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
50
docs/docs.go
50
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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 接口列表
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user