[#612] Make Content-Md5 header check optional
All checks were successful
/ DCO (pull_request) Successful in 34s
/ Vulncheck (pull_request) Successful in 51s
/ Builds (pull_request) Successful in 1m38s
/ OCI image (pull_request) Successful in 2m19s
/ Lint (pull_request) Successful in 2m27s
/ Tests (pull_request) Successful in 1m53s

Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
Marina Biryukova 2025-02-12 14:01:46 +03:00
parent ee46382a68
commit 853036e44e
5 changed files with 85 additions and 82 deletions

View file

@ -60,7 +60,6 @@ const (
ErrMalformedACL
ErrMalformedXML
ErrMissingContentLength
ErrMissingContentMD5
ErrMissingRequestBodyError
ErrMissingSecurityHeader
ErrNoSuchBucket
@ -478,12 +477,6 @@ var errorCodes = errorCodeMap{
Description: "You must provide the Content-Length HTTP header.",
HTTPStatusCode: http.StatusLengthRequired,
},
ErrMissingContentMD5: {
ErrCode: ErrMissingContentMD5,
Code: "MissingContentMD5",
Description: "Missing required header for this request: Content-Md5.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingSecurityHeader: {
ErrCode: ErrMissingSecurityHeader,
Code: "MissingSecurityHeader",

View file

@ -131,13 +131,6 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
ctx := r.Context()
reqInfo := middleware.GetReqInfo(ctx)
// Content-Md5 is required and should be set
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if _, ok := r.Header[api.ContentMD5]; !ok {
h.logAndSendError(ctx, w, "missing Content-MD5", reqInfo, errors.GetAPIError(errors.ErrMissingContentMD5))
return
}
// Content-Length is required and should be non-zero
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if r.ContentLength <= 0 {

View file

@ -542,7 +542,6 @@ func deleteObjectsBase(hc *handlerContext, bktName string, objVersions [][2]stri
}
w, r := prepareTestRequest(hc, bktName, "", req)
r.Header.Set(api.ContentMD5, "")
hc.Handler().DeleteMultipleObjectsHandler(w, r)
assertStatus(hc.t, w, http.StatusOK)

View file

@ -55,34 +55,29 @@ func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
ctx := r.Context()
reqInfo := middleware.GetReqInfo(ctx)
// Content-Md5 is required and should be set
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
if _, ok := r.Header[api.ContentMD5]; !ok {
h.logAndSendError(ctx, w, "missing Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrMissingContentMD5))
return
}
headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5))
if err != nil {
h.logAndSendError(ctx, w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
return
}
cfg := new(data.LifecycleConfiguration)
if err = h.cfg.NewXMLDecoder(tee, r.UserAgent()).Decode(cfg); err != nil {
if err := h.cfg.NewXMLDecoder(tee, r.UserAgent()).Decode(cfg); err != nil {
h.logAndSendError(ctx, w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
return
}
bodyMD5, err := getContentMD5(&buf)
if err != nil {
h.logAndSendError(ctx, w, "could not get content md5", reqInfo, err)
return
}
if _, ok := r.Header[api.ContentMD5]; ok {
headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5))
if err != nil {
h.logAndSendError(ctx, w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
return
}
if !bytes.Equal(headerMD5, bodyMD5) {
h.logAndSendError(ctx, w, "Content-MD5 does not match", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
return
bodyMD5, err := getContentMD5(&buf)
if err != nil {
h.logAndSendError(ctx, w, "could not get content md5", reqInfo, err)
return
}
if !bytes.Equal(headerMD5, bodyMD5) {
h.logAndSendError(ctx, w, "Content-MD5 does not match", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
return
}
}
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -29,6 +29,8 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
for _, tc := range []struct {
name string
body *data.LifecycleConfiguration
headers map[string]string
addMD5 bool
errorCode apierr.ErrorCode
}{
{
@ -70,6 +72,22 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
},
},
},
{
name: "correct Content-Md5 header",
body: &data.LifecycleConfiguration{
Rules: []data.LifecycleRule{
{
ID: "rule",
Status: data.LifecycleStatusEnabled,
Expiration: &data.LifecycleExpiration{
Days: ptr(21),
},
},
},
},
addMD5: true,
},
{
name: "too many rules",
body: func() *data.LifecycleConfiguration {
@ -407,14 +425,44 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
},
errorCode: apierr.ErrInvalidRequest,
},
{
name: "invalid Content-Md5 header",
body: &data.LifecycleConfiguration{
Rules: []data.LifecycleRule{
{
Status: data.LifecycleStatusEnabled,
Expiration: &data.LifecycleExpiration{
Days: ptr(21),
},
},
},
},
headers: map[string]string{api.ContentMD5: "invalid"},
errorCode: apierr.ErrInvalidDigest,
},
{
name: "Content-Md5 header does not match body md5 hash",
body: &data.LifecycleConfiguration{
Rules: []data.LifecycleRule{
{
Status: data.LifecycleStatusEnabled,
Expiration: &data.LifecycleExpiration{
Days: ptr(21),
},
},
},
},
headers: map[string]string{api.ContentMD5: base64.StdEncoding.EncodeToString([]byte("some-hash"))},
errorCode: apierr.ErrInvalidDigest,
},
} {
t.Run(tc.name, func(t *testing.T) {
if tc.errorCode > 0 {
putBucketLifecycleConfigurationErr(hc, bktName, tc.body, apierr.GetAPIError(tc.errorCode))
putBucketLifecycleConfigurationErr(hc, bktName, tc.body, tc.headers, apierr.GetAPIError(tc.errorCode))
return
}
putBucketLifecycleConfiguration(hc, bktName, tc.body)
putBucketLifecycleConfiguration(hc, bktName, tc.body, tc.headers, tc.addMD5)
cfg := getBucketLifecycleConfiguration(hc, bktName)
require.Equal(t, tc.body.Rules, cfg.Rules)
@ -448,45 +496,13 @@ func TestPutBucketLifecycleIDGeneration(t *testing.T) {
},
}
putBucketLifecycleConfiguration(hc, bktName, lifecycle)
putBucketLifecycleConfiguration(hc, bktName, lifecycle, nil, false)
cfg := getBucketLifecycleConfiguration(hc, bktName)
require.Len(t, cfg.Rules, 2)
require.NotEmpty(t, cfg.Rules[0].ID)
require.NotEmpty(t, cfg.Rules[1].ID)
}
func TestPutBucketLifecycleInvalidMD5(t *testing.T) {
hc := prepareHandlerContext(t)
bktName := "bucket-lifecycle-md5"
createBucket(hc, bktName)
lifecycle := &data.LifecycleConfiguration{
Rules: []data.LifecycleRule{
{
Status: data.LifecycleStatusEnabled,
Expiration: &data.LifecycleExpiration{
Days: ptr(21),
},
},
},
}
w, r := prepareTestRequest(hc, bktName, "", lifecycle)
hc.Handler().PutBucketLifecycleHandler(w, r)
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMissingContentMD5))
w, r = prepareTestRequest(hc, bktName, "", lifecycle)
r.Header.Set(api.ContentMD5, "")
hc.Handler().PutBucketLifecycleHandler(w, r)
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrInvalidDigest))
w, r = prepareTestRequest(hc, bktName, "", lifecycle)
r.Header.Set(api.ContentMD5, "some-hash")
hc.Handler().PutBucketLifecycleHandler(w, r)
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrInvalidDigest))
}
func TestPutBucketLifecycleInvalidXML(t *testing.T) {
hc := prepareHandlerContext(t)
@ -505,25 +521,32 @@ func TestPutBucketLifecycleInvalidXML(t *testing.T) {
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMalformedXML))
}
func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration) {
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg)
func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, headers map[string]string, addMD5 bool) {
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg, headers, addMD5)
assertStatus(hc.t, w, http.StatusOK)
}
func putBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, err apierr.Error) {
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg)
func putBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, headers map[string]string, err apierr.Error) {
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg, headers, false)
assertS3Error(hc.t, w, err)
}
func putBucketLifecycleConfigurationBase(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration) *httptest.ResponseRecorder {
func putBucketLifecycleConfigurationBase(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, headers map[string]string, addMD5 bool) *httptest.ResponseRecorder {
w, r := prepareTestRequest(hc, bktName, "", cfg)
rawBody, err := xml.Marshal(cfg)
require.NoError(hc.t, err)
for k, v := range headers {
r.Header.Set(k, v)
}
if addMD5 {
rawBody, err := xml.Marshal(cfg)
require.NoError(hc.t, err)
hash := md5.New()
hash.Write(rawBody)
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString(hash.Sum(nil)))
}
hash := md5.New()
hash.Write(rawBody)
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString(hash.Sum(nil)))
hc.Handler().PutBucketLifecycleHandler(w, r)
return w
}