[#516] Check Content-Md5 of lifecycle configuration
All checks were successful
/ DCO (pull_request) Successful in 1m17s
/ Vulncheck (pull_request) Successful in 1m37s
/ Builds (pull_request) Successful in 1m55s
/ Lint (pull_request) Successful in 2m29s
/ Tests (pull_request) Successful in 1m48s

Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
Marina Biryukova 2024-10-15 14:18:38 +03:00
parent 924038c1ac
commit ff57dbb1dc
2 changed files with 60 additions and 9 deletions

View file

@ -1,8 +1,11 @@
package handler package handler
import ( import (
"bytes"
"crypto/md5"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"math" "math"
"net/http" "net/http"
"time" "time"
@ -44,6 +47,9 @@ func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
} }
func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) { func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
var buf bytes.Buffer
tee := io.TeeReader(r.Body, &buf)
ctx := r.Context() ctx := r.Context()
reqInfo := middleware.GetReqInfo(ctx) reqInfo := middleware.GetReqInfo(ctx)
@ -54,23 +60,35 @@ func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
return return
} }
if _, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5)); err != nil { headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5))
if err != nil {
h.logAndSendError(w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest)) h.logAndSendError(w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
return return
} }
cfg := new(data.LifecycleConfiguration)
if err = h.cfg.NewXMLDecoder(tee).Decode(cfg); err != nil {
h.logAndSendError(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(w, "could not get content md5", reqInfo, err)
return
}
if !bytes.Equal(headerMD5, bodyMD5) {
h.logAndSendError(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)
if err != nil { if err != nil {
h.logAndSendError(w, "could not get bucket info", reqInfo, err) h.logAndSendError(w, "could not get bucket info", reqInfo, err)
return return
} }
cfg := new(data.LifecycleConfiguration)
if err = h.cfg.NewXMLDecoder(r.Body).Decode(cfg); err != nil {
h.logAndSendError(w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
return
}
networkInfo, err := h.obj.GetNetworkInfo(ctx) networkInfo, err := h.obj.GetNetworkInfo(ctx)
if err != nil { if err != nil {
h.logAndSendError(w, "could not get network info", reqInfo, err) h.logAndSendError(w, "could not get network info", reqInfo, err)
@ -286,3 +304,24 @@ func timeToEpoch(ni *netmap.NetworkInfo, t time.Time) (uint64, error) {
return epoch, nil return epoch, nil
} }
func getContentMD5(reader io.Reader) ([]byte, error) {
hash := md5.New()
buf := make([]byte, 64*1024)
for {
n, err := reader.Read(buf)
if n > 0 {
println(string(buf[:n]))
hash.Write(buf[:n])
}
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
return hash.Sum(nil), nil
}

View file

@ -1,6 +1,7 @@
package handler package handler
import ( import (
"bytes"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
@ -377,6 +378,11 @@ func TestPutBucketLifecycleInvalidMD5(t *testing.T) {
hc.Handler().PutBucketLifecycleHandler(w, r) hc.Handler().PutBucketLifecycleHandler(w, r)
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMissingContentMD5)) 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) w, r = prepareTestRequest(hc, bktName, "", lifecycle)
r.Header.Set(api.ContentMD5, "some-hash") r.Header.Set(api.ContentMD5, "some-hash")
hc.Handler().PutBucketLifecycleHandler(w, r) hc.Handler().PutBucketLifecycleHandler(w, r)
@ -389,8 +395,14 @@ func TestPutBucketLifecycleInvalidXML(t *testing.T) {
bktName := "bucket-lifecycle-invalid-xml" bktName := "bucket-lifecycle-invalid-xml"
createBucket(hc, bktName) createBucket(hc, bktName)
w, r := prepareTestRequest(hc, bktName, "", &data.CORSConfiguration{}) cfg := &data.CORSConfiguration{}
r.Header.Set(api.ContentMD5, "") body, err := xml.Marshal(cfg)
require.NoError(t, err)
contentMD5, err := getContentMD5(bytes.NewReader(body))
require.NoError(t, err)
w, r := prepareTestRequest(hc, bktName, "", cfg)
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString(contentMD5))
hc.Handler().PutBucketLifecycleHandler(w, r) hc.Handler().PutBucketLifecycleHandler(w, r)
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMalformedXML)) assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrMalformedXML))
} }