From d5e71eb0d80d1763c92ee2a5290671d5b0f6d676 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 25 Feb 2026 12:28:44 -0800 Subject: [PATCH] Revert "s3api: preserve Host header port in signature verification (#8434)" This reverts commit 98d89ffad7af3abe482750e7b0d4e87e5df337e1. --- weed/s3api/auth_signature_v4.go | 50 +++++++++++++++++++++------- weed/s3api/auth_signature_v4_test.go | 24 +++++-------- weed/s3api/auto_signature_v4_test.go | 8 ++--- 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go index f9e125816..963cf12a0 100644 --- a/weed/s3api/auth_signature_v4.go +++ b/weed/s3api/auth_signature_v4.go @@ -788,9 +788,25 @@ func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, func extractHostHeader(r *http.Request) string { forwardedHost := r.Header.Get("X-Forwarded-Host") forwardedPort := r.Header.Get("X-Forwarded-Port") + forwardedProto := r.Header.Get("X-Forwarded-Proto") + + // Determine the effective scheme with correct order of precedence: + // 1. X-Forwarded-Proto (most authoritative, reflects client's original protocol) + // 2. r.TLS (authoritative for direct connection to server) + // 3. r.URL.Scheme (fallback, may not always be set correctly) + // 4. Default to "http" + scheme := "http" + if r.URL.Scheme != "" { + scheme = r.URL.Scheme + } + if r.TLS != nil { + scheme = "https" + } + if forwardedProto != "" { + scheme = forwardedProto + } var host, port string - explicitPort := false if forwardedHost != "" { // X-Forwarded-Host can be a comma-separated list of hosts when there are multiple proxies. // Use only the first host in the list and trim spaces for robustness. @@ -799,16 +815,12 @@ func extractHostHeader(r *http.Request) string { } else { host = strings.TrimSpace(forwardedHost) } - // Baseline port from forwarded port if available - if forwardedPort != "" { - port = forwardedPort - explicitPort = true - } - // If the host itself contains a port, it should take precedence if h, p, err := net.SplitHostPort(host); err == nil { host = h port = p - explicitPort = true + } + if forwardedPort != "" && isDefaultPort(scheme, port) { + port = forwardedPort } } else { host = r.Host @@ -818,13 +830,12 @@ func extractHostHeader(r *http.Request) string { if h, p, err := net.SplitHostPort(host); err == nil { host = h port = p - explicitPort = true } } - // If a port was explicitly provided, join it with the host. + // If we have a non-default port, join it with the host. // net.JoinHostPort will handle bracketing for IPv6. - if explicitPort && port != "" { + if port != "" && !isDefaultPort(scheme, port) { // Strip existing brackets before calling JoinHostPort, which automatically adds // brackets for IPv6 addresses. This prevents double-bracketing like [[::1]]:8080. // Using Trim handles both well-formed and malformed bracketed hosts. @@ -832,7 +843,7 @@ func extractHostHeader(r *http.Request) string { return net.JoinHostPort(host, port) } - // No explicit port was provided (or port was empty). According to AWS SDK behavior (aws-sdk-go-v2), + // No port or default port was stripped. According to AWS SDK behavior (aws-sdk-go-v2), // when a default port is removed from an IPv6 address, the brackets should also be removed. // This matches AWS S3 signature calculation requirements. // Reference: https://github.com/aws/aws-sdk-go-v2/blob/main/aws/signer/internal/v4/host.go @@ -844,6 +855,21 @@ func extractHostHeader(r *http.Request) string { return host } +func isDefaultPort(scheme, port string) bool { + if port == "" { + return true + } + + switch port { + case "80": + return strings.EqualFold(scheme, "http") + case "443": + return strings.EqualFold(scheme, "https") + default: + return false + } +} + // getScope generate a string of a specific date, an AWS region, and a service. func getScope(t time.Time, region string, service string) string { scope := strings.Join([]string{ diff --git a/weed/s3api/auth_signature_v4_test.go b/weed/s3api/auth_signature_v4_test.go index dc6f1b462..782ce6a5b 100644 --- a/weed/s3api/auth_signature_v4_test.go +++ b/weed/s3api/auth_signature_v4_test.go @@ -208,7 +208,7 @@ func TestExtractHostHeader(t *testing.T) { forwardedHost: "example.com", forwardedPort: "80", forwardedProto: "http", - expected: "example.com:80", + expected: "example.com", }, { name: "X-Forwarded-Host with X-Forwarded-Port (HTTPS standard port 443)", @@ -216,7 +216,7 @@ func TestExtractHostHeader(t *testing.T) { forwardedHost: "example.com", forwardedPort: "443", forwardedProto: "https", - expected: "example.com:443", + expected: "example.com", }, // Issue #6649: X-Forwarded-Host already contains port (Traefik/HAProxy style) { @@ -227,14 +227,6 @@ func TestExtractHostHeader(t *testing.T) { forwardedProto: "https", expected: "127.0.0.1:8433", }, - { - name: "X-Forwarded-Host with standard port already included (HTTPS 443)", - hostHeader: "backend:8333", - forwardedHost: "example.com:443", - forwardedPort: "443", - forwardedProto: "https", - expected: "example.com:443", - }, { name: "X-Forwarded-Host with port, no X-Forwarded-Port header", hostHeader: "backend:8333", @@ -261,20 +253,20 @@ func TestExtractHostHeader(t *testing.T) { expected: "[::1]:8080", }, { - name: "IPv6 address without brackets and standard port, should include brackets and port when explicit", + name: "IPv6 address without brackets and standard port, should strip brackets per AWS SDK", hostHeader: "backend:8333", forwardedHost: "::1", forwardedPort: "80", forwardedProto: "http", - expected: "[::1]:80", + expected: "::1", }, { - name: "IPv6 address without brackets and standard HTTPS port, should include brackets and port when explicit", + name: "IPv6 address without brackets and standard HTTPS port, should strip brackets per AWS SDK", hostHeader: "backend:8333", forwardedHost: "2001:db8::1", forwardedPort: "443", forwardedProto: "https", - expected: "[2001:db8::1]:443", + expected: "2001:db8::1", }, { name: "IPv6 address with brackets but no port, should add port", @@ -285,12 +277,12 @@ func TestExtractHostHeader(t *testing.T) { expected: "[2001:db8::1]:8080", }, { - name: "IPv6 full address with brackets and default port (should preserve port if explicit)", + name: "IPv6 full address with brackets and default port (should strip port and brackets)", hostHeader: "backend:8333", forwardedHost: "[2001:db8:85a3::8a2e:370:7334]:443", forwardedPort: "443", forwardedProto: "https", - expected: "[2001:db8:85a3::8a2e:370:7334]:443", + expected: "2001:db8:85a3::8a2e:370:7334", }, { name: "IPv4-mapped IPv6 address without brackets, should add brackets with port", diff --git a/weed/s3api/auto_signature_v4_test.go b/weed/s3api/auto_signature_v4_test.go index a3f032353..7079273ee 100644 --- a/weed/s3api/auto_signature_v4_test.go +++ b/weed/s3api/auto_signature_v4_test.go @@ -415,13 +415,13 @@ func TestSignatureV4WithoutProxy(t *testing.T) { name: "HTTP with standard port", host: "backend:80", proto: "http", - expectedHost: "backend:80", + expectedHost: "backend", }, { name: "HTTPS with standard port", host: "backend:443", proto: "https", - expectedHost: "backend:443", + expectedHost: "backend", }, { name: "HTTP without port", @@ -451,13 +451,13 @@ func TestSignatureV4WithoutProxy(t *testing.T) { name: "IPv6 HTTP with standard port", host: "[::1]:80", proto: "http", - expectedHost: "[::1]:80", + expectedHost: "::1", }, { name: "IPv6 HTTPS with standard port", host: "[::1]:443", proto: "https", - expectedHost: "[::1]:443", + expectedHost: "::1", }, { name: "IPv6 HTTP without port",