diff --git a/api/errors/errors.go b/api/errors/errors.go index d35750c4..27ac5669 100644 --- a/api/errors/errors.go +++ b/api/errors/errors.go @@ -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", diff --git a/api/handler/delete.go b/api/handler/delete.go index 67b025d5..a83242c8 100644 --- a/api/handler/delete.go +++ b/api/handler/delete.go @@ -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 { diff --git a/api/handler/delete_test.go b/api/handler/delete_test.go index 83299de2..ed0c992b 100644 --- a/api/handler/delete_test.go +++ b/api/handler/delete_test.go @@ -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) diff --git a/api/handler/lifecycle.go b/api/handler/lifecycle.go index 4ebdb2ed..c82b4130 100644 --- a/api/handler/lifecycle.go +++ b/api/handler/lifecycle.go @@ -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) diff --git a/api/handler/lifecycle_test.go b/api/handler/lifecycle_test.go index 69382abe..12097a78 100644 --- a/api/handler/lifecycle_test.go +++ b/api/handler/lifecycle_test.go @@ -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 }