mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-12 00:16:37 +08:00
feat(system): add session check for agent api (#8258)
This commit is contained in:
parent
732bbd8951
commit
220b6223a3
11 changed files with 356 additions and 278 deletions
|
@ -2,10 +2,16 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/utils/security"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
||||||
|
@ -38,6 +44,13 @@ func Proxy() gin.HandlerFunc {
|
||||||
currentNode = c.Request.Header.Get("CurrentNode")
|
currentNode = c.Request.Header.Get("CurrentNode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/") && !checkSession(c) {
|
||||||
|
data, _ := res.ErrorMsg.ReadFile("html/401.html")
|
||||||
|
c.Data(401, "text/html; charset=utf-8", data)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0 || currentNode == "127.0.0.1") {
|
if !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0 || currentNode == "127.0.0.1") {
|
||||||
sockPath := "/etc/1panel/agent.sock"
|
sockPath := "/etc/1panel/agent.sock"
|
||||||
if _, err := os.Stat(sockPath); err != nil {
|
if _, err := os.Stat(sockPath); err != nil {
|
||||||
|
@ -58,6 +71,13 @@ func Proxy() gin.HandlerFunc {
|
||||||
req.URL.Host = "unix"
|
req.URL.Host = "unix"
|
||||||
},
|
},
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
|
ModifyResponse: func(response *http.Response) error {
|
||||||
|
if response.StatusCode == 404 {
|
||||||
|
security.HandleNotSecurity(c, "")
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
proxy.ServeHTTP(c.Writer, c.Request)
|
proxy.ServeHTTP(c.Writer, c.Request)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
|
@ -67,3 +87,22 @@ func Proxy() gin.HandlerFunc {
|
||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkSession(c *gin.Context) bool {
|
||||||
|
psession, err := global.SESSION.Get(c)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
settingRepo := repo.NewISettingRepo()
|
||||||
|
setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout"))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
lifeTime, _ := strconv.Atoi(setting.Value)
|
||||||
|
httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL"))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ = global.SESSION.Set(c, psession, httpsSetting.Value == constant.StatusEnable, lifeTime)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -3,157 +3,26 @@ package router
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/service"
|
"github.com/1Panel-dev/1Panel/core/app/service"
|
||||||
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/constant"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/cmd/server/docs"
|
"github.com/1Panel-dev/1Panel/core/cmd/server/docs"
|
||||||
"github.com/1Panel-dev/1Panel/core/cmd/server/web"
|
"github.com/1Panel-dev/1Panel/core/cmd/server/web"
|
||||||
"github.com/1Panel-dev/1Panel/core/global"
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
"github.com/1Panel-dev/1Panel/core/i18n"
|
"github.com/1Panel-dev/1Panel/core/i18n"
|
||||||
"github.com/1Panel-dev/1Panel/core/middleware"
|
"github.com/1Panel-dev/1Panel/core/middleware"
|
||||||
rou "github.com/1Panel-dev/1Panel/core/router"
|
rou "github.com/1Panel-dev/1Panel/core/router"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/utils/security"
|
||||||
"github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
swaggerfiles "github.com/swaggo/files"
|
swaggerfiles "github.com/swaggo/files"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Router *gin.Engine
|
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, resType string) {
|
|
||||||
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)
|
|
||||||
if resPage == "200" && resType != "" {
|
|
||||||
file = fmt.Sprintf("html/200_%s.html", resType)
|
|
||||||
}
|
|
||||||
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 checkBindDomain(c *gin.Context) bool {
|
|
||||||
settingRepo := repo.NewISettingRepo()
|
|
||||||
status, _ := settingRepo.Get(repo.WithByKey("BindDomain"))
|
|
||||||
if len(status.Value) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
domains := c.Request.Host
|
|
||||||
parts := strings.Split(c.Request.Host, ":")
|
|
||||||
if len(parts) > 0 {
|
|
||||||
domains = parts[0]
|
|
||||||
}
|
|
||||||
return domains == status.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIPLimit(c *gin.Context) bool {
|
|
||||||
settingRepo := repo.NewISettingRepo()
|
|
||||||
status, _ := settingRepo.Get(repo.WithByKey("AllowIPs"))
|
|
||||||
if len(status.Value) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
clientIP := c.ClientIP()
|
|
||||||
for _, ip := range strings.Split(status.Value, ",") {
|
|
||||||
if len(ip) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ip == clientIP || (strings.Contains(ip, "/") && common.CheckIpInCidr(ip, clientIP)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSession(c *gin.Context) bool {
|
|
||||||
_, err := global.SESSION.Get(c)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setWebStatic(rootRouter *gin.RouterGroup) {
|
func setWebStatic(rootRouter *gin.RouterGroup) {
|
||||||
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
rootRouter.StaticFS("/public", http.FS(web.Favicon))
|
||||||
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
|
rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon))
|
||||||
|
@ -172,23 +41,14 @@ func setWebStatic(rootRouter *gin.RouterGroup) {
|
||||||
rootRouter.GET("/"+entrance, func(c *gin.Context) {
|
rootRouter.GET("/"+entrance, func(c *gin.Context) {
|
||||||
currentEntrance := authService.GetSecurityEntrance()
|
currentEntrance := authService.GetSecurityEntrance()
|
||||||
if currentEntrance != entrance {
|
if currentEntrance != entrance {
|
||||||
handleNoRoute(c, "")
|
security.HandleNotSecurity(c, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toIndexHtml(c)
|
security.ToIndexHtml(c)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
rootRouter.GET("/", func(c *gin.Context) {
|
rootRouter.GET("/", func(c *gin.Context) {
|
||||||
if !checkEntrance(c) && !checkSession(c) {
|
if !security.CheckSecurity(c) {
|
||||||
handleNoRoute(c, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkBindDomain(c) {
|
|
||||||
handleNoRoute(c, "err_domain")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkIPLimit(c) {
|
|
||||||
handleNoRoute(c, "err_ip_limit")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entrance = authService.GetSecurityEntrance()
|
entrance = authService.GetSecurityEntrance()
|
||||||
|
@ -229,26 +89,13 @@ func Routers() *gin.Engine {
|
||||||
router.InitRouter(PrivateGroup)
|
router.InitRouter(PrivateGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
Router.Use(middleware.SessionAuth())
|
Router.Use(middleware.ApiAuth())
|
||||||
Router.Use(Proxy())
|
Router.Use(Proxy())
|
||||||
Router.NoRoute(func(c *gin.Context) {
|
Router.NoRoute(func(c *gin.Context) {
|
||||||
if !checkBindDomain(c) {
|
if !security.HandleNotRoute(c) {
|
||||||
handleNoRoute(c, "err_domain")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !checkIPLimit(c) {
|
security.HandleNotSecurity(c, "")
|
||||||
handleNoRoute(c, "err_ip_limit")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if checkFrontendPath(c) {
|
|
||||||
toIndexHtml(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isEntrancePath(c) {
|
|
||||||
toIndexHtml(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handleNoRoute(c, "")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return Router
|
return Router
|
||||||
|
|
117
core/middleware/api_auth.go
Normal file
117
core/middleware/api_auth.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApiAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panelToken := c.GetHeader("1Panel-Token")
|
||||||
|
panelTimestamp := c.GetHeader("1Panel-Timestamp")
|
||||||
|
if panelToken != "" || panelTimestamp != "" {
|
||||||
|
if global.Api.ApiInterfaceStatus == constant.StatusEnable {
|
||||||
|
clientIP := c.ClientIP()
|
||||||
|
if !isValid1PanelTimestamp(panelTimestamp) {
|
||||||
|
helper.BadAuth(c, "ErrApiConfigKeyTimeInvalid", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !isValid1PanelToken(panelToken, panelTimestamp) {
|
||||||
|
helper.BadAuth(c, "ErrApiConfigKeyInvalid", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isIPInWhiteList(clientIP) {
|
||||||
|
helper.BadAuth(c, "ErrApiConfigIPInvalid", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
helper.BadAuth(c, "ErrApiConfigStatusInvalid", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValid1PanelTimestamp(panelTimestamp string) bool {
|
||||||
|
apiKeyValidityTime := global.Api.ApiKeyValidityTime
|
||||||
|
apiTime, err := strconv.Atoi(apiKeyValidityTime)
|
||||||
|
if err != nil || apiTime < 0 {
|
||||||
|
global.LOG.Errorf("apiTime %d, err: %v", apiTime, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if apiTime == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
panelTime, err := strconv.ParseInt(panelTimestamp, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("panelTimestamp %s, panelTime %d, apiTime %d, err: %v", panelTimestamp, apiTime, panelTime, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
nowTime := time.Now().Unix()
|
||||||
|
tolerance := int64(60)
|
||||||
|
if panelTime > nowTime+tolerance {
|
||||||
|
global.LOG.Errorf("Valid Panel Timestamp, apiTime %d, panelTime %d, nowTime %d, err: %v", apiTime, panelTime, nowTime, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return nowTime-panelTime <= int64(apiTime)*60+tolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValid1PanelToken(panelToken string, panelTimestamp string) bool {
|
||||||
|
system1PanelToken := global.Api.ApiKey
|
||||||
|
return panelToken == GenerateMD5("1panel"+system1PanelToken+panelTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIPInWhiteList(clientIP string) bool {
|
||||||
|
ipWhiteString := global.Api.IpWhiteList
|
||||||
|
if len(ipWhiteString) == 0 {
|
||||||
|
global.LOG.Error("IP whitelist is empty")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ipWhiteList, ipErr := common.HandleIPList(ipWhiteString)
|
||||||
|
if ipErr != nil {
|
||||||
|
global.LOG.Errorf("Failed to handle IP list: %v", ipErr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
clientParsedIP := net.ParseIP(clientIP)
|
||||||
|
if clientParsedIP == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iPv4 := clientParsedIP.To4()
|
||||||
|
iPv6 := clientParsedIP.To16()
|
||||||
|
for _, cidr := range ipWhiteList {
|
||||||
|
if (iPv4 != nil && (cidr == "0.0.0.0" || cidr == "0.0.0.0/0" || iPv4.String() == cidr)) || (iPv6 != nil && (cidr == "::/0" || iPv6.String() == cidr)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ipNet, err := net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (iPv4 != nil && ipNet.Contains(iPv4)) || (iPv6 != nil && ipNet.Contains(iPv6)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateMD5(param string) string {
|
||||||
|
hash := md5.New()
|
||||||
|
hash.Write([]byte(param))
|
||||||
|
return hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
|
@ -1,19 +1,13 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
||||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/core/constant"
|
"github.com/1Panel-dev/1Panel/core/constant"
|
||||||
"github.com/1Panel-dev/1Panel/core/global"
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SessionAuth() gin.HandlerFunc {
|
func SessionAuth() gin.HandlerFunc {
|
||||||
|
@ -23,32 +17,6 @@ func SessionAuth() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
panelToken := c.GetHeader("1Panel-Token")
|
|
||||||
panelTimestamp := c.GetHeader("1Panel-Timestamp")
|
|
||||||
if panelToken != "" || panelTimestamp != "" {
|
|
||||||
if global.Api.ApiInterfaceStatus == constant.StatusEnable {
|
|
||||||
clientIP := c.ClientIP()
|
|
||||||
if !isValid1PanelTimestamp(panelTimestamp) {
|
|
||||||
helper.BadAuth(c, "ErrApiConfigKeyTimeInvalid", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !isValid1PanelToken(panelToken, panelTimestamp) {
|
|
||||||
helper.BadAuth(c, "ErrApiConfigKeyInvalid", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isIPInWhiteList(clientIP) {
|
|
||||||
helper.BadAuth(c, "ErrApiConfigIPInvalid", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
helper.BadAuth(c, "ErrApiConfigStatusInvalid", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
psession, err := global.SESSION.Get(c)
|
psession, err := global.SESSION.Get(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.BadAuth(c, "ErrNotLogin", err)
|
helper.BadAuth(c, "ErrNotLogin", err)
|
||||||
|
@ -70,70 +38,3 @@ func SessionAuth() gin.HandlerFunc {
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValid1PanelTimestamp(panelTimestamp string) bool {
|
|
||||||
apiKeyValidityTime := global.Api.ApiKeyValidityTime
|
|
||||||
apiTime, err := strconv.Atoi(apiKeyValidityTime)
|
|
||||||
if err != nil || apiTime < 0 {
|
|
||||||
global.LOG.Errorf("apiTime %d, err: %v", apiTime, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if apiTime == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
panelTime, err := strconv.ParseInt(panelTimestamp, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("panelTimestamp %s, panelTime %d, apiTime %d, err: %v", panelTimestamp, apiTime, panelTime, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
nowTime := time.Now().Unix()
|
|
||||||
tolerance := int64(60)
|
|
||||||
if panelTime > nowTime+tolerance {
|
|
||||||
global.LOG.Errorf("Valid Panel Timestamp, apiTime %d, panelTime %d, nowTime %d, err: %v", apiTime, panelTime, nowTime, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return nowTime-panelTime <= int64(apiTime)*60+tolerance
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValid1PanelToken(panelToken string, panelTimestamp string) bool {
|
|
||||||
system1PanelToken := global.Api.ApiKey
|
|
||||||
return panelToken == GenerateMD5("1panel"+system1PanelToken+panelTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIPInWhiteList(clientIP string) bool {
|
|
||||||
ipWhiteString := global.Api.IpWhiteList
|
|
||||||
if len(ipWhiteString) == 0 {
|
|
||||||
global.LOG.Error("IP whitelist is empty")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ipWhiteList, ipErr := common.HandleIPList(ipWhiteString)
|
|
||||||
if ipErr != nil {
|
|
||||||
global.LOG.Errorf("Failed to handle IP list: %v", ipErr)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
clientParsedIP := net.ParseIP(clientIP)
|
|
||||||
if clientParsedIP == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
iPv4 := clientParsedIP.To4()
|
|
||||||
iPv6 := clientParsedIP.To16()
|
|
||||||
for _, cidr := range ipWhiteList {
|
|
||||||
if (iPv4 != nil && (cidr == "0.0.0.0" || cidr == "0.0.0.0/0" || iPv4.String() == cidr)) || (iPv6 != nil && (cidr == "::/0" || iPv6.String() == cidr)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
_, ipNet, err := net.ParseCIDR(cidr)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (iPv4 != nil && ipNet.Contains(iPv4)) || (iPv6 != nil && ipNet.Contains(iPv6)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateMD5(param string) string {
|
|
||||||
hash := md5.New()
|
|
||||||
hash.Write([]byte(param))
|
|
||||||
return hex.EncodeToString(hash.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
176
core/utils/security/security.go
Normal file
176
core/utils/security/security.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package security
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/app/service"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/cmd/server/web"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleNotRoute(c *gin.Context) bool {
|
||||||
|
if !checkBindDomain(c) {
|
||||||
|
HandleNotSecurity(c, "err_domain")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkIPLimit(c) {
|
||||||
|
HandleNotSecurity(c, "err_ip_limit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if checkFrontendPath(c) {
|
||||||
|
ToIndexHtml(c)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if isEntrancePath(c) {
|
||||||
|
ToIndexHtml(c)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckSecurity(c *gin.Context) bool {
|
||||||
|
if !checkEntrance(c) && !checkSession(c) {
|
||||||
|
HandleNotSecurity(c, "")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkBindDomain(c) {
|
||||||
|
HandleNotSecurity(c, "err_domain")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !checkIPLimit(c) {
|
||||||
|
HandleNotSecurity(c, "err_ip_limit")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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 HandleNotSecurity(c *gin.Context, resType string) {
|
||||||
|
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)
|
||||||
|
if resPage == "200" && resType != "" {
|
||||||
|
file = fmt.Sprintf("html/200_%s.html", resType)
|
||||||
|
}
|
||||||
|
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 checkBindDomain(c *gin.Context) bool {
|
||||||
|
settingRepo := repo.NewISettingRepo()
|
||||||
|
status, _ := settingRepo.Get(repo.WithByKey("BindDomain"))
|
||||||
|
if len(status.Value) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
domains := c.Request.Host
|
||||||
|
parts := strings.Split(c.Request.Host, ":")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
domains = parts[0]
|
||||||
|
}
|
||||||
|
return domains == status.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkIPLimit(c *gin.Context) bool {
|
||||||
|
settingRepo := repo.NewISettingRepo()
|
||||||
|
status, _ := settingRepo.Get(repo.WithByKey("AllowIPs"))
|
||||||
|
if len(status.Value) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
clientIP := c.ClientIP()
|
||||||
|
for _, ip := range strings.Split(status.Value, ",") {
|
||||||
|
if len(ip) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip == clientIP || (strings.Contains(ip, "/") && common.CheckIpInCidr(ip, clientIP)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSession(c *gin.Context) bool {
|
||||||
|
_, err := global.SESSION.Get(c)
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ const config = reactive({
|
||||||
});
|
});
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
const showTail = ref(true);
|
const showTail = ref(true);
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const openWithTaskID = (id: string, tail: boolean) => {
|
const openWithTaskID = (id: string, tail: boolean) => {
|
||||||
config.taskID = id;
|
config.taskID = id;
|
||||||
|
@ -65,7 +66,7 @@ const openWithResourceID = (taskType: string, taskOperate: string, resourceID: n
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
bus.emit('close', true);
|
emit('close', true);
|
||||||
bus.emit('refreshTask', true);
|
bus.emit('refreshTask', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -42,13 +42,14 @@ router.beforeEach((to, from, next) => {
|
||||||
if (to.path === '/apps/all' && to.query.install != undefined) {
|
if (to.path === '/apps/all' && to.query.install != undefined) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || '');
|
||||||
if (to.query.uncached != undefined) {
|
if (to.query.uncached != undefined) {
|
||||||
const query = { ...to.query };
|
const query = { ...to.query };
|
||||||
delete query.uncached;
|
delete query.uncached;
|
||||||
|
localStorage.removeItem(activeMenuKey);
|
||||||
return next({ path: to.path, query });
|
return next({ path: to.path, query });
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || '');
|
|
||||||
const cachedRoute = localStorage.getItem(activeMenuKey);
|
const cachedRoute = localStorage.getItem(activeMenuKey);
|
||||||
if (
|
if (
|
||||||
to.meta.activeMenu &&
|
to.meta.activeMenu &&
|
||||||
|
|
|
@ -185,7 +185,7 @@ import Install from '../detail/install/index.vue';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
import { newUUID } from '@/utils/util';
|
import { newUUID, jumpToPath } from '@/utils/util';
|
||||||
import Detail from '../detail/index.vue';
|
import Detail from '../detail/index.vue';
|
||||||
import TaskLog from '@/components/log/task/index.vue';
|
import TaskLog from '@/components/log/task/index.vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
@ -254,7 +254,7 @@ const openInstall = (app: App.App) => {
|
||||||
case 'go':
|
case 'go':
|
||||||
case 'python':
|
case 'python':
|
||||||
case 'dotnet':
|
case 'dotnet':
|
||||||
router.push({ path: '/websites/runtimes/' + app.type });
|
jumpToPath(router, '/websites/runtimes/' + app.type);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const params = {
|
const params = {
|
||||||
|
|
|
@ -72,7 +72,7 @@ import { ref } from 'vue';
|
||||||
import Install from './install/index.vue';
|
import Install from './install/index.vue';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
import { computeSizeFromMB } from '@/utils/util';
|
import { computeSizeFromMB, jumpToPath } from '@/utils/util';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
const globalStore = GlobalStore();
|
const globalStore = GlobalStore();
|
||||||
|
@ -133,16 +133,12 @@ const toLink = (link: string) => {
|
||||||
const openInstall = () => {
|
const openInstall = () => {
|
||||||
switch (app.value.type) {
|
switch (app.value.type) {
|
||||||
case 'php':
|
case 'php':
|
||||||
router.push({ path: '/websites/runtimes/php' });
|
|
||||||
break;
|
|
||||||
case 'node':
|
case 'node':
|
||||||
router.push({ path: '/websites/runtimes/node' });
|
|
||||||
break;
|
|
||||||
case 'java':
|
case 'java':
|
||||||
router.push({ path: '/websites/runtimes/java' });
|
|
||||||
break;
|
|
||||||
case 'go':
|
case 'go':
|
||||||
router.push({ path: '/websites/runtimes/go' });
|
case 'python':
|
||||||
|
case 'dotnet':
|
||||||
|
jumpToPath(router, '/websites/runtimes/' + app.value.type);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const params = {
|
const params = {
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
|
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
|
||||||
<div v-for="(p, index) in params" :key="index">
|
<div v-for="(p, index) in params" :key="index">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:prop="p.key"
|
:prop="'params.' + p.key"
|
||||||
:label="getLabel(p)"
|
:label="getLabel(p)"
|
||||||
v-if="p.showValue == undefined || p.showValue == ''"
|
v-if="p.showValue == undefined || p.showValue == ''"
|
||||||
>
|
>
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :prop="p.key" :label="getLabel(p)" v-else>
|
<el-form-item :prop="'params.' + p.key" :label="getLabel(p)" v-else>
|
||||||
<el-input v-model.trim="p.showValue" :disabled="!p.edit"></el-input>
|
<el-input v-model.trim="p.showValue" :disabled="!p.edit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -354,7 +354,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
<Backups ref="backupRef" @close="search" />
|
<Backups ref="backupRef" />
|
||||||
<Uploads ref="uploadRef" />
|
<Uploads ref="uploadRef" />
|
||||||
<AppResources ref="checkRef" @close="search" />
|
<AppResources ref="checkRef" @close="search" />
|
||||||
<AppDelete ref="deleteRef" @close="search" />
|
<AppDelete ref="deleteRef" @close="search" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue