1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-21 06:19:41 +02:00

feat(csrf): add trusted origins cli flags [BE-11972] (#856)

Co-authored-by: oscarzhou <oscar.zhou@portainer.io>
Co-authored-by: andres-portainer <andres-portainer@users.noreply.github.com>
Co-authored-by: Malcolm Lockyer <segfault88@users.noreply.github.com>
This commit is contained in:
andres-portainer 2025-07-02 21:00:39 -03:00 committed by GitHub
parent d0e74d6ef4
commit d7794a06b3
9 changed files with 359 additions and 9 deletions

View file

@ -3,6 +3,7 @@ package middlewares
import (
"net/http"
"slices"
"strings"
"github.com/gorilla/csrf"
)
@ -16,6 +17,45 @@ type plainTextHTTPRequestHandler struct {
next http.Handler
}
// parseForwardedHeaderProto parses the Forwarded header and extracts the protocol.
// The Forwarded header format supports:
// - Single proxy: Forwarded: by=<identifier>;for=<identifier>;host=<host>;proto=<http|https>
// - Multiple proxies: Forwarded: for=192.0.2.43, for=198.51.100.17
// We take the first (leftmost) entry as it represents the original client
func parseForwardedHeaderProto(forwarded string) string {
if forwarded == "" {
return ""
}
// Parse the first part (leftmost proxy, closest to original client)
firstPart, _, _ := strings.Cut(forwarded, ",")
firstPart = strings.TrimSpace(firstPart)
// Split by semicolon to get key-value pairs within this proxy entry
// Format: key=value;key=value;key=value
pairs := strings.Split(firstPart, ";")
for _, pair := range pairs {
// Split by equals sign to separate key and value
key, value, found := strings.Cut(pair, "=")
if !found {
continue
}
if strings.EqualFold(strings.TrimSpace(key), "proto") {
return strings.Trim(strings.TrimSpace(value), `"'`)
}
}
return ""
}
// isHTTPSRequest checks if the original request was made over HTTPS
// by examining both X-Forwarded-Proto and Forwarded headers
func isHTTPSRequest(r *http.Request) bool {
return strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") ||
strings.EqualFold(parseForwardedHeaderProto(r.Header.Get("Forwarded")), "https")
}
func (h *plainTextHTTPRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if slices.Contains(safeMethods, r.Method) {
h.next.ServeHTTP(w, r)
@ -24,7 +64,7 @@ func (h *plainTextHTTPRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.R
req := r
// If original request was HTTPS (via proxy), keep CSRF checks.
if xfproto := r.Header.Get("X-Forwarded-Proto"); xfproto != "https" {
if !isHTTPSRequest(r) {
req = csrf.PlaintextHTTPRequest(r)
}