Skip to content

Commit

Permalink
feat(system-security): Support Hot Reloading of System Certificates (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengkunwang223 authored Nov 22, 2024
1 parent 7fdb0a5 commit c3565f7
Show file tree
Hide file tree
Showing 18 changed files with 89 additions and 117 deletions.
3 changes: 3 additions & 0 deletions backend/app/service/website_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,9 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
}
}

reloadSystemSSL(websiteSSL, logger)

return websiteSSL, nil
}

Expand Down
28 changes: 28 additions & 0 deletions backend/app/service/website_ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package service
import (
"context"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
Expand Down Expand Up @@ -188,6 +189,31 @@ func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{
logger.Println(i18n.GetMsgWithMap(msgKey, params))
}

func reloadSystemSSL(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
systemSSLEnable, sslID := GetSystemSSL()
if systemSSLEnable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
certPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")
printSSLLog(logger, "StartUpdateSystemSSL", nil, logger == nil)
if err := fileOp.WriteFile(certPath, strings.NewReader(websiteSSL.Pem), 0600); err != nil {
logger.Printf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
if err := fileOp.WriteFile(keyPath, strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
newCert, err := tls.X509KeyPair([]byte(websiteSSL.Pem), []byte(websiteSSL.PrivateKey))
if err != nil {
logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
printSSLLog(logger, "UpdateSystemSSLSuccess", nil, logger == nil)
constant.CertStore.Store(&newCert)
}
}

func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var (
err error
Expand Down Expand Up @@ -344,6 +370,8 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
}
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
}

reloadSystemSSL(websiteSSL, logger)
}()

return nil
Expand Down
28 changes: 6 additions & 22 deletions backend/app/service/website_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -1002,23 +1002,22 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
}
}

func GetSystemSSL() (bool, bool, uint) {
func GetSystemSSL() (bool, uint) {
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
return false, false, 0
return false, 0
}
if sslSetting.Value == "enable" {
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
idValue, _ := strconv.Atoi(sslID.Value)
if idValue <= 0 {
return false, false, 0
return false, 0
}

auto, _ := settingRepo.Get(settingRepo.WithByKey("AutoRestart"))
return true, auto.Value == "enable", uint(idValue)
return true, uint(idValue)
}
return false, false, 0
return false, 0
}

func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
Expand All @@ -1037,22 +1036,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
return buserr.WithErr(constant.ErrSSLApply, err)
}
}
enable, auto, sslID := GetSystemSSL()
if enable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err
}
if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
reloadSystemSSL(&websiteSSL, nil)
return nil
}

Expand Down
4 changes: 4 additions & 0 deletions backend/constant/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package constant

import "sync/atomic"

type DBContext string

const (
Expand Down Expand Up @@ -123,3 +125,5 @@ var DynamicRoutes = []string{
`^/databases/postgresql/setting/[^/]+/[^/]+$`,
`^/websites/[^/]+/config/[^/]+$`,
}

var CertStore atomic.Value
21 changes: 0 additions & 21 deletions backend/cron/job/ssl.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package job

import (
"path"
"strings"
"time"

"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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
)

type ssl struct {
Expand All @@ -23,7 +19,6 @@ func NewSSLJob() *ssl {
}

func (ssl *ssl) Run() {
systemSSLEnable, auto, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
Expand Down Expand Up @@ -59,22 +54,6 @@ func (ssl *ssl) Run() {
continue
}
}
if systemSSLEnable && sslID == s.ID {
websiteSSL, _ := sslRepo.GetFirst(repo.NewCommonRepo().WithByID(s.ID))
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
}
}
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ ErrDefaultCA: "The default organization cannot be deleted"
ApplyWebSiteSSLLog: "Start updating {{ .name }} website certificate"
ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}"
ApplyWebSiteSSLSuccess: "Update website certificate successfully"
StartUpdateSystemSSL: "Start updating system certificate"
UpdateSystemSSLSuccess: "Update system certificate successfully"

#mysql
ErrUserIsExist: "The current user already exists. Please enter a new user"
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/lang/zh-Hant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ ErrDefaultCA: "默認機構不能刪除"
ApplyWebSiteSSLLog: "開始更新 {{ .name }} 網站憑證"
ErrUpdateWebsiteSSL: "{{ .name }} 網站更新憑證失敗: {{ .err }}"
ApplyWebSiteSSLSuccess: "更新網站憑證成功"
StartUpdateSystemSSL: "開始更新系統證書"
UpdateSystemSSLSuccess: "更新系統證書成功"


#mysql
Expand Down
2 changes: 2 additions & 0 deletions backend/i18n/lang/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ ApplyWebSiteSSLSuccess: "更新网站证书成功"
ErrExecShell: "执行脚本失败 {{ .err }}"
ExecShellStart: "开始执行脚本"
ExecShellSuccess: "脚本执行成功"
StartUpdateSystemSSL: "开始更新系统证书"
UpdateSystemSSLSuccess: "更新系统证书成功"

