/* * 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 cmd import ( "bytes" "fmt" "io" "io/ioutil" "net/http" mux "github.com/gorilla/mux" "github.com/minio/minio/pkg/wildcard" ) // maximum supported access policy size. const maxAccessPolicySize = 20 * 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 []policyStatement) bool { for _, statement := range statements { if bucketPolicyMatchStatement(action, resource, conditions, statement) { if statement.Effect == "Allow" { return true } // Do not uncomment kept here for readability. // 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 policyStatement) 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 policyStatement) bool { for _, policyAction := range statement.Actions { if matched := actionMatch(policyAction, action); matched { return true } } return false } // Match function matches wild cards in 'pattern' for resource. func resourceMatch(pattern, resource string) bool { return wildcard.MatchExtended(pattern, resource) } // Match function matches wild cards in 'pattern' for action. func actionMatch(pattern, action string) bool { return wildcard.Match(pattern, action) } // Verify if given resource matches with policy statement. func bucketPolicyResourceMatch(resource string, statement policyStatement) bool { for _, resourcep := range statement.Resources { // the resource rule for object could contain "*" wild card. // the requested object can be given access based on the already set bucket policy if // the match is successful. // More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html . if matched := resourceMatch(resourcep, resource); matched { return true } } return false } // Verify if given condition matches with policy statement. func bucketPolicyConditionMatch(conditions map[string]string, statement policyStatement) 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 objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bucket := vars["bucket"] switch getRequestAuthType(r) { default: // For all unknown auth types return error. writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path) return case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r); s3Error != ErrNone { writeErrorResponse(w, r, s3Error, r.URL.Path) return } } // 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, ErrMissingContentLength, r.URL.Path) return } // If Content-Length is greater than maximum allowed policy size. if r.ContentLength > maxAccessPolicySize { writeErrorResponse(w, r, ErrEntityTooLarge, 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. policyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize)) if err != nil { errorIf(err, "Unable to read from client.") writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path) return } // Parse bucket policy. var policy = &bucketPolicy{} err = parseBucketPolicy(bytes.NewReader(policyBytes), policy) if err != nil { errorIf(err, "Unable to parse bucket policy.") writeErrorResponse(w, r, ErrInvalidPolicyDocument, r.URL.Path) return } // Parse check bucket policy. if s3Error := checkBucketPolicyResources(bucket, policy); s3Error != ErrNone { writeErrorResponse(w, r, s3Error, r.URL.Path) return } // Save bucket policy. if err = writeBucketPolicy(bucket, api.ObjectAPI, bytes.NewReader(policyBytes), int64(len(policyBytes))); err != nil { errorIf(err, "Unable to write bucket policy.") switch err.(type) { case BucketNameInvalid: writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path) default: writeErrorResponse(w, r, ErrInternalError, r.URL.Path) } return } // Set the bucket policy in memory. globalBucketPolicies.SetBucketPolicy(bucket, policy) // Success. 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 objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bucket := vars["bucket"] switch getRequestAuthType(r) { default: // For all unknown auth types return error. writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path) return case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r); s3Error != ErrNone { writeErrorResponse(w, r, s3Error, r.URL.Path) return } } // Delete bucket access policy. if err := removeBucketPolicy(bucket, api.ObjectAPI); err != nil { errorIf(err, "Unable to remove bucket policy.") switch err.(type) { case BucketNameInvalid: writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path) case BucketPolicyNotFound: writeErrorResponse(w, r, ErrNoSuchBucketPolicy, r.URL.Path) default: writeErrorResponse(w, r, ErrInternalError, r.URL.Path) } return } // Remove bucket policy. globalBucketPolicies.RemoveBucketPolicy(bucket) // Success. writeSuccessNoContent(w) } // GetBucketPolicyHandler - GET Bucket policy // ----------------- // This operation uses the policy // subresource to return the policy of a specified bucket. func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bucket := vars["bucket"] switch getRequestAuthType(r) { default: // For all unknown auth types return error. writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path) return case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r); s3Error != ErrNone { writeErrorResponse(w, r, s3Error, r.URL.Path) return } } // Read bucket access policy. policy, err := readBucketPolicy(bucket, api.ObjectAPI) if err != nil { errorIf(err, "Unable to read bucket policy.") switch err.(type) { case BucketNameInvalid: writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path) case BucketPolicyNotFound: writeErrorResponse(w, r, ErrNoSuchBucketPolicy, r.URL.Path) default: writeErrorResponse(w, r, ErrInternalError, r.URL.Path) } return } // Write to client. fmt.Fprint(w, policy) }