From b56ddaa05709b87ac8fc3942869beb67f3d3d35f Mon Sep 17 00:00:00 2001 From: wanghe-fit2cloud Date: Tue, 19 May 2026 18:12:42 +0800 Subject: [PATCH 1/3] fix: harden agent socket and permission checks --- agent/server/server.go | 79 ++++++++++++++++++++++++-- agent/server/server_test.go | 79 ++++++++++++++++++++++++++ core/init/router/proxy.go | 13 ++--- core/middleware/demo_handle.go | 2 - core/middleware/helper.go | 24 ++++++++ core/middleware/operation.go | 15 +---- core/router/ro_setting.go | 31 ++++++---- frontend/src/store/modules/global.ts | 10 +--- frontend/src/utils/permission-codes.ts | 23 ++++++++ frontend/src/utils/permission.ts | 8 +-- 10 files changed, 232 insertions(+), 52 deletions(-) create mode 100644 agent/server/server_test.go create mode 100644 core/middleware/helper.go create mode 100644 frontend/src/utils/permission-codes.ts diff --git a/agent/server/server.go b/agent/server/server.go index 66d395fee1c3..5fcb4914db10 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -7,11 +7,11 @@ import ( "net" "net/http" "os" + "syscall" "github.com/gin-gonic/gin" "github.com/1Panel-dev/1Panel/agent/app/repo" - "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/cron" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/i18n" @@ -32,6 +32,71 @@ import ( "github.com/1Panel-dev/1Panel/agent/utils/re" ) +const ( + masterSocketDir = "/etc/1panel" + masterSocketPath = masterSocketDir + "/agent.sock" + masterSocketDirPerm = 0o700 + masterSocketFilePerm = 0o600 + masterSocketDirPermMask = 0o077 + masterSocketFilePermMask = 0o077 +) + +// prepareMasterSocketDir ensures the directory holding the master agent socket +// exists with locked-down permissions (0700) and is owned by the current +// process. Any other-readable/writable bits are stripped so that arbitrary +// local users cannot reach the socket through the directory. +func prepareMasterSocketDir(dir string) error { + if err := os.MkdirAll(dir, masterSocketDirPerm); err != nil { + return fmt.Errorf("create master socket dir %s failed: %w", dir, err) + } + if err := os.Chmod(dir, masterSocketDirPerm); err != nil { + return fmt.Errorf("chmod master socket dir %s failed: %w", dir, err) + } + info, err := os.Stat(dir) + if err != nil { + return fmt.Errorf("stat master socket dir %s failed: %w", dir, err) + } + if info.Mode().Perm()&masterSocketDirPermMask != 0 { + return fmt.Errorf("master socket dir %s permission %#o is too permissive", dir, info.Mode().Perm()) + } + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + if int(stat.Uid) != os.Geteuid() { + return fmt.Errorf( + "master socket dir %s owner uid %d does not match current process uid %d", + dir, stat.Uid, os.Geteuid(), + ) + } + } + return nil +} + +// secureMasterSocket ensures the unix domain socket has 0600 permissions and is +// owned by the current process. Without this, any local user able to traverse +// the parent directory could connect and hit privileged agent APIs. +func secureMasterSocket(sockPath string) error { + if err := os.Chmod(sockPath, masterSocketFilePerm); err != nil { + return fmt.Errorf("chmod master socket %s failed: %w", sockPath, err) + } + info, err := os.Stat(sockPath) + if err != nil { + return fmt.Errorf("stat master socket %s failed: %w", sockPath, err) + } + if info.Mode().Perm()&masterSocketFilePermMask != 0 { + return fmt.Errorf("master socket %s permission %#o is too permissive", sockPath, info.Mode().Perm()) + } + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return nil + } + if int(stat.Uid) != os.Geteuid() { + return fmt.Errorf( + "master socket %s owner uid %d does not match current process uid %d", + sockPath, stat.Uid, os.Geteuid(), + ) + } + return nil +} + func Start() { re.Init() viper.Init() @@ -62,12 +127,18 @@ func Start() { } if global.IsMaster { - _ = os.Remove("/etc/1panel/agent.sock") - _ = os.Mkdir("/etc/1panel", constant.DirPerm) - listener, err := net.Listen("unix", "/etc/1panel/agent.sock") + if err := prepareMasterSocketDir(masterSocketDir); err != nil { + panic(err) + } + _ = os.Remove(masterSocketPath) + listener, err := net.Listen("unix", masterSocketPath) if err != nil { panic(err) } + if err := secureMasterSocket(masterSocketPath); err != nil { + _ = listener.Close() + panic(err) + } business.Init() _ = server.Serve(listener) return diff --git a/agent/server/server_test.go b/agent/server/server_test.go new file mode 100644 index 000000000000..7acf9617f618 --- /dev/null +++ b/agent/server/server_test.go @@ -0,0 +1,79 @@ +package server + +import ( + "net" + "os" + "path/filepath" + "syscall" + "testing" +) + +// TestPrepareMasterSocketDir verifies the master socket directory is created +// with locked-down permissions and rejects directories owned by another user. +func TestPrepareMasterSocketDir_CreatesWithRestrictedPerm(t *testing.T) { + dir := filepath.Join(t.TempDir(), "1panel") + if err := prepareMasterSocketDir(dir); err != nil { + t.Fatalf("prepareMasterSocketDir failed: %v", err) + } + + info, err := os.Stat(dir) + if err != nil { + t.Fatalf("stat dir failed: %v", err) + } + if perm := info.Mode().Perm(); perm != masterSocketDirPerm { + t.Fatalf("expected dir perm %#o, got %#o", masterSocketDirPerm, perm) + } +} + +func TestPrepareMasterSocketDir_RejectsWorldReadable(t *testing.T) { + dir := filepath.Join(t.TempDir(), "1panel") + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("mkdir failed: %v", err) + } + + if err := prepareMasterSocketDir(dir); err != nil { + // prepareMasterSocketDir Chmods the dir, so it should succeed. + t.Fatalf("prepareMasterSocketDir failed: %v", err) + } + info, err := os.Stat(dir) + if err != nil { + t.Fatalf("stat dir failed: %v", err) + } + if perm := info.Mode().Perm(); perm != masterSocketDirPerm { + t.Fatalf("expected dir perm %#o after chmod, got %#o", masterSocketDirPerm, perm) + } +} + +// TestSecureMasterSocket verifies the helper enforces 0600 + matching uid on a +// freshly-created unix socket. +func TestSecureMasterSocket(t *testing.T) { + dir := t.TempDir() + sockPath := filepath.Join(dir, "agent.sock") + + listener, err := net.Listen("unix", sockPath) + if err != nil { + t.Fatalf("listen unix failed: %v", err) + } + defer listener.Close() + + // The socket starts with whatever umask leaves; secureMasterSocket should + // always force 0600. + if err := secureMasterSocket(sockPath); err != nil { + t.Fatalf("secureMasterSocket failed: %v", err) + } + + info, err := os.Stat(sockPath) + if err != nil { + t.Fatalf("stat sock failed: %v", err) + } + if perm := info.Mode().Perm(); perm != masterSocketFilePerm { + t.Fatalf("expected sock perm %#o, got %#o", masterSocketFilePerm, perm) + } + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + t.Fatalf("expected *syscall.Stat_t, got %T", info.Sys()) + } + if int(stat.Uid) != os.Geteuid() { + t.Fatalf("expected uid=%d, got uid=%d", os.Geteuid(), stat.Uid) + } +} diff --git a/core/init/router/proxy.go b/core/init/router/proxy.go index 030e3de7b989..0fd7f6fb46b0 100644 --- a/core/init/router/proxy.go +++ b/core/init/router/proxy.go @@ -11,6 +11,7 @@ import ( "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/init/proxy" psessionUtils "github.com/1Panel-dev/1Panel/core/init/session/psession" + "github.com/1Panel-dev/1Panel/core/middleware" "github.com/1Panel-dev/1Panel/core/utils/xpack" "github.com/gin-gonic/gin" ) @@ -18,11 +19,7 @@ import ( func Proxy() gin.HandlerFunc { return func(c *gin.Context) { reqPath := c.Request.URL.Path - if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(c.Request.URL.Path, "/api/v2") { - c.Next() - return - } - if strings.HasPrefix(reqPath, "/api/v2/core") && !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/xpack") { + if !middleware.ShouldProxyToAgent(reqPath) { c.Next() return } @@ -41,19 +38,19 @@ func Proxy() gin.HandlerFunc { apiReq := c.GetBool("API_AUTH") - if !apiReq && strings.HasPrefix(c.Request.URL.Path, "/api/v2/") && !isLocalAPI(c.Request.URL.Path) && !isPublicFileShareAPI(c.Request.URL.Path) && !checkSession(c) { + if !apiReq && !isLocalAPI(reqPath) && !isPublicFileShareAPI(reqPath) && !checkSession(c) { data, _ := res.ErrorMsg.ReadFile("html/401.html") c.Data(401, "text/html; charset=utf-8", data) c.Abort() return } - if c.Request.URL.Path == "/api/v2/hosts/terminal" && (currentNode == "local" || len(currentNode) == 0) { + if reqPath == "/api/v2/hosts/terminal" && (currentNode == "local" || len(currentNode) == 0) { proxyLocalAgent(c) return } - if !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0) { + if !strings.HasPrefix(reqPath, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0) { proxyLocalAgent(c) return } diff --git a/core/middleware/demo_handle.go b/core/middleware/demo_handle.go index 532d75f8f36b..431bc2b2c727 100644 --- a/core/middleware/demo_handle.go +++ b/core/middleware/demo_handle.go @@ -13,7 +13,6 @@ var whiteUrlList = map[string]struct{}{ "/api/v2/dashboard/app/launcher/option": {}, "/api/v2/websites/config": {}, "/api/v2/websites/waf/config": {}, - "/api/v2/files/loadfile": {}, "/api/v2/files/size": {}, "/api/v2/runtimes/sync": {}, "/api/v2/toolbox/device/base": {}, @@ -21,7 +20,6 @@ var whiteUrlList = map[string]struct{}{ "/api/v2/files/mount": {}, "/api/v2/hosts/ssh/log": {}, "/api/v2/toolbox/clam/base": {}, - "/api/v2/hosts/too": {}, "/api/v2/backups/record/size": {}, "/api/v2/core/auth/login": {}, diff --git a/core/middleware/helper.go b/core/middleware/helper.go new file mode 100644 index 000000000000..c3d0eef24552 --- /dev/null +++ b/core/middleware/helper.go @@ -0,0 +1,24 @@ +package middleware + +import "strings" + +// ShouldProxyToAgent reports whether a request will eventually be forwarded to +// an agent (local or remote) instead of being handled by core itself. This is +// the single source of truth for the router-level Proxy gate and the +// operation-log middleware that needs to decide whether to ask the agent to +// resolve operation metadata. +// +// Rules: +// - Non-`/api/v2/...` (including swagger UI assets) is always handled by core. +// - `/api/v2/core/...` is handled by core, except `/api/v2/core/xpack/...` +// which is allowed to be re-routed to the active node. +// - Everything else is forwarded to the agent. +func ShouldProxyToAgent(reqPath string) bool { + if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(reqPath, "/api/v2") { + return false + } + if strings.HasPrefix(reqPath, "/api/v2/core") && !strings.HasPrefix(reqPath, "/api/v2/core/xpack") { + return false + } + return true +} diff --git a/core/middleware/operation.go b/core/middleware/operation.go index a4b889631983..bf3fab64bf85 100644 --- a/core/middleware/operation.go +++ b/core/middleware/operation.go @@ -90,7 +90,7 @@ func OperationLog() gin.HandlerFunc { } } needAgentResolve := len(operationDic.BeforeFunctions) != 0 && len(currentNode) != 0 && currentNode != "local" && !strings.HasPrefix(record.Path, "/core") - allowCoreFallback := strings.HasPrefix(record.Path, "/core/xpack") || !willProxy(c.Request.URL.Path, currentNode) || len(currentNode) == 0 || currentNode == "local" + allowCoreFallback := strings.HasPrefix(record.Path, "/core/xpack") || !ShouldProxyToAgent(c.Request.URL.Path) || len(currentNode) == 0 || currentNode == "local" if needAgentResolve { c.Request.Header.Set(headerNeedOperationResolve, "1") defer func() { @@ -403,16 +403,3 @@ func hasAllResolvedData(values map[string]interface{}, beforeFunctions []functio } return true } - -func willProxy(reqPath, currentNode string) bool { - if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(reqPath, "/api/v2") { - return false - } - if strings.HasPrefix(reqPath, "/api/v2/core") && !strings.HasPrefix(reqPath, "/api/v2/core/xpack") { - return false - } - if !strings.HasPrefix(reqPath, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0) { - return true - } - return true -} diff --git a/core/router/ro_setting.go b/core/router/ro_setting.go index 7aad0985643a..e81fad6a6d04 100644 --- a/core/router/ro_setting.go +++ b/core/router/ro_setting.go @@ -9,17 +9,22 @@ import ( type SettingRouter struct{} func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { - router := Router.Group("settings"). + baseApi := v2.ApiGroupApp.BaseApi + + // authRouter: routes that require an authenticated session but no further + // password-expired enforcement. + authRouter := Router.Group("settings"). Use(middleware.SessionAuth()) + { + authRouter.POST("/search/base", baseApi.GetSettingBaseInfo) + } + + // settingRouter: routes that require both authentication and a non-expired + // password. This is the default tier for the settings module. settingRouter := Router.Group("settings"). Use(middleware.SessionAuth()). Use(middleware.PasswordExpired()) - - noAuthRouter := Router.Group("settings") - baseApi := v2.ApiGroupApp.BaseApi { - router.POST("/search/base", baseApi.GetSettingBaseInfo) - settingRouter.POST("/search", baseApi.GetSettingInfo) settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo) settingRouter.GET("/search/available", baseApi.GetSystemAvailable) @@ -38,13 +43,19 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { settingRouter.POST("/upgrade/notes", baseApi.GetNotesByVersion) settingRouter.GET("/upgrade/releases", baseApi.LoadRelease) settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo) - - noAuthRouter.POST("/ssl/reload", baseApi.ReloadSSL) - settingRouter.POST("/apps/store/update", baseApi.UpdateAppstoreConfig) settingRouter.GET("/apps/store/config", baseApi.GetAppstoreConfig) - settingRouter.GET("/memo", baseApi.GetMemo) settingRouter.POST("/memo", baseApi.UpdateMemo) } + + // internalRouter: routes that bypass session auth on purpose because the + // caller is the local node itself (e.g. the agent calling back into core + // after writing the system SSL files). The handler is responsible for + // verifying that the caller is local; do NOT add new endpoints here + // without local-only enforcement. + internalRouter := Router.Group("settings") + { + internalRouter.POST("/ssl/reload", baseApi.ReloadSSL) + } } diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts index c1e332217c32..9da77b20a120 100644 --- a/frontend/src/store/modules/global.ts +++ b/frontend/src/store/modules/global.ts @@ -3,6 +3,7 @@ import piniaPersistConfig from '@/config/pinia-persist'; import { GlobalState } from '../interface'; import { DeviceType } from '@/enums/app'; import i18n, { setActiveLocale } from '@/lang'; +import { toManageCode } from '@/utils/permission-codes'; const CN_DOCS_URL = 'https://1panel.cn/docs/v2'; const INTL_DOCS_URL = 'https://docs.1panel.pro/v2'; @@ -130,7 +131,7 @@ const GlobalStore = defineStore({ if (this.permissions.includes(normalizedPermission)) { return true; } - const managePermission = getManagePermission(normalizedPermission); + const managePermission = toManageCode(normalizedPermission); return managePermission ? this.permissions.includes(managePermission) : false; }, async updateLanguage(language: string) { @@ -149,10 +150,3 @@ const GlobalStore = defineStore({ }); export default GlobalStore; - -function getManagePermission(permission: string) { - if (permission.endsWith('_view')) { - return permission.replace(/_view$/, '_manage'); - } - return ''; -} diff --git a/frontend/src/utils/permission-codes.ts b/frontend/src/utils/permission-codes.ts new file mode 100644 index 000000000000..1b8db4d0b04d --- /dev/null +++ b/frontend/src/utils/permission-codes.ts @@ -0,0 +1,23 @@ +/** + * Returns the manage-flavoured code that corresponds to a `_view` permission. + * Returns an empty string if the input is not a `_view` code (including empty + * input), so callers can decide whether to fall back to the original code. + */ +export const toManageCode = (permission: string): string => { + if (!permission) { + return ''; + } + return permission.endsWith('_view') ? permission.replace(/_view$/, '_manage') : ''; +}; + +/** + * Normalises a single permission code to its manage flavour. `_view` codes are + * upgraded to `_manage`; manage/other codes are kept as-is. Empty input is + * preserved so existing filters behave the same. + */ +export const normalizeToManageCode = (permission: string): string => { + if (!permission) { + return ''; + } + return toManageCode(permission) || permission; +}; diff --git a/frontend/src/utils/permission.ts b/frontend/src/utils/permission.ts index b0496957ac2f..8477a473679d 100644 --- a/frontend/src/utils/permission.ts +++ b/frontend/src/utils/permission.ts @@ -1,5 +1,6 @@ import router from '@/routers'; import { GlobalStore } from '@/store'; +import { normalizeToManageCode } from '@/utils/permission-codes'; export type PermissionBindingValue = string | string[] | undefined; export type PermissionMode = 'manage' | 'view'; @@ -27,12 +28,7 @@ const getRoutePermission = (): PermissionBindingValue => { return ''; }; -export const toManagePermission = (permission: string) => { - if (!permission) { - return ''; - } - return permission.endsWith('_view') ? permission.replace(/_view$/, '_manage') : permission; -}; +export const toManagePermission = normalizeToManageCode; export const toPermissionList = (value: PermissionBindingValue) => { if (Array.isArray(value)) { From 88aa18c34700461b812ef3015745bd7d973afa98 Mon Sep 17 00:00:00 2001 From: wanghe-fit2cloud Date: Tue, 19 May 2026 18:15:22 +0800 Subject: [PATCH 2/3] chore: remove agent socket test --- agent/server/server_test.go | 79 ------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 agent/server/server_test.go diff --git a/agent/server/server_test.go b/agent/server/server_test.go deleted file mode 100644 index 7acf9617f618..000000000000 --- a/agent/server/server_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package server - -import ( - "net" - "os" - "path/filepath" - "syscall" - "testing" -) - -// TestPrepareMasterSocketDir verifies the master socket directory is created -// with locked-down permissions and rejects directories owned by another user. -func TestPrepareMasterSocketDir_CreatesWithRestrictedPerm(t *testing.T) { - dir := filepath.Join(t.TempDir(), "1panel") - if err := prepareMasterSocketDir(dir); err != nil { - t.Fatalf("prepareMasterSocketDir failed: %v", err) - } - - info, err := os.Stat(dir) - if err != nil { - t.Fatalf("stat dir failed: %v", err) - } - if perm := info.Mode().Perm(); perm != masterSocketDirPerm { - t.Fatalf("expected dir perm %#o, got %#o", masterSocketDirPerm, perm) - } -} - -func TestPrepareMasterSocketDir_RejectsWorldReadable(t *testing.T) { - dir := filepath.Join(t.TempDir(), "1panel") - if err := os.MkdirAll(dir, 0o755); err != nil { - t.Fatalf("mkdir failed: %v", err) - } - - if err := prepareMasterSocketDir(dir); err != nil { - // prepareMasterSocketDir Chmods the dir, so it should succeed. - t.Fatalf("prepareMasterSocketDir failed: %v", err) - } - info, err := os.Stat(dir) - if err != nil { - t.Fatalf("stat dir failed: %v", err) - } - if perm := info.Mode().Perm(); perm != masterSocketDirPerm { - t.Fatalf("expected dir perm %#o after chmod, got %#o", masterSocketDirPerm, perm) - } -} - -// TestSecureMasterSocket verifies the helper enforces 0600 + matching uid on a -// freshly-created unix socket. -func TestSecureMasterSocket(t *testing.T) { - dir := t.TempDir() - sockPath := filepath.Join(dir, "agent.sock") - - listener, err := net.Listen("unix", sockPath) - if err != nil { - t.Fatalf("listen unix failed: %v", err) - } - defer listener.Close() - - // The socket starts with whatever umask leaves; secureMasterSocket should - // always force 0600. - if err := secureMasterSocket(sockPath); err != nil { - t.Fatalf("secureMasterSocket failed: %v", err) - } - - info, err := os.Stat(sockPath) - if err != nil { - t.Fatalf("stat sock failed: %v", err) - } - if perm := info.Mode().Perm(); perm != masterSocketFilePerm { - t.Fatalf("expected sock perm %#o, got %#o", masterSocketFilePerm, perm) - } - stat, ok := info.Sys().(*syscall.Stat_t) - if !ok { - t.Fatalf("expected *syscall.Stat_t, got %T", info.Sys()) - } - if int(stat.Uid) != os.Geteuid() { - t.Fatalf("expected uid=%d, got uid=%d", os.Geteuid(), stat.Uid) - } -} From 2075340ca4833cfcb2970c2862cef01413096009 Mon Sep 17 00:00:00 2001 From: wanghe-fit2cloud Date: Tue, 19 May 2026 18:47:31 +0800 Subject: [PATCH 3/3] chore: remove redundant comments --- agent/server/server.go | 7 ------- core/middleware/helper.go | 11 ----------- core/router/ro_setting.go | 9 --------- frontend/src/utils/permission-codes.ts | 10 ---------- 4 files changed, 37 deletions(-) diff --git a/agent/server/server.go b/agent/server/server.go index 5fcb4914db10..26eabb93ed52 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -41,10 +41,6 @@ const ( masterSocketFilePermMask = 0o077 ) -// prepareMasterSocketDir ensures the directory holding the master agent socket -// exists with locked-down permissions (0700) and is owned by the current -// process. Any other-readable/writable bits are stripped so that arbitrary -// local users cannot reach the socket through the directory. func prepareMasterSocketDir(dir string) error { if err := os.MkdirAll(dir, masterSocketDirPerm); err != nil { return fmt.Errorf("create master socket dir %s failed: %w", dir, err) @@ -70,9 +66,6 @@ func prepareMasterSocketDir(dir string) error { return nil } -// secureMasterSocket ensures the unix domain socket has 0600 permissions and is -// owned by the current process. Without this, any local user able to traverse -// the parent directory could connect and hit privileged agent APIs. func secureMasterSocket(sockPath string) error { if err := os.Chmod(sockPath, masterSocketFilePerm); err != nil { return fmt.Errorf("chmod master socket %s failed: %w", sockPath, err) diff --git a/core/middleware/helper.go b/core/middleware/helper.go index c3d0eef24552..4f42c35a8805 100644 --- a/core/middleware/helper.go +++ b/core/middleware/helper.go @@ -2,17 +2,6 @@ package middleware import "strings" -// ShouldProxyToAgent reports whether a request will eventually be forwarded to -// an agent (local or remote) instead of being handled by core itself. This is -// the single source of truth for the router-level Proxy gate and the -// operation-log middleware that needs to decide whether to ask the agent to -// resolve operation metadata. -// -// Rules: -// - Non-`/api/v2/...` (including swagger UI assets) is always handled by core. -// - `/api/v2/core/...` is handled by core, except `/api/v2/core/xpack/...` -// which is allowed to be re-routed to the active node. -// - Everything else is forwarded to the agent. func ShouldProxyToAgent(reqPath string) bool { if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(reqPath, "/api/v2") { return false diff --git a/core/router/ro_setting.go b/core/router/ro_setting.go index e81fad6a6d04..e7cf8c3e3039 100644 --- a/core/router/ro_setting.go +++ b/core/router/ro_setting.go @@ -11,16 +11,12 @@ type SettingRouter struct{} func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { baseApi := v2.ApiGroupApp.BaseApi - // authRouter: routes that require an authenticated session but no further - // password-expired enforcement. authRouter := Router.Group("settings"). Use(middleware.SessionAuth()) { authRouter.POST("/search/base", baseApi.GetSettingBaseInfo) } - // settingRouter: routes that require both authentication and a non-expired - // password. This is the default tier for the settings module. settingRouter := Router.Group("settings"). Use(middleware.SessionAuth()). Use(middleware.PasswordExpired()) @@ -49,11 +45,6 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { settingRouter.POST("/memo", baseApi.UpdateMemo) } - // internalRouter: routes that bypass session auth on purpose because the - // caller is the local node itself (e.g. the agent calling back into core - // after writing the system SSL files). The handler is responsible for - // verifying that the caller is local; do NOT add new endpoints here - // without local-only enforcement. internalRouter := Router.Group("settings") { internalRouter.POST("/ssl/reload", baseApi.ReloadSSL) diff --git a/frontend/src/utils/permission-codes.ts b/frontend/src/utils/permission-codes.ts index 1b8db4d0b04d..cffba553fa52 100644 --- a/frontend/src/utils/permission-codes.ts +++ b/frontend/src/utils/permission-codes.ts @@ -1,8 +1,3 @@ -/** - * Returns the manage-flavoured code that corresponds to a `_view` permission. - * Returns an empty string if the input is not a `_view` code (including empty - * input), so callers can decide whether to fall back to the original code. - */ export const toManageCode = (permission: string): string => { if (!permission) { return ''; @@ -10,11 +5,6 @@ export const toManageCode = (permission: string): string => { return permission.endsWith('_view') ? permission.replace(/_view$/, '_manage') : ''; }; -/** - * Normalises a single permission code to its manage flavour. `_view` codes are - * upgraded to `_manage`; manage/other codes are kept as-is. Empty input is - * preserved so existing filters behave the same. - */ export const normalizeToManageCode = (permission: string): string => { if (!permission) { return '';