From 3d4e8409a53cf8103c9b93e2fde13be8e8652a25 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 1 Aug 2025 15:45:34 -0700 Subject: [PATCH] Support X-Forwarded-Port (#7070) * support for the X-Forwarded-Prefix header * remove comments * refactoring * refactoring * path.Clean * support X-Forwarded-Port * Update weed/s3api/auth_signature_v4.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update weed/s3api/auto_signature_v4_test.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * more tests --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- weed/s3api/auth_signature_v4.go | 11 +++- weed/s3api/auto_signature_v4_test.go | 95 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go index b42547de7..226ae9a26 100644 --- a/weed/s3api/auth_signature_v4.go +++ b/weed/s3api/auth_signature_v4.go @@ -490,7 +490,16 @@ func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, func extractHostHeader(r *http.Request) string { // Check for X-Forwarded-Host header first, which is set by reverse proxies if forwardedHost := r.Header.Get("X-Forwarded-Host"); forwardedHost != "" { - // Using reverse proxy with X-Forwarded-Host. + // Check if reverse proxy also forwarded the port + if forwardedPort := r.Header.Get("X-Forwarded-Port"); forwardedPort != "" { + // Determine the protocol to check for standard ports + proto := r.Header.Get("X-Forwarded-Proto") + // Only add port if it's not the standard port for the protocol + if (proto == "https" && forwardedPort != "443") || (proto != "https" && forwardedPort != "80") { + return forwardedHost + ":" + forwardedPort + } + } + // Using reverse proxy with X-Forwarded-Host (standard port or no port forwarded). return forwardedHost } diff --git a/weed/s3api/auto_signature_v4_test.go b/weed/s3api/auto_signature_v4_test.go index 61da40aff..27d25d745 100644 --- a/weed/s3api/auto_signature_v4_test.go +++ b/weed/s3api/auto_signature_v4_test.go @@ -322,6 +322,101 @@ func TestSignatureV4WithForwardedPrefix(t *testing.T) { } } +// Test X-Forwarded-Port support for reverse proxy scenarios +func TestSignatureV4WithForwardedPort(t *testing.T) { + tests := []struct { + name string + host string + forwardedHost string + forwardedPort string + forwardedProto string + expectedHost string + }{ + { + name: "HTTP with non-standard port", + host: "backend:8333", + forwardedHost: "example.com", + forwardedPort: "8080", + forwardedProto: "http", + expectedHost: "example.com:8080", + }, + { + name: "HTTPS with non-standard port", + host: "backend:8333", + forwardedHost: "example.com", + forwardedPort: "8443", + forwardedProto: "https", + expectedHost: "example.com:8443", + }, + { + name: "HTTP with standard port (80)", + host: "backend:8333", + forwardedHost: "example.com", + forwardedPort: "80", + forwardedProto: "http", + expectedHost: "example.com", + }, + { + name: "HTTPS with standard port (443)", + host: "backend:8333", + forwardedHost: "example.com", + forwardedPort: "443", + forwardedProto: "https", + expectedHost: "example.com", + }, + { + name: "empty proto with non-standard port", + host: "backend:8333", + forwardedHost: "example.com", + forwardedPort: "8080", + forwardedProto: "", + expectedHost: "example.com:8080", + }, + { + name: "empty proto with standard http port", + host: "backend:8333", + forwardedHost: "example.com", + forwardedPort: "80", + forwardedProto: "", + expectedHost: "example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iam := newTestIAM() + + // Create a request + r, err := newTestRequest("GET", "https://"+tt.host+"/test-bucket/test-object", 0, nil) + if err != nil { + t.Fatalf("Failed to create test request: %v", err) + } + + // Set the mux variables manually since we're not going through the actual router + r = mux.SetURLVars(r, map[string]string{ + "bucket": "test-bucket", + "object": "test-object", + }) + + // Set forwarded headers + r.Header.Set("Host", tt.host) + r.Header.Set("X-Forwarded-Host", tt.forwardedHost) + r.Header.Set("X-Forwarded-Port", tt.forwardedPort) + r.Header.Set("X-Forwarded-Proto", tt.forwardedProto) + + // Sign the request with the expected host header + // We need to temporarily modify the Host header for signing + signV4WithPath(r, "AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", r.URL.Path) + + // Test signature verification + _, errCode := iam.doesSignatureMatch(getContentSha256Cksum(r), r) + if errCode != s3err.ErrNone { + t.Errorf("Expected successful signature validation with forwarded port, got error: %v (code: %d)", errCode, int(errCode)) + } + }) + } +} + // Test basic presigned URL functionality without prefix func TestPresignedSignatureV4Basic(t *testing.T) { iam := newTestIAM()