From 9f07bca9cc9a6bd26a29e567ed8bd9b2ffc8aea0 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 30 Oct 2025 21:06:00 -0700 Subject: [PATCH] Fix IPv6 host header formatting to match AWS SDK behavior (#7414) * Add nginx reverse proxy documentation for S3 API Fixes #7407 Add comprehensive documentation and example configuration for using nginx as a reverse proxy with SeaweedFS S3 API while maintaining AWS Signature V4 authentication compatibility. Changes: - Add docker/nginx/README.md with detailed setup guide - Add docker/nginx/s3-example.conf with working configuration - Update docker/nginx/proxy.conf with important S3 notes The documentation covers: - Critical requirements for AWS Signature V4 authentication - Common mistakes and why they break S3 authentication - Complete working nginx configurations - Debugging tips and troubleshooting - Performance tuning recommendations * Fix IPv6 host header formatting to match AWS SDK behavior Follow-up to PR #7403 When a default port (80 for HTTP, 443 for HTTPS) is stripped from an IPv6 address, the square brackets should also be removed to match AWS SDK behavior for S3 signature calculation. Reference: https://github.com/aws/aws-sdk-go-v2/blob/main/aws/signer/internal/v4/host.go The AWS SDK's stripPort function explicitly removes brackets when returning an IPv6 address without a port. Changes: - Update extractHostHeader to strip brackets from IPv6 addresses when no port or default port is used - Update test expectations to match AWS SDK behavior - Add detailed comments explaining the AWS SDK compatibility requirement This ensures S3 signature validation works correctly with IPv6 addresses behind reverse proxies, matching AWS S3 canonical request format. Fixes the issue raised in PR #7403 comment: https://github.com/seaweedfs/seaweedfs/pull/7403#issuecomment-3471105438 * Update docker/nginx/README.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Add nginx reverse proxy documentation for S3 API Fixes #7407 Add comprehensive documentation and example configuration for using nginx as a reverse proxy with SeaweedFS S3 API while maintaining AWS Signature V4 authentication compatibility. Changes: - Add docker/nginx/README.md with detailed setup guide - Add docker/nginx/s3-example.conf with working configuration - Update docker/nginx/proxy.conf with important S3 notes The documentation covers: - Critical requirements for AWS Signature V4 authentication - Common mistakes and why they break S3 authentication - Complete working nginx configurations - Debugging tips and troubleshooting - Performance tuning recommendations Fix IPv6 host header formatting to match AWS SDK behavior Follow-up to PR #7403 When a default port (80 for HTTP, 443 for HTTPS) is stripped from an IPv6 address, the square brackets should also be removed to match AWS SDK behavior for S3 signature calculation. Reference: https://github.com/aws/aws-sdk-go-v2/blob/main/aws/signer/internal/v4/host.go The AWS SDK's stripPort function explicitly removes brackets when returning an IPv6 address without a port. Changes: - Update extractHostHeader to strip brackets from IPv6 addresses when no port or default port is used - Update test expectations to match AWS SDK behavior - Add detailed comments explaining the AWS SDK compatibility requirement This ensures S3 signature validation works correctly with IPv6 addresses behind reverse proxies, matching AWS S3 canonical request format. Fixes the issue raised in PR #7403 comment: https://github.com/seaweedfs/seaweedfs/pull/7403#issuecomment-3471105438 * Revert "Merge branch 'fix-ipv6-brackets-default-port' of https://github.com/seaweedfs/seaweedfs into fix-ipv6-brackets-default-port" This reverts commit cca3f3985ff5263698d4be27a919cf52bbc5739f, reversing changes made to 2b8f9de78ebaa285f43f38eec5e0be88a4e56715. --------- 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/auth_signature_v4_test.go | 12 ++++++------ weed/s3api/auto_signature_v4_test.go | 12 ++++++------ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go index 4d273de6a..b77540255 100644 --- a/weed/s3api/auth_signature_v4.go +++ b/weed/s3api/auth_signature_v4.go @@ -648,9 +648,14 @@ func extractHostHeader(r *http.Request) string { return net.JoinHostPort(host, port) } - // No port or default port, just ensure host is correctly formatted (IPv6 brackets). - if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") { - return "[" + host + "]" + // 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 + // The stripPort function returns IPv6 without brackets when port is stripped. + if strings.Contains(host, ":") { + // This is an IPv6 address. Strip brackets to match AWS SDK behavior. + return strings.Trim(host, "[]") } return host } diff --git a/weed/s3api/auth_signature_v4_test.go b/weed/s3api/auth_signature_v4_test.go index 6850e9d2b..9ec4f232e 100644 --- a/weed/s3api/auth_signature_v4_test.go +++ b/weed/s3api/auth_signature_v4_test.go @@ -192,20 +192,20 @@ func TestExtractHostHeader(t *testing.T) { expected: "[::1]:8080", }, { - name: "IPv6 address without brackets and standard port, should return bracketed IPv6", + 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]", + expected: "::1", }, { - name: "IPv6 address without brackets and standard HTTPS port, should return bracketed IPv6", + 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]", + expected: "2001:db8::1", }, { name: "IPv6 address with brackets but no port, should add port", @@ -216,12 +216,12 @@ func TestExtractHostHeader(t *testing.T) { expected: "[2001:db8::1]:8080", }, { - name: "IPv6 full address with brackets and default port (should strip port)", + 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]", + 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 6039081a1..47c55e077 100644 --- a/weed/s3api/auto_signature_v4_test.go +++ b/weed/s3api/auto_signature_v4_test.go @@ -451,25 +451,25 @@ func TestSignatureV4WithoutProxy(t *testing.T) { name: "IPv6 HTTP with standard port", host: "[::1]:80", proto: "http", - expectedHost: "[::1]", + expectedHost: "::1", }, { name: "IPv6 HTTPS with standard port", host: "[::1]:443", proto: "https", - expectedHost: "[::1]", + expectedHost: "::1", }, { name: "IPv6 HTTP without port", host: "::1", proto: "http", - expectedHost: "[::1]", + expectedHost: "::1", }, { name: "IPv6 HTTPS without port", host: "::1", proto: "https", - expectedHost: "[::1]", + expectedHost: "::1", }, } @@ -608,7 +608,7 @@ func TestSignatureV4WithForwardedPort(t *testing.T) { forwardedHost: "[::1]:443", forwardedPort: "443", forwardedProto: "https", - expectedHost: "[::1]", + expectedHost: "::1", }, { name: "IPv6 X-Forwarded-Host with standard http port already included (Traefik/HAProxy style)", @@ -616,7 +616,7 @@ func TestSignatureV4WithForwardedPort(t *testing.T) { forwardedHost: "[::1]:80", forwardedPort: "80", forwardedProto: "http", - expectedHost: "[::1]", + expectedHost: "::1", }, { name: "IPv6 with port in brackets",