diff --git a/backend/s3proxy/s3.go b/backend/s3proxy/s3.go index a6d9fdb3..3b8ec26b 100644 --- a/backend/s3proxy/s3.go +++ b/backend/s3proxy/s3.go @@ -204,6 +204,16 @@ func (s *S3Proxy) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, } } + // the bucket is created in the backend region, which is independent of the + // gateway region the client's constraint was validated against; a regional + // S3 endpoint outside us-east-1 rejects a CreateBucket without a constraint + if region := s.client.Options().Region; region != "" && region != "us-east-1" { + if input.CreateBucketConfiguration == nil { + input.CreateBucketConfiguration = &types.CreateBucketConfiguration{} + } + input.CreateBucketConfiguration.LocationConstraint = types.BucketLocationConstraint(region) + } + // drop an empty CreateBucketConfiguration: strict backends (Ceph RGW) reject the empty element if input.CreateBucketConfiguration != nil && len(input.CreateBucketConfiguration.Tags) == 0 && diff --git a/backend/s3proxy/s3_test.go b/backend/s3proxy/s3_test.go index 5b26e9e1..31f74e28 100644 --- a/backend/s3proxy/s3_test.go +++ b/backend/s3proxy/s3_test.go @@ -54,10 +54,15 @@ func (c *captureHTTPClient) Do(req *http.Request) (*http.Response, error) { } func newCaptureProxy(t *testing.T) (*S3Proxy, *captureHTTPClient) { + t.Helper() + return newCaptureProxyWithRegion(t, "us-east-1") +} + +func newCaptureProxyWithRegion(t *testing.T, region string) (*S3Proxy, *captureHTTPClient) { t.Helper() capture := &captureHTTPClient{} client := s3.New(s3.Options{ - Region: "us-east-1", + Region: region, BaseEndpoint: aws.String("http://backend.test"), UsePathStyle: true, Credentials: credentials.NewStaticCredentialsProvider("access", "secret", ""), @@ -169,3 +174,45 @@ func TestCreateBucketKeepsBucketInfo(t *testing.T) { t.Errorf("CreateBucket with bucket info must forward the configuration, got %q", capture.body) } } + +// The bucket is created in the backend region, not the gateway region the +// api layer validated the client's constraint against. A regional endpoint +// outside us-east-1 rejects a CreateBucket that carries no constraint, so the +// backend has to add its own even when the api layer sent none. +func TestCreateBucketSetsBackendRegionConstraint(t *testing.T) { + p, capture := newCaptureProxyWithRegion(t, "eu-west-1") + + err := p.CreateBucket(context.Background(), &s3.CreateBucketInput{ + Bucket: aws.String("test-bucket"), + CreateBucketConfiguration: &types.CreateBucketConfiguration{}, + }, []byte{}) + if err != nil { + t.Fatalf("CreateBucket: %v", err) + } + if !strings.Contains(string(capture.body), "eu-west-1") { + t.Errorf("CreateBucket against a regional backend must carry the backend region constraint, got %q", capture.body) + } +} + +func TestCreateBucketKeepsTagsAndSetsBackendRegionConstraint(t *testing.T) { + p, capture := newCaptureProxyWithRegion(t, "eu-west-1") + + err := p.CreateBucket(context.Background(), &s3.CreateBucketInput{ + Bucket: aws.String("test-bucket"), + CreateBucketConfiguration: &types.CreateBucketConfiguration{ + Tags: []types.Tag{ + {Key: aws.String("env"), Value: aws.String("test")}, + }, + }, + }, []byte{}) + if err != nil { + t.Fatalf("CreateBucket: %v", err) + } + body := string(capture.body) + if !strings.Contains(body, "eu-west-1") { + t.Errorf("CreateBucket against a regional backend must carry the backend region constraint, got %q", body) + } + if !strings.Contains(body, "env") || !strings.Contains(body, "test") { + t.Errorf("CreateBucket must keep the requested tags, got %q", body) + } +}