mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-20 06:07:05 +00:00
fix(s3): keep host-less bucket catch-all so reverse proxies work (#9540)
When s3.domainName is set, all bucket-prefix routes were gated on a matching Host header. Requests that arrive via an IP, an unlisted hostname, or a reverse proxy that rewrites Host hit no router and bounce back as 405/404 (and 503 once a proxy maps the upstream error). Register the path-style catch-all unconditionally, after the host-specific routers, so it only fires when no Host matcher applies.
This commit is contained in:
@@ -684,9 +684,12 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
|
||||
routers = append(routers, apiRouter.Host(
|
||||
fmt.Sprintf("%s.%s", "{bucket:.+}", virtualHost)).Subrouter())
|
||||
}
|
||||
} else {
|
||||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
||||
}
|
||||
// Always register a Host-less path-style catch-all last so requests that
|
||||
// arrive via an IP, an unlisted hostname, or a reverse proxy that rewrites
|
||||
// the Host header still match bucket routes. Host-specific routers above
|
||||
// take precedence because they were registered first.
|
||||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
||||
|
||||
// Get CORS middleware instance with caching
|
||||
corsMiddleware := s3a.getCORSMiddleware()
|
||||
|
||||
@@ -296,3 +296,43 @@ func TestRouting_IAMMatcherLogic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRouting_BucketRouteMatchesAnyHost is a regression test for issue #9539.
|
||||
// When DomainName is configured, bucket-prefix routes must still match for
|
||||
// requests whose Host header does not match a configured domain (for example,
|
||||
// requests forwarded by a reverse proxy that rewrites Host to an internal
|
||||
// upstream address). A Host-less path-style catch-all is registered after the
|
||||
// host-specific routers so it only fires when no Host matcher applies.
|
||||
func TestRouting_BucketRouteMatchesAnyHost(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
domainName string
|
||||
host string
|
||||
}{
|
||||
{"virtual-host domain configured, request via IP", "s3.example.com", "10.0.0.5:8333"},
|
||||
{"virtual-host domain configured, request via unrelated host", "s3.example.com", "internal-upstream:8333"},
|
||||
{"path-style + virtual-host configured, request via IP", "s3.example.com,api.s3.example.com", "10.0.0.5:8333"},
|
||||
{"domain configured, request via the bare configured host (no bucket subdomain)", "s3.example.com", "s3.example.com"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
s3a := setupRoutingTestServer(t)
|
||||
s3a.option.DomainName = tc.domainName
|
||||
s3a.registerRouter(router)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodHead, "http://"+tc.host+"/some-bucket", nil)
|
||||
req.Host = tc.host
|
||||
|
||||
var match mux.RouteMatch
|
||||
if !router.Match(req, &match) {
|
||||
t.Fatalf("expected HEAD /some-bucket with Host=%q to match a bucket route (domainName=%q); got no match (err=%v)",
|
||||
tc.host, tc.domainName, match.MatchErr)
|
||||
}
|
||||
if match.MatchErr != nil {
|
||||
t.Fatalf("route matched but reported error: %v", match.MatchErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user