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

pull/3087/head
zhengkunwang 1 year ago committed by GitHub
parent 807a5071a7
commit e41661e8d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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)
}

@ -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 申请证书

@ -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"`
}

@ -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"`

@ -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
}

@ -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

@ -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)
ips = append(ips, ipAddress)
}
}
} 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 {
ips = append(ips, ipAddress)
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
}
}
}
}
if len(domains) > 0 {
newSSL.PrimaryDomain = domains[0]
newSSL.Domains = strings.Join(domains[1:], ",")
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
}

@ -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

@ -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 (

@ -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)
}

@ -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"

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

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

@ -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

@ -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)
}
}

@ -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)

@ -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": [

@ -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": [

@ -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:

@ -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;
}
}

@ -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);
};

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

@ -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 () {

@ -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',

@ -1698,11 +1698,11 @@ const message = {
key: '',
startDate: '',
organization: '',
renewConfirm: '',
renewConfirm: ' {0} ',
autoRenew: '',
autoRenewHelper: '7',
autoRenewHelper: '30',
renewSuccess: '',
renewWebsite: '',
renewWebsite: '',
createAcme: '',
acmeHelper: 'Acme ',
upload: '',

@ -1698,11 +1698,11 @@ const message = {
key: '',
startDate: '',
organization: '',
renewConfirm: '',
renewConfirm: ' {0} ',
autoRenew: '',
autoRenewHelper: '7',
autoRenewHelper: '30',
renewSuccess: '',
renewWebsite: '',
renewWebsite: '',
createAcme: '',
acmeHelper: 'Acme ',
upload: '',

@ -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(() => {});
};

@ -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());

@ -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) => {

@ -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({
Loading…
Cancel
Save