From 44865596dbe4153c8b2edf63332595d40731b8ae Mon Sep 17 00:00:00 2001 From: Praveen raj Mani Date: Wed, 11 Jul 2018 08:57:10 +0530 Subject: [PATCH] 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 --- cmd/bucket-handlers.go | 3 +- cmd/handler-utils.go | 60 ++++++++++++++++++++++++--------------- cmd/handler-utils_test.go | 7 +++-- cmd/object-handlers.go | 17 +++++------ cmd/signature-v4.go | 6 ++++ cmd/web-handlers.go | 2 +- 6 files changed, 59 insertions(+), 36 deletions(-) diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 0434bf1e3..9e99befa9 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -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 diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index b990b2ced..17a785708 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -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 + } + + // Extract all header values. + err = extractMetadataFromMap(ctx, header, metadata) + if err != nil { + return nil, err } - metadata := make(map[string]string) + // 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 diff --git a/cmd/handler-utils_test.go b/cmd/handler-utils_test.go index 74ddbd654..a4f52171d 100644 --- a/cmd/handler-utils_test.go +++ b/cmd/handler-utils_test.go @@ -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) } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 63cd9b287..c8aff2351 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -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 diff --git a/cmd/signature-v4.go b/cmd/signature-v4.go index ede827884..f293acb69 100644 --- a/cmd/signature-v4.go +++ b/cmd/signature-v4.go @@ -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 } diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index c75a7222d..4b7c8a62f 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -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