diff --git a/agent/server/server.go b/agent/server/server.go index 66d395fee1c3..26eabb93ed52 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,64 @@ 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 +) + +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 +} + +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 +120,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/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..4f42c35a8805 --- /dev/null +++ b/core/middleware/helper.go @@ -0,0 +1,13 @@ +package middleware + +import "strings" + +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..e7cf8c3e3039 100644 --- a/core/router/ro_setting.go +++ b/core/router/ro_setting.go @@ -9,17 +9,18 @@ import ( type SettingRouter struct{} func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { - router := Router.Group("settings"). + baseApi := v2.ApiGroupApp.BaseApi + + authRouter := Router.Group("settings"). Use(middleware.SessionAuth()) + { + authRouter.POST("/search/base", baseApi.GetSettingBaseInfo) + } + 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 +39,14 @@ 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 := 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..cffba553fa52 --- /dev/null +++ b/frontend/src/utils/permission-codes.ts @@ -0,0 +1,13 @@ +export const toManageCode = (permission: string): string => { + if (!permission) { + return ''; + } + return permission.endsWith('_view') ? permission.replace(/_view$/, '_manage') : ''; +}; + +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)) {