mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-06 16:05:21 +00:00
* feat(mcp): add Panel MCP task configuration and website binding structures * fix(mcp): correct variable assignment in mcpWebsiteDomain function
77 lines
2.7 KiB
Go
77 lines
2.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/1Panel-dev/1Panel/core/app/model"
|
|
"github.com/1Panel-dev/1Panel/core/global"
|
|
"github.com/1Panel-dev/1Panel/core/utils/common"
|
|
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
|
contract "github.com/1Panel-dev/1Panel/core/utils/mcp"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func resolveMCPWebsiteClientIP(c *gin.Context, trustedProxies string) (string, error) {
|
|
keys := c.Request.Header.Values(contract.WebsiteProxyKeyHeader)
|
|
peers := c.Request.Header.Values(contract.WebsiteProxyPeerHeader)
|
|
c.Request.Header.Del(contract.WebsiteProxyKeyHeader)
|
|
c.Request.Header.Del(contract.WebsiteProxyPeerHeader)
|
|
if c.Request.URL.Path != "/mcp" || len(keys) == 0 && len(peers) == 0 {
|
|
return common.ResolveClientIP(c, trustedProxies), nil
|
|
}
|
|
if len(keys) != 1 || len(peers) != 1 || !isLocalMCPWebsitePeer(common.GetRealClientIP(c)) {
|
|
return "", contract.Forbidden()
|
|
}
|
|
peer := net.ParseIP(strings.TrimSpace(peers[0]))
|
|
if peer == nil {
|
|
return "", contract.Forbidden()
|
|
}
|
|
if global.DB == nil {
|
|
return "", contract.Fail("MCP_POLICY_UNAVAILABLE", 503, "MCP source policy is unavailable")
|
|
}
|
|
var setting model.MCPSetting
|
|
if err := global.DB.Select("website_id", "website_binding_id").First(&setting, 1).Error; err != nil {
|
|
return "", contract.Fail("MCP_POLICY_UNAVAILABLE", 503, "MCP source policy is unavailable")
|
|
}
|
|
if setting.WebsiteID == 0 {
|
|
return "", contract.Forbidden()
|
|
}
|
|
masterKey, err := encrypt.APIKeyEncryptionKey()
|
|
if err != nil {
|
|
return "", contract.Fail("MCP_POLICY_UNAVAILABLE", 503, "MCP source policy is unavailable")
|
|
}
|
|
expected, err := contract.WebsiteProxyKey(masterKey, setting.WebsiteBindingID)
|
|
if err != nil || !hmac.Equal([]byte(expected), []byte(keys[0])) {
|
|
return "", contract.Forbidden()
|
|
}
|
|
request := c.Request.Clone(c.Request.Context())
|
|
request.RemoteAddr = net.JoinHostPort(peer.String(), "0")
|
|
proxyContext := &gin.Context{Request: request}
|
|
c.Set("MCP_WEBSITE_PROXY", true)
|
|
return common.ResolveClientIP(proxyContext, trustedProxies), nil
|
|
}
|
|
|
|
func isLocalMCPWebsitePeer(value string) bool {
|
|
peer := net.ParseIP(value)
|
|
if peer == nil {
|
|
return false
|
|
}
|
|
if peer.IsLoopback() {
|
|
return true
|
|
}
|
|
bound := net.ParseIP(strings.Trim(global.CONF.Conn.BindAddress, "[]"))
|
|
return bound != nil && !bound.IsUnspecified() && bound.Equal(peer)
|
|
}
|
|
|
|
func rejectMCPWebsiteSource(c *gin.Context, err error) {
|
|
var known *contract.Error
|
|
if errors.As(err, &known) && known.Status == 503 {
|
|
c.AbortWithStatusJSON(503, gin.H{"code": "MCP_POLICY_UNAVAILABLE", "message": "MCP source policy is unavailable"})
|
|
return
|
|
}
|
|
c.AbortWithStatusJSON(403, gin.H{"code": "MCP_SOURCE_DENIED", "message": "MCP website proxy identity is invalid"})
|
|
}
|