diff --git a/backend/app/api/v1/website_ca.go b/backend/app/api/v1/website_ca.go index 3dabf736a..e3b2ee301 100644 --- a/backend/app/api/v1/website_ca.go +++ b/backend/app/api/v1/website_ca.go @@ -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) +} diff --git a/backend/app/api/v1/website_ssl.go b/backend/app/api/v1/website_ssl.go index 177555dbf..324d575da 100644 --- a/backend/app/api/v1/website_ssl.go +++ b/backend/app/api/v1/website_ssl.go @@ -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 申请证书 diff --git a/backend/app/dto/request/website_ssl.go b/backend/app/dto/request/website_ssl.go index 3a1535fb0..3a36d1326 100644 --- a/backend/app/dto/request/website_ssl.go +++ b/backend/app/dto/request/website_ssl.go @@ -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"` } diff --git a/backend/app/model/website_ssl.go b/backend/app/model/website_ssl.go index 026121ed2..3e5ce0954 100644 --- a/backend/app/model/website_ssl.go +++ b/backend/app/model/website_ssl.go @@ -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"` diff --git a/backend/app/repo/website_ssl.go b/backend/app/repo/website_ssl.go index 496d2b3fd..7d875a140 100644 --- a/backend/app/repo/website_ssl.go +++ b/backend/app/repo/website_ssl.go @@ -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 } diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 43a167a19..7616db638 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -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 diff --git a/backend/app/service/website_ca.go b/backend/app/service/website_ca.go index 8a7577599..c71b6dcad 100644 --- a/backend/app/service/website_ca.go +++ b/backend/app/service/website_ca.go @@ -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 } diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index 1aba60e61..837b08dc9 100644 --- a/backend/app/service/website_ssl.go +++ b/backend/app/service/website_ssl.go @@ -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 diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index f679a15c3..b46f887b6 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -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 ( diff --git a/backend/cron/job/ssl.go b/backend/cron/job/ssl.go index a92979c9b..6c9efefde 100644 --- a/backend/cron/job/ssl.go +++ b/backend/cron/job/ssl.go @@ -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) } diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 9f2c19b3e..c706d6951 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -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" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 5d9c591ff..7fede492f 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -103,6 +103,8 @@ ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} ' ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! ! ' DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]' PushDirLog: '憑證推送到目錄 [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: "目前機構下存在已簽發證書,無法刪除" +ErrDeleteWithPanelSSL: "面板 SSL 配置使用此證書,無法刪除" #mysql diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index a65b08d00..be94628cd 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -103,6 +103,8 @@ ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} ' ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!' DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]' PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: "当前机构下存在已签发证书,无法删除" +ErrDeleteWithPanelSSL: "面板 SSL 配置使用此证书,无法删除" #mysql ErrUserIsExist: "当前用户已存在,请重新输入" diff --git a/backend/init/migration/migrations/v_1_9.go b/backend/init/migration/migrations/v_1_9.go index 4a8427734..b2af274f8 100644 --- a/backend/init/migration/migrations/v_1_9.go +++ b/backend/init/migration/migrations/v_1_9.go @@ -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 diff --git a/backend/router/ro_website_ca.go b/backend/router/ro_website_ca.go index a5df56baa..965b31ea3 100644 --- a/backend/router/ro_website_ca.go +++ b/backend/router/ro_website_ca.go @@ -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) } } diff --git a/backend/router/ro_website_ssl.go b/backend/router/ro_website_ssl.go index d34c74f58..736e057ed 100644 --- a/backend/router/ro_website_ssl.go +++ b/backend/router/ro_website_ssl.go @@ -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) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index ea118b48a..a4ade107c 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -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": [ diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index f1d598250..f5cde54ad 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -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": [ diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 5a4a1d49d..e520ec657 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -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: diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 5f41ece56..82f8a0c52 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -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; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index ee5a29f2d..0a273bf42 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -124,10 +124,6 @@ export const ObtainSSL = (req: Website.SSLObtain) => { return http.post(`/websites/ssl/obtain`, req); }; -export const RenewSSL = (req: Website.SSLRenew) => { - return http.post(`/websites/ssl/renew`, req, TimeoutEnum.T_10M); -}; - export const UpdateSSL = (req: Website.SSLUpdate) => { return http.post(`/websites/ssl/update`, req); }; @@ -263,3 +259,7 @@ export const ObtainSSLByCA = (req: Website.SSLObtainByCA) => { export const DeleteCA = (req: Website.DelReq) => { return http.post(`/websites/ca/del`, req); }; + +export const RenewSSLByCA = (req: Website.RenewSSLByCA) => { + return http.post(`/websites/ca/renew`, req); +}; diff --git a/frontend/src/components/log-dialog/index.vue b/frontend/src/components/log-dialog/index.vue index 511e5cb67..097023d2d 100644 --- a/frontend/src/components/log-dialog/index.vue +++ b/frontend/src/components/log-dialog/index.vue @@ -37,6 +37,7 @@ interface LogProps { type: string; style: string; name: string; + tail: boolean; } const open = ref(false); diff --git a/frontend/src/components/log-file/index.vue b/frontend/src/components/log-file/index.vue index 2c1015d18..a849d227f 100644 --- a/frontend/src/components/log-file/index.vue +++ b/frontend/src/components/log-file/index.vue @@ -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 () { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 02d7beb17..b518e6ad9 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index b234743ad..a86fb50e1 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1698,11 +1698,11 @@ const message = { key: '私鑰', startDate: '生效時間', organization: '簽發機構', - renewConfirm: '是否確定續簽?', + renewConfirm: '是否確定給網域名稱 {0} 申請證書? ', autoRenew: '自動續簽', - autoRenewHelper: '距離到期時間7天自動續簽', + autoRenewHelper: '距離到期時間30天自動續約', renewSuccess: '續簽成功', - renewWebsite: '該證書已經和以下網站關聯,續簽會同步應用到這些網站', + renewWebsite: '該證書已經和以下網站關聯,申請會同步應用到這些網站', createAcme: '創建賬戶', acmeHelper: 'Acme 賬戶用於申請免費證書', upload: '上傳證書', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 3c69fd3fa..335ff60a7 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1698,11 +1698,11 @@ const message = { key: '私钥', startDate: '生效时间', organization: '签发机构', - renewConfirm: '是否确定续签?', + renewConfirm: '是否确定给域名 {0} 申请证书?', autoRenew: '自动续签', - autoRenewHelper: '距离到期时间7天自动续签', + autoRenewHelper: '距离到期时间30天自动续签', renewSuccess: '续签成功', - renewWebsite: '该证书已经和以下网站关联,续签会同步应用到这些网站', + renewWebsite: '该证书已经和以下网站关联,申请会同步应用到这些网站', createAcme: '创建账户', acmeHelper: 'Acme 账户用于申请免费证书', upload: '上传证书', diff --git a/frontend/src/views/website/ssl/apply/index.vue b/frontend/src/views/website/ssl/apply/index.vue index 72d28c100..18b04672f 100644 --- a/frontend/src/views/website/ssl/apply/index.vue +++ b/frontend/src/views/website/ssl/apply/index.vue @@ -47,7 +47,7 @@ const open = ref(false); const loading = ref(false); const dnsResolve = ref([]); 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(() => {}); }; diff --git a/frontend/src/views/website/ssl/ca/obtain/index.vue b/frontend/src/views/website/ssl/ca/obtain/index.vue index 8d7000e6d..2f256d347 100644 --- a/frontend/src/views/website/ssl/ca/obtain/index.vue +++ b/frontend/src/views/website/ssl/ca/obtain/index.vue @@ -37,6 +37,9 @@ + + + @@ -93,6 +96,7 @@ const initData = () => ({ unit: 'day', pushDir: false, dir: '', + autoRenew: true, }); const obtain = ref(initData()); diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue index 75c5c30f4..fd16cf45b 100644 --- a/frontend/src/views/website/ssl/index.vue +++ b/frontend/src/views/website/ssl/index.vue @@ -96,7 +96,9 @@ @@ -149,7 +148,7 @@