Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions agent/init/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (

func Routers() *gin.Engine {
Router = gin.Default()
configureTrustedProxies(Router)
Router.Use(i18n.UseI18n())

PrivateGroup := Router.Group("/api/v2")
Expand All @@ -29,3 +30,7 @@ func Routers() *gin.Engine {

return Router
}

func configureTrustedProxies(router *gin.Engine) {
_ = router.SetTrustedProxies(nil)
}
96 changes: 90 additions & 6 deletions agent/utils/req_helper/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@ package req_helper

import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"

"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"net/http"
)

const LocalTokenHeader = "X-Panel-Local-Token"

func PostLocalCore(url string) error {
var serverPortSetting model.Setting
_ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ServerPort").First(&serverPortSetting).Error
Expand All @@ -22,22 +34,94 @@ func PostLocalCore(url string) error {
prefix = "https"
}

reloadURL := fmt.Sprintf("%s://127.0.0.1:%s/api/v2%s", prefix, serverPortSetting.Value, url)
req, err := http.NewRequest("POST", reloadURL, bytes.NewBuffer([]byte{}))
if err != nil {
return err
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Transport: tr,
}
defer client.CloseIdleConnections()
return postLocalCoreTo(fmt.Sprintf("%s://127.0.0.1:%s", prefix, serverPortSetting.Value), url, client)
}

func postLocalCoreTo(baseURL string, url string, client *http.Client) error {
token, err := ensureLocalToken()
if err != nil {
return err
}
if client == nil {
client = http.DefaultClient
}

reloadURL := strings.TrimRight(baseURL, "/") + "/api/v2" + url
req, err := http.NewRequest(http.MethodPost, reloadURL, bytes.NewBuffer([]byte{}))
if err != nil {
return err
}
req.Header.Set(LocalTokenHeader, token)

resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("request local core failed: status %s", resp.Status)
}

var result dto.Response
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
if result.Code != http.StatusOK {
return fmt.Errorf("request local core failed: code %d, message: %s", result.Code, result.Message)
}
return nil
}

func ensureLocalToken() (string, error) {
secret, err := ensureLocalSecret()
if err != nil {
return "", err
}
return generateLocalToken(secret), nil
}

func ensureLocalSecret() (string, error) {
secretPath := localSecretPath()
data, err := os.ReadFile(secretPath)
if err == nil && len(data) != 0 {
return string(data), nil
}
if err != nil && !os.IsNotExist(err) {
return "", err
}

if err := os.MkdirAll(filepath.Dir(secretPath), 0700); err != nil {
return "", err
}
secretBytes := make([]byte, 32)
if _, err := rand.Read(secretBytes); err != nil {
return "", err
}
secret := hex.EncodeToString(secretBytes)
if err := os.WriteFile(secretPath, []byte(secret), 0600); err != nil {
return "", err
}
return secret, nil
}

func localSecretPath() string {
tmpDir := global.Dir.TmpDir
if tmpDir == "" {
tmpDir = filepath.Join(global.CONF.Base.InstallDir, "1panel/tmp")
}
return filepath.Join(tmpDir, ".secret")
}

func generateLocalToken(secret string) string {
today := time.Now().Format("2006-01-02")
h := md5.New()
h.Write([]byte(secret + "-" + today))
return hex.EncodeToString(h.Sum(nil))[:16]
}
5 changes: 0 additions & 5 deletions core/app/api/v2/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,6 @@ func (b *BaseApi) UpdatePort(c *gin.Context) {
}

