forked from TrueCloudLab/frostfs-s3-gw
[#612] Make Content-Md5 header check optional
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
ee46382a68
commit
853036e44e
5 changed files with 85 additions and 82 deletions
|
@ -60,7 +60,6 @@ const (
|
||||||
ErrMalformedACL
|
ErrMalformedACL
|
||||||
ErrMalformedXML
|
ErrMalformedXML
|
||||||
ErrMissingContentLength
|
ErrMissingContentLength
|
||||||
ErrMissingContentMD5
|
|
||||||
ErrMissingRequestBodyError
|
ErrMissingRequestBodyError
|
||||||
ErrMissingSecurityHeader
|
ErrMissingSecurityHeader
|
||||||
ErrNoSuchBucket
|
ErrNoSuchBucket
|
||||||
|
@ -478,12 +477,6 @@ var errorCodes = errorCodeMap{
|
||||||
Description: "You must provide the Content-Length HTTP header.",
|
Description: "You must provide the Content-Length HTTP header.",
|
||||||
HTTPStatusCode: http.StatusLengthRequired,
|
HTTPStatusCode: http.StatusLengthRequired,
|
||||||
},
|
},
|
||||||
ErrMissingContentMD5: {
|
|
||||||
ErrCode: ErrMissingContentMD5,
|
|
||||||
Code: "MissingContentMD5",
|
|
||||||
Description: "Missing required header for this request: Content-Md5.",
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
ErrMissingSecurityHeader: {
|
ErrMissingSecurityHeader: {
|
||||||
ErrCode: ErrMissingSecurityHeader,
|
ErrCode: ErrMissingSecurityHeader,
|
||||||
Code: "MissingSecurityHeader",
|
Code: "MissingSecurityHeader",
|
||||||
|
|
|
@ -131,13 +131,6 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
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
|
// Content-Length is required and should be non-zero
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||||
if r.ContentLength <= 0 {
|
if r.ContentLength <= 0 {
|
||||||
|
|
|
@ -542,7 +542,6 @@ func deleteObjectsBase(hc *handlerContext, bktName string, objVersions [][2]stri
|
||||||
}
|
}
|
||||||
|
|
||||||
w, r := prepareTestRequest(hc, bktName, "", req)
|
w, r := prepareTestRequest(hc, bktName, "", req)
|
||||||
r.Header.Set(api.ContentMD5, "")
|
|
||||||
hc.Handler().DeleteMultipleObjectsHandler(w, r)
|
hc.Handler().DeleteMultipleObjectsHandler(w, r)
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
|
||||||
|
|
|
@ -55,34 +55,29 @@ func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
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)
|
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()))
|
h.logAndSendError(ctx, w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyMD5, err := getContentMD5(&buf)
|
if _, ok := r.Header[api.ContentMD5]; ok {
|
||||||
if err != nil {
|
headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5))
|
||||||
h.logAndSendError(ctx, w, "could not get content md5", reqInfo, err)
|
if err != nil {
|
||||||
return
|
h.logAndSendError(ctx, w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !bytes.Equal(headerMD5, bodyMD5) {
|
bodyMD5, err := getContentMD5(&buf)
|
||||||
h.logAndSendError(ctx, w, "Content-MD5 does not match", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
if err != nil {
|
||||||
return
|
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)
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
|
|
|
@ -29,6 +29,8 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
body *data.LifecycleConfiguration
|
body *data.LifecycleConfiguration
|
||||||
|
headers map[string]string
|
||||||
|
addMD5 bool
|
||||||
errorCode apierr.ErrorCode
|
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",
|
name: "too many rules",
|
||||||
body: func() *data.LifecycleConfiguration {
|
body: func() *data.LifecycleConfiguration {
|
||||||
|
@ -407,14 +425,44 @@ func TestPutBucketLifecycleConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
errorCode: apierr.ErrInvalidRequest,
|
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) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.errorCode > 0 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
putBucketLifecycleConfiguration(hc, bktName, tc.body)
|
putBucketLifecycleConfiguration(hc, bktName, tc.body, tc.headers, tc.addMD5)
|
||||||
|
|
||||||
cfg := getBucketLifecycleConfiguration(hc, bktName)
|
cfg := getBucketLifecycleConfiguration(hc, bktName)
|
||||||
require.Equal(t, tc.body.Rules, cfg.Rules)
|
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)
|
cfg := getBucketLifecycleConfiguration(hc, bktName)
|
||||||
require.Len(t, cfg.Rules, 2)
|
require.Len(t, cfg.Rules, 2)
|
||||||
require.NotEmpty(t, cfg.Rules[0].ID)
|
require.NotEmpty(t, cfg.Rules[0].ID)
|
||||||
require.NotEmpty(t, cfg.Rules[1].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) {
|
func TestPutBucketLifecycleInvalidXML(t *testing.T) {
|
||||||
hc := prepareHandlerContext(t)
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
@ -505,25 +521,32 @@ func TestPutBucketLifecycleInvalidXML(t *testing.T) {
|
||||||
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMalformedXML))
|
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMalformedXML))
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration) {
|
func putBucketLifecycleConfiguration(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, headers map[string]string, addMD5 bool) {
|
||||||
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg)
|
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg, headers, addMD5)
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func putBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, err apierr.Error) {
|
func putBucketLifecycleConfigurationErr(hc *handlerContext, bktName string, cfg *data.LifecycleConfiguration, headers map[string]string, err apierr.Error) {
|
||||||
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg)
|
w := putBucketLifecycleConfigurationBase(hc, bktName, cfg, headers, false)
|
||||||
assertS3Error(hc.t, w, err)
|
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)
|
w, r := prepareTestRequest(hc, bktName, "", cfg)
|
||||||
|
|
||||||
rawBody, err := xml.Marshal(cfg)
|
for k, v := range headers {
|
||||||
require.NoError(hc.t, err)
|
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)
|
hc.Handler().PutBucketLifecycleHandler(w, r)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue