diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index 21aef60..3c3b7d0 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -33,7 +33,8 @@ import ( ) const ( - iso8601Format = "20060102T150405Z" + iso8601Format = "20060102T150405Z" + maxObjSizeLimit = 5 * 1024 * 1024 * 1024 // 5gb ) type RootUserConfig struct { @@ -105,6 +106,16 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au return sendResponse(ctx, err, logger, mm) } + var contentLength int64 + contentLengthStr := ctx.Get("Content-Length") + if contentLengthStr != "" { + contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64) + //TODO: not sure if InvalidRequest should be returned in this case + if err != nil { + return sendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), logger, mm) + } + } + hashPayload := ctx.Get("X-Amz-Content-Sha256") if utils.IsBigDataAction(ctx) { // for streaming PUT actions, authorization is deferred @@ -126,6 +137,18 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au if err != nil { return sendResponse(ctx, err, logger, mm) } + + return ctx.Next() + } + + // Content-Length has to be set for data uploads: PutObject, UploadPart + if contentLengthStr == "" { + return sendResponse(ctx, s3err.GetAPIError(s3err.ErrMissingContentLength), logger, mm) + } + // the upload limit for big data actions: PutObject, UploadPart + // is 5gb. If the size exceeds the limit, return 'EntityTooLarge' err + if contentLength > maxObjSizeLimit { + return sendResponse(ctx, s3err.GetAPIError(s3err.ErrEntityTooLarge), logger, mm) } return ctx.Next() @@ -142,15 +165,6 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au } } - var contentLength int64 - contentLengthStr := ctx.Get("Content-Length") - if contentLengthStr != "" { - contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64) - if err != nil { - return sendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), logger, mm) - } - } - err = utils.CheckValidSignature(ctx, authData, account.Secret, hashPayload, tdate, contentLength, debug) if err != nil { return sendResponse(ctx, err, logger, mm) diff --git a/s3api/middlewares/presign-auth.go b/s3api/middlewares/presign-auth.go index aee10a7..eb86ccd 100644 --- a/s3api/middlewares/presign-auth.go +++ b/s3api/middlewares/presign-auth.go @@ -16,6 +16,7 @@ package middlewares import ( "io" + "strconv" "time" "github.com/gofiber/fiber/v2" @@ -54,7 +55,26 @@ func VerifyPresignedV4Signature(root RootUserConfig, iam auth.IAMService, logger } ctx.Locals("account", account) + var contentLength int64 + contentLengthStr := ctx.Get("Content-Length") + if contentLengthStr != "" { + contentLength, err = strconv.ParseInt(contentLengthStr, 10, 64) + //TODO: not sure if InvalidRequest should be returned in this case + if err != nil { + return sendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), logger, mm) + } + } + if utils.IsBigDataAction(ctx) { + // Content-Length has to be set for data uploads: PutObject, UploadPart + if contentLengthStr == "" { + return sendResponse(ctx, s3err.GetAPIError(s3err.ErrMissingContentLength), logger, mm) + } + // the upload limit for big data actions: PutObject, UploadPart + // is 5gb. If the size exceeds the limit, return 'EntityTooLarge' err + if contentLength > maxObjSizeLimit { + return sendResponse(ctx, s3err.GetAPIError(s3err.ErrEntityTooLarge), logger, mm) + } wrapBodyReader(ctx, func(r io.Reader) io.Reader { return utils.NewPresignedAuthReader(ctx, r, authData, account.Secret, debug) }) diff --git a/s3api/utils/chunk-reader.go b/s3api/utils/chunk-reader.go index 1255599..81bd52f 100644 --- a/s3api/utils/chunk-reader.go +++ b/s3api/utils/chunk-reader.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "net/http" + "strconv" "strings" "time" @@ -25,6 +26,10 @@ import ( "github.com/versity/versitygw/s3err" ) +const ( + maxObjSizeLimit = 5 * 1024 * 1024 * 1024 // 5gb +) + type payloadType string const ( @@ -96,10 +101,20 @@ func IsStreamingPayload(str string) bool { } func NewChunkReader(ctx *fiber.Ctx, r io.Reader, authdata AuthData, region, secret string, date time.Time, debug bool) (io.Reader, error) { - decContLength := ctx.Get("X-Amz-Decoded-Content-Length") - if decContLength == "" { - return nil, s3err.GetAPIError(s3err.ErrMissingDecodedContentLength) + decContLengthStr := ctx.Get("X-Amz-Decoded-Content-Length") + if decContLengthStr == "" { + return nil, s3err.GetAPIError(s3err.ErrMissingContentLength) } + decContLength, err := strconv.ParseInt(decContLengthStr, 10, 64) + //TODO: not sure if InvalidRequest should be returned in this case + if err != nil { + return nil, s3err.GetAPIError(s3err.ErrInvalidRequest) + } + + if decContLength > maxObjSizeLimit { + return nil, s3err.GetAPIError(s3err.ErrEntityTooLarge) + } + contentSha256 := payloadType(ctx.Get("X-Amz-Content-Sha256")) if !contentSha256.isValid() { //TODO: Add proper APIError diff --git a/s3err/s3err.go b/s3err/s3err.go index 2d64605..bf121e7 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -118,7 +118,7 @@ const ( ErrSignatureTerminationStr ErrSignatureIncorrService ErrContentSHA256Mismatch - ErrMissingDecodedContentLength + ErrMissingContentLength ErrInvalidAccessKeyID ErrRequestNotReadyYet ErrMissingDateHeader @@ -486,7 +486,7 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The provided 'x-amz-content-sha256' header does not match what was computed.", HTTPStatusCode: http.StatusBadRequest, }, - ErrMissingDecodedContentLength: { + ErrMissingContentLength: { Code: "MissingContentLength", Description: "You must provide the Content-Length HTTP header.", HTTPStatusCode: http.StatusLengthRequired,