mirror of
https://github.com/versity/versitygw.git
synced 2026-01-04 19:13:57 +00:00
Closes #1549 Fixes #1593 Fixes #1521 Fixes #1427 Fixes #1311 Fixes #1301 Fixes #1040 This PR primarily focuses on checksum calculation within the gateway, but it also includes several related fixes and improvements. It introduces a middleware responsible for handling and calculating checksums for the `x-amz-checksum-*` headers and `Content-MD5`. The middleware is applied only to actions that expect a request body or checksum headers. It also enforces validation for actions that require a non-empty request body, returning an error if the body is missing. Similarly, it returns an error for actions where at least one checksum header (`Content-MD5` or `x-amz-checksum-*`) is required but none is provided. The implementation is based on [https://gist.github.com/niksis02/eec3198f03e561a0998d67af75c648d7](the reference table), tested directly against S3: It also fixes the error case where the `x-amz-sdk-checksum-algorithm` header is present but no corresponding `x-amz-checksum-*` or `x-amz-trailer` header is included. Additionally, the PR improves validation for the `x-amz-content-sha256` header. For actions that require this header, an error is now returned when it’s missing. For actions that don’t require it, the middleware no longer enforces its presence. Following the common S3 pattern, the header remains mandatory for admin routes. Finally, the `x-amz-content-sha256` header is now optional for anonymous requests, as it is not required in that case.
122 lines
3.0 KiB
Go
122 lines
3.0 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 middlewares
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/versity/versitygw/s3api/utils"
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
// VerifyChecksums parses, validates, and calculates the
|
|
// Content-MD5 and x-amz-checksum-* headers.
|
|
// Additionally, it ensures that the request body is not empty
|
|
// for actions that require a non-empty body. For large data actions(PutObject, UploadPart),
|
|
// it wraps the body reader to handle Content-MD5:
|
|
// the x-amz-checksum-* headers are explicitly processed by the backend.
|
|
func VerifyChecksums(streamBody bool, requireBody bool, requireChecksum bool) fiber.Handler {
|
|
return func(ctx *fiber.Ctx) error {
|
|
md5sum := ctx.Get("Content-Md5")
|
|
|
|
if streamBody {
|
|
// for large data actions(PutObject, UploadPart)
|
|
// only stack the md5 reader,as x-amz-checksum-*
|
|
// calculation is explicitly handled in back-end
|
|
if md5sum == "" {
|
|
return nil
|
|
}
|
|
|
|
if !isValidMD5(md5sum) {
|
|
return s3err.GetAPIError(s3err.ErrInvalidDigest)
|
|
}
|
|
|
|
var err error
|
|
wrapBodyReader(ctx, func(r io.Reader) io.Reader {
|
|
r, err = utils.NewHashReader(r, md5sum, utils.HashTypeMd5)
|
|
return r
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
body := ctx.Body()
|
|
if requireBody && len(body) == 0 {
|
|
return s3err.GetAPIError(s3err.ErrMissingRequestBody)
|
|
}
|
|
|
|
var rdr io.Reader
|
|
var err error
|
|
if md5sum != "" {
|
|
if !isValidMD5(md5sum) {
|
|
return s3err.GetAPIError(s3err.ErrInvalidDigest)
|
|
}
|
|
|
|
rdr, err = utils.NewHashReader(bytes.NewReader(body), md5sum, utils.HashTypeMd5)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// parse and validate checksum headers
|
|
algo, checksums, err := utils.ParseChecksumHeadersAndSdkAlgo(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if algo != "" {
|
|
r, err := utils.NewHashReader(bytes.NewReader(body), checksums[algo], utils.HashType(strings.ToLower(string(algo))))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if rdr != nil {
|
|
// combine both md5 and the checksum readers
|
|
rdr = io.MultiReader(rdr, r)
|
|
} else {
|
|
rdr = r
|
|
}
|
|
}
|
|
|
|
if rdr == nil && requireChecksum {
|
|
return s3err.GetAPIError(s3err.ErrChecksumRequired)
|
|
}
|
|
|
|
if rdr != nil {
|
|
_, err = io.Copy(io.Discard, rdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func isValidMD5(s string) bool {
|
|
decoded, err := base64.StdEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return len(decoded) == 16
|
|
}
|