Browse Source

s3: fix signature mismatch with non-standard ports and capitalized host (#8386)

* s3: fix signature mismatch with non-standard ports and capitalized host

- ensure host header extraction is case-insensitive in SignedHeaders
- prioritize non-standard ports in X-Forwarded-Host over default ports in X-Forwarded-Port
- add regression tests for both scenarios

fixes https://github.com/seaweedfs/seaweedfs/issues/8382

* simplify
pull/8313/merge
Chris Lu 23 hours ago
committed by GitHub
parent
commit
5ecee9e64d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      weed/s3api/auth_signature_v4.go
  2. 78
      weed/s3api/auth_signature_v4_test.go

10
weed/s3api/auth_signature_v4.go

@ -769,7 +769,7 @@ func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header,
extractedSignedHeaders := make(http.Header)
for _, header := range signedHeaders {
// `host` is not a case-sensitive header, unlike other headers such as `x-amz-date`.
if header == "host" {
if strings.ToLower(header) == "host" {
// Get host value.
hostHeaderValue := extractHostHeader(r)
extractedSignedHeaders[header] = []string{hostHeaderValue}
@ -815,12 +815,12 @@ func extractHostHeader(r *http.Request) string {
} else {
host = strings.TrimSpace(forwardedHost)
}
port = forwardedPort
if h, p, err := net.SplitHostPort(host); err == nil {
host = h
if port == "" {
port = p
}
port = p
}
if forwardedPort != "" && isDefaultPort(scheme, port) {
port = forwardedPort
}
} else {
host = r.Host

78
weed/s3api/auth_signature_v4_test.go

@ -292,6 +292,47 @@ func TestExtractHostHeader(t *testing.T) {
forwardedProto: "http",
expected: "[::ffff:127.0.0.1]:8080",
},
{
name: "Simple port 442",
hostHeader: "bucket.domain.com:442",
expected: "bucket.domain.com:442",
},
{
name: "Port 442 with X-Forwarded-Host",
hostHeader: "backend:8333",
forwardedHost: "bucket.domain.com:442",
expected: "bucket.domain.com:442",
},
{
name: "Port 442 with X-Forwarded-Port",
hostHeader: "backend:8333",
forwardedHost: "bucket.domain.com",
forwardedPort: "442",
expected: "bucket.domain.com:442",
},
{
name: "HTTPS with port 442 (should NOT strip)",
hostHeader: "bucket.domain.com:442",
forwardedProto: "https",
expected: "bucket.domain.com:442",
},
{
name: "X-Forwarded-Host with multiple hosts (including port)",
forwardedHost: "bucket.domain.com:442, internal.proxy",
expected: "bucket.domain.com:442",
},
{
name: "IPv6 with port",
hostHeader: "[2001:db8::1]:442",
expected: "[2001:db8::1]:442",
},
{
name: "X-Forwarded-Host with port 442, but X-Forwarded-Port is 80 (should PREFER 442)",
forwardedHost: "bucket.domain.com:442",
forwardedPort: "80",
forwardedProto: "http",
expected: "bucket.domain.com:442",
},
}
for _, tt := range tests {
@ -322,3 +363,40 @@ func TestExtractHostHeader(t *testing.T) {
})
}
}
func TestExtractSignedHeadersCase(t *testing.T) {
tests := []struct {
name string
host string
signedHeads []string
expected string
}{
{
name: "lowercase host",
host: "bucket.domain.com:442",
signedHeads: []string{"host"},
expected: "host:bucket.domain.com:442\n",
},
{
name: "uppercase Host",
host: "bucket.domain.com:442",
signedHeads: []string{"Host"},
expected: "host:bucket.domain.com:442\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r, _ := http.NewRequest("GET", "http://"+tt.host+"/", nil)
r.Host = tt.host
extracted, errCode := extractSignedHeaders(tt.signedHeads, r)
if errCode != s3err.ErrNone {
t.Fatalf("extractSignedHeaders failed: %v", errCode)
}
actual := getCanonicalHeaders(extracted)
if actual != tt.expected {
t.Errorf("%s: expected %q, got %q", tt.name, tt.expected, actual)
}
})
}
}
Loading…
Cancel
Save