Files
1Panel/core/middleware/mcp_website.go
蘭 d32cbb40f6 feat(mcp): add Panel MCP task configuration and website binding structures (#14229)
* feat(mcp): add Panel MCP task configuration and website binding structures

* fix(mcp): correct variable assignment in mcpWebsiteDomain function
2026-10-28 14:04:38 +08:00

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"})
}