From d5057b3c51c58ca645da819be175b01609bbc76b Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 3 Feb 2016 16:46:56 -0800 Subject: [PATCH] accessPolicy: Implement Put, Get, Delete access policy. This patch implements Get,Put,Delete bucket policies Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html Currently supports following actions. "*": true, "s3:*": true, "s3:GetObject": true, "s3:ListBucket": true, "s3:PutObject": true, "s3:CreateBucket": true, "s3:GetBucketLocation": true, "s3:DeleteBucket": true, "s3:DeleteObject": true, "s3:AbortMultipartUpload": true, "s3:ListBucketMultipartUploads": true, "s3:ListMultipartUploadParts": true, following conditions for "StringEquals" and "StringNotEquals" "s3:prefix", "s3:max-keys" --- api-errors.go | 21 ++- api-response.go | 37 ---- auth-handler.go | 96 ++++++---- bucket-acl.go | 71 ------- bucket-handlers.go | 305 +++++++++++++++---------------- bucket-policy-handlers.go | 283 ++++++++++++++++++++++++++++ bucket-policy.go | 147 +++++++++++++++ generic-handlers.go | 2 +- object-handlers.go | 211 ++++++++++++--------- pkg/fs/api_suite_nix_test.go | 47 ++--- pkg/fs/api_suite_windows_test.go | 47 ++--- pkg/fs/fs-backend-metadata.go | 34 +--- pkg/fs/fs-bucket-acl.go | 96 ---------- pkg/fs/fs-bucket.go | 91 +-------- pkg/fs/fs-bucket_test.go | 38 +--- pkg/fs/fs-datatypes.go | 1 - pkg/fs/fs-errors.go | 18 +- pkg/fs/fs.go | 25 +-- pkg/s3/access/README.md | 33 ++++ pkg/s3/access/policy.go | 230 +++++++++++++++++++++++ routers.go | 12 +- server-config.go | 4 +- server-main.go | 3 + server_fs_test.go | 10 - 24 files changed, 1107 insertions(+), 755 deletions(-) delete mode 100644 bucket-acl.go create mode 100644 bucket-policy-handlers.go create mode 100644 bucket-policy.go delete mode 100644 pkg/fs/fs-bucket-acl.go create mode 100644 pkg/s3/access/README.md create mode 100644 pkg/s3/access/policy.go diff --git a/api-errors.go b/api-errors.go index a052666ba..05462aa74 100644 --- a/api-errors.go +++ b/api-errors.go @@ -42,7 +42,8 @@ type APIErrorResponse struct { // Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html const ( - AccessDenied = iota + None = iota + AccessDenied BadDigest BucketAlreadyExists EntityTooSmall @@ -60,11 +61,13 @@ const ( InvalidRequestBody InvalidCopySource InvalidCopyDest + InvalidPolicyDocument MalformedXML MissingContentLength MissingContentMD5 MissingRequestBodyError NoSuchBucket + NoSuchBucketPolicy NoSuchKey NoSuchUpload NotImplemented @@ -80,6 +83,7 @@ const ( RootPathFull ObjectExistsAsPrefix AllAccessDisabled + MalformedPolicy ) // APIError code to Error structure map @@ -119,6 +123,11 @@ var errorCodeResponse = map[int]APIError{ Description: "Argument partNumberMarker must be an integer.", HTTPStatusCode: http.StatusBadRequest, }, + InvalidPolicyDocument: { + Code: "InvalidPolicyDocument", + Description: "The content of the form does not meet the conditions specified in the policy document.", + HTTPStatusCode: http.StatusBadRequest, + }, AccessDenied: { Code: "AccessDenied", Description: "Access Denied.", @@ -199,6 +208,11 @@ var errorCodeResponse = map[int]APIError{ Description: "The specified bucket does not exist.", HTTPStatusCode: http.StatusNotFound, }, + NoSuchBucketPolicy: { + Code: "NoSuchBucketPolicy", + Description: "The specified bucket does not have a bucket policy.", + HTTPStatusCode: http.StatusNotFound, + }, NoSuchKey: { Code: "NoSuchKey", Description: "The specified key does not exist.", @@ -274,6 +288,11 @@ var errorCodeResponse = map[int]APIError{ Description: "All access to this bucket has been disabled.", HTTPStatusCode: http.StatusForbidden, }, + MalformedPolicy: { + Code: "MalformedPolicy", + Description: "Policy has invalid resource", + HTTPStatusCode: http.StatusBadRequest, + }, } // errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown diff --git a/api-response.go b/api-response.go index 5d5453493..2b75f0efa 100644 --- a/api-response.go +++ b/api-response.go @@ -268,43 +268,6 @@ func generateListBucketsResponse(buckets []fs.BucketMetadata) ListBucketsRespons return data } -// generates an AccessControlPolicy response for the said ACL. -func generateAccessControlPolicyResponse(acl fs.BucketACL) AccessControlPolicyResponse { - accessCtrlPolicyResponse := AccessControlPolicyResponse{} - accessCtrlPolicyResponse.Owner = Owner{ - ID: "minio", - DisplayName: "minio", - } - defaultGrant := Grant{} - defaultGrant.Grantee.ID = "minio" - defaultGrant.Grantee.DisplayName = "minio" - defaultGrant.Permission = "FULL_CONTROL" - accessCtrlPolicyResponse.AccessControlList.Grants = append(accessCtrlPolicyResponse.AccessControlList.Grants, defaultGrant) - switch { - case acl.IsPublicRead(): - publicReadGrant := Grant{} - publicReadGrant.Grantee.ID = "minio" - publicReadGrant.Grantee.DisplayName = "minio" - publicReadGrant.Grantee.URI = "http://acs.amazonaws.com/groups/global/AllUsers" - publicReadGrant.Permission = "READ" - accessCtrlPolicyResponse.AccessControlList.Grants = append(accessCtrlPolicyResponse.AccessControlList.Grants, publicReadGrant) - case acl.IsPublicReadWrite(): - publicReadGrant := Grant{} - publicReadGrant.Grantee.ID = "minio" - publicReadGrant.Grantee.DisplayName = "minio" - publicReadGrant.Grantee.URI = "http://acs.amazonaws.com/groups/global/AllUsers" - publicReadGrant.Permission = "READ" - publicReadWriteGrant := Grant{} - publicReadWriteGrant.Grantee.ID = "minio" - publicReadWriteGrant.Grantee.DisplayName = "minio" - publicReadWriteGrant.Grantee.URI = "http://acs.amazonaws.com/groups/global/AllUsers" - publicReadWriteGrant.Permission = "WRITE" - accessCtrlPolicyResponse.AccessControlList.Grants = append(accessCtrlPolicyResponse.AccessControlList.Grants, publicReadGrant) - accessCtrlPolicyResponse.AccessControlList.Grants = append(accessCtrlPolicyResponse.AccessControlList.Grants, publicReadWriteGrant) - } - return accessCtrlPolicyResponse -} - // generates an ListObjects response for the said bucket with other enumerated options. func generateListObjectsResponse(bucket, prefix, marker, delimiter string, maxKeys int, resp fs.ListObjectsResult) ListObjectsResponse { var contents []Object diff --git a/auth-handler.go b/auth-handler.go index dc7fc73cf..df740654e 100644 --- a/auth-handler.go +++ b/auth-handler.go @@ -61,41 +61,77 @@ func isRequestPresignedSignatureV4(r *http.Request) bool { // Verify if request has AWS Post policy Signature Version '4'. func isRequestPostPolicySignatureV4(r *http.Request) bool { if _, ok := r.Header["Content-Type"]; ok { - if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") { + if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") && r.Method == "POST" { return true } } return false } -// Verify if request requires ACL check. -func isRequestRequiresACLCheck(r *http.Request) bool { - if isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) { +// Verify if incoming request is anonymous. +func isRequestAnonymous(r *http.Request) bool { + if isRequestJWT(r) || isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) { return false } return true } +// Authorization type. +type authType int + +// List of all supported auth types. +const ( + authTypeUnknown authType = iota + authTypeAnonymous + authTypePresigned + authTypePostPolicy + authTypeSigned + authTypeJWT +) + +// Get request authentication type. +func getRequestAuthType(r *http.Request) authType { + if _, ok := r.Header["Authorization"]; !ok { + return authTypeAnonymous + } + if isRequestSignatureV4(r) { + return authTypeSigned + } else if isRequestPresignedSignatureV4(r) { + return authTypePresigned + } else if isRequestJWT(r) { + return authTypeJWT + } else if isRequestPostPolicySignatureV4(r) { + return authTypePostPolicy + } + return authTypeUnknown +} + // Verify if request has valid AWS Signature Version '4'. -func isSignV4ReqAuthenticated(sign *signature4.Sign, r *http.Request) bool { +func isSignV4ReqAuthenticated(sign *signature4.Sign, r *http.Request) (match bool, s3Error int) { auth := sign.SetHTTPRequestToVerify(r) if isRequestSignatureV4(r) { dummyPayload := sha256.Sum256([]byte("")) ok, err := auth.DoesSignatureMatch(hex.EncodeToString(dummyPayload[:])) if err != nil { errorIf(err.Trace(), "Signature verification failed.", nil) - return false + return false, InternalError } - return ok + if !ok { + return false, SignatureDoesNotMatch + } + return ok, None } else if isRequestPresignedSignatureV4(r) { ok, err := auth.DoesPresignedSignatureMatch() if err != nil { errorIf(err.Trace(), "Presigned signature verification failed.", nil) - return false + return false, InternalError } - return ok + if !ok { + return false, SignatureDoesNotMatch + } + return ok, None } - return false + return false, AccessDenied } // authHandler - handles all the incoming authorization headers and @@ -111,41 +147,21 @@ func setAuthHandler(h http.Handler) http.Handler { // handler for validating incoming authorization headers. func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Verify if request is presigned, validate signature inside each handlers. - if isRequestPresignedSignatureV4(r) { + switch getRequestAuthType(r) { + case authTypeAnonymous, authTypePresigned, authTypeSigned, authTypePostPolicy: + // Let top level caller validate for anonymous and known + // signed requests. a.handler.ServeHTTP(w, r) return - } - - // Verify if request has post policy signature, validate signature - // inside POST policy handler. - if isRequestPostPolicySignatureV4(r) && r.Method == "POST" { - a.handler.ServeHTTP(w, r) - return - } - - // No authorization found, let the top level caller validate if - // public request is allowed. - if _, ok := r.Header["Authorization"]; !ok { - a.handler.ServeHTTP(w, r) - return - } - - // Verify if the signature algorithms are known. - if !isRequestSignatureV4(r) && !isRequestJWT(r) { - writeErrorResponse(w, r, SignatureVersionNotSupported, r.URL.Path) - return - } - - // Verify JWT authorization header is present. - if isRequestJWT(r) { - // Validate Authorization header if its valid. + case authTypeJWT: + // Validate Authorization header if its valid for JWT request. if !isJWTReqAuthenticated(r) { w.WriteHeader(http.StatusUnauthorized) return } + a.handler.ServeHTTP(w, r) + default: + writeErrorResponse(w, r, SignatureVersionNotSupported, r.URL.Path) + return } - - // For all other signed requests, let top level caller verify. - a.handler.ServeHTTP(w, r) } diff --git a/bucket-acl.go b/bucket-acl.go deleted file mode 100644 index 283f51e32..000000000 --- a/bucket-acl.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import "net/http" - -// Please read for more information - http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl -// -// Here We are only supporting 'acl's through request headers not through their request body -// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#setting-acls - -// Minio only supports three types for now i.e 'private, public-read, public-read-write' - -// ACLType - different acl types -type ACLType int - -const ( - unsupportedACLType ACLType = iota - privateACLType - publicReadACLType - publicReadWriteACLType -) - -// Get acl type requested from 'x-amz-acl' header -func getACLType(req *http.Request) ACLType { - aclHeader := req.Header.Get("x-amz-acl") - if aclHeader != "" { - switch { - case aclHeader == "private": - return privateACLType - case aclHeader == "public-read": - return publicReadACLType - case aclHeader == "public-read-write": - return publicReadWriteACLType - default: - return unsupportedACLType - } - } - // make it default private - return privateACLType -} - -// ACL type to human readable string -func getACLTypeString(acl ACLType) string { - switch acl { - case privateACLType: - return "private" - case publicReadACLType: - return "public-read" - case publicReadWriteACLType: - return "public-read-write" - case unsupportedACLType: - return "" - default: - return "private" - } -} diff --git a/bucket-handlers.go b/bucket-handlers.go index b91f91549..2c440fac1 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -26,14 +26,56 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "net/url" + "strings" mux "github.com/gorilla/mux" "github.com/minio/minio/pkg/crypto/sha256" "github.com/minio/minio/pkg/fs" "github.com/minio/minio/pkg/probe" + "github.com/minio/minio/pkg/s3/access" "github.com/minio/minio/pkg/s3/signature4" ) +// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html +func enforceBucketPolicy(action string, bucket string, reqURL *url.URL) (isAllowed bool, s3Error int) { + // Read saved bucket policy. + policy, err := readBucketPolicy(bucket) + if err != nil { + errorIf(err.Trace(bucket), "GetBucketPolicy failed.", nil) + switch err.ToGoError().(type) { + case fs.BucketNotFound: + return false, NoSuchBucket + case fs.BucketNameInvalid: + return false, InvalidBucketName + default: + // For any other error just return AccessDenied. + return false, AccessDenied + } + } + // Parse the saved policy. + accessPolicy, e := accesspolicy.Validate(policy) + if e != nil { + errorIf(probe.NewError(e), "Parse policy failed.", nil) + return false, AccessDenied + } + + // Construct resource in 'arn:aws:s3:::examplebucket' format. + resource := accesspolicy.AWSResourcePrefix + strings.TrimPrefix(reqURL.Path, "/") + + // Get conditions for policy verification. + conditions := make(map[string]string) + for queryParam := range reqURL.Query() { + conditions[queryParam] = reqURL.Query().Get("queryParam") + } + + // Validate action, resource and conditions with current policy statements. + if !bucketPolicyEvalStatements(action, resource, conditions, accessPolicy.Statements) { + return false, AccessDenied + } + return true, None +} + // GetBucketLocationHandler - GET Bucket location. // ------------------------- // This operation returns bucket location. @@ -41,14 +83,23 @@ func (api storageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *http.Re vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. writeErrorResponse(w, r, AccessDenied, r.URL.Path) return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:GetBucketLocation", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypeSigned, authTypePresigned: + match, s3Error := isSignV4ReqAuthenticated(api.Signature, r) + if !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } } _, err := api.Filesystem.GetBucketMetadata(bucket) @@ -88,14 +139,23 @@ func (api storageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. writeErrorResponse(w, r, AccessDenied, r.URL.Path) return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:ListBucketMultipartUploads", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypePresigned, authTypeSigned: + match, s3Error := isSignV4ReqAuthenticated(api.Signature, r) + if !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } } resources := getBucketMultipartResources(r.URL.Query()) @@ -137,16 +197,23 @@ func (api storageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request) vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:ListBucket", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypeSigned, authTypePresigned: + match, s3Error := isSignV4ReqAuthenticated(api.Signature, r) + if !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return } // TODO handle encoding type. @@ -190,14 +257,17 @@ func (api storageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request) // This implementation of the GET operation returns a list of all buckets // owned by the authenticated sender of the request. func (api storageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { - if isRequestRequiresACLCheck(r) { + // List buckets does not support bucket policies. + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. writeErrorResponse(w, r, AccessDenied, r.URL.Path) return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return + case authTypeSigned, authTypePresigned: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } } buckets, err := api.Filesystem.ListBuckets() @@ -220,11 +290,6 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - // Content-Length is required and should be non-zero // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html if r.ContentLength <= 0 { @@ -253,8 +318,19 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt return } - // Check if request is presigned. - if isRequestPresignedSignatureV4(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:DeleteObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypePresigned: + // Check if request is presigned. ok, err := auth.DoesPresignedSignatureMatch() if err != nil { errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) @@ -265,7 +341,7 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) return } - } else if isRequestSignatureV4(r) { + case authTypeSigned: // Check if request is signed. sha := sha256.New() mdSh := md5.New() @@ -356,31 +432,20 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - - // read from 'x-amz-acl' - aclType := getACLType(r) - if aclType == unsupportedACLType { - writeErrorResponse(w, r, NotImplemented, r.URL.Path) - return - } - - // if body of request is non-nil then check for validity of Content-Length - if r.Body != nil { - /// if Content-Length is unknown/missing, deny the request - if r.ContentLength == -1 && !contains(r.TransferEncoding, "chunked") { - writeErrorResponse(w, r, MissingContentLength, r.URL.Path) - return - } - } - // Set http request for signature. auth := api.Signature.SetHTTPRequestToVerify(r) - - if isRequestPresignedSignatureV4(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:CreateBucket", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypePresigned: ok, err := auth.DoesPresignedSignatureMatch() if err != nil { errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) @@ -391,7 +456,7 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) { writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) return } - } else if isRequestSignatureV4(r) { + case authTypeSigned: // Verify signature for the incoming body if any. locationBytes, e := ioutil.ReadAll(r.Body) if e != nil { @@ -414,7 +479,7 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) { } // Make bucket. - err := api.Filesystem.MakeBucket(bucket, getACLTypeString(aclType)) + err := api.Filesystem.MakeBucket(bucket) if err != nil { errorIf(err.Trace(), "MakeBucket failed.", nil) switch err.ToGoError().(type) { @@ -538,87 +603,6 @@ func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Req writeSuccessResponse(w, nil) } -// PutBucketACLHandler - PUT Bucket ACL -// ---------- -// This implementation of the PUT operation modifies the bucketACL for authenticated request -func (api storageAPI) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bucket := vars["bucket"] - - if isRequestRequiresACLCheck(r) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return - } - - // read from 'x-amz-acl' - aclType := getACLType(r) - if aclType == unsupportedACLType { - writeErrorResponse(w, r, NotImplemented, r.URL.Path) - return - } - err := api.Filesystem.SetBucketMetadata(bucket, map[string]string{"acl": getACLTypeString(aclType)}) - if err != nil { - errorIf(err.Trace(), "PutBucketACL failed.", nil) - switch err.ToGoError().(type) { - case fs.BucketNameInvalid: - writeErrorResponse(w, r, InvalidBucketName, r.URL.Path) - case fs.BucketNotFound: - writeErrorResponse(w, r, NoSuchBucket, r.URL.Path) - default: - writeErrorResponse(w, r, InternalError, r.URL.Path) - } - return - } - writeSuccessResponse(w, nil) -} - -// GetBucketACLHandler - GET ACL on a Bucket -// ---------- -// This operation uses acl subresource to the return the ``acl`` -// of a bucket. One must have permission to access the bucket to -// know its ``acl``. This operation willl return response of 404 -// if bucket not found and 403 for invalid credentials. -func (api storageAPI) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bucket := vars["bucket"] - - if isRequestRequiresACLCheck(r) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return - } - - bucketMetadata, err := api.Filesystem.GetBucketMetadata(bucket) - if err != nil { - errorIf(err.Trace(), "GetBucketMetadata failed.", nil) - switch err.ToGoError().(type) { - case fs.BucketNotFound: - writeErrorResponse(w, r, NoSuchBucket, r.URL.Path) - case fs.BucketNameInvalid: - writeErrorResponse(w, r, InvalidBucketName, r.URL.Path) - default: - writeErrorResponse(w, r, InternalError, r.URL.Path) - } - return - } - // Generate response - response := generateAccessControlPolicyResponse(bucketMetadata.ACL) - encodedSuccessResponse := encodeResponse(response) - // Write headers - setCommonHeaders(w) - // Write success response. - writeSuccessResponse(w, encodedSuccessResponse) -} - // HeadBucketHandler - HEAD Bucket // ---------- // This operation is useful to determine if a bucket exists. @@ -629,18 +613,18 @@ func (api storageAPI) HeadBucketHandler(w http.ResponseWriter, r *http.Request) vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypePresigned, authTypeSigned: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } } - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return - } - _, err := api.Filesystem.GetBucketMetadata(bucket) if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) @@ -662,14 +646,22 @@ func (api storageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request vars := mux.Vars(r) bucket := vars["bucket"] - if isRequestRequiresACLCheck(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. writeErrorResponse(w, r, AccessDenied, r.URL.Path) return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:DeleteBucket", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypePresigned, authTypeSigned: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } } err := api.Filesystem.DeleteBucket(bucket) @@ -685,5 +677,10 @@ func (api storageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request } return } + + // Delete bucket access policy, if present - ignore any errors. + removeBucketPolicy(bucket) + + // Write success response. writeSuccessNoContent(w) } diff --git a/bucket-policy-handlers.go b/bucket-policy-handlers.go new file mode 100644 index 000000000..3d0a7535d --- /dev/null +++ b/bucket-policy-handlers.go @@ -0,0 +1,283 @@ +/* + * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "io" + "io/ioutil" + "net/http" + "regexp" + "strings" + + mux "github.com/gorilla/mux" + "github.com/minio/minio/pkg/fs" + "github.com/minio/minio/pkg/probe" + "github.com/minio/minio/pkg/s3/access" +) + +// maximum supported access policy size. +const maxAccessPolicySize = 20 * 1024 * 1024 // 20KiB. + +// Verify if a given action is valid for the url path based on the +// existing bucket access policy. +func bucketPolicyEvalStatements(action string, resource string, conditions map[string]string, statements []accesspolicy.Statement) bool { + for _, statement := range statements { + if bucketPolicyMatchStatement(action, resource, conditions, statement) { + if statement.Effect == "Allow" { + return true + } + // else statement.Effect == "Deny" + return false + } + } + // None match so deny. + return false +} + +// Verify if action, resource and conditions match input policy statement. +func bucketPolicyMatchStatement(action string, resource string, conditions map[string]string, statement accesspolicy.Statement) bool { + // Verify if action matches. + if bucketPolicyActionMatch(action, statement) { + // Verify if resource matches. + if bucketPolicyResourceMatch(resource, statement) { + // Verify if condition matches. + if bucketPolicyConditionMatch(conditions, statement) { + return true + } + } + } + return false +} + +// Verify if given action matches with policy statement. +func bucketPolicyActionMatch(action string, statement accesspolicy.Statement) bool { + for _, policyAction := range statement.Actions { + // Policy action can be a regex, validate the action with matching string. + matched, e := regexp.MatchString(policyAction, action) + fatalIf(probe.NewError(e), "Invalid pattern, please verify the pattern string.", nil) + if matched { + return true + } + } + return false +} + +// Verify if given resource matches with policy statement. +func bucketPolicyResourceMatch(resource string, statement accesspolicy.Statement) bool { + for _, presource := range statement.Resources { + matched, e := regexp.MatchString(presource, strings.TrimPrefix(resource, "/")) + fatalIf(probe.NewError(e), "Invalid pattern, please verify the pattern string.", nil) + // For any path matches, we return quickly and the let the caller continue. + if matched { + return true + } + } + return false +} + +// Verify if given condition matches with policy statement. +func bucketPolicyConditionMatch(conditions map[string]string, statement accesspolicy.Statement) bool { + // Supports following conditions. + // - StringEquals + // - StringNotEquals + // + // Supported applicable condition keys for each conditions. + // - s3:prefix + // - s3:max-keys + var conditionMatches = true + for condition, conditionKeys := range statement.Conditions { + if condition == "StringEquals" { + if conditionKeys["s3:prefix"] != conditions["prefix"] { + conditionMatches = false + break + } + if conditionKeys["s3:max-keys"] != conditions["max-keys"] { + conditionMatches = false + break + } + } else if condition == "StringNotEquals" { + if conditionKeys["s3:prefix"] == conditions["prefix"] { + conditionMatches = false + break + } + if conditionKeys["s3:max-keys"] == conditions["max-keys"] { + conditionMatches = false + break + } + } + } + return conditionMatches +} + +// PutBucketPolicyHandler - PUT Bucket policy +// ----------------- +// This implementation of the PUT operation uses the policy +// subresource to add to or replace a policy on a bucket +func (api storageAPI) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bucket := vars["bucket"] + + // If Content-Length is unknown or zero, deny the + // request. PutBucketPolicy always needs a Content-Length if + // incoming request is not chunked. + if !contains(r.TransferEncoding, "chunked") { + if r.ContentLength == -1 || r.ContentLength == 0 { + writeErrorResponse(w, r, MissingContentLength, r.URL.Path) + return + } + // If Content-Length is greater than maximum allowed policy size. + if r.ContentLength > maxAccessPolicySize { + writeErrorResponse(w, r, EntityTooLarge, r.URL.Path) + return + } + } + + // Read access policy up to maxAccessPolicySize. + // http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html + // bucket policies are limited to 20KB in size, using a limit reader. + accessPolicyBytes, e := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize)) + if e != nil { + errorIf(probe.NewError(e).Trace(bucket), "Reading policy failed.", nil) + writeErrorResponse(w, r, InternalError, r.URL.Path) + return + } + + // Parse access access. + accessPolicy, e := accesspolicy.Validate(accessPolicyBytes) + if e != nil { + writeErrorResponse(w, r, InvalidPolicyDocument, r.URL.Path) + return + } + + // If the policy resource has different bucket name, reject it. + for _, statement := range accessPolicy.Statements { + for _, resource := range statement.Resources { + resourcePrefix := strings.SplitAfter(resource, accesspolicy.AWSResourcePrefix)[1] + if !strings.HasPrefix(resourcePrefix, bucket) { + writeErrorResponse(w, r, MalformedPolicy, r.URL.Path) + return + } + } + } + + // Set http request for signature verification. + auth := api.Signature.SetHTTPRequestToVerify(r) + if isRequestPresignedSignatureV4(r) { + ok, err := auth.DoesPresignedSignatureMatch() + if err != nil { + errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil) + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + if !ok { + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + } else if isRequestSignatureV4(r) { + sh := sha256.New() + sh.Write(accessPolicyBytes) + ok, err := api.Signature.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil))) + if err != nil { + errorIf(err.Trace(string(accessPolicyBytes)), "SaveBucketPolicy failed.", nil) + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + if !ok { + writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + return + } + } + + // Save bucket policy. + err := writeBucketPolicy(bucket, accessPolicyBytes) + if err != nil { + errorIf(err.Trace(bucket), "SaveBucketPolicy failed.", nil) + switch err.ToGoError().(type) { + case fs.BucketNameInvalid: + writeErrorResponse(w, r, InvalidBucketName, r.URL.Path) + default: + writeErrorResponse(w, r, InternalError, r.URL.Path) + } + return + } + writeSuccessNoContent(w) +} + +// DeleteBucketPolicyHandler - DELETE Bucket policy +// ----------------- +// This implementation of the DELETE operation uses the policy +// subresource to add to remove a policy on a bucket. +func (api storageAPI) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bucket := vars["bucket"] + + // Validate incoming signature. + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + + // Delete bucket access policy. + err := removeBucketPolicy(bucket) + if err != nil { + errorIf(err.Trace(bucket), "DeleteBucketPolicy failed.", nil) + switch err.ToGoError().(type) { + case fs.BucketNameInvalid: + writeErrorResponse(w, r, InvalidBucketName, r.URL.Path) + case fs.BucketPolicyNotFound: + writeErrorResponse(w, r, NoSuchBucketPolicy, r.URL.Path) + default: + writeErrorResponse(w, r, InternalError, r.URL.Path) + } + return + } + writeSuccessNoContent(w) +} + +// GetBucketPolicyHandler - GET Bucket policy +// ----------------- +// This operation uses the policy +// subresource to return the policy of a specified bucket. +func (api storageAPI) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bucket := vars["bucket"] + + // Validate incoming signature. + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + + // Read bucket access policy. + p, err := readBucketPolicy(bucket) + if err != nil { + errorIf(err.Trace(bucket), "GetBucketPolicy failed.", nil) + switch err.ToGoError().(type) { + case fs.BucketNameInvalid: + writeErrorResponse(w, r, InvalidBucketName, r.URL.Path) + case fs.BucketPolicyNotFound: + writeErrorResponse(w, r, NoSuchBucketPolicy, r.URL.Path) + default: + writeErrorResponse(w, r, InternalError, r.URL.Path) + } + return + } + io.Copy(w, bytes.NewReader(p)) +} diff --git a/bucket-policy.go b/bucket-policy.go new file mode 100644 index 000000000..fb995bfd9 --- /dev/null +++ b/bucket-policy.go @@ -0,0 +1,147 @@ +/* + * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/minio/minio/pkg/fs" + "github.com/minio/minio/pkg/probe" +) + +// getBucketsConfigPath - get buckets path. +func getBucketsConfigPath() (string, *probe.Error) { + configPath, err := getConfigPath() + if err != nil { + return "", err.Trace() + } + return filepath.Join(configPath, "buckets"), nil +} + +// createBucketsConfigPath - create buckets directory. +func createBucketsConfigPath() *probe.Error { + bucketsConfigPath, err := getBucketsConfigPath() + if err != nil { + return err + } + if e := os.MkdirAll(bucketsConfigPath, 0700); e != nil { + return probe.NewError(e) + } + return nil +} + +// getBucketConfigPath - get bucket path. +func getBucketConfigPath(bucket string) (string, *probe.Error) { + bucketsConfigPath, err := getBucketsConfigPath() + if err != nil { + return "", err.Trace() + } + return filepath.Join(bucketsConfigPath, bucket), nil +} + +// createBucketConfigPath - create bucket directory. +func createBucketConfigPath(bucket string) *probe.Error { + bucketConfigPath, err := getBucketConfigPath(bucket) + if err != nil { + return err + } + if e := os.MkdirAll(bucketConfigPath, 0700); e != nil { + return probe.NewError(e) + } + return nil +} + +// readBucketPolicy - read bucket policy. +func readBucketPolicy(bucket string) ([]byte, *probe.Error) { + // Verify bucket is valid. + if !fs.IsValidBucketName(bucket) { + return nil, probe.NewError(fs.BucketNameInvalid{Bucket: bucket}) + } + + bucketConfigPath, err := getBucketConfigPath(bucket) + if err != nil { + return nil, err.Trace() + } + + // Get policy file. + bucketPolicyFile := filepath.Join(bucketConfigPath, "access-policy.json") + if _, e := os.Stat(bucketPolicyFile); e != nil { + if os.IsNotExist(e) { + return nil, probe.NewError(fs.BucketPolicyNotFound{Bucket: bucket}) + } + return nil, probe.NewError(e) + } + + accessPolicyBytes, e := ioutil.ReadFile(bucketPolicyFile) + if e != nil { + return nil, probe.NewError(e) + } + return accessPolicyBytes, nil +} + +// removeBucketPolicy - remove bucket policy. +func removeBucketPolicy(bucket string) *probe.Error { + // Verify bucket is valid. + if !fs.IsValidBucketName(bucket) { + return probe.NewError(fs.BucketNameInvalid{Bucket: bucket}) + } + + bucketConfigPath, err := getBucketConfigPath(bucket) + if err != nil { + return err.Trace(bucket) + } + + // Get policy file. + bucketPolicyFile := filepath.Join(bucketConfigPath, "access-policy.json") + if _, e := os.Stat(bucketPolicyFile); e != nil { + if os.IsNotExist(e) { + return probe.NewError(fs.BucketPolicyNotFound{Bucket: bucket}) + } + return probe.NewError(e) + } + return nil +} + +// writeBucketPolicy - save bucket policy. +func writeBucketPolicy(bucket string, accessPolicyBytes []byte) *probe.Error { + // Verify if bucket path legal + if !fs.IsValidBucketName(bucket) { + return probe.NewError(fs.BucketNameInvalid{Bucket: bucket}) + } + + bucketConfigPath, err := getBucketConfigPath(bucket) + if err != nil { + return err.Trace() + } + + // Get policy file. + bucketPolicyFile := filepath.Join(bucketConfigPath, "access-policy.json") + if _, e := os.Stat(bucketPolicyFile); e != nil { + if !os.IsNotExist(e) { + return probe.NewError(e) + } + } + + // Write bucket policy. + if e := ioutil.WriteFile(bucketPolicyFile, accessPolicyBytes, 0600); e != nil { + return probe.NewError(e) + } + + return nil +} diff --git a/generic-handlers.go b/generic-handlers.go index fe46762f5..5410317ac 100644 --- a/generic-handlers.go +++ b/generic-handlers.go @@ -280,7 +280,7 @@ func ignoreNotImplementedObjectResources(req *http.Request) bool { // List of not implemented bucket queries var notimplementedBucketResourceNames = map[string]bool{ - "policy": true, + "acl": true, "cors": true, "lifecycle": true, "logging": true, diff --git a/object-handlers.go b/object-handlers.go index 12b3706d3..a4b9d9013 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -61,16 +61,22 @@ func (api storageAPI) GetObjectHandler(w http.ResponseWriter, r *http.Request) { bucket = vars["bucket"] object = vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:GetObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypePresigned, authTypeSigned: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return } metadata, err := api.Filesystem.GetObjectMetadata(bucket, object) @@ -225,15 +231,8 @@ func (api storageAPI) HeadObjectHandler(w http.ResponseWriter, r *http.Request) bucket = vars["bucket"] object = vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } @@ -286,14 +285,22 @@ func (api storageAPI) CopyObjectHandler(w http.ResponseWriter, r *http.Request) bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. writeErrorResponse(w, r, AccessDenied, r.URL.Path) return - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:GetBucketLocation", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypePresigned, authTypeSigned: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } } // TODO: Reject requests where body/payload is present, for now we @@ -413,13 +420,6 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) { bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - } - // get Content-Md5 sent by client and verify if valid md5 := r.Header.Get("Content-Md5") if !isValidMD5(md5) { @@ -440,11 +440,23 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) { // Set http request for signature. auth := api.Signature.SetHTTPRequestToVerify(r) - var metadata fs.ObjectMetadata var err *probe.Error - // For presigned requests verify them right here. - if isRequestPresignedSignatureV4(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:PutObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + // Create anonymous object. + metadata, err = api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, nil) + case authTypePresigned: + // For presigned requests verify them right here. var ok bool ok, err = auth.DoesPresignedSignatureMatch() if err != nil { @@ -458,7 +470,7 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) { } // Create presigned object. metadata, err = api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, nil) - } else { + case authTypeSigned: // Create object. metadata, err = api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, &auth) } @@ -501,16 +513,18 @@ func (api storageAPI) NewMultipartUploadHandler(w http.ResponseWriter, r *http.R bucket = vars["bucket"] object = vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:PutObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + default: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return } uploadID, err := api.Filesystem.NewMultipartUpload(bucket, object) @@ -547,13 +561,6 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - } - // get Content-Md5 sent by client and verify if valid md5 := r.Header.Get("Content-Md5") if !isValidMD5(md5) { @@ -585,11 +592,20 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques // Set http request for signature. auth := api.Signature.SetHTTPRequestToVerify(r) - var partMD5 string var err *probe.Error - // For presigned requests verify right here. - if isRequestPresignedSignatureV4(r) { + switch getRequestAuthType(r) { + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:PutObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + // No need to verify signature, anonymous request access is + // already allowed. + partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, nil) + case authTypePresigned: + // For presigned requests verify right here. var ok bool ok, err = auth.DoesPresignedSignatureMatch() if err != nil { @@ -602,7 +618,7 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques return } partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, nil) - } else { + default: partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, &auth) } if err != nil { @@ -637,16 +653,18 @@ func (api storageAPI) AbortMultipartUploadHandler(w http.ResponseWriter, r *http bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:AbortMultipartUpload", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + default: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return } objectResourcesMetadata := getObjectResources(r.URL.Query()) @@ -678,16 +696,18 @@ func (api storageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http.Requ bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:ListMultipartUploadParts", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + default: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } - } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return } objectResourcesMetadata := getObjectResources(r.URL.Query()) @@ -724,9 +744,9 @@ func (api storageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http.Requ } response := generateListPartsResponse(objectResourcesMetadata) encodedSuccessResponse := encodeResponse(response) - // write headers. + // Write headers. setCommonHeaders(w) - // write success response. + // Write success response. writeSuccessResponse(w, encodedSuccessResponse) } @@ -736,13 +756,6 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) - return - } - } - // Extract object resources. objectResourcesMetadata := getObjectResources(r.URL.Query()) @@ -751,8 +764,21 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h var metadata fs.ObjectMetadata var err *probe.Error - // For presigned requests verify right here. - if isRequestPresignedSignatureV4(r) { + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:PutObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + // Complete multipart upload anonymous. + metadata, err = api.Filesystem.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, r.Body, nil) + case authTypePresigned: + // For presigned requests verify right here. var ok bool ok, err = auth.DoesPresignedSignatureMatch() if err != nil { @@ -766,7 +792,7 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h } // Complete multipart upload presigned. metadata, err = api.Filesystem.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, r.Body, nil) - } else { + case authTypeSigned: // Complete multipart upload. metadata, err = api.Filesystem.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, r.Body, &auth) } @@ -817,18 +843,23 @@ func (api storageAPI) DeleteObjectHandler(w http.ResponseWriter, r *http.Request bucket := vars["bucket"] object := vars["object"] - if isRequestRequiresACLCheck(r) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + switch getRequestAuthType(r) { + default: + // For all unknown auth types return error. + writeErrorResponse(w, r, AccessDenied, r.URL.Path) + return + case authTypeAnonymous: + // http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html + if isAllowed, s3Error := enforceBucketPolicy("s3:DeleteObject", bucket, r.URL); !isAllowed { + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + case authTypeSigned, authTypePresigned: + if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match { + writeErrorResponse(w, r, s3Error, r.URL.Path) return } } - - if !isSignV4ReqAuthenticated(api.Signature, r) { - writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path) - return - } - err := api.Filesystem.DeleteObject(bucket, object) if err != nil { errorIf(err.Trace(), "DeleteObject failed.", nil) diff --git a/pkg/fs/api_suite_nix_test.go b/pkg/fs/api_suite_nix_test.go index a0aaff9d3..fa6653740 100644 --- a/pkg/fs/api_suite_nix_test.go +++ b/pkg/fs/api_suite_nix_test.go @@ -38,7 +38,6 @@ func APITestSuite(c *check.C, create func() Filesystem) { testPaging(c, create) testObjectOverwriteWorks(c, create) testNonExistantBucketOperations(c, create) - testBucketMetadata(c, create) testBucketRecreateFails(c, create) testPutObjectInSubdir(c, create) testListBuckets(c, create) @@ -53,13 +52,13 @@ func APITestSuite(c *check.C, create func() Filesystem) { func testMakeBucket(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) } func testMultipartObjectCreation(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) uploadID, err := fs.NewMultipartUpload("bucket", "key") c.Assert(err, check.IsNil) @@ -94,7 +93,7 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { func testMultipartObjectAbort(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) uploadID, err := fs.NewMultipartUpload("bucket", "key") c.Assert(err, check.IsNil) @@ -126,7 +125,7 @@ func testMultipartObjectAbort(c *check.C, create func() Filesystem) { func testMultipleObjectCreation(c *check.C, create func() Filesystem) { objects := make(map[string][]byte) fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) for i := 0; i < 10; i++ { randomPerm := rand.Perm(10) @@ -161,7 +160,7 @@ func testMultipleObjectCreation(c *check.C, create func() Filesystem) { func testPaging(c *check.C, create func() Filesystem) { fs := create() - fs.MakeBucket("bucket", "") + fs.MakeBucket("bucket") result, err := fs.ListObjects("bucket", "", "", "", 0) c.Assert(err, check.IsNil) c.Assert(len(result.Objects), check.Equals, 0) @@ -262,7 +261,7 @@ func testPaging(c *check.C, create func() Filesystem) { func testObjectOverwriteWorks(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) hasher1 := md5.New() @@ -292,27 +291,17 @@ func testNonExistantBucketOperations(c *check.C, create func() Filesystem) { c.Assert(err, check.Not(check.IsNil)) } -func testBucketMetadata(c *check.C, create func() Filesystem) { - fs := create() - err := fs.MakeBucket("string", "") - c.Assert(err, check.IsNil) - - metadata, err := fs.GetBucketMetadata("string") - c.Assert(err, check.IsNil) - c.Assert(metadata.ACL, check.Equals, BucketACL("private")) -} - func testBucketRecreateFails(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("string", "") + err := fs.MakeBucket("string") c.Assert(err, check.IsNil) - err = fs.MakeBucket("string", "") + err = fs.MakeBucket("string") c.Assert(err, check.Not(check.IsNil)) } func testPutObjectInSubdir(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) hasher := md5.New() @@ -339,7 +328,7 @@ func testListBuckets(c *check.C, create func() Filesystem) { c.Assert(len(buckets), check.Equals, 0) // add one and test exists - err = fs.MakeBucket("bucket1", "") + err = fs.MakeBucket("bucket1") c.Assert(err, check.IsNil) buckets, err = fs.ListBuckets() @@ -347,7 +336,7 @@ func testListBuckets(c *check.C, create func() Filesystem) { c.Assert(err, check.IsNil) // add two and test exists - err = fs.MakeBucket("bucket2", "") + err = fs.MakeBucket("bucket2") c.Assert(err, check.IsNil) buckets, err = fs.ListBuckets() @@ -355,7 +344,7 @@ func testListBuckets(c *check.C, create func() Filesystem) { c.Assert(err, check.IsNil) // add three and test exists + prefix - err = fs.MakeBucket("bucket22", "") + err = fs.MakeBucket("bucket22") buckets, err = fs.ListBuckets() c.Assert(len(buckets), check.Equals, 3) @@ -368,9 +357,9 @@ func testListBucketsOrder(c *check.C, create func() Filesystem) { for i := 0; i < 10; i++ { fs := create() // add one and test exists - err := fs.MakeBucket("bucket1", "") + err := fs.MakeBucket("bucket1") c.Assert(err, check.IsNil) - err = fs.MakeBucket("bucket2", "") + err = fs.MakeBucket("bucket2") c.Assert(err, check.IsNil) buckets, err := fs.ListBuckets() c.Assert(err, check.IsNil) @@ -390,7 +379,7 @@ func testListObjectsTestsForNonExistantBucket(c *check.C, create func() Filesyst func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) var byteBuffer bytes.Buffer @@ -408,7 +397,7 @@ func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) { func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) _, err = fs.CreateObject("bucket", "dir1/dir2/object", "", int64(len("hello world")), bytes.NewBufferString("hello world"), nil) @@ -443,7 +432,7 @@ func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) func testDefaultContentType(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) // test empty @@ -455,7 +444,7 @@ func testDefaultContentType(c *check.C, create func() Filesystem) { func testContentMD5Set(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) // test md5 invalid diff --git a/pkg/fs/api_suite_windows_test.go b/pkg/fs/api_suite_windows_test.go index 4f43c5486..6f78a0a04 100644 --- a/pkg/fs/api_suite_windows_test.go +++ b/pkg/fs/api_suite_windows_test.go @@ -37,7 +37,6 @@ func APITestSuite(c *check.C, create func() Filesystem) { testPaging(c, create) testObjectOverwriteWorks(c, create) testNonExistantBucketOperations(c, create) - testBucketMetadata(c, create) testBucketRecreateFails(c, create) testPutObjectInSubdir(c, create) testListBuckets(c, create) @@ -52,13 +51,13 @@ func APITestSuite(c *check.C, create func() Filesystem) { func testMakeBucket(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) } func testMultipartObjectCreation(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) uploadID, err := fs.NewMultipartUpload("bucket", "key") c.Assert(err, check.IsNil) @@ -93,7 +92,7 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { func testMultipartObjectAbort(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) uploadID, err := fs.NewMultipartUpload("bucket", "key") c.Assert(err, check.IsNil) @@ -125,7 +124,7 @@ func testMultipartObjectAbort(c *check.C, create func() Filesystem) { func testMultipleObjectCreation(c *check.C, create func() Filesystem) { objects := make(map[string][]byte) fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) for i := 0; i < 10; i++ { randomPerm := rand.Perm(10) @@ -160,7 +159,7 @@ func testMultipleObjectCreation(c *check.C, create func() Filesystem) { func testPaging(c *check.C, create func() Filesystem) { fs := create() - fs.MakeBucket("bucket", "") + fs.MakeBucket("bucket") result, err := fs.ListObjects("bucket", "", "", "", 0) c.Assert(err, check.IsNil) c.Assert(len(result.Objects), check.Equals, 0) @@ -260,7 +259,7 @@ func testPaging(c *check.C, create func() Filesystem) { func testObjectOverwriteWorks(c *check.C, create func() Filesystem) { fs := create() - fs.MakeBucket("bucket", "") + fs.MakeBucket("bucket") hasher1 := md5.New() hasher1.Write([]byte("one")) @@ -289,27 +288,17 @@ func testNonExistantBucketOperations(c *check.C, create func() Filesystem) { c.Assert(err, check.Not(check.IsNil)) } -func testBucketMetadata(c *check.C, create func() Filesystem) { - fs := create() - err := fs.MakeBucket("string", "private") - c.Assert(err, check.IsNil) - - metadata, err := fs.GetBucketMetadata("string") - c.Assert(err, check.IsNil) - c.Assert(metadata.ACL, check.Equals, BucketACL("private")) -} - func testBucketRecreateFails(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("string", "private") + err := fs.MakeBucket("string") c.Assert(err, check.IsNil) - err = fs.MakeBucket("string", "private") + err = fs.MakeBucket("string") c.Assert(err, check.Not(check.IsNil)) } func testPutObjectInSubdir(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "private") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) hasher := md5.New() @@ -336,7 +325,7 @@ func testListBuckets(c *check.C, create func() Filesystem) { c.Assert(len(buckets), check.Equals, 0) // add one and test exists - err = fs.MakeBucket("bucket1", "") + err = fs.MakeBucket("bucket1") c.Assert(err, check.IsNil) buckets, err = fs.ListBuckets() @@ -344,7 +333,7 @@ func testListBuckets(c *check.C, create func() Filesystem) { c.Assert(err, check.IsNil) // add two and test exists - err = fs.MakeBucket("bucket2", "") + err = fs.MakeBucket("bucket2") c.Assert(err, check.IsNil) buckets, err = fs.ListBuckets() @@ -352,7 +341,7 @@ func testListBuckets(c *check.C, create func() Filesystem) { c.Assert(err, check.IsNil) // add three and test exists + prefix - err = fs.MakeBucket("bucket22", "") + err = fs.MakeBucket("bucket22") buckets, err = fs.ListBuckets() c.Assert(len(buckets), check.Equals, 3) @@ -365,9 +354,9 @@ func testListBucketsOrder(c *check.C, create func() Filesystem) { for i := 0; i < 10; i++ { fs := create() // add one and test exists - err := fs.MakeBucket("bucket1", "") + err := fs.MakeBucket("bucket1") c.Assert(err, check.IsNil) - err = fs.MakeBucket("bucket2", "") + err = fs.MakeBucket("bucket2") c.Assert(err, check.IsNil) buckets, err := fs.ListBuckets() c.Assert(err, check.IsNil) @@ -387,7 +376,7 @@ func testListObjectsTestsForNonExistantBucket(c *check.C, create func() Filesyst func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) var byteBuffer bytes.Buffer @@ -409,7 +398,7 @@ func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) { func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) _, err = fs.CreateObject("bucket", "dir1/dir2/object", "", int64(len("hello world")), bytes.NewBufferString("hello world"), nil) @@ -444,7 +433,7 @@ func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) func testDefaultContentType(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) // test empty @@ -456,7 +445,7 @@ func testDefaultContentType(c *check.C, create func() Filesystem) { func testContentMD5Set(c *check.C, create func() Filesystem) { fs := create() - err := fs.MakeBucket("bucket", "") + err := fs.MakeBucket("bucket") c.Assert(err, check.IsNil) // test md5 invalid diff --git a/pkg/fs/fs-backend-metadata.go b/pkg/fs/fs-backend-metadata.go index 6f28965d9..18e1a0cc4 100644 --- a/pkg/fs/fs-backend-metadata.go +++ b/pkg/fs/fs-backend-metadata.go @@ -21,12 +21,7 @@ import ( "github.com/minio/minio/pkg/quick" ) -var multipartsMetadataPath, bucketsMetadataPath string - -// setFSBucketsMetadataPath - set fs buckets metadata path. -func setFSBucketsMetadataPath(metadataPath string) { - bucketsMetadataPath = metadataPath -} +var multipartsMetadataPath string // SetFSMultipartsMetadataPath - set custom multiparts session // metadata path. @@ -46,18 +41,6 @@ func saveMultipartsSession(multiparts Multiparts) *probe.Error { return nil } -// saveBucketsMetadata - save metadata of all buckets -func saveBucketsMetadata(buckets Buckets) *probe.Error { - qc, err := quick.New(buckets) - if err != nil { - return err.Trace() - } - if err := qc.Save(bucketsMetadataPath); err != nil { - return err.Trace() - } - return nil -} - // loadMultipartsSession load multipart session file func loadMultipartsSession() (*Multiparts, *probe.Error) { multiparts := &Multiparts{} @@ -72,18 +55,3 @@ func loadMultipartsSession() (*Multiparts, *probe.Error) { } return qc.Data().(*Multiparts), nil } - -// loadBucketsMetadata load buckets metadata file -func loadBucketsMetadata() (*Buckets, *probe.Error) { - buckets := &Buckets{} - buckets.Version = "1" - buckets.Metadata = make(map[string]*BucketMetadata) - qc, err := quick.New(buckets) - if err != nil { - return nil, err.Trace() - } - if err := qc.Load(bucketsMetadataPath); err != nil { - return nil, err.Trace() - } - return qc.Data().(*Buckets), nil -} diff --git a/pkg/fs/fs-bucket-acl.go b/pkg/fs/fs-bucket-acl.go deleted file mode 100644 index fe2c9d26b..000000000 --- a/pkg/fs/fs-bucket-acl.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package fs - -// IsPrivateBucket - is private bucket -func (fs Filesystem) IsPrivateBucket(bucket string) bool { - fs.rwLock.RLock() - defer fs.rwLock.RUnlock() - bucketMetadata, ok := fs.buckets.Metadata[bucket] - if !ok { - return true - } - return bucketMetadata.ACL.IsPrivate() -} - -// IsPublicBucket - is public bucket -func (fs Filesystem) IsPublicBucket(bucket string) bool { - fs.rwLock.RLock() - defer fs.rwLock.RUnlock() - bucketMetadata, ok := fs.buckets.Metadata[bucket] - if !ok { - return true - } - return bucketMetadata.ACL.IsPublicReadWrite() -} - -// IsReadOnlyBucket - is read only bucket -func (fs Filesystem) IsReadOnlyBucket(bucket string) bool { - fs.rwLock.RLock() - defer fs.rwLock.RUnlock() - bucketMetadata, ok := fs.buckets.Metadata[bucket] - if !ok { - return true - } - return bucketMetadata.ACL.IsPublicRead() -} - -// BucketACL - bucket level access control -type BucketACL string - -// different types of ACL's currently supported for buckets -const ( - BucketPrivate = BucketACL("private") - BucketPublicRead = BucketACL("public-read") - BucketPublicReadWrite = BucketACL("public-read-write") -) - -func (b BucketACL) String() string { - return string(b) -} - -// IsPrivate - is acl Private -func (b BucketACL) IsPrivate() bool { - return b == BucketACL("private") -} - -// IsPublicRead - is acl PublicRead -func (b BucketACL) IsPublicRead() bool { - return b == BucketACL("public-read") -} - -// IsPublicReadWrite - is acl PublicReadWrite -func (b BucketACL) IsPublicReadWrite() bool { - return b == BucketACL("public-read-write") -} - -// IsValidBucketACL - is provided acl string supported -func IsValidBucketACL(acl string) bool { - switch acl { - case "private": - fallthrough - case "public-read": - fallthrough - case "public-read-write": - return true - case "": - // by default its "private" - return true - default: - return false - } -} diff --git a/pkg/fs/fs-bucket.go b/pkg/fs/fs-bucket.go index 1a598e82a..b4127810e 100644 --- a/pkg/fs/fs-bucket.go +++ b/pkg/fs/fs-bucket.go @@ -52,15 +52,6 @@ func (fs Filesystem) DeleteBucket(bucket string) *probe.Error { } return probe.NewError(e) } - - // Critical region hold write lock. - fs.rwLock.Lock() - defer fs.rwLock.Unlock() - - delete(fs.buckets.Metadata, bucket) - if err := saveBucketsMetadata(*fs.buckets); err != nil { - return err.Trace(bucket) - } return nil } @@ -112,8 +103,8 @@ func removeDuplicateBuckets(buckets []BucketMetadata) []BucketMetadata { return buckets } -// MakeBucket - PUT Bucket. -func (fs Filesystem) MakeBucket(bucket, acl string) *probe.Error { +// MakeBucket - PUT Bucket +func (fs Filesystem) MakeBucket(bucket string) *probe.Error { di, err := disk.GetInfo(fs.path) if err != nil { return probe.NewError(err) @@ -131,12 +122,6 @@ func (fs Filesystem) MakeBucket(bucket, acl string) *probe.Error { return probe.NewError(BucketNameInvalid{Bucket: bucket}) } - // Verify if bucket acl is valid. - if !IsValidBucketACL(acl) { - return probe.NewError(InvalidACL{ACL: acl}) - } - - // Get bucket path. bucket = fs.denormalizeBucket(bucket) bucketDir := filepath.Join(fs.path, bucket) if _, e := os.Stat(bucketDir); e == nil { @@ -147,33 +132,6 @@ func (fs Filesystem) MakeBucket(bucket, acl string) *probe.Error { if e := os.Mkdir(bucketDir, 0700); e != nil { return probe.NewError(err) } - - fi, e := os.Stat(bucketDir) - // Check if bucket exists. - if e != nil { - if os.IsNotExist(e) { - return probe.NewError(BucketNotFound{Bucket: bucket}) - } - return probe.NewError(e) - } - if acl == "" { - acl = "private" - } - - // Get a new bucket name metadata. - bucketMetadata := &BucketMetadata{} - bucketMetadata.Name = fi.Name() - bucketMetadata.Created = fi.ModTime() - bucketMetadata.ACL = BucketACL(acl) - - // Critical region hold a write lock. - fs.rwLock.Lock() - defer fs.rwLock.Unlock() - - fs.buckets.Metadata[bucket] = bucketMetadata - if err := saveBucketsMetadata(*fs.buckets); err != nil { - return err.Trace(bucket) - } return nil } @@ -200,7 +158,6 @@ func (fs Filesystem) GetBucketMetadata(bucket string) (BucketMetadata, *probe.Er if !IsValidBucketName(bucket) { return BucketMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket}) } - bucket = fs.denormalizeBucket(bucket) // Get bucket path. bucketDir := filepath.Join(fs.path, bucket) @@ -212,44 +169,8 @@ func (fs Filesystem) GetBucketMetadata(bucket string) (BucketMetadata, *probe.Er } return BucketMetadata{}, probe.NewError(e) } - fs.rwLock.RLock() - bucketMetadata, ok := fs.buckets.Metadata[bucket] - fs.rwLock.RUnlock() - // If metadata value is not found, get it from disk. - if !ok { - bucketMetadata = &BucketMetadata{} - bucketMetadata.Name = fi.Name() - bucketMetadata.Created = fi.ModTime() - bucketMetadata.ACL = BucketACL("private") - } - return *bucketMetadata, nil -} - -// SetBucketMetadata - set bucket metadata. -func (fs Filesystem) SetBucketMetadata(bucket string, metadata map[string]string) *probe.Error { - bucketMetadata, err := fs.GetBucketMetadata(bucket) - if err != nil { - return err - } - - // Save the acl. - acl := metadata["acl"] - if !IsValidBucketACL(acl) { - return probe.NewError(InvalidACL{ACL: acl}) - } else if acl == "" { - acl = "private" - } - bucketMetadata.ACL = BucketACL(acl) - - bucket = fs.denormalizeBucket(bucket) - - // Critical region handle write lock. - fs.rwLock.Lock() - defer fs.rwLock.Unlock() - - fs.buckets.Metadata[bucket] = &bucketMetadata - if err := saveBucketsMetadata(*fs.buckets); err != nil { - return err.Trace(bucket) - } - return nil + bucketMetadata := BucketMetadata{} + bucketMetadata.Name = fi.Name() + bucketMetadata.Created = fi.ModTime() + return bucketMetadata, nil } diff --git a/pkg/fs/fs-bucket_test.go b/pkg/fs/fs-bucket_test.go index c50ac1eb6..d8dd9b699 100644 --- a/pkg/fs/fs-bucket_test.go +++ b/pkg/fs/fs-bucket_test.go @@ -65,7 +65,7 @@ func BenchmarkDeleteBucket(b *testing.B) { b.StopTimer() // Create and delete the bucket over and over. - err = filesystem.MakeBucket("bucket", "public-read-write") + err = filesystem.MakeBucket("bucket") if err != nil { b.Fatal(err) } @@ -94,7 +94,7 @@ func BenchmarkGetBucketMetadata(b *testing.B) { } // Put up a bucket with some metadata. - err = filesystem.MakeBucket("bucket", "public-read-write") + err = filesystem.MakeBucket("bucket") if err != nil { b.Fatal(err) } @@ -109,37 +109,3 @@ func BenchmarkGetBucketMetadata(b *testing.B) { } } } - -func BenchmarkSetBucketMetadata(b *testing.B) { - // Make a temporary directory to use as the filesystem. - directory, fserr := ioutil.TempDir("", "minio-benchmark") - if fserr != nil { - b.Fatal(fserr) - } - defer os.RemoveAll(directory) - - // Create the filesystem. - filesystem, err := New(directory, 0) - if err != nil { - b.Fatal(err) - } - - // Put up a bucket with some metadata. - err = filesystem.MakeBucket("bucket", "public-read-write") - if err != nil { - b.Fatal(err) - } - - metadata := make(map[string]string) - metadata["acl"] = "public-read-write" - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - // Set all the metadata! - err = filesystem.SetBucketMetadata("bucket", metadata) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/pkg/fs/fs-datatypes.go b/pkg/fs/fs-datatypes.go index 3167a7fd3..77099afa1 100644 --- a/pkg/fs/fs-datatypes.go +++ b/pkg/fs/fs-datatypes.go @@ -25,7 +25,6 @@ import ( type BucketMetadata struct { Name string Created time.Time - ACL BucketACL } // ObjectMetadata - object key and its relevant metadata diff --git a/pkg/fs/fs-errors.go b/pkg/fs/fs-errors.go index 8ac82b9aa..a360bc702 100644 --- a/pkg/fs/fs-errors.go +++ b/pkg/fs/fs-errors.go @@ -163,6 +163,13 @@ type GenericBucketError struct { Bucket string } +// BucketPolicyNotFound - no bucket policy found. +type BucketPolicyNotFound GenericBucketError + +func (e BucketPolicyNotFound) Error() string { + return "No bucket policy found for bucket: " + e.Bucket +} + // GenericObjectError - generic object error type GenericObjectError struct { Bucket string @@ -183,17 +190,6 @@ type DigestError struct { MD5 string } -/// ACL related errors - -// InvalidACL - acl invalid -type InvalidACL struct { - ACL string -} - -func (e InvalidACL) Error() string { - return "Requested ACL is " + e.ACL + " invalid" -} - /// Bucket related errors // BucketNameInvalid - bucketname provided is invalid diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go index b04675d93..e8a781e2e 100644 --- a/pkg/fs/fs.go +++ b/pkg/fs/fs.go @@ -31,17 +31,10 @@ type Filesystem struct { minFreeDisk int64 rwLock *sync.RWMutex multiparts *Multiparts - buckets *Buckets listServiceReqCh chan<- listServiceReq timeoutReqCh chan<- uint32 } -// Buckets holds acl information -type Buckets struct { - Version string `json:"version"` - Metadata map[string]*BucketMetadata -} - // MultipartSession holds active session information type MultipartSession struct { TotalParts int @@ -59,7 +52,6 @@ type Multiparts struct { // New instantiate a new donut func New(rootPath string, minFreeDisk int64) (Filesystem, *probe.Error) { - setFSBucketsMetadataPath(filepath.Join(rootPath, "$buckets.json")) setFSMultipartsMetadataPath(filepath.Join(rootPath, "$multiparts-session.json")) var err *probe.Error @@ -80,27 +72,12 @@ func New(rootPath string, minFreeDisk int64) (Filesystem, *probe.Error) { } } - var buckets *Buckets - buckets, err = loadBucketsMetadata() - if err != nil { - if os.IsNotExist(err.ToGoError()) { - buckets = &Buckets{ - Version: "1", - Metadata: make(map[string]*BucketMetadata), - } - if err = saveBucketsMetadata(*buckets); err != nil { - return Filesystem{}, err.Trace() - } - } else { - return Filesystem{}, err.Trace() - } - } fs := Filesystem{ rwLock: &sync.RWMutex{}, } fs.path = rootPath fs.multiparts = multiparts - fs.buckets = buckets + /// Defaults // minium free disk required for i/o operations to succeed. diff --git a/pkg/s3/access/README.md b/pkg/s3/access/README.md new file mode 100644 index 000000000..b1ff17075 --- /dev/null +++ b/pkg/s3/access/README.md @@ -0,0 +1,33 @@ +## Access Policy + +This package implements parsing and validating bucket access policies based on Access Policy Language specification - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html + +### Supports following effects. + + Allow + Deny + +### Supports following set of operations. + + * + s3:* + s3:GetObject + s3:ListBucket + s3:PutObject + s3:CreateBucket + s3:GetBucketLocation + s3:DeleteBucket + s3:DeleteObject + s3:AbortMultipartUpload + s3:ListBucketMultipartUploads + s3:ListMultipartUploadParts + +### Supports following conditions. + + StringEquals + StringNotEquals + +Supported applicable condition keys for each conditions. + + s3:prefix + s3:max-keys diff --git a/pkg/s3/access/policy.go b/pkg/s3/access/policy.go new file mode 100644 index 000000000..a659720ee --- /dev/null +++ b/pkg/s3/access/policy.go @@ -0,0 +1,230 @@ +/* + * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package accesspolicy implements AWS Access Policy Language parser in +// accordance with http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html +package accesspolicy + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +const ( + // AWSResourcePrefix - bucket policy resource prefix. + AWSResourcePrefix = "arn:aws:s3:::" +) + +// supportedActionMap - lists all the actions supported by minio. +var supportedActionMap = map[string]struct{}{ + "*": {}, + "s3:*": {}, + "s3:GetObject": {}, + "s3:ListBucket": {}, + "s3:PutObject": {}, + "s3:CreateBucket": {}, + "s3:GetBucketLocation": {}, + "s3:DeleteBucket": {}, + "s3:DeleteObject": {}, + "s3:AbortMultipartUpload": {}, + "s3:ListBucketMultipartUploads": {}, + "s3:ListMultipartUploadParts": {}, +} + +// User - canonical users list. +type User struct { + AWS []string +} + +// Statement - minio policy statement +type Statement struct { + Sid string + Effect string + Principal User + Actions []string `json:"Action"` + Resources []string `json:"Resource"` + Conditions map[string]map[string]string `json:"Condition"` +} + +// BucketPolicy - minio policy collection +type BucketPolicy struct { + Version string // date in 0000-00-00 format + Statements []Statement `json:"Statement"` +} + +// supportedEffectMap - supported effects. +var supportedEffectMap = map[string]struct{}{ + "Allow": {}, + "Deny": {}, +} + +// isValidActions - are actions valid. +func isValidActions(actions []string) (err error) { + // Statement actions cannot be empty. + if len(actions) == 0 { + err = errors.New("Action list cannot be empty.") + return err + } + for _, action := range actions { + if _, ok := supportedActionMap[action]; !ok { + err = errors.New("Unsupported action found: ‘" + action + "’, please validate your policy document.") + return err + } + } + return nil +} + +// isValidEffect - is effect valid. +func isValidEffect(effect string) error { + // Statement effect cannot be empty. + if len(effect) == 0 { + err := errors.New("Policy effect cannot be empty.") + return err + } + _, ok := supportedEffectMap[effect] + if !ok { + err := errors.New("Unsupported Effect found: ‘" + effect + "’, please validate your policy document.") + return err + } + return nil +} + +// isValidResources - are valid resources. +func isValidResources(resources []string) (err error) { + // Statement resources cannot be empty. + if len(resources) == 0 { + err = errors.New("Resource list cannot be empty.") + return err + } + for _, resource := range resources { + if !strings.HasPrefix(resource, AWSResourcePrefix) { + err = errors.New("Unsupported resource style found: ‘" + resource + "’, please validate your policy document.") + return err + } + resourceSuffix := strings.SplitAfter(resource, AWSResourcePrefix)[1] + if len(resourceSuffix) == 0 || strings.HasPrefix(resourceSuffix, "/") { + err = errors.New("Invalid resource style found: ‘" + resource + "’, please validate your policy document.") + return err + } + } + return nil +} + +// isValidPrincipals - are valid principals. +func isValidPrincipals(principals []string) (err error) { + // Statement principal should have a value. + if len(principals) == 0 { + err = errors.New("Principal cannot be empty.") + return err + } + var ok bool + for _, principal := range principals { + // Minio does not support or implement IAM, "*" is the only valid value. + if principal == "*" { + ok = true + continue + } + ok = false + } + if !ok { + err = errors.New("Unsupported principal style found: ‘" + strings.Join(principals, " ") + "’, please validate your policy document.") + return err + } + return nil +} + +func isValidConditions(conditions map[string]map[string]string) (err error) { + // Verify conditions should be valid. + if len(conditions) > 0 { + // Validate if stringEquals, stringNotEquals are present + // if not throw an error. + _, stringEqualsOK := conditions["StringEquals"] + _, stringNotEqualsOK := conditions["StringNotEquals"] + if !stringEqualsOK && !stringNotEqualsOK { + err = fmt.Errorf("Unsupported condition type found: ‘%s’, please validate your policy document.", conditions) + return err + } + // Validate s3:prefix, s3:max-keys are present if not + // throw an error. + if len(conditions["StringEquals"]) > 0 { + _, s3PrefixOK := conditions["StringEquals"]["s3:prefix"] + _, s3MaxKeysOK := conditions["StringEquals"]["s3:max-keys"] + if !s3PrefixOK && !s3MaxKeysOK { + err = fmt.Errorf("Unsupported condition keys found: ‘%s’, please validate your policy document.", + conditions["StringEquals"]) + return err + } + } + if len(conditions["StringNotEquals"]) > 0 { + _, s3PrefixOK := conditions["StringNotEquals"]["s3:prefix"] + _, s3MaxKeysOK := conditions["StringNotEquals"]["s3:max-keys"] + if !s3PrefixOK && !s3MaxKeysOK { + err = fmt.Errorf("Unsupported condition keys found: ‘%s’, please validate your policy document.", + conditions["StringNotEquals"]) + return err + } + } + } + return nil +} + +// Validate - validate if request body is of proper JSON and in +// accordance with policy standards. +func Validate(bucketPolicyBuf []byte) (policy BucketPolicy, err error) { + if err = json.Unmarshal(bucketPolicyBuf, &policy); err != nil { + return BucketPolicy{}, err + } + + // Policy version cannot be empty. + if len(policy.Version) == 0 { + err = errors.New("Policy version cannot be empty.") + return BucketPolicy{}, err + } + + // Policy statements cannot be empty. + if len(policy.Statements) == 0 { + err = errors.New("Policy statement cannot be empty.") + return BucketPolicy{}, err + } + + // Loop through all policy statements and validate entries. + for _, statement := range policy.Statements { + // Statement effect should be valid. + if err := isValidEffect(statement.Effect); err != nil { + return BucketPolicy{}, err + } + // Statement principal should be supported format. + if err := isValidPrincipals(statement.Principal.AWS); err != nil { + return BucketPolicy{}, err + } + // Statement actions should be valid. + if err := isValidActions(statement.Actions); err != nil { + return BucketPolicy{}, err + } + // Statment resources should be valid. + if err := isValidResources(statement.Resources); err != nil { + return BucketPolicy{}, err + } + // Statement conditions should be valid. + if err := isValidConditions(statement.Conditions); err != nil { + return BucketPolicy{}, err + } + } + // Return successfully parsed policy structure. + return policy, nil +} diff --git a/routers.go b/routers.go index d0a2f5bb0..78023bb42 100644 --- a/routers.go +++ b/routers.go @@ -137,14 +137,14 @@ func registerAPIHandlers(mux *router.Router, a storageAPI, w *webAPI) { // GetBucketLocation bucket.Methods("GET").HandlerFunc(a.GetBucketLocationHandler).Queries("location", "") - // GetBucketACL - bucket.Methods("GET").HandlerFunc(a.GetBucketACLHandler).Queries("acl", "") + // GetBucketPolicy + bucket.Methods("GET").HandlerFunc(a.GetBucketPolicyHandler).Queries("policy", "") // ListMultipartUploads bucket.Methods("GET").HandlerFunc(a.ListMultipartUploadsHandler).Queries("uploads", "") // ListObjects bucket.Methods("GET").HandlerFunc(a.ListObjectsHandler) - // PutBucketACL - bucket.Methods("PUT").HandlerFunc(a.PutBucketACLHandler).Queries("acl", "") + // PutBucketPolicy + bucket.Methods("PUT").HandlerFunc(a.PutBucketPolicyHandler).Queries("policy", "") // PutBucket bucket.Methods("PUT").HandlerFunc(a.PutBucketHandler) // HeadBucket @@ -152,7 +152,9 @@ func registerAPIHandlers(mux *router.Router, a storageAPI, w *webAPI) { // DeleteMultipleObjects bucket.Methods("POST").HandlerFunc(a.DeleteMultipleObjectsHandler) // PostPolicy - bucket.Methods("POST").HandlerFunc(a.PostPolicyBucketHandler) + bucket.Methods("POST").Headers("Content-Type", "multipart/form-data").HandlerFunc(a.PostPolicyBucketHandler) + // DeleteBucketPolicy + bucket.Methods("DELETE").HandlerFunc(a.DeleteBucketPolicyHandler).Queries("policy", "") // DeleteBucket bucket.Methods("DELETE").HandlerFunc(a.DeleteBucketHandler) diff --git a/server-config.go b/server-config.go index 1d8ef4644..aae9e6731 100644 --- a/server-config.go +++ b/server-config.go @@ -154,8 +154,8 @@ func createConfigPath() *probe.Error { if err != nil { return err.Trace() } - if err := os.MkdirAll(configPath, 0700); err != nil { - return probe.NewError(err) + if e := os.MkdirAll(configPath, 0700); e != nil { + return probe.NewError(e) } return nil } diff --git a/server-main.go b/server-main.go index 86e792b90..f328d0754 100644 --- a/server-main.go +++ b/server-main.go @@ -187,6 +187,9 @@ func getConfig() (*configV2, *probe.Error) { if err := createConfigPath(); err != nil { return nil, err.Trace() } + if err := createBucketsConfigPath(); err != nil { + return nil, err.Trace() + } config, err := loadConfigV2() if err != nil { if os.IsNotExist(err.ToGoError()) { diff --git a/server_fs_test.go b/server_fs_test.go index 5263e61f9..3c6aff822 100644 --- a/server_fs_test.go +++ b/server_fs_test.go @@ -524,7 +524,6 @@ func (s *MyAPIFSCacheSuite) TestHeader(c *C) { func (s *MyAPIFSCacheSuite) TestPutBucket(c *C) { request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-bucket", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client := http.Client{} response, err := client.Do(request) @@ -533,7 +532,6 @@ func (s *MyAPIFSCacheSuite) TestPutBucket(c *C) { request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-bucket-slash/", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client = http.Client{} response, err = client.Do(request) @@ -582,7 +580,6 @@ func (s *MyAPIFSCacheSuite) TestCopyObject(c *C) { func (s *MyAPIFSCacheSuite) TestPutObject(c *C) { request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-object", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client := http.Client{} response, err := client.Do(request) @@ -627,7 +624,6 @@ func (s *MyAPIFSCacheSuite) TestNotBeAbleToCreateObjectInNonexistantBucket(c *C) func (s *MyAPIFSCacheSuite) TestHeadOnObject(c *C) { request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/headonobject", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client := http.Client{} response, err := client.Do(request) @@ -671,7 +667,6 @@ func (s *MyAPIFSCacheSuite) TestHeadOnObject(c *C) { func (s *MyAPIFSCacheSuite) TestHeadOnBucket(c *C) { request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/headonbucket", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client := http.Client{} response, err := client.Do(request) @@ -837,7 +832,6 @@ func (s *MyAPIFSCacheSuite) TestListObjectsHandlerErrors(c *C) { request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/objecthandlererrors", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client = http.Client{} response, err = client.Do(request) @@ -855,7 +849,6 @@ func (s *MyAPIFSCacheSuite) TestListObjectsHandlerErrors(c *C) { func (s *MyAPIFSCacheSuite) TestPutBucketErrors(c *C) { request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket-.", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client := http.Client{} response, err := client.Do(request) @@ -864,7 +857,6 @@ func (s *MyAPIFSCacheSuite) TestPutBucketErrors(c *C) { request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") client = http.Client{} response, err = client.Do(request) @@ -873,7 +865,6 @@ func (s *MyAPIFSCacheSuite) TestPutBucketErrors(c *C) { request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "private") response, err = client.Do(request) c.Assert(err, IsNil) @@ -881,7 +872,6 @@ func (s *MyAPIFSCacheSuite) TestPutBucketErrors(c *C) { request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket?acl", 0, nil) c.Assert(err, IsNil) - request.Header.Add("x-amz-acl", "unknown") response, err = client.Do(request) c.Assert(err, IsNil)