func (b *BaseApi) ReloadSSL(c *gin.Context) {
clientIP := c.ClientIP()
if clientIP != "127.0.0.1" {
helper.InternalServer(c, errors.New("only localhost can reload ssl"))
return
}
if err := settingService.UpdateSystemSSL(); err != nil {
helper.InternalServer(c, err)
return
Expand Down
5 changes: 5 additions & 0 deletions core/init/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func setWebStatic(rootRouter *gin.RouterGroup) {

func Routers() *gin.Engine {
Router = gin.New()
configureTrustedProxies(Router)
Router.Use(i18n.UseI18n())
Router.Use(middleware.WhiteAllow())
Router.Use(middleware.BindDomain())
Expand Down Expand Up @@ -109,6 +110,10 @@ func Routers() *gin.Engine {
return Router
}

func configureTrustedProxies(router *gin.Engine) {
_ = router.SetTrustedProxies(nil)
}

func RegisterImages(rootRouter *gin.RouterGroup) {
staticDir := filepath.Join(global.CONF.Base.InstallDir, "1panel/uploads/theme")
rootRouter.GET("/api/v2/images/*filename", func(c *gin.Context) {
Expand Down
3 changes: 3 additions & 0 deletions core/middleware/csrf_protect.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ func CSRFTokenGuard() gin.HandlerFunc {
}

func requiresCSRFTokenCheck(c *gin.Context) bool {
if IsLocalRequest(c) {
return false
}
unsafeMethod := c.Request.Method != http.MethodGet &&
c.Request.Method != http.MethodHead &&
c.Request.Method != http.MethodOptions &&
Expand Down
58 changes: 58 additions & 0 deletions core/middleware/local_req_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package middleware

import (
"fmt"
"net/http"

"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/gin-gonic/gin"
)

const (
localRequestContextKey = "LOCAL_REQUEST"
localSSLReloadPath = "/api/v2/core/settings/ssl/reload"
)

func LocalReqCheck() gin.HandlerFunc {
return func(c *gin.Context) {
clientIP := common.GetRealClientIP(c)
if !isLocalClientIP(clientIP) {
abortLocalRequest(c, fmt.Sprintf("invalid client ip: %s", clientIP))
return
}
if !common.ValidateLocalToken(c.GetHeader(common.LocalTokenHeader)) {
abortLocalRequest(c, "local token invalid")
return
}
c.Next()
}
}

func IsLocalRequest(c *gin.Context) bool {
if c.GetBool(localRequestContextKey) {
return true
}
if c.Request.URL.Path != localSSLReloadPath {
return false
}
if !isLocalClientIP(common.GetRealClientIP(c)) {
return false
}
if !common.ValidateLocalToken(c.GetHeader(common.LocalTokenHeader)) {
return false
}
c.Set(localRequestContextKey, true)
return true
}

func isLocalClientIP(clientIP string) bool {
return clientIP == "127.0.0.1" || clientIP == "::1"
}

func abortLocalRequest(c *gin.Context, message string) {
c.AbortWithStatusJSON(http.StatusForbidden, dto.Response{
Code: http.StatusForbidden,
Message: message,
})
}
4 changes: 4 additions & 0 deletions core/middleware/password_expired.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import (

func PasswordExpired() gin.HandlerFunc {
return func(c *gin.Context) {
if IsLocalRequest(c) {
c.Next()
return
}
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") ||
c.Request.URL.Path == "/api/v2/core/settings/search" ||
c.Request.URL.Path == "/api/v2/core/settings/search/base" ||
Expand Down
2 changes: 1 addition & 1 deletion core/router/ro_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
settingRouter.GET("/upgrade/releases", baseApi.LoadRelease)
settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo)

noAuthRouter.POST("/ssl/reload", baseApi.ReloadSSL)
noAuthRouter.POST("/ssl/reload", middleware.LocalReqCheck(), baseApi.ReloadSSL)
Comment thread
zhengkunwang223 marked this conversation as resolved.

settingRouter.POST("/apps/store/update", baseApi.UpdateAppstoreConfig)
settingRouter.GET("/apps/store/config", baseApi.GetAppstoreConfig)
Expand Down
69 changes: 69 additions & 0 deletions core/utils/common/local_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package common

import (
"crypto/md5"
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"os"
"path/filepath"
"time"

"github.com/1Panel-dev/1Panel/core/global"
)

const LocalTokenHeader = "X-Panel-Local-Token"

func EnsureLocalToken() (string, error) {
secret, err := ensureLocalSecret()
if err != nil {
return "", err
}
return generateLocalToken(secret), nil
}

func ValidateLocalToken(token string) bool {
if token == "" {
return false
}
expected, err := EnsureLocalToken()
if err != nil {
return false
}
return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
}

func ensureLocalSecret() (string, error) {
secretPath := localSecretPath()
data, err := os.ReadFile(secretPath)
if err == nil && len(data) != 0 {
return string(data), nil
}
if err != nil && !os.IsNotExist(err) {
return "", err
}

if err := os.MkdirAll(filepath.Dir(secretPath), 0700); err != nil {
return "", err
}
secretBytes := make([]byte, 32)
if _, err := rand.Read(secretBytes); err != nil {
return "", err
}
secret := hex.EncodeToString(secretBytes)
if err := os.WriteFile(secretPath, []byte(secret), 0600); err != nil {
return "", err
}
return secret, nil
}

func localSecretPath() string {
return filepath.Join(global.CONF.Base.InstallDir, "1panel/tmp/.secret")
}

func generateLocalToken(secret string) string {
today := time.Now().Format("2006-01-02")
h := md5.New()
h.Write([]byte(secret + "-" + today))
return hex.EncodeToString(h.Sum(nil))[:16]
}
Loading