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. // 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 { if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL) writeErrorResponse(w, ErrInternalError, r.URL)
return return

View File

@ -114,40 +114,54 @@ var userMetadataKeyPrefixes = []string{
"X-Minio-Meta-", "X-Minio-Meta-",
} }
// extractMetadataFromHeader extracts metadata from HTTP header. // extractMetadata extracts metadata from HTTP header and HTTP queryString.
func extractMetadataFromHeader(ctx context.Context, header http.Header) (map[string]string, error) { func extractMetadata(ctx context.Context, r *http.Request) (metadata map[string]string, err error) {
if header == nil { query := r.URL.Query()
logger.LogIf(ctx, errInvalidArgument) header := r.Header
return nil, errInvalidArgument 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. // Save all supported headers.
for _, supportedHeader := range supportedHeaders { for _, supportedHeader := range supportedHeaders {
canonicalHeader := http.CanonicalHeaderKey(supportedHeader) if value, ok := v[http.CanonicalHeaderKey(supportedHeader)]; ok {
// HTTP headers are case insensitive, look for both canonical m[supportedHeader] = value[0]
// and non canonical entries. } else if value, ok := v[supportedHeader]; ok {
if _, ok := header[canonicalHeader]; ok { m[supportedHeader] = value[0]
metadata[supportedHeader] = header.Get(canonicalHeader)
} else if _, ok := header[supportedHeader]; ok {
metadata[supportedHeader] = header.Get(supportedHeader)
} }
} }
for key := range v {
// 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 _, prefix := range userMetadataKeyPrefixes { for _, prefix := range userMetadataKeyPrefixes {
if strings.HasPrefix(key, prefix) { if !strings.HasPrefix(strings.ToLower(key), strings.ToLower(prefix)) {
metadata[key] = header.Get(key) continue
}
value, ok := v[key]
if ok {
m[key] = value[0]
break break
} }
} }
} }
return metadata, nil return nil
} }
// The Query string for the redirect URL the client is // 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"}, "x-amz-meta-appid": []string{"amz-meta"},
}, },
metadata: map[string]string{ 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. // Empty header input returns empty metadata.
{ {
@ -179,7 +179,8 @@ func TestExtractMetadataHeaders(t *testing.T) {
// Validate if the extracting headers. // Validate if the extracting headers.
for i, testCase := range testCases { 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 { if err != nil && !testCase.shouldFail {
t.Fatalf("Test %d failed to extract metadata: %v", i+1, err) 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 // Extract metadata relevant for an CopyObject operation based on conditional
// header values specified in X-Amz-Metadata-Directive. // 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 // Make a copy of the supplied metadata to avoid
// to change the original one. // to change the original one.
defaultMeta := make(map[string]string, len(userMeta)) 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 // if x-amz-metadata-directive says REPLACE then
// we extract metadata from the input headers. // we extract metadata from the input headers.
if isMetadataReplace(header) { if isMetadataReplace(r.Header) {
return extractMetadataFromHeader(ctx, header) return extractMetadata(ctx, r)
} }
// if x-amz-metadata-directive says COPY then we // if x-amz-metadata-directive says COPY then we
// return the default metadata. // return the default metadata.
if isMetadataCopy(header) { if isMetadataCopy(r.Header) {
return defaultMeta, nil return defaultMeta, nil
} }
@ -514,7 +514,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
} }
srcInfo.Writer = writer srcInfo.Writer = writer
srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r.Header, srcInfo.UserDefined) srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r, srcInfo.UserDefined)
if err != nil { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
writeErrorResponse(w, ErrInternalError, r.URL) writeErrorResponse(w, ErrInternalError, r.URL)
@ -699,12 +699,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return return
} }
// Extract metadata to be saved from incoming HTTP header. metadata, err := extractMetadata(ctx, r)
metadata, err := extractMetadataFromHeader(ctx, r.Header)
if err != nil { if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL) writeErrorResponse(w, ErrInternalError, r.URL)
return return
} }
if rAuthType == authTypeStreamingSigned { if rAuthType == authTypeStreamingSigned {
if contentEncoding, ok := metadata["content-encoding"]; ok { if contentEncoding, ok := metadata["content-encoding"]; ok {
contentEncoding = trimAwsChunkedContentEncoding(contentEncoding) contentEncoding = trimAwsChunkedContentEncoding(contentEncoding)
@ -806,6 +806,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
if api.CacheAPI() != nil && !hasSSECustomerHeader(r.Header) { if api.CacheAPI() != nil && !hasSSECustomerHeader(r.Header) {
putObject = api.CacheAPI().PutObject putObject = api.CacheAPI().PutObject
} }
// Create the object.. // Create the object..
objInfo, err := putObject(ctx, bucket, object, hashReader, metadata) objInfo, err := putObject(ctx, bucket, object, hashReader, metadata)
if err != nil { if err != nil {
@ -901,7 +902,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
} }
// Extract metadata that needs to be saved. // Extract metadata that needs to be saved.
metadata, err := extractMetadataFromHeader(ctx, r.Header) metadata, err := extractMetadata(ctx, r)
if err != nil { if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL) writeErrorResponse(w, ErrInternalError, r.URL)
return return

View File

@ -249,6 +249,12 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s
// Save other headers available in the request parameters. // Save other headers available in the request parameters.
for k, v := range req.URL.Query() { 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") { if strings.HasPrefix(strings.ToLower(k), "x-amz") {
continue continue
} }

View File

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