feat: 自签证书增加续签功能 (#3079)

This commit is contained in:
zhengkunwang 2023-11-27 18:36:10 +08:00 committed by GitHub
parent 807a5071a7
commit e41661e8d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 300 additions and 406 deletions

View File

@ -116,3 +116,29 @@ func (b *BaseApi) ObtainWebsiteCA(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Website CA
// @Summary Obtain SSL
// @Description 续签 SSL 证书
// @Accept json
// @Param request body request.WebsiteCAObtain true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ca/obtain [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"自签 SSL 证书 [name]","formatEN":"Obtain SSL [name]"}
func (b *BaseApi) RenewWebsiteCA(c *gin.Context) {
var req request.WebsiteCARenew
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteCAService.ObtainSSL(request.WebsiteCAObtain{
SSLID: req.SSLID,
Renew: true,
Unit: "year",
Time: 1,
}); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -65,27 +65,6 @@ func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) {
helper.SuccessWithData(c, res)
}
// @Tags Website SSL
// @Summary Reset website ssl
// @Description 重置网站 ssl
// @Accept json
// @Param request body request.WebsiteSSLRenew true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/ssl/renew [post]
// @x-panel-log {"bodyKeys":["SSLId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"SSLId","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"重置 ssl [domain]","formatEN":"Renew ssl [domain]"}
func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) {
var req request.WebsiteSSLRenew
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteSSLService.Renew(req.SSLID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website SSL
// @Summary Apply ssl
// @Description 申请证书

View File

@ -92,11 +92,18 @@ type WebsiteCACreate struct {
}
type WebsiteCAObtain struct {
ID uint `json:"id" validate:"required"`
Domains string `json:"domains" validate:"required"`
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
Time int `json:"time" validate:"required"`
Unit string `json:"unit" validate:"required"`
PushDir bool `json:"pushDir"`
Dir string `json:"dir"`
ID uint `json:"id" validate:"required"`
Domains string `json:"domains" validate:"required"`
KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"`
Time int `json:"time" validate:"required"`
Unit string `json:"unit" validate:"required"`
PushDir bool `json:"pushDir"`
Dir string `json:"dir"`
AutoRenew bool `json:"autoRenew"`
Renew bool `json:"renew"`
SSLID uint `json:"sslID"`
}
type WebsiteCARenew struct {
SSLID uint `json:"SSLID" validate:"required"`
}

View File

@ -19,6 +19,7 @@ type WebsiteSSL struct {
Organization string `gorm:"type:varchar(64);not null" json:"organization"`
DnsAccountID uint `gorm:"type:integer;not null" json:"dnsAccountId"`
AcmeAccountID uint `gorm:"type:integer;not null" json:"acmeAccountId"`
CaID uint `gorm:"type:integer;not null;default:0" json:"caId"`
AutoRenew bool `gorm:"type:varchar(64);not null" json:"autoRenew"`
ExpireDate time.Time `json:"expireDate"`
StartDate time.Time `json:"startDate"`

View File

@ -14,11 +14,12 @@ type ISSLRepo interface {
WithByAlias(alias string) DBOption
WithByAcmeAccountId(acmeAccountId uint) DBOption
WithByDnsAccountId(dnsAccountId uint) DBOption
WithByCAID(caID uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error)
GetFirst(opts ...DBOption) (model.WebsiteSSL, error)
GetFirst(opts ...DBOption) (*model.WebsiteSSL, error)
List(opts ...DBOption) ([]model.WebsiteSSL, error)
Create(ctx context.Context, ssl *model.WebsiteSSL) error
Save(ssl model.WebsiteSSL) error
Save(ssl *model.WebsiteSSL) error
DeleteBy(opts ...DBOption) error
}
@ -43,6 +44,12 @@ func (w WebsiteSSLRepo) WithByDnsAccountId(dnsAccountId uint) DBOption {
}
}
func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("ca_id = ?", caID)
}
}
func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) {
var sslList []model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
@ -52,8 +59,8 @@ func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.W
return count, sslList, err
}
func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (model.WebsiteSSL, error) {
var website model.WebsiteSSL
func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) {
var website *model.WebsiteSSL
db := getDb(opts...).Model(&model.WebsiteSSL{})
if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil {
return website, err
@ -74,7 +81,7 @@ func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebsiteSSL) error
return getTx(ctx).Create(ssl).Error
}
func (w WebsiteSSLRepo) Save(ssl model.WebsiteSSL) error {
func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error {
return getDb().Save(&ssl).Error
}

View File

@ -619,7 +619,7 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
if err != nil {
return response.WebsiteHTTPS{}, err
}
res.SSL = websiteSSL
res.SSL = *websiteSSL
res.Enable = true
if website.HttpConfig != "" {
res.HttpConfig = website.HttpConfig
@ -648,7 +648,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
}
var (
res response.WebsiteHTTPS
websiteSSL model.WebsiteSSL
websiteSSL *model.WebsiteSSL
)
res.Enable = req.Enable
res.SSLProtocol = req.SSLProtocol
@ -698,7 +698,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
return nil, err
}
website.WebsiteSSLID = websiteSSL.ID
res.SSL = websiteSSL
res.SSL = *websiteSSL
}
if req.Type == constant.SSLManual {
var (
@ -758,16 +758,16 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
websiteSSL.PrivateKey = privateKey
websiteSSL.Pem = certificate
res.SSL = websiteSSL
res.SSL = *websiteSSL
}
website.Protocol = constant.ProtocolHTTPS
if err := applySSL(website, websiteSSL, req); err != nil {
if err := applySSL(website, *websiteSSL, req); err != nil {
return nil, err
}
website.HttpConfig = req.HttpConfig
if websiteSSL.ID == 0 {
if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil {
if err := websiteSSLRepo.Create(ctx, websiteSSL); err != nil {
return nil, err
}
website.WebsiteSSLID = websiteSSL.ID

View File

@ -149,48 +149,78 @@ func (w WebsiteCAService) GetCA(id uint) (response.WebsiteCADTO, error) {
}
func (w WebsiteCAService) Delete(id uint) error {
ssls, _ := websiteSSLRepo.List(websiteSSLRepo.WithByCAID(id))
if len(ssls) > 0 {
return buserr.New("ErrDeleteCAWithSSL")
}
return websiteCARepo.DeleteBy(commonRepo.WithByID(id))
}
func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
ca, err := websiteCARepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
newSSL := &model.WebsiteSSL{
Provider: constant.SelfSigned,
KeyType: req.KeyType,
PushDir: req.PushDir,
}
if req.PushDir {
if !files.NewFileOp().Stat(req.Dir) {
return buserr.New(constant.ErrLinkPathNotFound)
}
newSSL.Dir = req.Dir
}
var (
domains []string
ips []net.IP
domains []string
ips []net.IP
websiteSSL = &model.WebsiteSSL{}
err error
ca model.WebsiteCA
)
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return err
if req.Renew {
websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
if err != nil {
return err
}
ca, err = websiteCARepo.GetFirst(commonRepo.WithByID(websiteSSL.CaID))
if err != nil {
return err
}
existDomains := []string{websiteSSL.PrimaryDomain}
if websiteSSL.Domains != "" {
existDomains = append(existDomains, strings.Split(websiteSSL.Domains, ",")...)
}
for _, domain := range existDomains {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
}
ips = append(ips, ipAddress)
}
}
if len(domains) > 0 {
newSSL.PrimaryDomain = domains[0]
newSSL.Domains = strings.Join(domains[1:], ",")
} else {
ca, err = websiteCARepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
websiteSSL = &model.WebsiteSSL{
Provider: constant.SelfSigned,
KeyType: req.KeyType,
PushDir: req.PushDir,
CaID: ca.ID,
AutoRenew: req.AutoRenew,
}
if req.PushDir {
if !files.NewFileOp().Stat(req.Dir) {
return buserr.New(constant.ErrLinkPathNotFound)
}
websiteSSL.Dir = req.Dir
}
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return err
} else {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
}
}
}
if len(domains) > 0 {
websiteSSL.PrimaryDomain = domains[0]
websiteSSL.Domains = strings.Join(domains[1:], ",")
}
}
}
@ -208,7 +238,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
}
var rootPrivateKey any
if ssl.KeyType(ca.KeyType) == certcrypto.EC256 || ssl.KeyType(ca.KeyType) == certcrypto.EC384 {
if ssl.KeyType(websiteSSL.KeyType) == certcrypto.EC256 || ssl.KeyType(websiteSSL.KeyType) == certcrypto.EC384 {
rootPrivateKey, err = x509.ParseECPrivateKey(rootPrivateKeyBlock.Bytes)
if err != nil {
return err
@ -219,7 +249,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
return err
}
}
interPrivateKey, interPublicKey, _, err := createPrivateKey(req.KeyType)
interPrivateKey, interPublicKey, _, err := createPrivateKey(websiteSSL.KeyType)
if err != nil {
return err
}
@ -249,7 +279,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
return err
}
_, publicKey, privateKeyBytes, err := createPrivateKey(req.KeyType)
_, publicKey, privateKeyBytes, err := createPrivateKey(websiteSSL.KeyType)
if err != nil {
return err
}
@ -281,21 +311,28 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error {
Bytes: cert.Raw,
}
pemData := pem.EncodeToMemory(certBlock)
newSSL.Pem = string(pemData)
newSSL.PrivateKey = string(privateKeyBytes)
newSSL.ExpireDate = cert.NotAfter
newSSL.StartDate = cert.NotBefore
newSSL.Type = cert.Issuer.CommonName
newSSL.Organization = rootCsr.Subject.Organization[0]
websiteSSL.Pem = string(pemData)
websiteSSL.PrivateKey = string(privateKeyBytes)
websiteSSL.ExpireDate = cert.NotAfter
websiteSSL.StartDate = cert.NotBefore
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = rootCsr.Subject.Organization[0]
if err := websiteSSLRepo.Create(context.Background(), newSSL); err != nil {
return err
if req.Renew {
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
}
} else {
if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil {
return err
}
}
logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", newSSL.PrimaryDomain, newSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
logFile, _ := os.OpenFile(path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags)
logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
saveCertificateFile(*newSSL, logger)
saveCertificateFile(websiteSSL, logger)
return nil
}

