mirror of
https://github.com/versity/versitygw.git
synced 2026-01-04 11:03:57 +00:00
fix: adds error routes to reject x-amz-copy-source for GET, POST, HEAD, DELETErequests
Fixes #1612 `x-amz-copy-source` is rejected with an **InvalidArgument** error in S3 for all HTTP methods other than **PUT** (i.e., **GET**, **POST**, **HEAD**, and **DELETE**). For **POST** requests, the behavior is slightly different: the error is returned only when the **uploadId** query parameter is present; otherwise, **MethodNotAllowed** is returned. This behavior applies to both bucket-level and object-level operations.
This commit is contained in:
@@ -89,6 +89,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
}
|
||||
|
||||
// ListBuckets action
|
||||
|
||||
// copy source is not allowed on '/'
|
||||
app.Get("/", middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
app.Get("/",
|
||||
controllers.ProcessHandlers(
|
||||
ctrl.ListBuckets,
|
||||
@@ -384,6 +390,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
))
|
||||
|
||||
// HeadBucket action
|
||||
|
||||
// copy source is not allowed on bucket HEAD operation
|
||||
bucketRouter.Head("/", middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
bucketRouter.Head("",
|
||||
controllers.ProcessHandlers(
|
||||
ctrl.HeadBucket,
|
||||
@@ -399,6 +411,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
))
|
||||
|
||||
// DELETE bucket operations
|
||||
|
||||
// copy source is not allowed on bucket DELETE operation
|
||||
bucketRouter.Delete("/", middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
bucketRouter.Delete("",
|
||||
middlewares.MatchQueryArgs("tagging"),
|
||||
controllers.ProcessHandlers(
|
||||
@@ -582,6 +600,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
))
|
||||
|
||||
// GET bucket operations
|
||||
|
||||
// copy source is not allowed on bucket GET operation
|
||||
bucketRouter.Get("/", middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
bucketRouter.Get("",
|
||||
middlewares.MatchQueryArgs("location"),
|
||||
controllers.ProcessHandlers(
|
||||
@@ -973,6 +997,13 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
// bucket POST operation is not allowed with uploadId and copy source
|
||||
bucketRouter.Post("/",
|
||||
middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
middlewares.MatchQueryArgs("uploadId"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
// DeleteObjects action
|
||||
bucketRouter.Post("",
|
||||
middlewares.MatchQueryArgs("delete"),
|
||||
@@ -989,6 +1020,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
// object HEAD operation is not allowed with copy source
|
||||
objectRouter.Head("/",
|
||||
middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
// HeadObject
|
||||
objectRouter.Head("",
|
||||
controllers.ProcessHandlers(
|
||||
@@ -1011,6 +1048,12 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrGetUploadsWithKey)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
// object GET operation is not allowed with copy source
|
||||
objectRouter.Get("/",
|
||||
middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
objectRouter.Get("",
|
||||
middlewares.MatchQueryArgs("tagging"),
|
||||
controllers.ProcessHandlers(
|
||||
@@ -1103,6 +1146,13 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
))
|
||||
|
||||
// DELETE object operations
|
||||
|
||||
// object DELETE operation is not allowed with copy source
|
||||
objectRouter.Delete("/",
|
||||
middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
objectRouter.Delete("",
|
||||
middlewares.MatchQueryArgs("tagging"),
|
||||
controllers.ProcessHandlers(
|
||||
@@ -1142,6 +1192,15 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ
|
||||
middlewares.ParseAcl(be),
|
||||
))
|
||||
|
||||
// object POST operations
|
||||
|
||||
// object POST operation is not allowed with copy source and uploadId
|
||||
objectRouter.Post("/",
|
||||
middlewares.MatchHeader("X-Amz-Copy-Source"),
|
||||
middlewares.MatchQueryArgs("uploadId"),
|
||||
controllers.ProcessHandlers(ctrl.HandleErrorRoute(s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)), metrics.ActionUndetected, services),
|
||||
)
|
||||
|
||||
objectRouter.Post("",
|
||||
middlewares.MatchQueryArgs("restore"),
|
||||
controllers.ProcessHandlers(
|
||||
|
||||
@@ -124,6 +124,7 @@ const (
|
||||
ErrRequestNotReadyYet
|
||||
ErrMissingDateHeader
|
||||
ErrGetUploadsWithKey
|
||||
ErrCopySourceNotAllowed
|
||||
ErrInvalidRequest
|
||||
ErrAuthNotSetup
|
||||
ErrNotImplemented
|
||||
@@ -521,6 +522,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Key is not expected for the GET method ?uploads subresource",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrCopySourceNotAllowed: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "You can only specify a copy source header for copy requests.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRequest: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Invalid Request.",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -141,6 +142,53 @@ func RouterGetUploadsWithKey(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func RouterCopySourceNotAllowed(s *S3Conf) error {
|
||||
testName := "RouterCopySourceNotAllowed"
|
||||
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
for _, method := range []string{
|
||||
http.MethodPost,
|
||||
http.MethodDelete,
|
||||
http.MethodGet,
|
||||
http.MethodHead,
|
||||
} {
|
||||
for _, path := range []string{
|
||||
"/bucket",
|
||||
"/bucket/object",
|
||||
} {
|
||||
if method == http.MethodPost {
|
||||
// the error for POST request occurs only when uploadId is there
|
||||
path += "?uploadId=something"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, s.endpoint+path, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make %s request to %s", method, path)
|
||||
}
|
||||
|
||||
req.Header.Add("x-amz-copy-source", "bucket/object")
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send %s request to %s", method, path)
|
||||
}
|
||||
|
||||
if method == http.MethodHead {
|
||||
// for head requests only check the status code
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
return fmt.Errorf("expected 400 status code for HEAD %s request, instead got %v", path, resp.StatusCode)
|
||||
}
|
||||
} else {
|
||||
if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCopySourceNotAllowed)); err != nil {
|
||||
return fmt.Errorf("%s %s: %w", method, path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CORS middleware tests
|
||||
func CORSMiddleware_invalid_method(s *S3Conf) error {
|
||||
testName := "CORSMiddleware_invalid_method"
|
||||
|
||||
@@ -1087,6 +1087,7 @@ func TestRouter(ts *TestState) {
|
||||
ts.Run(RouterPostObjectWithoutQuery)
|
||||
ts.Run(RouterPUTObjectOnlyUploadId)
|
||||
ts.Run(RouterGetUploadsWithKey)
|
||||
ts.Run(RouterCopySourceNotAllowed)
|
||||
}
|
||||
|
||||
type IntTest func(s3 *S3Conf) error
|
||||
@@ -1724,5 +1725,6 @@ func GetIntTests() IntTests {
|
||||
"RouterPostObjectWithoutQuery": RouterPostObjectWithoutQuery,
|
||||
"RouterPUTObjectOnlyUploadId": RouterPUTObjectOnlyUploadId,
|
||||
"RouterGetUploadsWithKey": RouterGetUploadsWithKey,
|
||||
"RouterCopySourceNotAllowed": RouterCopySourceNotAllowed,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user