mirror of
https://github.com/versity/versitygw.git
synced 2026-07-02 16:54:25 +00:00
6fafc15d08
Fixes #1870 Fixes #1863 A validation has been added to **PutBucketCors** for `CORSRule.AllowedOrigins`. The `AllowedOrigins` list can no longer be empty—otherwise a **MalformedXML** error is returned. Additionally, each origin is now validated to ensure it does not contain more than one wildcard. A similar validation has been added for `AllowedMethods`. The list must not be empty, or a **MalformedXML** error is returned. Previously, empty method values (e.g., `[]string{""}`) were incorrectly treated as valid. This has been fixed, and an **UnsupportedCORSMethod** error is now returned.
274 lines
9.2 KiB
Go
274 lines
9.2 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
func PutBucketCors_non_existing_bucket(s *S3Conf) error {
|
|
testName := "PutBucketCors_non_existing_bucket"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
err := putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: getPtr("non-existing-bucket"),
|
|
CORSConfiguration: &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"http://origin.com"},
|
|
AllowedMethods: []string{http.MethodGet},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket))
|
|
})
|
|
}
|
|
|
|
func PutBucketCors_empty_cors_rules(s *S3Conf) error {
|
|
testName := "PutBucketCors_empty_cors_rules"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
err := putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{},
|
|
},
|
|
})
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMalformedXML))
|
|
})
|
|
}
|
|
|
|
func PutBucketCors_invalid_allowed_origins(s *S3Conf) error {
|
|
testName := "PutBucketCors_invalid_allowed_origins"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
for i, test := range []struct {
|
|
allowedOrigins []string
|
|
expectedErr s3err.APIError
|
|
}{
|
|
// empty allowed origins
|
|
{[]string{}, s3err.GetAPIError(s3err.ErrMalformedXML)},
|
|
// multiple wildcards
|
|
{[]string{"*example.com*"}, s3err.GetMultipleWildcardCORSOriginErr("*example.com*")},
|
|
{[]string{"http://127.0.0.1:7070", "https://*:7070/*"}, s3err.GetMultipleWildcardCORSOriginErr("https://*:7070/*")},
|
|
{[]string{"a/really/*/long/*/path/*"}, s3err.GetMultipleWildcardCORSOriginErr("a/really/*/long/*/path/*")},
|
|
{[]string{"http://***", "www.example.net"}, s3err.GetMultipleWildcardCORSOriginErr("http://***")},
|
|
} {
|
|
err := putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{
|
|
{
|
|
AllowedOrigins: test.allowedOrigins,
|
|
AllowedMethods: []string{http.MethodPost},
|
|
AllowedHeaders: []string{"x-amz-expected-bucket-owner"},
|
|
ExposeHeaders: []string{"x-vgw-something"},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err := checkApiErr(err, test.expectedErr); err != nil {
|
|
return fmt.Errorf("test %v failed: %w", i+1, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketCors_invalid_method(s *S3Conf) error {
|
|
testName := "PutBucketCors_invalid_method"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
for _, test := range []struct {
|
|
allowedMethods []string
|
|
expectedErr s3err.APIError
|
|
}{
|
|
// empty array
|
|
{[]string{}, s3err.GetAPIError(s3err.ErrMalformedXML)},
|
|
// empty method
|
|
{[]string{""}, s3err.GetUnsopportedCORSMethodErr("")},
|
|
// invalid methods
|
|
{[]string{"get"}, s3err.GetUnsopportedCORSMethodErr("get")},
|
|
{[]string{"put"}, s3err.GetUnsopportedCORSMethodErr("put")},
|
|
{[]string{"post"}, s3err.GetUnsopportedCORSMethodErr("post")},
|
|
{[]string{"head"}, s3err.GetUnsopportedCORSMethodErr("head")},
|
|
{[]string{"delete"}, s3err.GetUnsopportedCORSMethodErr("delete")},
|
|
{[]string{http.MethodGet, http.MethodPatch}, s3err.GetUnsopportedCORSMethodErr(http.MethodPatch)},
|
|
{[]string{http.MethodPost, http.MethodOptions}, s3err.GetUnsopportedCORSMethodErr(http.MethodOptions)},
|
|
{[]string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodDelete, "invalid_method"}, s3err.GetUnsopportedCORSMethodErr("invalid_method")},
|
|
} {
|
|
err := putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"http://origin.com"},
|
|
AllowedMethods: test.allowedMethods,
|
|
AllowedHeaders: []string{"X-Amz-Date"},
|
|
ExposeHeaders: []string{"Authorization"},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if err := checkApiErr(err, test.expectedErr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketCors_invalid_header(s *S3Conf) error {
|
|
testName := "PutBucketCors_invalid_header"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
for _, test := range []struct {
|
|
invalidHeader string
|
|
headers []string
|
|
}{
|
|
{"invalid header", []string{"X-Amz-Date", "X-Amz-Content-Sha256", "invalid header"}},
|
|
{"X-Custom:Header", []string{"Authorization", "X-Custom:Header"}},
|
|
{"X(Custom)", []string{"Content-Length", "X(Custom)"}},
|
|
{"Bad/Header", []string{"Content-Encoding", "Bad/Header"}},
|
|
{"X[Key]", []string{"Date", "X[Key]"}},
|
|
{"Bad=Name", []string{"X-Amz-Custome-Header", "Bad=Name"}},
|
|
{`X"Quote"`, []string{`X"Quote"`}},
|
|
} {
|
|
// first check for allowed headers
|
|
cfg := &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"http://origin.com"},
|
|
AllowedMethods: []string{http.MethodPost},
|
|
AllowedHeaders: test.headers,
|
|
ExposeHeaders: []string{"Authorization"},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: cfg,
|
|
})
|
|
if err := checkApiErr(err, s3err.GetInvalidCORSHeaderErr(test.invalidHeader)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// second check for expose headers
|
|
cfg.CORSRules[0].AllowedHeaders = []string{"X-Amz-Date"} // set to any valid header
|
|
cfg.CORSRules[0].ExposeHeaders = test.headers
|
|
|
|
err = putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: cfg,
|
|
})
|
|
if err := checkApiErr(err, s3err.GetInvalidCORSHeaderErr(test.invalidHeader)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketCors_md5(s *S3Conf) error {
|
|
testName := "PutBucketCors_md5"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
cfg := &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"http://origin.com", "something.net"},
|
|
AllowedMethods: []string{http.MethodPost, http.MethodPut, http.MethodHead},
|
|
AllowedHeaders: []string{"X-Amz-Date", "X-Amz-Meta-Something", "Content-Type"},
|
|
ExposeHeaders: []string{"Authorization", "Content-Disposition"},
|
|
MaxAgeSeconds: getPtr(int32(125)),
|
|
ID: getPtr("my-id"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, test := range []struct {
|
|
md5 string
|
|
err error
|
|
}{
|
|
// invalid content-md5
|
|
{"invalid", s3err.GetAPIError(s3err.ErrInvalidDigest)},
|
|
// incorrect content-md5
|
|
{"uU0nuZNNPgilLlLX2n2r+s==", s3err.GetAPIError(s3err.ErrBadDigest)},
|
|
// correct content-md5
|
|
{"liZChnDYdpG46exsGGaBhg==", nil},
|
|
} {
|
|
err := putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: cfg,
|
|
ContentMD5: &test.md5,
|
|
})
|
|
if test.err == nil && err != nil {
|
|
return fmt.Errorf("test %v failed: expected no error but got %v", i+1, err)
|
|
}
|
|
if test.err != nil {
|
|
apiErr, ok := test.err.(s3err.APIError)
|
|
if !ok {
|
|
return fmt.Errorf("test %v failed: expected s3err.APIError", i+1)
|
|
}
|
|
|
|
if err := checkApiErr(err, apiErr); err != nil {
|
|
return fmt.Errorf("test %v failed: %v", i+1, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func PutBucketCors_success(s *S3Conf) error {
|
|
testName := "PutBucketCors_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
maxAgePositive, maxAgeNegative := int32(3000), int32(-100)
|
|
return putBucketCors(s3client, &s3.PutBucketCorsInput{
|
|
Bucket: &bucket,
|
|
CORSConfiguration: &types.CORSConfiguration{
|
|
CORSRules: []types.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"http://origin.com"},
|
|
AllowedMethods: []string{http.MethodPost, http.MethodPut},
|
|
AllowedHeaders: []string{"X-Amz-Date"},
|
|
ExposeHeaders: []string{"Authorization"},
|
|
// weirdely negative max age seconds are also considered valid
|
|
MaxAgeSeconds: &maxAgeNegative,
|
|
},
|
|
{
|
|
AllowedOrigins: []string{"*"},
|
|
AllowedMethods: []string{http.MethodDelete, http.MethodGet, http.MethodHead},
|
|
AllowedHeaders: []string{"Content-Type", "Content-Encoding", "Content-MD5"},
|
|
ExposeHeaders: []string{"Authorization", "X-Amz-Date", "X-Amz-Conten-Sha256"},
|
|
ID: getPtr("id"),
|
|
MaxAgeSeconds: &maxAgePositive,
|
|
},
|
|
{
|
|
AllowedOrigins: []string{"http://example.com", "https://something.net", "http://*origin.com"},
|
|
AllowedMethods: []string{http.MethodGet},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
}
|