fix: prevent rate limit bypass in public file shares (#13632)

This commit is contained in:
2026-08-24 21:50:48 +08:00
committed by GitHub
parent 205f76c65d
commit 2dea44acf6
4 changed files with 153 additions and 21 deletions
+84 -12
View File
@@ -5,7 +5,12 @@ import (
"net"
"net/http"
"net/http/httputil"
"slices"
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/utils/clientip"
"github.com/1Panel-dev/1Panel/core/utils/publicshare"
)
const SockPath = "/etc/1panel/agent.sock"
@@ -28,20 +33,26 @@ func Init() {
MaxIdleConnsPerHost: 50,
IdleConnTimeout: 30 * time.Second,
}
LocalAgentProxy = &httputil.ReverseProxy{
Director: func(req *http.Request) {
if req.Header.Get("X-Forwarded-Proto") == "" {
if req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https")
} else {
req.Header.Set("X-Forwarded-Proto", "http")
}
LocalAgentProxy = newLocalAgentProxy(transport)
}
func newLocalAgentProxy(transport http.RoundTripper) *httputil.ReverseProxy {
return &httputil.ReverseProxy{
Rewrite: func(proxyReq *httputil.ProxyRequest) {
if proxyReq.In.Form == nil {
proxyReq.Out.URL.RawQuery = proxyReq.In.URL.RawQuery
}
if req.Header.Get("X-Forwarded-Host") == "" && req.Host != "" {
req.Header.Set("X-Forwarded-Host", req.Host)
if publicshare.IsAPI(proxyReq.In.URL.Path) {
proxyReq.SetXForwarded()
clientip.ReplaceForwardingHeaders(
proxyReq.Out.Header,
clientip.FromRemoteAddr(proxyReq.In.RemoteAddr),
)
} else {
restoreLegacyForwardingHeaders(proxyReq)
}
req.URL.Scheme = "http"
req.URL.Host = "unix"
proxyReq.Out.URL.Scheme = "http"
proxyReq.Out.URL.Host = "unix"
},
Transport: transport,
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
@@ -50,3 +61,64 @@ func Init() {
},
}
}
func restoreLegacyForwardingHeaders(proxyReq *httputil.ProxyRequest) {
restoreInboundHeader(proxyReq, "Forwarded")
restoreLegacyForwardedFor(proxyReq)
restoreLegacyForwardedMetadata(proxyReq, "X-Forwarded-Host", proxyReq.In.Host)
forwardedProto := "http"
if proxyReq.In.TLS != nil {
forwardedProto = "https"
}
restoreLegacyForwardedMetadata(proxyReq, "X-Forwarded-Proto", forwardedProto)
}
func restoreLegacyForwardedFor(proxyReq *httputil.ProxyRequest) {
restoreInboundHeader(proxyReq, "X-Forwarded-For")
clientIP, _, err := net.SplitHostPort(proxyReq.In.RemoteAddr)
if err != nil {
return
}
prior, ok := proxyReq.Out.Header["X-Forwarded-For"]
omit := ok && prior == nil
if len(prior) > 0 {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
if !omit {
proxyReq.Out.Header.Set("X-Forwarded-For", clientIP)
}
}
func restoreLegacyForwardedMetadata(proxyReq *httputil.ProxyRequest, name, fallback string) {
if !isConnectionHeader(proxyReq.In.Header, name) && proxyReq.In.Header.Get(name) != "" {
restoreInboundHeader(proxyReq, name)
return
}
proxyReq.Out.Header.Del(name)
if fallback != "" {
proxyReq.Out.Header.Set(name, fallback)
}
}
func restoreInboundHeader(proxyReq *httputil.ProxyRequest, name string) {
proxyReq.Out.Header.Del(name)
if isConnectionHeader(proxyReq.In.Header, name) {
return
}
canonicalName := http.CanonicalHeaderKey(name)
if values, ok := proxyReq.In.Header[canonicalName]; ok {
proxyReq.Out.Header[canonicalName] = slices.Clone(values)
}
}
func isConnectionHeader(header http.Header, name string) bool {
for _, value := range header.Values("Connection") {
for _, token := range strings.Split(value, ",") {
if strings.EqualFold(strings.TrimSpace(token), name) {
return true
}
}
}
return false
}
+6 -9
View File
@@ -1,6 +1,10 @@
package middleware
import "strings"
import (
"strings"
"github.com/1Panel-dev/1Panel/core/utils/publicshare"
)
func ShouldProxyToAgent(reqPath string) bool {
if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(reqPath, "/api/v2") {
@@ -13,12 +17,5 @@ func ShouldProxyToAgent(reqPath string) bool {
}
func IsPublicFileShareAPI(reqPath string) bool {
switch reqPath {
case "/api/v2/files/share/info",
"/api/v2/files/share/check",
"/api/v2/files/share/download":
return true
default:
return false
}
return publicshare.IsAPI(reqPath)
}
+51
View File
@@ -0,0 +1,51 @@
package clientip
import (
"net"
"net/http"
"strings"
)
const (
forwardedHeader = "Forwarded"
forwardedForHeader = "X-Forwarded-For"
realIPHeader = "X-Real-IP"
panelClientIPHeader = "X-Panel-Client-IP"
)
func FromRemoteAddr(remoteAddr string) string {
host, _, err := net.SplitHostPort(strings.TrimSpace(remoteAddr))
if err != nil {
return ""
}
ip := net.ParseIP(strings.TrimSpace(host))
if ip == nil {
return ""
}
return ip.String()
}
func ReplaceForwardingHeaders(header http.Header, clientIP string) {
if header == nil {
return
}
RemoveForwardingHeaders(header)
ip := net.ParseIP(strings.TrimSpace(clientIP))
if ip == nil {
return
}
normalized := ip.String()
header.Set(forwardedForHeader, normalized)
header.Set(realIPHeader, normalized)
}
func RemoveForwardingHeaders(header http.Header) {
if header == nil {
return
}
header.Del(forwardedHeader)
header.Del(forwardedForHeader)
header.Del(realIPHeader)
header.Del(panelClientIPHeader)
}
+12
View File
@@ -0,0 +1,12 @@
package publicshare
func IsAPI(path string) bool {
switch path {
case "/api/v2/files/share/info",
"/api/v2/files/share/check",
"/api/v2/files/share/download":
return true
default:
return false
}
}