#mysql
ErrUserIsExist: "当前用户已存在,请重新输入"
Expand Down
12 changes: 8 additions & 4 deletions backend/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import (
"crypto/tls"
"encoding/gob"
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/i18n"
"net"
"net/http"
"os"
"path"

"github.com/1Panel-dev/1Panel/backend/i18n"

"github.com/1Panel-dev/1Panel/backend/init/app"
"github.com/1Panel-dev/1Panel/backend/init/business"

Expand Down Expand Up @@ -81,12 +81,16 @@ func Start() {
if err != nil {
panic(err)
}
constant.CertStore.Store(&cert)

server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return constant.CertStore.Load().(*tls.Certificate), nil
},
}
global.LOG.Infof("listen at https://%s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem)

if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, certPath, keyPath); err != nil {
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, "", ""); err != nil {
panic(err)
}
} else {
Expand Down
1 change: 0 additions & 1 deletion frontend/src/api/interface/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export namespace Setting {
bindAddress: string;
ssl: string;
sslType: string;
autoRestart: string;
allowIPs: string;
bindDomain: string;
securityEntrance: string;
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1551,11 +1551,6 @@ const message = {
bindDomain: 'Bind Domain',
unBindDomain: 'Unbind domain',
panelSSL: 'Panel SSL',
sslAutoRestart: 'Restart 1Panel service after certificate auto-renewal',
sslChangeHelper1:
'Currently, automatic restart of 1Panel service is not selected. The certificate auto-renewal will not take effect immediately and will still require a manual restart of 1Panel.',
sslChangeHelper2:
'The 1Panel service will automatically restart after setting the panel SSL. Do you want to continue?',
unBindDomainHelper:
'The action of unbinding a domain name may cause system insecurity. Do you want to continue?',
bindDomainHelper:
Expand Down
3 changes: 0 additions & 3 deletions frontend/src/lang/modules/tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1494,9 +1494,6 @@ const message = {
bindDomain: '域名綁定',
unBindDomain: '域名解綁',
panelSSL: '面 SSL',
sslAutoRestart: '證書自動續期後重啟 1Panel 服務',
sslChangeHelper1: '當前未勾選自動重啟 1Panel 服務證書自動續期後不會立即生效仍需手動重啟 1Panel。',
sslChangeHelper2: '設置面板 SSL 後將自動重啟 1Panel 服務是否繼續?',
unBindDomainHelper: '解除域名綁定可能造成系統不安全是否繼續?',
bindDomainHelper: '設置域名綁定後僅能通過設置中域名訪問 1Panel 服務',
bindDomainHelper1: '綁定域名為空時則取消域名綁定',
Expand Down
3 changes: 0 additions & 3 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1496,9 +1496,6 @@ const message = {
bindDomain: '域名绑定',
unBindDomain: '域名解绑',
panelSSL: '面 SSL',
sslAutoRestart: '证书自动续期后重启 1Panel 服务',
sslChangeHelper1: '当前未勾选自动重启 1Panel 服务证书自动续期后不会立即生效仍需手动重启 1Panel。',
sslChangeHelper2: '设置面板 SSL 后将自动重启 1Panel 服务是否继续?',
unBindDomainHelper: '解除域名绑定可能造成系统不安全是否继续?',
bindDomainHelper: '设置域名绑定后仅能通过设置中域名访问 1Panel 服务',
bindDomainHelper1: '绑定域名为空时则取消域名绑定',
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/routers/modules/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const settingRouter = {
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
activeMenu: '/settings',
},
},
{
Expand All @@ -44,7 +44,7 @@ const settingRouter = {
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
activeMenu: '/settings',
},
},
{
Expand All @@ -54,7 +54,7 @@ const settingRouter = {
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
activeMenu: '/settings',
},
},
{
Expand All @@ -64,7 +64,7 @@ const settingRouter = {
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
activeMenu: '/settings',
},
},
{
Expand All @@ -74,7 +74,7 @@ const settingRouter = {
component: () => import('@/views/setting/snapshot/index.vue'),
meta: {
requiresAuth: true,
activeMenu: 'Setting',
activeMenu: '/settings',
},
},
{
Expand All @@ -84,7 +84,7 @@ const settingRouter = {
component: () => import('@/views/setting/expired.vue'),
meta: {
requiresAuth: true,
activeMenu: 'Expired',
activeMenu: '/settings',
},
},
],
Expand Down
3 changes: 0 additions & 3 deletions frontend/src/views/setting/safe/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ const form = reactive({
bindAddress: '',
ssl: 'disable',
sslType: 'self',
autoRestart: 'disable',
securityEntrance: '',
expirationDays: 0,
expirationTime: '',
Expand All @@ -250,7 +249,6 @@ const search = async () => {
if (form.ssl === 'enable') {
loadInfo();
}
form.autoRestart = res.data.autoRestart;
form.securityEntrance = res.data.securityEntrance;
form.expirationDays = Number(res.data.expirationDays);
form.expirationTime = res.data.expirationTime;
Expand Down Expand Up @@ -330,7 +328,6 @@ const handleSSL = async () => {
ssl: form.ssl,
sslType: form.sslType,
sslInfo: sslInfo.value,
autoRestart: form.autoRestart,
};
sslRef.value!.acceptParams(params);
return;
Expand Down
Loading

0 comments on commit c3565f7

Please sign in to comment.