mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 增加系统 ssl 设置功能 (#780)
This commit is contained in:
parent
edf07be281
commit
34e84081e3
@ -119,6 +119,48 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags System Setting
|
||||||
|
// @Summary Update system ssl
|
||||||
|
// @Description 修改系统 ssl 登录
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SSLUpdate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /settings/ssl/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[ssl],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"}
|
||||||
|
func (b *BaseApi) UpdateSSL(c *gin.Context) {
|
||||||
|
var req dto.SSLUpdate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := settingService.UpdateSSL(c, req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags System Setting
|
||||||
|
// @Summary Load system cert info
|
||||||
|
// @Description 获取证书信息
|
||||||
|
// @Success 200 {object} dto.SettingInfo
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /settings/ssl/info [get]
|
||||||
|
func (b *BaseApi) LoadFromCert(c *gin.Context) {
|
||||||
|
info, err := settingService.LoadFromCert()
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, info)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags System Setting
|
// @Tags System Setting
|
||||||
// @Summary Update system port
|
// @Summary Update system port
|
||||||
// @Description 更新系统端口
|
// @Description 更新系统端口
|
||||||
|
@ -17,6 +17,8 @@ type SettingInfo struct {
|
|||||||
|
|
||||||
ServerPort string `json:"serverPort"`
|
ServerPort string `json:"serverPort"`
|
||||||
SecurityEntranceStatus string `json:"securityEntranceStatus"`
|
SecurityEntranceStatus string `json:"securityEntranceStatus"`
|
||||||
|
SSL string `json:"ssl"`
|
||||||
|
SSLType string `json:"sslType"`
|
||||||
SecurityEntrance string `json:"securityEntrance"`
|
SecurityEntrance string `json:"securityEntrance"`
|
||||||
ExpirationDays string `json:"expirationDays"`
|
ExpirationDays string `json:"expirationDays"`
|
||||||
ExpirationTime string `json:"expirationTime"`
|
ExpirationTime string `json:"expirationTime"`
|
||||||
@ -40,6 +42,23 @@ type SettingUpdate struct {
|
|||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSLUpdate struct {
|
||||||
|
SSLType string `json:"sslType"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
|
||||||
|
Cert string `json:"cert"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
SSLID uint `json:"sslID"`
|
||||||
|
}
|
||||||
|
type SSLInfo struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Timeout string `json:"timeout"`
|
||||||
|
RootPath string `json:"rootPath"`
|
||||||
|
Cert string `json:"cert"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
SSLID uint `json:"sslID"`
|
||||||
|
}
|
||||||
|
|
||||||
type PasswordUpdate struct {
|
type PasswordUpdate struct {
|
||||||
OldPassword string `json:"oldPassword" validate:"required"`
|
OldPassword string `json:"oldPassword" validate:"required"`
|
||||||
NewPassword string `json:"newPassword" validate:"required"`
|
NewPassword string `json:"newPassword" validate:"required"`
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
@ -12,7 +18,9 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SettingService struct{}
|
type SettingService struct{}
|
||||||
@ -23,6 +31,8 @@ type ISettingService interface {
|
|||||||
UpdateEntrance(value string) error
|
UpdateEntrance(value string) error
|
||||||
UpdatePassword(c *gin.Context, old, new string) error
|
UpdatePassword(c *gin.Context, old, new string) error
|
||||||
UpdatePort(port uint) error
|
UpdatePort(port uint) error
|
||||||
|
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
|
||||||
|
LoadFromCert() (*dto.SSLInfo, error)
|
||||||
HandlePasswordExpired(c *gin.Context, old, new string) error
|
HandlePasswordExpired(c *gin.Context, old, new string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +121,126 @@ func (u *SettingService) UpdatePort(port uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||||
|
if req.SSL == "disable" {
|
||||||
|
if err := settingRepo.Update("SSL", "disable"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := settingRepo.Update("SSLType", "self"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = os.Remove(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
|
||||||
|
_ = os.Remove(global.CONF.System.BaseDir + "/1panel/secret/server.key")
|
||||||
|
go func() {
|
||||||
|
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret"); err != nil && os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(global.CONF.System.BaseDir+"/1panel/secret", os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := settingRepo.Update("SSLType", req.SSLType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if req.SSLType == "self" {
|
||||||
|
if len(req.Domain) == 0 {
|
||||||
|
return fmt.Errorf("load domain failed")
|
||||||
|
}
|
||||||
|
if err := ssl.GenerateSSL(req.Domain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.SSLType == "select" {
|
||||||
|
sslInfo, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Cert = sslInfo.Pem
|
||||||
|
req.Key = sslInfo.PrivateKey
|
||||||
|
req.SSLType = "import"
|
||||||
|
if err := settingRepo.Update("SSLID", strconv.Itoa(int(req.SSLID))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.SSLType == "import" {
|
||||||
|
cert, err := os.OpenFile("/opt/1panel/secret/server.crt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cert.Close()
|
||||||
|
if _, err := cert.WriteString(req.Cert); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key, err := os.OpenFile("/opt/1panel/secret/server.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := key.WriteString(req.Key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
}
|
||||||
|
if err := checkCertValid(req.Domain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := settingRepo.Update("SSL", req.SSL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
|
||||||
|
ssl, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ssl.Value == "disable" {
|
||||||
|
return &dto.SSLInfo{}, nil
|
||||||
|
}
|
||||||
|
sslType, err := settingRepo.Get(settingRepo.WithByKey("SSLType"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := loadInfoFromCert()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch sslType.Value {
|
||||||
|
case "import":
|
||||||
|
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.crt"); err != nil {
|
||||||
|
return nil, fmt.Errorf("load server.crt file failed, err: %v", err)
|
||||||
|
}
|
||||||
|
certFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
|
||||||
|
data.Cert = string(certFile)
|
||||||
|
|
||||||
|
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.key"); err != nil {
|
||||||
|
return nil, fmt.Errorf("load server.key file failed, err: %v", err)
|
||||||
|
}
|
||||||
|
keyFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
|
||||||
|
data.Key = string(keyFile)
|
||||||
|
case "select":
|
||||||
|
sslID, err := settingRepo.Get(settingRepo.WithByKey("SSLID"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id, _ := strconv.Atoi(sslID.Value)
|
||||||
|
data.SSLID = uint(id)
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error {
|
func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error {
|
||||||
setting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
setting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -149,3 +279,71 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
|
|||||||
_ = global.SESSION.Clean()
|
_ = global.SESSION.Clean()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadInfoFromCert() (*dto.SSLInfo, error) {
|
||||||
|
var info dto.SSLInfo
|
||||||
|
certFile := global.CONF.System.BaseDir + "/1panel/secret/server.crt"
|
||||||
|
if _, err := os.Stat(certFile); err != nil {
|
||||||
|
return &info, err
|
||||||
|
}
|
||||||
|
certData, err := os.ReadFile(certFile)
|
||||||
|
if err != nil {
|
||||||
|
return &info, err
|
||||||
|
}
|
||||||
|
certBlock, _ := pem.Decode(certData)
|
||||||
|
if certBlock == nil {
|
||||||
|
return &info, err
|
||||||
|
}
|
||||||
|
certObj, err := x509.ParseCertificate(certBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return &info, err
|
||||||
|
}
|
||||||
|
var domains []string
|
||||||
|
if len(certObj.IPAddresses) != 0 {
|
||||||
|
for _, ip := range certObj.IPAddresses {
|
||||||
|
domains = append(domains, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(certObj.DNSNames) != 0 {
|
||||||
|
domains = append(domains, certObj.DNSNames...)
|
||||||
|
}
|
||||||
|
return &dto.SSLInfo{
|
||||||
|
Domain: strings.Join(domains, ","),
|
||||||
|
Timeout: certObj.NotAfter.Format("2006-01-02 15:04:05"),
|
||||||
|
RootPath: global.CONF.System.BaseDir + "/1panel/secret/server.crt",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCertValid(domain string) error {
|
||||||
|
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = tls.X509KeyPair(certificate, key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
certObj, err := x509.ParseCertificate(certificate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certObj.IPAddresses) != 0 {
|
||||||
|
for _, ip := range certObj.IPAddresses {
|
||||||
|
if ip.String() == domain {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(certObj.DNSNames) != 0 {
|
||||||
|
for _, ip := range certObj.DNSNames {
|
||||||
|
if ip == domain {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("The domain name or ip address does not match")
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package configs
|
|||||||
|
|
||||||
type System struct {
|
type System struct {
|
||||||
Port string `mapstructure:"port"`
|
Port string `mapstructure:"port"`
|
||||||
|
SSL string `mapstructure:"ssl"`
|
||||||
DbFile string `mapstructure:"db_file"`
|
DbFile string `mapstructure:"db_file"`
|
||||||
DbPath string `mapstructure:"db_path"`
|
DbPath string `mapstructure:"db_path"`
|
||||||
LogPath string `mapstructure:"log_path"`
|
LogPath string `mapstructure:"log_path"`
|
||||||
|
@ -17,6 +17,11 @@ func Init() {
|
|||||||
global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err)
|
global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err)
|
||||||
}
|
}
|
||||||
global.CONF.System.EncryptKey = enptrySetting.Value
|
global.CONF.System.EncryptKey = enptrySetting.Value
|
||||||
|
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
|
||||||
|
}
|
||||||
|
global.CONF.System.SSL = sslSetting.Value
|
||||||
|
|
||||||
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
|
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
|
||||||
_ = settingRepo.Create("SystemStatus", "Free")
|
_ = settingRepo.Create("SystemStatus", "Free")
|
||||||
|
@ -25,7 +25,7 @@ func Init() {
|
|||||||
migrations.UpdateTableApp,
|
migrations.UpdateTableApp,
|
||||||
migrations.UpdateTableHost,
|
migrations.UpdateTableHost,
|
||||||
migrations.UpdateTableWebsite,
|
migrations.UpdateTableWebsite,
|
||||||
migrations.AddEntranceStatus,
|
migrations.AddEntranceAndSSL,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -288,8 +288,8 @@ var UpdateTableWebsite = &gormigrate.Migration{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var AddEntranceStatus = &gormigrate.Migration{
|
var AddEntranceAndSSL = &gormigrate.Migration{
|
||||||
ID: "20230414-add-entrance-status",
|
ID: "20230414-add-entrance-and-ssl",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
if err := tx.Create(&model.Setting{Key: "SecurityEntranceStatus", Value: "disable"}).Error; err != nil {
|
if err := tx.Create(&model.Setting{Key: "SecurityEntranceStatus", Value: "disable"}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
@ -297,6 +297,15 @@ var AddEntranceStatus = &gormigrate.Migration{
|
|||||||
if err := tx.Model(&model.Setting{}).Where("key = ?", "SecurityEntrance").Updates(map[string]interface{}{"value": ""}).Error; err != nil {
|
if err := tx.Model(&model.Setting{}).Where("key = ?", "SecurityEntrance").Updates(map[string]interface{}{"value": ""}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := tx.Create(&model.Setting{Key: "SSLType", Value: "self"}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Create(&model.Setting{Key: "SSLID", Value: "0"}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Create(&model.Setting{Key: "SSL", Value: "disable"}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return tx.AutoMigrate(&model.Website{})
|
return tx.AutoMigrate(&model.Website{})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
|||||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||||
settingRouter.POST("/entrance/enable", baseApi.UpdateEntrance)
|
settingRouter.POST("/entrance/enable", baseApi.UpdateEntrance)
|
||||||
settingRouter.POST("/port/update", baseApi.UpdatePort)
|
settingRouter.POST("/port/update", baseApi.UpdatePort)
|
||||||
|
settingRouter.POST("/ssl/update", baseApi.UpdateSSL)
|
||||||
|
settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
|
||||||
settingRouter.POST("/password/update", baseApi.UpdatePassword)
|
settingRouter.POST("/password/update", baseApi.UpdatePassword)
|
||||||
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
||||||
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/init/app"
|
"github.com/1Panel-dev/1Panel/backend/init/app"
|
||||||
@ -43,22 +46,42 @@ func Start() {
|
|||||||
|
|
||||||
rootRouter := router.Routers()
|
rootRouter := router.Routers()
|
||||||
address := fmt.Sprintf(":%s", global.CONF.System.Port)
|
address := fmt.Sprintf(":%s", global.CONF.System.Port)
|
||||||
s := initServer(address, rootRouter)
|
s := endless.NewServer(address, rootRouter)
|
||||||
global.LOG.Infof("server run success on %s", global.CONF.System.Port)
|
|
||||||
if err := s.ListenAndServe(); err != nil {
|
|
||||||
global.LOG.Error(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type server interface {
|
|
||||||
ListenAndServe() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func initServer(address string, router *gin.Engine) server {
|
|
||||||
s := endless.NewServer(address, router)
|
|
||||||
s.ReadHeaderTimeout = 20 * time.Second
|
s.ReadHeaderTimeout = 20 * time.Second
|
||||||
s.WriteTimeout = 60 * time.Second
|
s.WriteTimeout = 60 * time.Second
|
||||||
s.MaxHeaderBytes = 1 << 20
|
s.MaxHeaderBytes = 1 << 20
|
||||||
return s
|
|
||||||
|
if global.CONF.System.SSL == "disable" {
|
||||||
|
global.LOG.Infof("server run success on %s with http", global.CONF.System.Port)
|
||||||
|
if err := s.ListenAndServe(); err != nil {
|
||||||
|
global.LOG.Error(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
cert, err := tls.X509KeyPair(certificate, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: address,
|
||||||
|
Handler: rootRouter,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
global.LOG.Infof("server run success on %s with https", global.CONF.System.Port)
|
||||||
|
if err := s.ListenAndServeTLS("", ""); err != nil {
|
||||||
|
global.LOG.Error(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
112
backend/utils/ssl/ssl.go
Normal file
112
backend/utils/ssl/ssl.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package ssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateSSL(domain string) error {
|
||||||
|
rootPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
ipItem := net.ParseIP(domain)
|
||||||
|
isIP := false
|
||||||
|
if len(ipItem) != 0 {
|
||||||
|
isIP = true
|
||||||
|
}
|
||||||
|
|
||||||
|
rootTemplate := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{CommonName: "1Panel Root CA"},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
}
|
||||||
|
if isIP {
|
||||||
|
rootTemplate.IPAddresses = []net.IP{ipItem}
|
||||||
|
} else {
|
||||||
|
rootTemplate.DNSNames = []string{domain}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey)
|
||||||
|
rootCertBlock := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: rootCertBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
interTemplate := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(2),
|
||||||
|
Subject: pkix.Name{CommonName: "1Panel Intermediate CA"},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
}
|
||||||
|
if isIP {
|
||||||
|
interTemplate.IPAddresses = []net.IP{ipItem}
|
||||||
|
} else {
|
||||||
|
interTemplate.DNSNames = []string{domain}
|
||||||
|
}
|
||||||
|
|
||||||
|
interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey)
|
||||||
|
interCertBlock := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: interCertBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
clientTemplate := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(3),
|
||||||
|
Subject: pkix.Name{CommonName: domain},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
|
}
|
||||||
|
if isIP {
|
||||||
|
clientTemplate.IPAddresses = []net.IP{ipItem}
|
||||||
|
} else {
|
||||||
|
clientTemplate.DNSNames = []string{domain}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCertBytes, _ := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey)
|
||||||
|
clientCertBlock := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: clientCertBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
pemBytes := []byte{}
|
||||||
|
pemBytes = append(pemBytes, pem.EncodeToMemory(clientCertBlock)...)
|
||||||
|
pemBytes = append(pemBytes, pem.EncodeToMemory(interCertBlock)...)
|
||||||
|
pemBytes = append(pemBytes, pem.EncodeToMemory(rootCertBlock)...)
|
||||||
|
certOut, err := os.OpenFile(global.CONF.System.BaseDir+"/1panel/secret/server.crt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer certOut.Close()
|
||||||
|
if _, err := certOut.Write(pemBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyOut, err := os.OpenFile(global.CONF.System.BaseDir+"/1panel/secret/server.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer keyOut.Close()
|
||||||
|
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -26,7 +26,6 @@ class RequestHttp {
|
|||||||
...config.headers,
|
...config.headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,8 @@ export namespace Setting {
|
|||||||
|
|
||||||
serverPort: number;
|
serverPort: number;
|
||||||
securityEntranceStatus: string;
|
securityEntranceStatus: string;
|
||||||
|
ssl: string;
|
||||||
|
sslType: string;
|
||||||
securityEntrance: string;
|
securityEntrance: string;
|
||||||
expirationDays: number;
|
expirationDays: number;
|
||||||
expirationTime: string;
|
expirationTime: string;
|
||||||
@ -35,6 +37,22 @@ export namespace Setting {
|
|||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
export interface SSLUpdate {
|
||||||
|
ssl: string;
|
||||||
|
domain: string;
|
||||||
|
sslType: string;
|
||||||
|
cert: string;
|
||||||
|
key: string;
|
||||||
|
sslID: number;
|
||||||
|
}
|
||||||
|
export interface SSLInfo {
|
||||||
|
domain: string;
|
||||||
|
timeout: string;
|
||||||
|
rootPath: string;
|
||||||
|
cert: string;
|
||||||
|
key: string;
|
||||||
|
sslID: number;
|
||||||
|
}
|
||||||
export interface PasswordUpdate {
|
export interface PasswordUpdate {
|
||||||
oldPassword: string;
|
oldPassword: string;
|
||||||
newPassword: string;
|
newPassword: string;
|
||||||
|
@ -28,6 +28,13 @@ export const updatePort = (param: Setting.PortUpdate) => {
|
|||||||
return http.post(`/settings/port/update`, param);
|
return http.post(`/settings/port/update`, param);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateSSL = (param: Setting.SSLUpdate) => {
|
||||||
|
return http.post(`/settings/ssl/update`, param);
|
||||||
|
};
|
||||||
|
export const loadSSLInfo = () => {
|
||||||
|
return http.get<Setting.SSLInfo>(`/settings/ssl/info`);
|
||||||
|
};
|
||||||
|
|
||||||
export const handleExpired = (param: Setting.PasswordUpdate) => {
|
export const handleExpired = (param: Setting.PasswordUpdate) => {
|
||||||
return http.post(`/settings/expired/handle`, param);
|
return http.post(`/settings/expired/handle`, param);
|
||||||
};
|
};
|
||||||
|
@ -872,6 +872,18 @@ const message = {
|
|||||||
mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code',
|
mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code',
|
||||||
mfaHelper3: 'Enter six digits from the app',
|
mfaHelper3: 'Enter six digits from the app',
|
||||||
|
|
||||||
|
https: 'Setting up HTTPS protocol access for the panel can enhance the security of panel access.',
|
||||||
|
selfSigned: 'Self signed',
|
||||||
|
selfSignedHelper:
|
||||||
|
'It is normal for self-signed certificates to be not trusted by browsers and display a security warning as the certificate is not issued by a trusted third party.',
|
||||||
|
import: 'Import',
|
||||||
|
select: 'Select',
|
||||||
|
domainOrIP: 'Domain/IP:',
|
||||||
|
timeOut: 'Timeout',
|
||||||
|
rootCrtDownload: 'Root certificate download',
|
||||||
|
primaryKey: 'Primary key',
|
||||||
|
certificate: 'Certificate',
|
||||||
|
|
||||||
snapshot: 'Snapshot',
|
snapshot: 'Snapshot',
|
||||||
thirdPartySupport: 'Only third-party accounts are supported',
|
thirdPartySupport: 'Only third-party accounts are supported',
|
||||||
recoverDetail: 'Recover detail',
|
recoverDetail: 'Recover detail',
|
||||||
|
@ -857,6 +857,17 @@ const message = {
|
|||||||
password: '密码',
|
password: '密码',
|
||||||
path: '路径',
|
path: '路径',
|
||||||
|
|
||||||
|
https: '为面板设置 https 协议访问,提升面板访问安全性',
|
||||||
|
selfSigned: '自签名',
|
||||||
|
selfSignedHelper: '自签证书,不被浏览器信任,显示不安全是正常现象',
|
||||||
|
import: '导入',
|
||||||
|
select: '选择已有',
|
||||||
|
domainOrIP: '域名/IP:',
|
||||||
|
timeOut: '过期时间:',
|
||||||
|
rootCrtDownload: '根证书下载',
|
||||||
|
primaryKey: '密钥',
|
||||||
|
certificate: '证书',
|
||||||
|
|
||||||
snapshot: '快照',
|
snapshot: '快照',
|
||||||
thirdPartySupport: '仅支持第三方账号',
|
thirdPartySupport: '仅支持第三方账号',
|
||||||
recoverDetail: '恢复详情',
|
recoverDetail: '恢复详情',
|
||||||
@ -909,6 +920,8 @@ const message = {
|
|||||||
mfaHelper1: '下载两步验证手机应用 如:',
|
mfaHelper1: '下载两步验证手机应用 如:',
|
||||||
mfaHelper2: '使用手机应用扫描以下二维码,获取 6 位验证码',
|
mfaHelper2: '使用手机应用扫描以下二维码,获取 6 位验证码',
|
||||||
mfaHelper3: '输入手机应用上的 6 位数字',
|
mfaHelper3: '输入手机应用上的 6 位数字',
|
||||||
|
sslDisable: '禁用',
|
||||||
|
sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续!',
|
||||||
|
|
||||||
monitor: '监控',
|
monitor: '监控',
|
||||||
enableMonitor: '监控状态',
|
enableMonitor: '监控状态',
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<el-form :model="form" ref="panelFormRef" v-loading="loading" label-position="left" label-width="180px">
|
<el-form :model="form" ref="panelFormRef" v-loading="loading" label-position="left" label-width="180px">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="1"><br /></el-col>
|
<el-col :span="1"><br /></el-col>
|
||||||
<el-col :span="10">
|
<el-col :span="16">
|
||||||
<el-form-item :label="$t('setting.panelPort')" :rules="Rules.port" prop="serverPort">
|
<el-form-item :label="$t('setting.panelPort')" :rules="Rules.port" prop="serverPort">
|
||||||
<el-input clearable v-model.number="form.serverPort">
|
<el-input clearable v-model.number="form.serverPort">
|
||||||
<template #append>
|
<template #append>
|
||||||
@ -133,6 +133,22 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="https" required prop="ssl">
|
||||||
|
<el-switch
|
||||||
|
@change="handleSSL"
|
||||||
|
v-model="form.ssl"
|
||||||
|
active-value="enable"
|
||||||
|
inactive-value="disable"
|
||||||
|
/>
|
||||||
|
<span class="input-help">{{ $t('setting.https') }}</span>
|
||||||
|
<SSLSetting
|
||||||
|
:type="form.sslType"
|
||||||
|
:status="form.ssl"
|
||||||
|
v-if="sslShow"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -165,6 +181,7 @@ import { ref, reactive, onMounted } from 'vue';
|
|||||||
import { ElForm, ElMessageBox } from 'element-plus';
|
import { ElForm, ElMessageBox } from 'element-plus';
|
||||||
import { Setting } from '@/api/interface/setting';
|
import { Setting } from '@/api/interface/setting';
|
||||||
import LayoutContent from '@/layout/layout-content.vue';
|
import LayoutContent from '@/layout/layout-content.vue';
|
||||||
|
import SSLSetting from '@/views/setting/safe/ssl/index.vue';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import {
|
import {
|
||||||
updateSetting,
|
updateSetting,
|
||||||
@ -174,6 +191,7 @@ import {
|
|||||||
updatePort,
|
updatePort,
|
||||||
getSystemAvailable,
|
getSystemAvailable,
|
||||||
updateEntrance,
|
updateEntrance,
|
||||||
|
updateSSL,
|
||||||
} from '@/api/modules/setting';
|
} from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||||
@ -183,9 +201,12 @@ import { GlobalStore } from '@/store';
|
|||||||
const globalStore = GlobalStore();
|
const globalStore = GlobalStore();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
serverPort: 9999,
|
serverPort: 9999,
|
||||||
securityEntranceStatus: 'disable',
|
securityEntranceStatus: 'disable',
|
||||||
|
ssl: 'disable',
|
||||||
|
sslType: 'self',
|
||||||
securityEntrance: '',
|
securityEntrance: '',
|
||||||
expirationDays: 0,
|
expirationDays: 0,
|
||||||
expirationTime: '',
|
expirationTime: '',
|
||||||
@ -200,11 +221,20 @@ const timeoutForm = reactive({
|
|||||||
days: 0,
|
days: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sslShow = ref();
|
||||||
|
const oldSSLStatus = ref();
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
const res = await getSettingInfo();
|
const res = await getSettingInfo();
|
||||||
form.serverPort = Number(res.data.serverPort);
|
form.serverPort = Number(res.data.serverPort);
|
||||||
form.securityEntranceStatus = res.data.securityEntranceStatus;
|
form.securityEntranceStatus = res.data.securityEntranceStatus;
|
||||||
isEntranceShow.value = res.data.securityEntranceStatus === 'enable';
|
isEntranceShow.value = res.data.securityEntranceStatus === 'enable';
|
||||||
|
form.ssl = res.data.ssl;
|
||||||
|
oldSSLStatus.value = res.data.ssl;
|
||||||
|
form.sslType = res.data.sslType;
|
||||||
|
if (form.ssl === 'enable') {
|
||||||
|
sslShow.value = true;
|
||||||
|
}
|
||||||
form.securityEntrance = res.data.securityEntrance;
|
form.securityEntrance = res.data.securityEntrance;
|
||||||
form.expirationDays = Number(res.data.expirationDays);
|
form.expirationDays = Number(res.data.expirationDays);
|
||||||
form.expirationTime = res.data.expirationTime;
|
form.expirationTime = res.data.expirationTime;
|
||||||
@ -323,6 +353,33 @@ const handleEntrance = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSSL = async () => {
|
||||||
|
if (form.ssl === 'enable') {
|
||||||
|
sslShow.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (form.ssl === oldSSLStatus.value) {
|
||||||
|
sslShow.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ElMessageBox.confirm(i18n.global.t('setting.sslDisableHelper'), i18n.global.t('setting.sslDisable'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
sslShow.value = false;
|
||||||
|
await updateSSL({ ssl: 'disable', domain: '', sslType: '', key: '', cert: '', sslID: 0 });
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
let href = window.location.href;
|
||||||
|
let address = href.split('://')[1];
|
||||||
|
window.open(`http://${address}/`, '_self');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
form.ssl = 'enable';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onSaveEntrance = async () => {
|
const onSaveEntrance = async () => {
|
||||||
const reg = /^[A-Za-z0-9]{6,10}$/;
|
const reg = /^[A-Za-z0-9]{6,10}$/;
|
||||||
if ((!reg.test(form.securityEntrance) && form.securityEntrance !== '') || form.securityEntrance === '') {
|
if ((!reg.test(form.securityEntrance) && form.securityEntrance !== '') || form.securityEntrance === '') {
|
||||||
|
194
frontend/src/views/setting/safe/ssl/index.vue
Normal file
194
frontend/src/views/setting/safe/ssl/index.vue
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" :rules="rules">
|
||||||
|
<el-radio-group v-model="sslItemType">
|
||||||
|
<el-radio label="self">{{ $t('setting.selfSigned') }}</el-radio>
|
||||||
|
<el-radio label="select">{{ $t('setting.select') }}</el-radio>
|
||||||
|
<el-radio label="import">{{ $t('setting.import') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<span class="input-help" v-if="sslItemType === 'self'">{{ $t('setting.selfSignedHelper') }}</span>
|
||||||
|
<div v-if="sslInfo.timeout">
|
||||||
|
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
|
||||||
|
<el-tag style="margin-left: 5px">{{ $t('setting.timeOut') }} {{ sslInfo.timeout }}</el-tag>
|
||||||
|
<el-button
|
||||||
|
@click="onDownload"
|
||||||
|
style="margin-left: 5px"
|
||||||
|
v-if="sslItemType === 'self'"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
icon="Download"
|
||||||
|
>
|
||||||
|
{{ $t('setting.rootCrtDownload') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="sslItemType === 'import'">
|
||||||
|
<el-form-item :label="$t('setting.primaryKey')" prop="key">
|
||||||
|
<el-input v-model="form.key" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="margintop" :label="$t('setting.certificate')" prop="cert">
|
||||||
|
<el-input v-model="form.cert" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="sslItemType === 'select'">
|
||||||
|
<el-form-item :label="$t('setting.certificate')" prop="sslID">
|
||||||
|
<el-select v-model="form.sslID" @change="changeSSl(form.sslID)">
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in sslList"
|
||||||
|
:key="index"
|
||||||
|
:label="item.primaryDomain"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-descriptions
|
||||||
|
class="margintop"
|
||||||
|
:column="5"
|
||||||
|
border
|
||||||
|
direction="vertical"
|
||||||
|
v-if="form.sslID > 0 && itemSSL"
|
||||||
|
>
|
||||||
|
<el-descriptions-item :label="$t('website.primaryDomain')">
|
||||||
|
{{ itemSSL.primaryDomain }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('website.otherDomains')">
|
||||||
|
{{ itemSSL.domains }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('ssl.provider')">
|
||||||
|
{{ getProvider(itemSSL.provider) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item
|
||||||
|
:label="$t('ssl.acmeAccount')"
|
||||||
|
v-if="itemSSL.acmeAccount?.email && itemSSL.provider !== 'manual'"
|
||||||
|
>
|
||||||
|
{{ itemSSL.acmeAccount.email }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('website.expireDate')">
|
||||||
|
{{ dateFormatSimple(itemSSL.expireDate) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
<el-button style="margin-top: 20px" type="primary" @click="onSaveSSL(formRef)">
|
||||||
|
{{ $t('commons.button.saveAndEnable') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Website } from '@/api/interface/Website';
|
||||||
|
import { loadSSLInfo } from '@/api/modules/setting';
|
||||||
|
import { dateFormatSimple, getProvider } from '@/utils/util';
|
||||||
|
import { ListSSL } from '@/api/modules/website';
|
||||||
|
import { nextTick, onMounted, reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { updateSSL } from '@/api/modules/setting';
|
||||||
|
import { DownloadByPath } from '@/api/modules/files';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { FormInstance } from 'element-plus';
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
ssl: 'enable',
|
||||||
|
domain: '',
|
||||||
|
sslType: 'self',
|
||||||
|
sslID: null as number,
|
||||||
|
cert: '',
|
||||||
|
key: '',
|
||||||
|
rootPath: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
cert: [Rules.requiredInput],
|
||||||
|
key: [Rules.requiredInput],
|
||||||
|
sslID: [Rules.requiredSelect],
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'self',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sslInfo = reactive({
|
||||||
|
domain: '',
|
||||||
|
timeout: '',
|
||||||
|
});
|
||||||
|
const sslList = ref();
|
||||||
|
const itemSSL = ref();
|
||||||
|
const sslItemType = ref('self');
|
||||||
|
|
||||||
|
const loadInfo = async () => {
|
||||||
|
await loadSSLInfo().then(async (res) => {
|
||||||
|
sslInfo.domain = res.data.domain || '';
|
||||||
|
sslInfo.timeout = res.data.timeout || '';
|
||||||
|
form.cert = res.data.cert;
|
||||||
|
form.key = res.data.key;
|
||||||
|
form.rootPath = res.data.rootPath;
|
||||||
|
if (res.data.sslID) {
|
||||||
|
form.sslID = res.data.sslID;
|
||||||
|
const ssls = await ListSSL({});
|
||||||
|
sslList.value = ssls.data || [];
|
||||||
|
changeSSl(form.sslID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadSSLs = async () => {
|
||||||
|
const res = await ListSSL({});
|
||||||
|
sslList.value = res.data || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSSl = (sslid: number) => {
|
||||||
|
const res = sslList.value.filter((element: Website.SSL) => {
|
||||||
|
return element.id == sslid;
|
||||||
|
});
|
||||||
|
itemSSL.value = res[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDownload = async () => {
|
||||||
|
const file = await DownloadByPath(form.rootPath);
|
||||||
|
const downloadUrl = window.URL.createObjectURL(new Blob([file]));
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = 'server.crt';
|
||||||
|
const event = new MouseEvent('click');
|
||||||
|
a.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveSSL = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
form.sslType = sslItemType.value;
|
||||||
|
let href = window.location.href;
|
||||||
|
form.domain = href.split('//')[1].split(':')[0];
|
||||||
|
await updateSSL(form).then(() => {
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
let href = window.location.href;
|
||||||
|
let address = href.split('://')[1];
|
||||||
|
window.open(`https://${address}/`, '_self');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
sslItemType.value = props.type;
|
||||||
|
loadInfo();
|
||||||
|
});
|
||||||
|
loadSSLs();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.margintop {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user