[#583] Fix list-buckets vhs routing

The problem is that with VHS requests,
the list-buckets operation does not work
because the request is filtered on
list-objects-v1. Since list-buckets can
also have query parameters, in the end it
is necessary to distinguish list-buckets
from list-objects-v1 only by the presence
of the bucket name in the URL (provided
that the request is in VHS style).

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
Roman Loginov 2024-12-16 10:52:16 +03:00
parent c1f3110dcc
commit f9f4e5d0a2
4 changed files with 89 additions and 15 deletions

View file

@ -24,13 +24,16 @@ import (
) )
const ( const (
QueryVersionID = "versionId" QueryVersionID = "versionId"
QueryPrefix = "prefix" QueryPrefix = "prefix"
QueryDelimiter = "delimiter" QueryDelimiter = "delimiter"
QueryMaxKeys = "max-keys" QueryMaxKeys = "max-keys"
QueryMarker = "marker" QueryMarker = "marker"
QueryEncodingType = "encoding-type" QueryEncodingType = "encoding-type"
amzTagging = "x-amz-tagging" QueryMaxBuckets = "max-buckets"
QueryContinuationToken = "continuation-token"
QueryBucketRegion = "bucket-region"
amzTagging = "x-amz-tagging"
unmatchedBucketOperation = "UnmatchedBucketOperation" unmatchedBucketOperation = "UnmatchedBucketOperation"
) )

View file

@ -336,10 +336,8 @@ func bucketRouter(h Handler) chi.Router {
Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))). Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))).
Add(NewFilter(). Add(NewFilter().
AllowedQueries(s3middleware.QueryDelimiter, s3middleware.QueryMaxKeys, s3middleware.QueryPrefix, AllowedQueries(s3middleware.QueryDelimiter, s3middleware.QueryMaxKeys, s3middleware.QueryPrefix,
s3middleware.QueryMarker, s3middleware.QueryEncodingType). s3middleware.QueryMarker, s3middleware.QueryEncodingType, s3middleware.QueryBucketRegion,
Handler(named(s3middleware.ListObjectsV1Operation, h.ListObjectsV1Handler))). s3middleware.QueryContinuationToken, s3middleware.QueryMaxBuckets).
Add(NewFilter().
NoQueries().
Handler(listWrapper(h))). Handler(listWrapper(h))).
DefaultHandler(notSupportedHandler())) DefaultHandler(notSupportedHandler()))
}) })

View file

@ -138,13 +138,14 @@ func (hf *HandlerFilters) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
func (hf *HandlerFilters) match(r *http.Request) http.Handler { func (hf *HandlerFilters) match(r *http.Request) http.Handler {
queries := r.URL.Query()
LOOP: LOOP:
for _, filter := range hf.filters { for _, filter := range hf.filters {
if filter.noQueries && len(r.URL.Query()) > 0 { if filter.noQueries && len(queries) > 0 {
continue continue
} }
if len(filter.allowedQueries) > 0 { if len(filter.allowedQueries) > 0 {
queries := r.URL.Query()
for key := range queries { for key := range queries {
if _, ok := filter.allowedQueries[key]; !ok { if _, ok := filter.allowedQueries[key]; !ok {
continue LOOP continue LOOP
@ -158,8 +159,8 @@ LOOP:
} }
} }
for _, query := range filter.queries { for _, query := range filter.queries {
queryVal := r.URL.Query().Get(query.Key) queryVal := queries.Get(query.Key)
if !r.URL.Query().Has(query.Key) || query.Value != "" && query.Value != queryVal { if !queries.Has(query.Key) || query.Value != "" && query.Value != queryVal {
continue LOOP continue LOOP
} }
} }

View file

@ -903,6 +903,78 @@ func TestRouterListObjectsV2Domains(t *testing.T) {
require.Equal(t, s3middleware.ListObjectsV2Operation, resp.Method) require.Equal(t, s3middleware.ListObjectsV2Operation, resp.Method)
} }
func TestRouterListingVHS(t *testing.T) {
baseDomain := "domain.com"
baseDomainWithBkt := "bucket.domain.com"
chiRouter := prepareRouter(t, enableVHSDomains(baseDomain))
chiRouter.handler.buckets["bucket"] = &data.BucketInfo{}
for _, tc := range []struct {
name string
host string
queries string
expectedOperation string
notSupported bool
}{
{
name: "list-object-v1 without query params",
host: baseDomainWithBkt,
expectedOperation: s3middleware.ListObjectsV1Operation,
},
{
name: "list-buckets without query params",
host: baseDomain,
expectedOperation: s3middleware.ListBucketsOperation,
},
{
name: "list-objects-v1 with prefix param",
host: baseDomainWithBkt,
queries: func() string {
query := make(url.Values)
query.Set(s3middleware.QueryPrefix, "prefix")
return query.Encode()
}(),
expectedOperation: s3middleware.ListObjectsV1Operation,
},
{
name: "list-buckets with prefix param",
host: baseDomain,
queries: func() string {
query := make(url.Values)
query.Set(s3middleware.QueryPrefix, "prefix")
return query.Encode()
}(),
expectedOperation: s3middleware.ListBucketsOperation,
},
{
name: "not supported operation",
host: baseDomain,
queries: func() string {
query := make(url.Values)
query.Set("invalid", "invalid")
return query.Encode()
}(),
notSupported: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.URL.RawQuery = tc.queries
r.Host = tc.host
chiRouter.ServeHTTP(w, r)
if tc.notSupported {
assertAPIError(t, w, apierr.ErrNotSupported)
return
}
resp := readResponse(t, w)
require.Equal(t, tc.expectedOperation, resp.Method)
})
}
}
func enableVHSDomains(domains ...string) option { func enableVHSDomains(domains ...string) option {
return func(cfg *Config) { return func(cfg *Config) {
setting := cfg.MiddlewareSettings.(*middlewareSettingsMock) setting := cfg.MiddlewareSettings.(*middlewareSettingsMock)