SignatureV4 validation with Metadata in the presignedUrl (#5894)

The `X-Amz-Meta-`/`X-Minio-Meta-` will now be recognized in query string also.

Fixes #5857 #5950
This commit is contained in:
Praveen raj Mani 2018-07-11 08:57:10 +05:30 committed by kannappanr
parent c9bc7e47b9
commit 44865596db
6 changed files with 59 additions and 36 deletions

View File

@ -601,7 +601,8 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
}
// Extract metadata to be saved from received Form.
metadata, err := extractMetadataFromHeader(ctx, formValues)
metadata := make(map[string]string)
err = extractMetadataFromMap(ctx, formValues, metadata)
if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL)
return

View File

@ -114,40 +114,54 @@ var userMetadataKeyPrefixes = []string{
"X-Minio-Meta-",
}
// extractMetadataFromHeader extracts metadata from HTTP header.
func extractMetadataFromHeader(ctx context.Context, header http.Header) (map[string]string, error) {
if header == nil {
logger.LogIf(ctx, errInvalidArgument)
return nil, errInvalidArgument
// extractMetadata extracts metadata from HTTP header and HTTP queryString.
func extractMetadata(ctx context.Context, r *http.Request) (metadata map[string]string, err error) {
query := r.URL.Query()
header := r.Header
metadata = make(map[string]string)
// Extract all query values.
err = extractMetadataFromMap(ctx, query, metadata)
if err != nil {
return nil, err
}
metadata := make(map[string]string)
// Extract all header values.
err = extractMetadataFromMap(ctx, header, metadata)
if err != nil {
return nil, err
}
// Success.
return metadata, nil
}
// extractMetadata extracts metadata from map values.
func extractMetadataFromMap(ctx context.Context, v map[string][]string, m map[string]string) error {
if v == nil {
logger.LogIf(ctx, errInvalidArgument)
return errInvalidArgument
}
// Save all supported headers.
for _, supportedHeader := range supportedHeaders {
canonicalHeader := http.CanonicalHeaderKey(supportedHeader)
// HTTP headers are case insensitive, look for both canonical
// and non canonical entries.
if _, ok := header[canonicalHeader]; ok {
metadata[supportedHeader] = header.Get(canonicalHeader)
} else if _, ok := header[supportedHeader]; ok {
metadata[supportedHeader] = header.Get(supportedHeader)
if value, ok := v[http.CanonicalHeaderKey(supportedHeader)]; ok {
m[supportedHeader] = value[0]
} else if value, ok := v[supportedHeader]; ok {
m[supportedHeader] = value[0]
}
}
// Go through all other headers for any additional headers that needs to be saved.
for key := range header {
if key != http.CanonicalHeaderKey(key) {
logger.LogIf(ctx, errInvalidArgument)
return nil, errInvalidArgument
}
for key := range v {
for _, prefix := range userMetadataKeyPrefixes {
if strings.HasPrefix(key, prefix) {
metadata[key] = header.Get(key)
if !strings.HasPrefix(strings.ToLower(key), strings.ToLower(prefix)) {
continue
}
value, ok := v[key]
if ok {
m[key] = value[0]
break
}
}
}
return metadata, nil
return nil
}
// The Query string for the redirect URL the client is

View File

@ -165,9 +165,9 @@ func TestExtractMetadataHeaders(t *testing.T) {
"x-amz-meta-appid": []string{"amz-meta"},
},
metadata: map[string]string{
"X-Amz-Meta-Appid": "amz-meta",
"x-amz-meta-appid": "amz-meta",
},
shouldFail: true,
shouldFail: false,
},
// Empty header input returns empty metadata.
{
@ -179,7 +179,8 @@ func TestExtractMetadataHeaders(t *testing.T) {
// Validate if the extracting headers.
for i, testCase := range testCases {
metadata, err := extractMetadataFromHeader(context.Background(), testCase.header)
metadata := make(map[string]string)
err := extractMetadataFromMap(context.Background(), testCase.header, metadata)
if err != nil && !testCase.shouldFail {
t.Fatalf("Test %d failed to extract metadata: %v", i+1, err)
}

View File

@ -313,7 +313,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
// Extract metadata relevant for an CopyObject operation based on conditional
// header values specified in X-Amz-Metadata-Directive.
func getCpObjMetadataFromHeader(ctx context.Context, header http.Header, userMeta map[string]string) (map[string]string, error) {
func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta map[string]string) (map[string]string, error) {
// Make a copy of the supplied metadata to avoid
// to change the original one.
defaultMeta := make(map[string]string, len(userMeta))
@ -323,13 +323,13 @@ func getCpObjMetadataFromHeader(ctx context.Context, header http.Header, userMet
// if x-amz-metadata-directive says REPLACE then
// we extract metadata from the input headers.
if isMetadataReplace(header) {
return extractMetadataFromHeader(ctx, header)
if isMetadataReplace(r.Header) {
return extractMetadata(ctx, r)
}
// if x-amz-metadata-directive says COPY then we
// return the default metadata.
if isMetadataCopy(header) {
if isMetadataCopy(r.Header) {
return defaultMeta, nil
}
@ -514,7 +514,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
}
srcInfo.Writer = writer
srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r.Header, srcInfo.UserDefined)
srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r, srcInfo.UserDefined)
if err != nil {
pipeWriter.CloseWithError(err)
writeErrorResponse(w, ErrInternalError, r.URL)
@ -699,12 +699,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return
}
// Extract metadata to be saved from incoming HTTP header.
metadata, err := extractMetadataFromHeader(ctx, r.Header)
metadata, err := extractMetadata(ctx, r)
if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL)
return
}
if rAuthType == authTypeStreamingSigned {
if contentEncoding, ok := metadata["content-encoding"]; ok {
contentEncoding = trimAwsChunkedContentEncoding(contentEncoding)
@ -806,6 +806,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
if api.CacheAPI() != nil && !hasSSECustomerHeader(r.Header) {
putObject = api.CacheAPI().PutObject
}
// Create the object..
objInfo, err := putObject(ctx, bucket, object, hashReader, metadata)
if err != nil {
@ -901,7 +902,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
}
// Extract metadata that needs to be saved.
metadata, err := extractMetadataFromHeader(ctx, r.Header)
metadata, err := extractMetadata(ctx, r)
if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL)
return

View File

@ -249,6 +249,12 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s
// Save other headers available in the request parameters.
for k, v := range req.URL.Query() {
// Handle the metadata in presigned put query string
if strings.Contains(strings.ToLower(k), "x-amz-meta-") {
query.Set(k, v[0])
}
if strings.HasPrefix(strings.ToLower(k), "x-amz") {
continue
}

View File

@ -629,7 +629,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
}
// Extract incoming metadata if any.
metadata, err := extractMetadataFromHeader(context.Background(), r.Header)
metadata, err := extractMetadata(context.Background(), r)
if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL)
return