diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index 2291e70..9d42aac 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -109,6 +109,7 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au return sendResponse(ctx, err, logger, mm) } + hashPayload := ctx.Get("X-Amz-Content-Sha256") if utils.IsBigDataAction(ctx) { // for streaming PUT actions, authorization is deferred // until end of stream due to need to get length and @@ -116,10 +117,24 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, logger s3log.Au wrapBodyReader(ctx, func(r io.Reader) io.Reader { return utils.NewAuthReader(ctx, r, authData, account.Secret, debug) }) + + // wrap the io.Reader with ChunkReader if x-amz-content-sha256 + // provide chunk encoding value + if utils.IsStreamingPayload(hashPayload) { + var err error + wrapBodyReader(ctx, func(r io.Reader) io.Reader { + var cr io.Reader + cr, err = utils.NewChunkReader(ctx, r, authData, region, account.Secret, tdate) + return cr + }) + if err != nil { + return sendResponse(ctx, err, logger, mm) + } + } + return ctx.Next() } - hashPayload := ctx.Get("X-Amz-Content-Sha256") if !utils.IsSpecialPayload(hashPayload) { // Calculate the hash of the request payload hashedPayload := sha256.Sum256(ctx.Body()) diff --git a/s3api/middlewares/chunk.go b/s3api/middlewares/chunk.go deleted file mode 100644 index 0f81f4c..0000000 --- a/s3api/middlewares/chunk.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024 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 middlewares - -import ( - "io" - "time" - - "github.com/gofiber/fiber/v2" - "github.com/versity/versitygw/auth" - "github.com/versity/versitygw/metrics" - "github.com/versity/versitygw/s3api/utils" - "github.com/versity/versitygw/s3log" -) - -// ProcessChunkedBody initializes the chunked upload stream if the -// request appears to be a chunked upload -func ProcessChunkedBody(root RootUserConfig, iam auth.IAMService, logger s3log.AuditLogger, mm *metrics.Manager, region string) fiber.Handler { - return func(ctx *fiber.Ctx) error { - decodedLength := ctx.Get("X-Amz-Decoded-Content-Length") - if decodedLength == "" { - return ctx.Next() - } - // TODO: validate content length - - authData, err := utils.ParseAuthorization(ctx.Get("Authorization")) - if err != nil { - return sendResponse(ctx, err, logger, mm) - } - - acct := ctx.Locals("account").(auth.Account) - amzdate := ctx.Get("X-Amz-Date") - date, _ := time.Parse(iso8601Format, amzdate) - - if utils.IsBigDataAction(ctx) { - var err error - wrapBodyReader(ctx, func(r io.Reader) io.Reader { - var cr io.Reader - cr, err = utils.NewChunkReader(ctx, r, authData, region, acct.Secret, date) - return cr - }) - if err != nil { - return sendResponse(ctx, err, logger, mm) - } - return ctx.Next() - } - - return ctx.Next() - } -} diff --git a/s3api/server.go b/s3api/server.go index cbf960b..7d30a96 100644 --- a/s3api/server.go +++ b/s3api/server.go @@ -81,7 +81,6 @@ func New( // Authentication middlewares app.Use(middlewares.VerifyPresignedV4Signature(root, iam, l, mm, region, server.debug)) app.Use(middlewares.VerifyV4Signature(root, iam, l, mm, region, server.debug)) - app.Use(middlewares.ProcessChunkedBody(root, iam, l, mm, region)) app.Use(middlewares.VerifyMD5Body(l)) app.Use(middlewares.AclParser(be, l, server.readonly)) diff --git a/s3api/utils/chunk-reader.go b/s3api/utils/chunk-reader.go index 3bacaf3..109abdf 100644 --- a/s3api/utils/chunk-reader.go +++ b/s3api/utils/chunk-reader.go @@ -20,6 +20,7 @@ import ( "time" "github.com/gofiber/fiber/v2" + "github.com/versity/versitygw/s3err" ) type payloadType string @@ -71,12 +72,24 @@ func (c checksumType) isValid() bool { c == checksumTypeCrc64nvme } -// IsSpecialPayload checks for streaming/unsigned authorization types +// IsSpecialPayload checks for special authorization types func IsSpecialPayload(str string) bool { return specialValues[payloadType(str)] } +// IsChunkEncoding checks for streaming/unsigned authorization types +func IsStreamingPayload(str string) bool { + pt := payloadType(str) + return pt == payloadTypeStreamingUnsignedTrailer || + pt == payloadTypeStreamingSigned || + pt == payloadTypeStreamingSignedTrailer +} + func NewChunkReader(ctx *fiber.Ctx, r io.Reader, authdata AuthData, region, secret string, date time.Time) (io.Reader, error) { + decContLength := ctx.Get("X-Amz-Decoded-Content-Length") + if decContLength == "" { + return nil, s3err.GetAPIError(s3err.ErrMissingDecodedContentLength) + } 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 b5433b5..d3ab4e8 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -112,6 +112,7 @@ const ( ErrSignatureTerminationStr ErrSignatureIncorrService ErrContentSHA256Mismatch + ErrMissingDecodedContentLength ErrInvalidAccessKeyID ErrRequestNotReadyYet ErrMissingDateHeader @@ -444,6 +445,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The provided 'x-amz-content-sha256' header does not match what was computed.", HTTPStatusCode: http.StatusBadRequest, }, + ErrMissingDecodedContentLength: { + Code: "MissingContentLength", + Description: "You must provide the Content-Length HTTP header.", + HTTPStatusCode: http.StatusLengthRequired, + }, ErrMissingDateHeader: { Code: "AccessDenied", Description: "AWS authentication requires a valid Date or x-amz-date header.",