View File

@ -11,7 +11,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
@ -35,7 +34,6 @@ type IWebsiteSSLService interface {
GetSSL(id uint) (*response.WebsiteSSLDTO, error)
Search(req request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error)
Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error)
Renew(sslId uint) error
GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error)
GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error)
Delete(ids []uint) error
@ -72,7 +70,7 @@ func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) {
if err != nil {
return nil, err
}
res.WebsiteSSL = websiteSSL
res.WebsiteSSL = *websiteSSL
return &res, nil
}
@ -157,7 +155,7 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var (
err error
websiteSSL model.WebsiteSSL
websiteSSL *model.WebsiteSSL
acmeAccount *model.WebsiteAcmeAccount
dnsAccount *model.WebsiteDnsAccount
)
@ -257,91 +255,17 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
return nil
}
func handleError(websiteSSL model.WebsiteSSL, err error) {
func handleError(websiteSSL *model.WebsiteSSL, err error) {
if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError {
websiteSSL.Status = constant.Error
} else {
websiteSSL.Status = constant.SSLApplyError
}
websiteSSL.Message = err.Error()
legoLogger.Logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "err": err.Error()}))
legoLogger.Logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()}))
_ = websiteSSLRepo.Save(websiteSSL)
}
func (w WebsiteSSLService) Renew(sslId uint) error {
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(sslId))
if err != nil {
return err
}
acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
return err
}
client, err := ssl.NewAcmeClient(acmeAccount)
if err != nil {
return err
}
switch websiteSSL.Provider {
case constant.DNSAccount:
dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID))
if err != nil {
return err
}
if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil {
return err
}
case constant.Http:
appInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
if err := client.UseHTTP(path.Join(constant.AppInstallDir, constant.AppOpenresty, appInstall.Name, "root")); err != nil {
return err
}
case constant.SelfSigned:
}
resource, err := client.RenewSSL(websiteSSL.CertURL)
if err != nil {
return err
}
websiteSSL.PrivateKey = string(resource.PrivateKey)
websiteSSL.Pem = string(resource.Certificate)
websiteSSL.CertURL = resource.CertURL
certBlock, _ := pem.Decode(resource.Certificate)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return err
}
websiteSSL.ExpireDate = cert.NotAfter
websiteSSL.StartDate = cert.NotBefore
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
return err
}
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(sslId))
for _, website := range websites {
if err := createPemFile(website, websiteSSL); err != nil {
global.LOG.Errorf("create website [%s] ssl file failed! err:%s", website.PrimaryDomain, err.Error())
}
}
if len(websites) > 0 {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
return buserr.New(constant.ErrSSLApply)
}
}
return nil
}
func (w WebsiteSSLService) GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error) {
acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(req.AcmeAccountID))
if err != nil {
@ -378,7 +302,7 @@ func (w WebsiteSSLService) GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO
if err != nil {
return res, err
}
res.WebsiteSSL = websiteSSL
res.WebsiteSSL = *websiteSSL
return res, nil
}
@ -391,9 +315,19 @@ func (w WebsiteSSLService) Delete(ids []uint) error {
names = append(names, oldSSL.PrimaryDomain)
}
continue
} else {
_ = websiteSSLRepo.DeleteBy(commonRepo.WithByID(id))
}
sslSetting, _ := settingRepo.Get(settingRepo.WithByKey("SSL"))
if sslSetting.Value == "enable" {
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
idValue, _ := strconv.Atoi(sslID.Value)
if idValue > 0 {
oldSSL, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(uint(idValue)))
if oldSSL.ID > 0 {
return buserr.New("ErrDeleteWithPanelSSL")
}
}
}
_ = websiteSSLRepo.DeleteBy(commonRepo.WithByID(id))
}
if len(names) > 0 {
return buserr.WithName("ErrSSLCannotDelete", strings.Join(names, ","))
@ -491,7 +425,7 @@ func (w WebsiteSSLService) SyncForRestart() error {
if ssl.Status == constant.SSLApply {
ssl.Status = constant.SystemRestart
ssl.Message = "System restart causing interrupt"
_ = websiteSSLRepo.Save(ssl)
_ = websiteSSLRepo.Save(&ssl)
}
}
return nil

View File

@ -750,7 +750,7 @@ func getWebsiteDomains(domains string, defaultPort int, websiteID uint) (domainM
return
}
func saveCertificateFile(websiteSSL model.WebsiteSSL, logger *log.Logger) {
func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
if websiteSSL.PushDir {
fileOp := files.NewFileOp()
var (

View File

@ -1,8 +1,10 @@
package job
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"time"
@ -30,9 +32,25 @@ func (ssl *ssl) Run() {
sub := expireDate.Sub(now)
if sub.Hours() < 720 {
global.LOG.Errorf("Update the SSL certificate for the [%s] domain", s.PrimaryDomain)
if err := sslService.Renew(s.ID); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue
if s.Provider == constant.SelfSigned {
caService := service.NewIWebsiteCAService()
if err := caService.ObtainSSL(request.WebsiteCAObtain{
ID: s.CaID,
SSLID: s.ID,
Renew: true,
Unit: "year",
Time: 1,
}); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue
}
} else {
if err := sslService.ObtainSSL(request.WebsiteSSLApply{
ID: s.ID,
}); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue
}
}
global.LOG.Errorf("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
}

View File

@ -103,6 +103,8 @@ ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}}
ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! '
DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]'
PushDirLog: 'Certificate pushed to directory [{{ .path }}] {{ .status }}'
ErrDeleteCAWithSSL: "There is an issued certificate under the current organization and cannot be deleted"
ErrDeleteWithPanelSSL: "Panel SSL configuration uses this certificate and cannot be deleted"
#mysql
ErrUserIsExist: "The current user already exists. Please enter a new user"

View File

@ -103,6 +103,8 @@ ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} '
ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! '
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]'
PushDirLog: '憑證推送到目錄 [{{ .path }}] {{ .status }}'
ErrDeleteCAWithSSL: "目前機構下存在已簽發證書,無法刪除"
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此證書,無法刪除"
#mysql

