Browse Source

feat: 增加系统 ssl 设置功能 (#780)

pull/781/head
ssongliu 2 years ago committed by GitHub
parent
commit
34e84081e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      backend/app/api/v1/setting.go
  2. 19
      backend/app/dto/setting.go
  3. 198
      backend/app/service/setting.go
  4. 1
      backend/configs/system.go
  5. 5
      backend/init/hook/hook.go
  6. 2
      backend/init/migration/migrate.go
  7. 13
      backend/init/migration/migrations/init.go
  8. 2
      backend/router/ro_setting.go
  9. 53
      backend/server/server.go
  10. 112
      backend/utils/ssl/ssl.go
  11. 1
      frontend/src/api/index.ts
  12. 18
      frontend/src/api/interface/setting.ts
  13. 7
      frontend/src/api/modules/setting.ts
  14. 12
      frontend/src/lang/modules/en.ts
  15. 13
      frontend/src/lang/modules/zh.ts
  16. 59
      frontend/src/views/setting/safe/index.vue
  17. 194
      frontend/src/views/setting/safe/ssl/index.vue

42
backend/app/api/v1/setting.go

@ -119,6 +119,48 @@ func (b *BaseApi) UpdatePassword(c *gin.Context) {
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
// @Summary Update system port
// @Description 更新系统端口

19
backend/app/dto/setting.go

@ -17,6 +17,8 @@ type SettingInfo struct {
ServerPort string `json:"serverPort"`
SecurityEntranceStatus string `json:"securityEntranceStatus"`
SSL string `json:"ssl"`
SSLType string `json:"sslType"`
SecurityEntrance string `json:"securityEntrance"`
ExpirationDays string `json:"expirationDays"`
ExpirationTime string `json:"expirationTime"`
@ -40,6 +42,23 @@ type SettingUpdate struct {
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 {
OldPassword string `json:"oldPassword" validate:"required"`
NewPassword string `json:"newPassword" validate:"required"`

198
backend/app/service/setting.go

@ -1,8 +1,14 @@
package service
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"os"
"strconv"
"strings"
"time"
"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/common"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)
type SettingService struct{}
@ -23,6 +31,8 @@ type ISettingService interface {
UpdateEntrance(value string) error
UpdatePassword(c *gin.Context, old, new string) 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
}
@ -111,6 +121,126 @@ func (u *SettingService) UpdatePort(port uint) error {
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 {
setting, err := settingRepo.Get(settingRepo.WithByKey("Password"))
if err != nil {
@ -149,3 +279,71 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
_ = global.SESSION.Clean()
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")
}

1
backend/configs/system.go

@ -2,6 +2,7 @@ package configs
type System struct {
Port string `mapstructure:"port"`
SSL string `mapstructure:"ssl"`
DbFile string `mapstructure:"db_file"`
DbPath string `mapstructure:"db_path"`
LogPath string `mapstructure:"log_path"`

5
backend/init/hook/hook.go

@ -17,6 +17,11 @@ func Init() {
global.LOG.Errorf("load service encrypt key from setting failed, err: %v", err)
}
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 {
_ = settingRepo.Create("SystemStatus", "Free")

2
backend/init/migration/migrate.go

@ -25,7 +25,7 @@ func Init() {
migrations.UpdateTableApp,
migrations.UpdateTableHost,
migrations.UpdateTableWebsite,
migrations.AddEntranceStatus,
migrations.AddEntranceAndSSL,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

13
backend/init/migration/migrations/init.go

@ -288,8 +288,8 @@ var UpdateTableWebsite = &gormigrate.Migration{
},
}
var AddEntranceStatus = &gormigrate.Migration{
ID: "20230414-add-entrance-status",
var AddEntranceAndSSL = &gormigrate.Migration{
ID: "20230414-add-entrance-and-ssl",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "SecurityEntranceStatus", Value: "disable"}).Error; err != nil {
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 {
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{})
},
}

2
backend/router/ro_setting.go

@ -24,6 +24,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.POST("/update", baseApi.UpdateSetting)
settingRouter.POST("/entrance/enable", baseApi.UpdateEntrance)
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("/time/sync", baseApi.SyncTime)
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)

53
backend/server/server.go

@ -1,8 +1,11 @@
package server
import (
"crypto/tls"
"encoding/gob"
"fmt"
"net/http"
"os"
"time"
"github.com/1Panel-dev/1Panel/backend/init/app"
@ -43,22 +46,42 @@ func Start() {
rootRouter := router.Routers()
address := fmt.Sprintf(":%s", global.CONF.System.Port)
s := initServer(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 := endless.NewServer(address, rootRouter)
s.ReadHeaderTimeout = 20 * time.Second
s.WriteTimeout = 60 * time.Second
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

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

1
frontend/src/api/index.ts

@ -26,7 +26,6 @@ class RequestHttp {
...config.headers,
};
}
return {
...config,
};

18
frontend/src/api/interface/setting.ts

@ -16,6 +16,8 @@ export namespace Setting {
serverPort: number;
securityEntranceStatus: string;
ssl: string;
sslType: string;
securityEntrance: string;
expirationDays: number;
expirationTime: string;
@ -35,6 +37,22 @@ export namespace Setting {
key: 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 {
oldPassword: string;
newPassword: string;

7
frontend/src/api/modules/setting.ts

@ -28,6 +28,13 @@ export const updatePort = (param: Setting.PortUpdate) => {
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) => {
return http.post(`/settings/expired/handle`, param);
};

12
frontend/src/lang/modules/en.ts

@ -872,6 +872,18 @@ const message = {
mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code',
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',
thirdPartySupport: 'Only third-party accounts are supported',
recoverDetail: 'Recover detail',

13
frontend/src/lang/modules/zh.ts

@ -857,6 +857,17 @@ const message = {
password: '密码',
path: '路径',
https: '为面板设置 https 协议访问提升面板访问安全性',
selfSigned: '自签名',
selfSignedHelper: '自签证书不被浏览器信任显示不安全是正常现象',
import: '导入',
select: '选择已有',
domainOrIP: '域名/IP',
timeOut: '过期时间',
rootCrtDownload: '根证书下载',
primaryKey: '密钥',
certificate: '证书',
snapshot: '快照',
thirdPartySupport: '仅支持第三方账号',
recoverDetail: '恢复详情',
@ -909,6 +920,8 @@ const message = {
mfaHelper1: '下载两步验证手机应用 :',
mfaHelper2: '使用手机应用扫描以下二维码获取 6 位验证码',
mfaHelper3: '输入手机应用上的 6 位数字',
sslDisable: '禁用',
sslDisableHelper: '禁用 https 服务需要重启面板才能生效是否继续',
monitor: '监控',
enableMonitor: '监控状态',

59
frontend/src/views/setting/safe/index.vue

@ -5,7 +5,7 @@
<el-form :model="form" ref="panelFormRef" v-loading="loading" label-position="left" label-width="180px">
<el-row>
<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-input clearable v-model.number="form.serverPort">
<template #append>
@ -133,6 +133,22 @@
</ul>
</el-card>
</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-row>
</el-form>
@ -165,6 +181,7 @@ import { ref, reactive, onMounted } from 'vue';
import { ElForm, ElMessageBox } from 'element-plus';
import { Setting } from '@/api/interface/setting';
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 {
updateSetting,
@ -174,6 +191,7 @@ import {
updatePort,
getSystemAvailable,
updateEntrance,
updateSSL,
} from '@/api/modules/setting';
import i18n from '@/lang';
import { Rules, checkNumberRange } from '@/global/form-rules';
@ -183,9 +201,12 @@ import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const loading = ref(false);
const form = reactive({
serverPort: 9999,
securityEntranceStatus: 'disable',
ssl: 'disable',
sslType: 'self',
securityEntrance: '',
expirationDays: 0,
expirationTime: '',
@ -200,11 +221,20 @@ const timeoutForm = reactive({
days: 0,
});
const sslShow = ref();
const oldSSLStatus = ref();
const search = async () => {
const res = await getSettingInfo();
form.serverPort = Number(res.data.serverPort);
form.securityEntranceStatus = res.data.securityEntranceStatus;
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.expirationDays = Number(res.data.expirationDays);
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 reg = /^[A-Za-z0-9]{6,10}$/;
if ((!reg.test(form.securityEntrance) && form.securityEntrance !== '') || form.securityEntrance === '') {

194
frontend/src/views/setting/safe/ssl/index.vue

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