Revert "s3api: preserve Host header port in signature verification (#8434)"
This reverts commit 98d89ffad7.
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user