View File

@ -103,6 +103,8 @@ ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} '
ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!'
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]'
PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}'
ErrDeleteCAWithSSL: "当前机构下存在已签发证书,无法删除"
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此证书,无法删除"
#mysql
ErrUserIsExist: "当前用户已存在,请重新输入"

View File

@ -27,7 +27,7 @@ var AddWebsiteCA = &gormigrate.Migration{
}
var UpdateWebsiteSSL = &gormigrate.Migration{
ID: "20231126-update-website-ssl",
ID: "20231127-update-website-ssl",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil {
return err

View File

@ -16,5 +16,6 @@ func (a *WebsiteDnsAccountRouter) InitWebsiteCARouter(Router *gin.RouterGroup) {
groupRouter.POST("", baseApi.CreateWebsiteCA)
groupRouter.POST("/del", baseApi.DeleteWebsiteCA)
groupRouter.POST("/obtain", baseApi.ObtainWebsiteCA)
groupRouter.POST("/renew", baseApi.RenewWebsiteCA)
}
}

View File

@ -16,7 +16,6 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.POST("/search", baseApi.PageWebsiteSSL)
groupRouter.POST("/renew", baseApi.RenewWebsiteSSL)
groupRouter.POST("", baseApi.CreateWebsiteSSL)
groupRouter.POST("/resolve", baseApi.GetDNSResolve)
groupRouter.POST("/del", baseApi.DeleteWebsiteSSL)

View File

@ -11119,7 +11119,7 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "签 SSL 证书",
"description": "签 SSL 证书",
"consumes": [
"application/json"
],
@ -12930,57 +12930,6 @@ const docTemplate = `{
}
}
},
"/websites/ssl/renew": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "重置网站 ssl",
"consumes": [
"application/json"
],
"tags": [
"Website SSL"
],
"summary": "Reset website ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteSSLRenew"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "website_ssls",
"input_column": "id",
"input_value": "SSLId",
"isList": false,
"output_column": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"SSLId"
],
"formatEN": "Renew ssl [domain]",
"formatZH": "重置 ssl [domain]",
"paramKeys": []
}
}
},
"/websites/ssl/resolve": {
"post": {
"security": [
@ -17517,6 +17466,9 @@ const docTemplate = `{
"autoRenew": {
"type": "boolean"
},
"caId": {
"type": "integer"
},
"certURL": {
"type": "string"
},
@ -18979,6 +18931,9 @@ const docTemplate = `{
"unit"
],
"properties": {
"autoRenew": {
"type": "boolean"
},
"dir": {
"type": "string"
},
@ -19002,6 +18957,12 @@ const docTemplate = `{
"pushDir": {
"type": "boolean"
},
"renew": {
"type": "boolean"
},
"sslID": {
"type": "integer"
},
"time": {
"type": "integer"
},
@ -19537,17 +19498,6 @@ const docTemplate = `{
}
}
},
"request.WebsiteSSLRenew": {
"type": "object",
"required": [
"SSLId"
],
"properties": {
"SSLId": {
"type": "integer"
}
}
},
"request.WebsiteSSLSearch": {
"type": "object",
"required": [

View File

@ -11112,7 +11112,7 @@
"ApiKeyAuth": []
}
],
"description": "签 SSL 证书",
"description": "签 SSL 证书",
"consumes": [
"application/json"
],
@ -12923,57 +12923,6 @@
}
}
},
"/websites/ssl/renew": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "重置网站 ssl",
"consumes": [
"application/json"
],
"tags": [
"Website SSL"
],
"summary": "Reset website ssl",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteSSLRenew"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "website_ssls",
"input_column": "id",
"input_value": "SSLId",
"isList": false,
"output_column": "primary_domain",
"output_value": "domain"
}
],
"bodyKeys": [
"SSLId"
],
"formatEN": "Renew ssl [domain]",
"formatZH": "重置 ssl [domain]",
"paramKeys": []
}
}
},
"/websites/ssl/resolve": {
"post": {
"security": [
@ -17510,6 +17459,9 @@
"autoRenew": {
"type": "boolean"
},
"caId": {
"type": "integer"
},
"certURL": {
"type": "string"
},
@ -18972,6 +18924,9 @@
"unit"
],
"properties": {
"autoRenew": {
"type": "boolean"
},
"dir": {
"type": "string"
},
@ -18995,6 +18950,12 @@
"pushDir": {
"type": "boolean"
},
"renew": {
"type": "boolean"
},
"sslID": {
"type": "integer"
},
"time": {
"type": "integer"
},
@ -19530,17 +19491,6 @@
}
}
},
"request.WebsiteSSLRenew": {
"type": "object",
"required": [
"SSLId"
],
"properties": {
"SSLId": {
"type": "integer"
}
}
},
"request.WebsiteSSLSearch": {
"type": "object",
"required": [

View File

@ -2792,6 +2792,8 @@ definitions:
type: integer
autoRenew:
type: boolean
caId:
type: integer
certURL:
type: string
createdAt:
@ -3770,6 +3772,8 @@ definitions:
type: object
request.WebsiteCAObtain:
properties:
autoRenew:
type: boolean
dir:
type: string
domains:
@ -3787,6 +3791,10 @@ definitions:
type: string
pushDir:
type: boolean
renew:
type: boolean
sslID:
type: integer
time:
type: integer
unit:
@ -4152,13 +4160,6 @@ definitions:
- primaryDomain
- provider
type: object
request.WebsiteSSLRenew:
properties:
SSLId:
type: integer
required:
- SSLId
type: object
request.WebsiteSSLSearch:
properties:
acmeAccountID:
@ -11773,7 +11774,7 @@ paths:
post:
consumes:
- application/json
description: 签 SSL 证书
description: 签 SSL 证书
parameters:
- description: request
in: body
@ -12904,39 +12905,6 @@ paths:
formatEN: apply ssl [domain]
formatZH: 申请证书 [domain]
paramKeys: []
/websites/ssl/renew:
post:
consumes:
- application/json
description: 重置网站 ssl
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteSSLRenew'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Reset website ssl
tags:
- Website SSL
x-panel-log:
BeforeFunctions:
- db: website_ssls
input_column: id
input_value: SSLId
isList: false
output_column: primary_domain
output_value: domain
bodyKeys:
- SSLId
formatEN: Renew ssl [domain]
formatZH: 重置 ssl [domain]
paramKeys: []
/websites/ssl/resolve:
post:
consumes:

View File

@ -165,10 +165,10 @@ export namespace Website {
autoRenew: boolean;
acmeAccountId?: number;
status: string;
domains: string;
}
export interface SSLDTO extends SSL {
domains: string;
logPath: string;
}
@ -485,4 +485,8 @@ export namespace Website {
pushDir: boolean;
dir: string;
}
export interface RenewSSLByCA {
SSLID: number;
}
}

View File

@ -124,10 +124,6 @@ export const ObtainSSL = (req: Website.SSLObtain) => {
return http.post<any>(`/websites/ssl/obtain`, req);
};
export const RenewSSL = (req: Website.SSLRenew) => {
return http.post<any>(`/websites/ssl/renew`, req, TimeoutEnum.T_10M);
};
export const UpdateSSL = (req: Website.SSLUpdate) => {
return http.post<any>(`/websites/ssl/update`, req);
};
@ -263,3 +259,7 @@ export const ObtainSSLByCA = (req: Website.SSLObtainByCA) => {
export const DeleteCA = (req: Website.DelReq) => {
return http.post<any>(`/websites/ca/del`, req);
};
export const RenewSSLByCA = (req: Website.RenewSSLByCA) => {
return http.post<any>(`/websites/ca/renew`, req);
};

View File

@ -37,6 +37,7 @@ interface LogProps {
type: string;
style: string;
name: string;
tail: boolean;
}
const open = ref(false);

View File

@ -46,6 +46,7 @@ interface LogProps {
id?: number;
type: string;
name?: string;
tail?: boolean;
}
const props = defineProps({
@ -55,6 +56,7 @@ const props = defineProps({
id: 0,
type: '',
name: '',
tail: false,
}),
},
style: {
@ -224,9 +226,16 @@ function isScrolledToBottom(element: HTMLElement): boolean {
}
const init = () => {
tailLog.value = false;
if (props.config.tail) {
tailLog.value = props.config.tail;
} else {
tailLog.value = false;
}
if (tailLog.value) {
changeTail(false);
}
getContent();
nextTick(() => {
if (scrollerElement.value) {
scrollerElement.value.addEventListener('scroll', function () {

View File

@ -1809,12 +1809,12 @@ const message = {
key: 'Private Key',
startDate: 'Effective Time',
organization: 'issuing organization',
renewConfirm: 'Are you sure to renew? ',
renewConfirm: 'Are you sure you want to apply for a certificate for domain name {0}? ',
autoRenew: 'Automatic renewal',
autoRenewHelper: 'Automatic renewal 7 days from the expiration time',
renewSuccess: 'Renewal succeeded',
autoRenewHelper: 'Automatically renew 30 days before expiration',
renewSuccess: 'Renewal successful',
renewWebsite:
'This certificate has been associated with the following websites, and the renewal will be applied to these websites simultaneously',
'This certificate has been associated with the following websites, and the application will be applied to these websites simultaneously',
createAcme: 'Create Account',
acmeHelper: 'Acme account is used to apply for free certificates',
upload: 'Upload Certificate',

View File

@ -1698,11 +1698,11 @@ const message = {
key: '私鑰',
startDate: '生效時間',
organization: '簽發機構',
renewConfirm: '是否確定續簽',
renewConfirm: '是否確定給網域名稱 {0} 申請證書 ',
autoRenew: '自動續簽',
autoRenewHelper: '距離到期時間7天自動續簽',
autoRenewHelper: '距離到期時間30天自動續約',
renewSuccess: '續簽成功',
renewWebsite: '該證書已經和以下網站關聯續簽會同步應用到這些網站',
renewWebsite: '該證書已經和以下網站關聯申請會同步應用到這些網站',
createAcme: '創建賬戶',
acmeHelper: 'Acme 賬戶用於申請免費證書',
upload: '上傳證書',

View File

@ -1698,11 +1698,11 @@ const message = {
key: '私钥',
startDate: '生效时间',
organization: '签发机构',
renewConfirm: '是否确定续签',
renewConfirm: '是否确定给域名 {0} 申请证书',
autoRenew: '自动续签',
autoRenewHelper: '距离到期时间7天自动续签',
autoRenewHelper: '距离到期时间30天自动续签',
renewSuccess: '续签成功',
renewWebsite: '该证书已经和以下网站关联续签会同步应用到这些网站',
renewWebsite: '该证书已经和以下网站关联申请会同步应用到这些网站',
createAcme: '创建账户',
acmeHelper: 'Acme 账户用于申请免费证书',
upload: '上传证书',

View File

@ -47,7 +47,7 @@ const open = ref(false);
const loading = ref(false);
const dnsResolve = ref<Website.DNSResolve[]>([]);
const sslID = ref(0);
const em = defineEmits(['close']);
const em = defineEmits(['close', 'submit']);
const handleClose = () => {
open.value = false;
em('close', false);
@ -83,6 +83,7 @@ const submit = () => {
.then(() => {
MsgSuccess(i18n.global.t('ssl.applyStart'));
handleClose();
em('submit', sslID.value);
})
.finally(() => {});
};

View File

@ -37,6 +37,9 @@
</template>
</el-input>
</el-form-item>
<el-form-item :label="''" prop="autoRenew">
<el-checkbox v-model="obtain.autoRenew" :label="$t('ssl.autoRenew')" />
</el-form-item>
<el-form-item :label="''" prop="pushDir">
<el-checkbox v-model="obtain.pushDir" :label="$t('ssl.pushDir')" />
</el-form-item>
@ -93,6 +96,7 @@ const initData = () => ({
unit: 'day',
pushDir: false,
dir: '',
autoRenew: true,
});
const obtain = ref(initData());

View File

@ -96,7 +96,9 @@
</el-table-column>
<el-table-column :label="$t('website.log')" width="100px">
<template #default="{ row }">
<el-button @click="openLog(row)" link type="primary">{{ $t('website.check') }}</el-button>
<el-button @click="openLog(row.id)" link type="primary">
{{ $t('website.check') }}
</el-button>
</template>
</el-table-column>
<el-table-column
@ -108,11 +110,7 @@
<el-table-column :label="$t('ssl.autoRenew')" fix width="100px">
<template #default="{ row }">
<el-switch
:disabled="
row.provider === 'dnsManual' ||
row.provider === 'manual' ||
row.provider === 'selfSigned'
"
:disabled="row.provider === 'dnsManual' || row.provider === 'manual'"
v-model="row.autoRenew"
@change="updateConfig(row)"
/>
@ -138,10 +136,11 @@
<Create ref="sslCreateRef" @close="search()"></Create>
<Detail ref="detailRef"></Detail>
<SSLUpload ref="sslUploadRef" @close="search()"></SSLUpload>
<Apply ref="applyRef" @search="search" />
<Apply ref="applyRef" @search="search" @submit="openLog" />
<OpDialog ref="opRef" @search="search" @cancel="search" />
<Log ref="logRef" @close="search()" />
<CA ref="caRef" @close="search()" />
<Obtain ref="obtainRef" @close="search()" @submit="openLog" />
</LayoutContent>
</div>
</template>
@ -149,7 +148,7 @@
<script lang="ts" setup>
import { onMounted, reactive, ref, computed } from 'vue';
import OpDialog from '@/components/del-dialog/index.vue';
import { DeleteSSL, ObtainSSL, SearchSSL, UpdateSSL } from '@/api/modules/website';
import { DeleteSSL, SearchSSL, UpdateSSL } from '@/api/modules/website';
import DnsAccount from './dns-account/index.vue';
import AcmeAccount from './acme-account/index.vue';
import CA from './ca/index.vue';
@ -163,6 +162,7 @@ import { GlobalStore } from '@/store';
import SSLUpload from './upload/index.vue';
import Apply from './apply/index.vue';
import Log from '@/components/log-dialog/index.vue';
import Obtain from './obtain/index.vue';
const globalStore = GlobalStore();
const paginationConfig = reactive({
@ -183,6 +183,7 @@ const applyRef = ref();
const logRef = ref();
const caRef = ref();
let selects = ref<any>([]);
const obtainRef = ref();
const routerButton = [
{
@ -204,7 +205,7 @@ const buttons = [
{
label: i18n.global.t('ssl.apply'),
disabled: function (row: Website.SSLDTO) {
return row.status === 'applying' || row.provider === 'manual' || row.provider === 'selfSigned';
return row.status === 'applying' || row.provider === 'manual';
},
click: function (row: Website.SSLDTO) {
if (row.provider === 'dnsManual') {
@ -268,23 +269,16 @@ const openUpload = () => {
const openDetail = (id: number) => {
detailRef.value.acceptParams(id);
};
const openLog = (row: Website.SSLDTO) => {
logRef.value.acceptParams({ id: row.id, type: 'ssl' });
const openLog = (id: number) => {
logRef.value.acceptParams({ id: id, type: 'ssl', tail: true });
};
const openCA = () => {
caRef.value.acceptParams();
};
const applySSL = (row: Website.SSLDTO) => {
loading.value = true;
ObtainSSL({ ID: row.id })
.then(() => {
MsgSuccess(i18n.global.t('ssl.applyStart'));
search();
})
.finally(() => {
loading.value = false;
});
obtainRef.value.acceptParams({ ssl: row });
};
const deleteSSL = async (row: any) => {

View File

@ -1,26 +1,26 @@
<template>
<el-dialog
v-model="open"
:title="$t('website.renewSSL')"
:title="$t('ssl.apply')"
:destroy-on-close="true"
:close-on-click-modal="false"
width="30%"
:before-close="handleClose"
>
<div style="text-align: center" v-loading="loading">
<div v-if="websites.length > 0">
<div class="text-center" v-loading="loading">
<div v-if="ssl.websites && ssl.websites.length > 0">
<span>{{ $t('ssl.renewWebsite') }}</span>
<div>
<br />
<span>
<span v-for="(website, index) in websites" :key="index">
<span v-for="(website, index) in ssl.websites" :key="index">
<el-tag>{{ website.primaryDomain }}</el-tag>
</span>
</span>
</div>
<br />
</div>
<span>{{ $t('ssl.renewConfirm') }}</span>
<span>{{ $t('ssl.renewConfirm', [ssl.primaryDomain]) }}</span>
</div>
<template #footer>
<span class="dialog-footer">
@ -35,44 +35,42 @@
<script lang="ts" setup>
import { Website } from '@/api/interface/website';
import { RenewSSL } from '@/api/modules/website';
import { ObtainSSL, RenewSSLByCA } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { reactive, ref } from 'vue';
import { ref } from 'vue';
interface RenewProps {
id: number;
websites: Website.Website[];
ssl: Website.SSL;
}
let open = ref(false);
let loading = ref(false);
let renewReq = reactive({
SSLId: 0,
});
const em = defineEmits(['close']);
const open = ref(false);
const loading = ref(false);
const em = defineEmits(['close', 'submit']);
const handleClose = () => {
open.value = false;
em('close', false);
};
const websites = ref([]);
const ssl = ref();
const acceptParams = async (props: RenewProps) => {
renewReq.SSLId = Number(props.id);
websites.value = props.websites;
ssl.value = props.ssl;
open.value = true;
};
const submit = () => {
const submit = async () => {
loading.value = true;
RenewSSL(renewReq)
.then(() => {
handleClose();
MsgSuccess(i18n.global.t('ssl.renewSuccess'));
})
.finally(() => {
loading.value = false;
});
try {
if (ssl.value.provider == 'selfSigned') {
await RenewSSLByCA({ SSLID: ssl.value.id });
} else {
await ObtainSSL({ ID: ssl.value.id });
}
handleClose();
MsgSuccess(i18n.global.t('ssl.applyStart'));
loading.value = false;
em('submit', ssl.value.id);
} catch (error) {}
};
defineExpose({