From 909b10f422f035a382437df012fea0de59ee04ad Mon Sep 17 00:00:00 2001
From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com>
Date: Mon, 23 Dec 2024 15:49:57 +0800
Subject: [PATCH] feat(system-security): Optimized unauthenticated settings to
enhance system security (#7532)
---
core/app/service/auth.go | 18 ++
core/cmd/server/res/error_msg.go | 6 +
core/cmd/server/res/html/200.html | 55 ++++++
core/cmd/server/res/html/200_err_domain.html | 55 ++++++
.../cmd/server/res/html/200_err_ip_limit.html | 55 ++++++
core/cmd/server/res/html/400.html | 7 +
core/cmd/server/res/html/401.html | 7 +
core/cmd/server/res/html/403.html | 7 +
core/cmd/server/res/html/404.html | 7 +
core/cmd/server/res/html/408.html | 7 +
core/cmd/server/res/html/416.html | 7 +
core/cmd/server/res/html/500.html | 7 +
core/constant/common.go | 101 ++++++++++
core/init/router/router.go | 119 ++++++++++-
frontend/src/api/index.ts | 37 ----
frontend/src/lang/modules/en.ts | 2 +
frontend/src/lang/modules/tw.ts | 2 +
frontend/src/lang/modules/zh.ts | 2 +
frontend/src/routers/router.ts | 6 -
frontend/src/views/login/entrance/index.vue | 186 ------------------
.../src/views/setting/safe/response/index.vue | 8 +
21 files changed, 468 insertions(+), 233 deletions(-)
create mode 100644 core/cmd/server/res/error_msg.go
create mode 100644 core/cmd/server/res/html/200.html
create mode 100644 core/cmd/server/res/html/200_err_domain.html
create mode 100644 core/cmd/server/res/html/200_err_ip_limit.html
create mode 100644 core/cmd/server/res/html/400.html
create mode 100644 core/cmd/server/res/html/401.html
create mode 100644 core/cmd/server/res/html/403.html
create mode 100644 core/cmd/server/res/html/404.html
create mode 100644 core/cmd/server/res/html/408.html
create mode 100644 core/cmd/server/res/html/416.html
create mode 100644 core/cmd/server/res/html/500.html
delete mode 100644 frontend/src/views/login/entrance/index.vue
diff --git a/core/app/service/auth.go b/core/app/service/auth.go
index 4788839d3..2f59bcdfd 100644
--- a/core/app/service/auth.go
+++ b/core/app/service/auth.go
@@ -25,6 +25,8 @@ type IAuthService interface {
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error)
+ GetSecurityEntrance() string
+ IsLogin(c *gin.Context) bool
}
func NewIAuthService() IAuthService {
@@ -192,3 +194,19 @@ func (u *AuthService) GetResponsePage() (string, error) {
}
return pageCode.Value, nil
}
+
+func (u *AuthService) GetSecurityEntrance() string {
+ status, err := settingRepo.Get(repo.WithByKey("SecurityEntrance"))
+ if err != nil {
+ return ""
+ }
+ if len(status.Value) == 0 {
+ return ""
+ }
+ return status.Value
+}
+
+func (u *AuthService) IsLogin(c *gin.Context) bool {
+ _, err := global.SESSION.Get(c)
+ return err == nil
+}
diff --git a/core/cmd/server/res/error_msg.go b/core/cmd/server/res/error_msg.go
new file mode 100644
index 000000000..fb958d1e6
--- /dev/null
+++ b/core/cmd/server/res/error_msg.go
@@ -0,0 +1,6 @@
+package res
+
+import "embed"
+
+//go:embed html/*
+var ErrorMsg embed.FS
diff --git a/core/cmd/server/res/html/200.html b/core/cmd/server/res/html/200.html
new file mode 100644
index 000000000..62598c189
--- /dev/null
+++ b/core/cmd/server/res/html/200.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Access Temporarily Unavailable
+
+
+
+
+
Access Temporarily Unavailable
+
The current environment has enabled secure login access.
+
Run the following SSH command to view the panel login URL:
+
1pctl user-info
+
+
+
diff --git a/core/cmd/server/res/html/200_err_domain.html b/core/cmd/server/res/html/200_err_domain.html
new file mode 100644
index 000000000..8f48fc532
--- /dev/null
+++ b/core/cmd/server/res/html/200_err_domain.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Access Temporarily Unavailable
+
+
+
+
+
Access Temporarily Unavailable
+
The current environment has enabled domain name binding.
+
Run the following SSH command to reset the binding information:
+
1pctl reset domain
+
+
+
diff --git a/core/cmd/server/res/html/200_err_ip_limit.html b/core/cmd/server/res/html/200_err_ip_limit.html
new file mode 100644
index 000000000..662704ec0
--- /dev/null
+++ b/core/cmd/server/res/html/200_err_ip_limit.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+ Access Temporarily Unavailable
+
+
+
+
+
Access Temporarily Unavailable
+
The current environment has enabled authorized IP access.
+
Run the following SSH command to reset the binding information:
+
1pctl reset ips
+
+
+
diff --git a/core/cmd/server/res/html/400.html b/core/cmd/server/res/html/400.html
new file mode 100644
index 000000000..0d502a0a3
--- /dev/null
+++ b/core/cmd/server/res/html/400.html
@@ -0,0 +1,7 @@
+
+
+400 Bad Request
+
+400 Bad Request
+
nginx
+
\ No newline at end of file
diff --git a/core/cmd/server/res/html/401.html b/core/cmd/server/res/html/401.html
new file mode 100644
index 000000000..60e1498f7
--- /dev/null
+++ b/core/cmd/server/res/html/401.html
@@ -0,0 +1,7 @@
+
+
+401 Unauthorized
+
+401 Unauthorized
+
nginx
+
\ No newline at end of file
diff --git a/core/cmd/server/res/html/403.html b/core/cmd/server/res/html/403.html
new file mode 100644
index 000000000..a77b7cb64
--- /dev/null
+++ b/core/cmd/server/res/html/403.html
@@ -0,0 +1,7 @@
+
+
+403 Forbidden
+
+403 Forbidden
+
nginx
+
\ No newline at end of file
diff --git a/core/cmd/server/res/html/404.html b/core/cmd/server/res/html/404.html
new file mode 100644
index 000000000..6748be25f
--- /dev/null
+++ b/core/cmd/server/res/html/404.html
@@ -0,0 +1,7 @@
+
+
+404 Not Found
+
+404 Not Found
+
nginx
+
\ No newline at end of file
diff --git a/core/cmd/server/res/html/408.html b/core/cmd/server/res/html/408.html
new file mode 100644
index 000000000..15ba0cdaa
--- /dev/null
+++ b/core/cmd/server/res/html/408.html
@@ -0,0 +1,7 @@
+
+
+408 Request Timeout
+
+408 Request Timeout
+
nginx
+
\ No newline at end of file
diff --git a/core/cmd/server/res/html/416.html b/core/cmd/server/res/html/416.html
new file mode 100644
index 000000000..8104e724c
--- /dev/null
+++ b/core/cmd/server/res/html/416.html
@@ -0,0 +1,7 @@
+
+
+416 Requested Not Satisfiable
+
+416 Requested Not Satisfiable
+
nginx
+
\ No newline at end of file
diff --git a/core/cmd/server/res/html/500.html b/core/cmd/server/res/html/500.html
new file mode 100644
index 000000000..c580daa08
--- /dev/null
+++ b/core/cmd/server/res/html/500.html
@@ -0,0 +1,7 @@
+
+
+Internal Server Error
+
+Internal Server Error
+
nginx
+
\ No newline at end of file
diff --git a/core/constant/common.go b/core/constant/common.go
index e2fd84720..4da179844 100644
--- a/core/constant/common.go
+++ b/core/constant/common.go
@@ -40,4 +40,105 @@ const (
FilePerm = 0644
)
+var WebUrlMap = map[string]struct{}{
+ "/apps": {},
+ "/apps/all": {},
+ "/apps/installed": {},
+ "/apps/upgrade": {},
+
+ "/containers": {},
+ "/containers/container": {},
+ "/containers/image": {},
+ "/containers/network": {},
+ "/containers/volume": {},
+ "/containers/repo": {},
+ "/containers/compose": {},
+ "/containers/template": {},
+ "/containers/setting": {},
+
+ "/cronjobs": {},
+
+ "/databases": {},
+ "/databases/mysql": {},
+ "/databases/mysql/remote": {},
+ "/databases/postgresql": {},
+ "/databases/postgresql/remote": {},
+ "/databases/redis": {},
+ "/databases/redis/remote": {},
+
+ "/hosts": {},
+ "/hosts/files": {},
+ "/hosts/monitor/monitor": {},
+ "/hosts/monitor/setting": {},
+ "/hosts/terminal": {},
+ "/hosts/firewall/port": {},
+ "/hosts/firewall/forward": {},
+ "/hosts/firewall/ip": {},
+ "/hosts/process/process": {},
+ "/hosts/process/network": {},
+ "/hosts/ssh/ssh": {},
+ "/hosts/ssh/log": {},
+ "/hosts/ssh/session": {},
+
+ "/logs": {},
+ "/logs/operation": {},
+ "/logs/login": {},
+ "/logs/website": {},
+ "/logs/system": {},
+ "/logs/ssh": {},
+
+ "/settings": {},
+ "/settings/panel": {},
+ "/settings/backupaccount": {},
+ "/settings/license": {},
+ "/settings/about": {},
+ "/settings/safe": {},
+ "/settings/snapshot": {},
+ "/settings/expired": {},
+
+ "/toolbox": {},
+ "/toolbox/device": {},
+ "/toolbox/supervisor": {},
+ "/toolbox/clam": {},
+ "/toolbox/clam/setting": {},
+ "/toolbox/ftp": {},
+ "/toolbox/fail2ban": {},
+ "/toolbox/clean": {},
+
+ "/websites": {},
+ "/websites/ssl": {},
+ "/websites/runtimes/php": {},
+ "/websites/runtimes/node": {},
+ "/websites/runtimes/java": {},
+ "/websites/runtimes/go": {},
+ "/websites/runtimes/python": {},
+ "/websites/runtimes/dotnet": {},
+
+ "/login": {},
+
+ "/xpack": {},
+ "/xpack/waf/dashboard": {},
+ "/xpack/waf/global": {},
+ "/xpack/waf/websites": {},
+ "/xpack/waf/log": {},
+ "/xpack/waf/block": {},
+ "/xpack/monitor/dashboard": {},
+ "/xpack/monitor/setting": {},
+ "/xpack/monitor/rank": {},
+ "/xpack/monitor/log": {},
+ "/xpack/tamper": {},
+ "/xpack/gpu": {},
+ "/xpack/alert/dashboard": {},
+ "/xpack/alert/log": {},
+ "/xpack/alert/setting": {},
+ "/xpack/setting": {},
+}
+
+var DynamicRoutes = []string{
+ `^/containers/composeDetail/[^/]+$`,
+ `^/databases/mysql/setting/[^/]+/[^/]+$`,
+ `^/databases/postgresql/setting/[^/]+/[^/]+$`,
+ `^/websites/[^/]+/config/[^/]+$`,
+}
+
var CertStore atomic.Value
diff --git a/core/init/router/router.go b/core/init/router/router.go
index b40a11495..6712ae4ab 100644
--- a/core/init/router/router.go
+++ b/core/init/router/router.go
@@ -1,8 +1,15 @@
package router
import (
+ "encoding/base64"
"fmt"
+ "github.com/1Panel-dev/1Panel/core/app/service"
+ "github.com/1Panel-dev/1Panel/core/cmd/server/res"
+ "github.com/1Panel-dev/1Panel/core/constant"
"net/http"
+ "regexp"
+ "strconv"
+ "strings"
"github.com/1Panel-dev/1Panel/core/cmd/server/docs"
"github.com/1Panel-dev/1Panel/core/cmd/server/web"
@@ -21,8 +28,91 @@ var (
Router *gin.Engine
)
+func toIndexHtml(c *gin.Context) {
+ c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
+ c.Writer.WriteHeader(http.StatusOK)
+ _, _ = c.Writer.Write(web.IndexByte)
+ c.Writer.Flush()
+}
+
+func isEntrancePath(c *gin.Context) bool {
+ entrance := service.NewIAuthService().GetSecurityEntrance()
+ if entrance != "" && strings.TrimSuffix(c.Request.URL.Path, "/") == "/"+entrance {
+ return true
+ }
+ return false
+}
+
+func checkEntrance(c *gin.Context) bool {
+ authService := service.NewIAuthService()
+ entrance := authService.GetSecurityEntrance()
+ if entrance == "" {
+ return true
+ }
+
+ cookieValue, err := c.Cookie("SecurityEntrance")
+ if err != nil {
+ return false
+ }
+ entranceValue, err := base64.StdEncoding.DecodeString(cookieValue)
+ if err != nil {
+ return false
+ }
+ return string(entranceValue) == entrance
+}
+
+func handleNoRoute(c *gin.Context) {
+ resPage, err := service.NewIAuthService().GetResponsePage()
+ if err != nil {
+ c.String(http.StatusInternalServerError, "Internal Server Error")
+ return
+ }
+ if resPage == "444" {
+ c.String(444, "")
+ return
+ }
+
+ file := fmt.Sprintf("html/%s.html", resPage)
+ data, err := res.ErrorMsg.ReadFile(file)
+ if err != nil {
+ c.String(http.StatusInternalServerError, "Internal Server Error")
+ return
+ }
+ statusCode, err := strconv.Atoi(resPage)
+ if err != nil {
+ c.String(http.StatusInternalServerError, "Internal Server Error")
+ return
+ }
+ c.Data(statusCode, "text/html; charset=utf-8", data)
+}
+
+func isFrontendPath(c *gin.Context) bool {
+ reqUri := strings.TrimSuffix(c.Request.URL.Path, "/")
+ if _, ok := constant.WebUrlMap[reqUri]; ok {
+ return true
+ }
+ for _, route := range constant.DynamicRoutes {
+ if match, _ := regexp.MatchString(route, reqUri); match {
+ return true
+ }
+ }
+ return false
+}
+
+func checkFrontendPath(c *gin.Context) bool {
+ if !isFrontendPath(c) {
+ return false
+ }
+ authService := service.NewIAuthService()
+ if authService.GetSecurityEntrance() != "" {
+ return authService.IsLogin(c)
+ }
+ return true
+}
+
func setWebStatic(rootRouter *gin.RouterGroup) {
rootRouter.StaticFS("/public", http.FS(web.Favicon))
+ rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
rootRouter.Static("/api/v2/images", "./uploads")
rootRouter.Use(func(c *gin.Context) {
c.Next()
@@ -32,7 +122,23 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
staticServer := http.FileServer(http.FS(web.Assets))
staticServer.ServeHTTP(c.Writer, c.Request)
})
+ authService := service.NewIAuthService()
+ entrance := authService.GetSecurityEntrance()
+ if entrance != "" {
+ rootRouter.GET("/"+entrance, func(c *gin.Context) {
+ entrance = authService.GetSecurityEntrance()
+ if entrance == "" {
+ handleNoRoute(c)
+ return
+ }
+ toIndexHtml(c)
+ })
+ }
rootRouter.GET("/", func(c *gin.Context) {
+ if !checkEntrance(c) {
+ handleNoRoute(c)
+ return
+ }
staticServer := http.FileServer(http.FS(web.IndexHtml))
staticServer.ServeHTTP(c.Writer, c.Request)
})
@@ -69,10 +175,15 @@ func Routers() *gin.Engine {
}
Router.NoRoute(func(c *gin.Context) {
- c.Writer.WriteHeader(http.StatusOK)
- _, _ = c.Writer.Write(web.IndexByte)
- c.Writer.Header().Add("Accept", "text/html")
- c.Writer.Flush()
+ if checkFrontendPath(c) {
+ toIndexHtml(c)
+ return
+ }
+ if isEntrancePath(c) {
+ toIndexHtml(c)
+ return
+ }
+ handleNoRoute(c)
})
return Router
diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts
index a4063314c..2cb084a5f 100644
--- a/frontend/src/api/index.ts
+++ b/frontend/src/api/index.ts
@@ -53,22 +53,6 @@ class RequestHttp {
});
return Promise.reject(data);
}
- if (data.code == ResultEnum.NOTFOUND) {
- globalStore.errStatus = 'err-found';
- return;
- }
- if (data.code == ResultEnum.ERRIP) {
- globalStore.errStatus = 'err-ip';
- return;
- }
- if (data.code == ResultEnum.ERRDOMAIN) {
- globalStore.errStatus = 'err-domain';
- return;
- }
- if (data.code == ResultEnum.UNSAFETY) {
- globalStore.errStatus = 'err-unsafe';
- return;
- }
if (data.code == ResultEnum.EXPIRED) {
router.push({ name: 'Expired' });
return;
@@ -106,27 +90,6 @@ class RequestHttp {
if (error.message.indexOf('timeout') !== -1) MsgError('请求超时!请您稍后重试');
if (response) {
switch (response.status) {
- case 310:
- globalStore.errStatus = 'err-ip';
- router.push({
- name: 'entrance',
- params: { code: globalStore.entrance },
- });
- return;
- case 311:
- globalStore.errStatus = 'err-domain';
- router.push({
- name: 'entrance',
- params: { code: globalStore.entrance },
- });
- return;
- case 312:
- globalStore.errStatus = 'err-entrance';
- router.push({
- name: 'entrance',
- params: { code: globalStore.entrance },
- });
- return;
case 313:
router.push({ name: 'Expired' });
return;
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index cc7339e04..ff9715563 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -1638,6 +1638,8 @@ const message = {
error404: 'Not Found',
error408: 'Request Timeout',
error416: 'Range Not Satisfiable',
+ error444: 'Connection Closed',
+ error500: 'Internal Server Error',
https: 'Setting up HTTPS protocol access for the panel can enhance the security of panel access.',
certType: 'Certificate type',
diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts
index 9175d4c97..e1116f370 100644
--- a/frontend/src/lang/modules/tw.ts
+++ b/frontend/src/lang/modules/tw.ts
@@ -1606,6 +1606,8 @@ const message = {
error404: '未找到',
error408: '請求超時',
error416: '無效請求',
+ error444: '連線已關閉',
+ error500: '內部伺服器錯誤',
https: '為面板設置 https 協議訪問,提升面板訪問安全性',
certType: '證書類型',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 6e1b09004..b74a818f6 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -1604,6 +1604,8 @@ const message = {
error404: '未找到',
error408: '请求超时',
error416: '无效请求',
+ error444: '连接被关闭',
+ error500: '内部错误',
https: '为面板设置 https 协议访问,提升面板访问安全性',
certType: '证书类型',
diff --git a/frontend/src/routers/router.ts b/frontend/src/routers/router.ts
index fc319d367..314a57f3f 100644
--- a/frontend/src/routers/router.ts
+++ b/frontend/src/routers/router.ts
@@ -72,12 +72,6 @@ export const routes: RouteRecordRaw[] = [
key: 'login',
},
},
- {
- path: '/:code?',
- name: 'entrance',
- component: () => import('@/views/login/entrance/index.vue'),
- props: true,
- },
...routerArray,
{
path: '/:pathMatch(.*)',
diff --git a/frontend/src/views/login/entrance/index.vue b/frontend/src/views/login/entrance/index.vue
deleted file mode 100644
index af74d26af..000000000
--- a/frontend/src/views/login/entrance/index.vue
+++ /dev/null
@@ -1,186 +0,0 @@
-
-
-
-
-
-
-
- {{ globalStore.themeConfig.title || $t('setting.description') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/views/setting/safe/response/index.vue b/frontend/src/views/setting/safe/response/index.vue
index cdb2f8e67..b85d5325c 100644
--- a/frontend/src/views/setting/safe/response/index.vue
+++ b/frontend/src/views/setting/safe/response/index.vue
@@ -63,6 +63,14 @@ const options = [
value: '416',
label: '416 - ' + i18n.global.t('setting.error416'),
},
+ {
+ value: '444',
+ label: '444 - ' + i18n.global.t('setting.error444'),
+ },
+ {
+ value: '500',
+ label: '500 - ' + i18n.global.t('setting.error500'),
+ },
];
interface DialogProps {