Files
blitt001 3d81d5bef7 Fix S3 signature verification behind reverse proxies (#8444)
* Fix S3 signature verification behind reverse proxies

When SeaweedFS is deployed behind a reverse proxy (e.g. nginx, Kong,
Traefik), AWS S3 Signature V4 verification fails because the Host header
the client signed with (e.g. "localhost:9000") differs from the Host
header SeaweedFS receives on the backend (e.g. "seaweedfs:8333").

This commit adds a new -s3.externalUrl parameter (and S3_EXTERNAL_URL
environment variable) that tells SeaweedFS what public-facing URL clients
use to connect. When set, SeaweedFS uses this host value for signature
verification instead of the Host header from the incoming request.

New parameter:
  -s3.externalUrl  (flag) or S3_EXTERNAL_URL (environment variable)
  Example: -s3.externalUrl=http://localhost:9000
  Example: S3_EXTERNAL_URL=https://s3.example.com

The environment variable is particularly useful in Docker/Kubernetes
deployments where the external URL is injected via container config.
The flag takes precedence over the environment variable when both are set.

At startup, the URL is parsed and default ports are stripped to match
AWS SDK behavior (port 80 for HTTP, port 443 for HTTPS), so
"http://s3.example.com:80" and "http://s3.example.com" are equivalent.

Bugs fixed:
- Default port stripping was removed by a prior PR, causing signature
  mismatches when clients connect on standard ports (80/443)
- X-Forwarded-Port was ignored when X-Forwarded-Host was not present
- Scheme detection now uses proper precedence: X-Forwarded-Proto >
  TLS connection > URL scheme > "http"
- Test expectations for standard port stripping were incorrect
- expectedHost field in TestSignatureV4WithForwardedPort was declared
  but never actually checked (self-referential test)

* Add Docker integration test for S3 proxy signature verification

Docker Compose setup with nginx reverse proxy to validate that the
-s3.externalUrl parameter (or S3_EXTERNAL_URL env var) correctly
resolves S3 signature verification when SeaweedFS runs behind a proxy.

The test uses nginx proxying port 9000 to SeaweedFS on port 8333,
with X-Forwarded-Host/Port/Proto headers set. SeaweedFS is configured
with -s3.externalUrl=http://localhost:9000 so it uses "localhost:9000"
for signature verification, matching what the AWS CLI signs with.

The test can be run with aws CLI on the host or without it by using
the amazon/aws-cli Docker image with --network host.

Test covers: create-bucket, list-buckets, put-object, head-object,
list-objects-v2, get-object, content round-trip integrity,
delete-object, and delete-bucket — all through the reverse proxy.

* Create s3-proxy-signature-tests.yml

* fix CLI

* fix CI

* Update s3-proxy-signature-tests.yml

* address comments

* Update Dockerfile

* add user

* no need for fuse

* Update s3-proxy-signature-tests.yml

* debug

* weed mini

* fix health check

* health check

* fix health checking

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
2026-02-26 14:20:42 -08:00
..

S3 Proxy Signature Verification Test

Integration test that verifies S3 signature verification works correctly when SeaweedFS is deployed behind a reverse proxy (nginx).

What it tests

  • S3 operations (create bucket, put/get/head/list/delete) through an nginx reverse proxy with X-Forwarded-Host, X-Forwarded-Port, and X-Forwarded-Proto headers
  • SeaweedFS configured with -s3.externalUrl=http://localhost:9000 so the signature verification uses the client-facing host instead of the internal backend address

Architecture

AWS CLI (signs with Host: localhost:9000)
    |
    v
nginx (:9000)
    |  proxy_pass → seaweedfs:8333
    |  Sets: X-Forwarded-Host: localhost
    |         X-Forwarded-Port: 9000
    |         X-Forwarded-Proto: http
    v
SeaweedFS S3 (:8333, -s3.externalUrl=http://localhost:9000)
    |  externalHost = "localhost:9000" (parsed at startup)
    |  extractHostHeader() returns "localhost:9000"
    |  Matches what AWS CLI signed with
    v
Signature verification succeeds

Note: When -s3.externalUrl is configured, direct access to the backend port (8333) will fail signature verification because the client signs with a different Host header than what externalUrl specifies. This is expected — all S3 traffic should go through the proxy.

Prerequisites

  • Docker and Docker Compose
  • AWS CLI v2 (on host or via Docker, see below)

Running

# Build the weed binary first (from repo root):
cd /path/to/seaweedfs
go build -o test/s3/proxy_signature/weed ./weed
cd test/s3/proxy_signature

# Start services
docker compose up -d --build

# Option A: Run test with aws CLI installed locally
./test.sh

# Option B: Run test without aws CLI (uses Docker container)
docker run --rm --network host --entrypoint "" amazon/aws-cli:latest \
    bash < test.sh

# Tear down
docker compose down

# Clean up the weed binary
rm -f weed

Troubleshooting

If signature verification fails through the proxy, check:

  1. nginx is setting X-Forwarded-Host and X-Forwarded-Port correctly
  2. SeaweedFS is started with -s3.externalUrl matching the client endpoint
  3. The AWS CLI endpoint URL matches the proxy address

You can also set the S3_EXTERNAL_URL environment variable instead of the -s3.externalUrl flag.