mirror of
https://github.com/minio/minio.git
synced 2024-12-26 07:05:55 -05:00
Merge pull request #1080 from harshavardhana/bucket-policy
accessPolicy: Implement Put, Get, Delete access policy.
This commit is contained in:
commit
5ac4afa4d1
@ -42,7 +42,8 @@ type APIErrorResponse struct {
|
|||||||
|
|
||||||
// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||||
const (
|
const (
|
||||||
AccessDenied = iota
|
None = iota
|
||||||
|
AccessDenied
|
||||||
BadDigest
|
BadDigest
|
||||||
BucketAlreadyExists
|
BucketAlreadyExists
|
||||||
EntityTooSmall
|
EntityTooSmall
|
||||||
@ -60,11 +61,13 @@ const (
|
|||||||
InvalidRequestBody
|
InvalidRequestBody
|
||||||
InvalidCopySource
|
InvalidCopySource
|
||||||
InvalidCopyDest
|
InvalidCopyDest
|
||||||
|
InvalidPolicyDocument
|
||||||
MalformedXML
|
MalformedXML
|
||||||
MissingContentLength
|
MissingContentLength
|
||||||
MissingContentMD5
|
MissingContentMD5
|
||||||
MissingRequestBodyError
|
MissingRequestBodyError
|
||||||
NoSuchBucket
|
NoSuchBucket
|
||||||
|
NoSuchBucketPolicy
|
||||||
NoSuchKey
|
NoSuchKey
|
||||||
NoSuchUpload
|
NoSuchUpload
|
||||||
NotImplemented
|
NotImplemented
|
||||||
@ -80,6 +83,7 @@ const (
|
|||||||
RootPathFull
|
RootPathFull
|
||||||
ObjectExistsAsPrefix
|
ObjectExistsAsPrefix
|
||||||
AllAccessDisabled
|
AllAccessDisabled
|
||||||
|
MalformedPolicy
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError code to Error structure map
|
// APIError code to Error structure map
|
||||||
@ -119,6 +123,11 @@ var errorCodeResponse = map[int]APIError{
|
|||||||
Description: "Argument partNumberMarker must be an integer.",
|
Description: "Argument partNumberMarker must be an integer.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
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: {
|
AccessDenied: {
|
||||||
Code: "AccessDenied",
|
Code: "AccessDenied",
|
||||||
Description: "Access Denied.",
|
Description: "Access Denied.",
|
||||||
@ -199,6 +208,11 @@ var errorCodeResponse = map[int]APIError{
|
|||||||
Description: "The specified bucket does not exist.",
|
Description: "The specified bucket does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
NoSuchBucketPolicy: {
|
||||||
|
Code: "NoSuchBucketPolicy",
|
||||||
|
Description: "The specified bucket does not have a bucket policy.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
NoSuchKey: {
|
NoSuchKey: {
|
||||||
Code: "NoSuchKey",
|
Code: "NoSuchKey",
|
||||||
Description: "The specified key does not exist.",
|
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.",
|
Description: "All access to this bucket has been disabled.",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
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
|
// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown
|
||||||
|
@ -268,43 +268,6 @@ func generateListBucketsResponse(buckets []fs.BucketMetadata) ListBucketsRespons
|
|||||||
return data
|
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.
|
// 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 {
|
func generateListObjectsResponse(bucket, prefix, marker, delimiter string, maxKeys int, resp fs.ListObjectsResult) ListObjectsResponse {
|
||||||
var contents []Object
|
var contents []Object
|
||||||
|
@ -61,41 +61,77 @@ func isRequestPresignedSignatureV4(r *http.Request) bool {
|
|||||||
// Verify if request has AWS Post policy Signature Version '4'.
|
// Verify if request has AWS Post policy Signature Version '4'.
|
||||||
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
||||||
if _, ok := r.Header["Content-Type"]; ok {
|
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 true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request requires ACL check.
|
// Verify if incoming request is anonymous.
|
||||||
func isRequestRequiresACLCheck(r *http.Request) bool {
|
func isRequestAnonymous(r *http.Request) bool {
|
||||||
if isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) {
|
if isRequestJWT(r) || isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
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'.
|
// 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)
|
auth := sign.SetHTTPRequestToVerify(r)
|
||||||
if isRequestSignatureV4(r) {
|
if isRequestSignatureV4(r) {
|
||||||
dummyPayload := sha256.Sum256([]byte(""))
|
dummyPayload := sha256.Sum256([]byte(""))
|
||||||
ok, err := auth.DoesSignatureMatch(hex.EncodeToString(dummyPayload[:]))
|
ok, err := auth.DoesSignatureMatch(hex.EncodeToString(dummyPayload[:]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(), "Signature verification failed.", 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) {
|
} else if isRequestPresignedSignatureV4(r) {
|
||||||
ok, err := auth.DoesPresignedSignatureMatch()
|
ok, err := auth.DoesPresignedSignatureMatch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(), "Presigned signature verification failed.", 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
|
// 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.
|
// handler for validating incoming authorization headers.
|
||||||
func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// Verify if request is presigned, validate signature inside each handlers.
|
switch getRequestAuthType(r) {
|
||||||
if isRequestPresignedSignatureV4(r) {
|
case authTypeAnonymous, authTypePresigned, authTypeSigned, authTypePostPolicy:
|
||||||
|
// Let top level caller validate for anonymous and known
|
||||||
|
// signed requests.
|
||||||
a.handler.ServeHTTP(w, r)
|
a.handler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
case authTypeJWT:
|
||||||
|
// Validate Authorization header if its valid for JWT request.
|
||||||
// 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.
|
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isJWTReqAuthenticated(r) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
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)
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,14 +26,56 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
mux "github.com/gorilla/mux"
|
mux "github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/pkg/crypto/sha256"
|
"github.com/minio/minio/pkg/crypto/sha256"
|
||||||
"github.com/minio/minio/pkg/fs"
|
"github.com/minio/minio/pkg/fs"
|
||||||
"github.com/minio/minio/pkg/probe"
|
"github.com/minio/minio/pkg/probe"
|
||||||
|
"github.com/minio/minio/pkg/s3/access"
|
||||||
"github.com/minio/minio/pkg/s3/signature4"
|
"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.
|
// GetBucketLocationHandler - GET Bucket location.
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// This operation returns 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)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
|
default:
|
||||||
|
// For all unknown auth types return error.
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
case authTypeAnonymous:
|
||||||
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
if isAllowed, s3Error := enforceBucketPolicy("s3:GetBucketLocation", bucket, r.URL); !isAllowed {
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
return
|
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)
|
_, err := api.Filesystem.GetBucketMetadata(bucket)
|
||||||
@ -88,14 +139,23 @@ func (api storageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
|
default:
|
||||||
|
// For all unknown auth types return error.
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
case authTypeAnonymous:
|
||||||
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
if isAllowed, s3Error := enforceBucketPolicy("s3:ListBucketMultipartUploads", bucket, r.URL); !isAllowed {
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
return
|
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())
|
resources := getBucketMultipartResources(r.URL.Query())
|
||||||
@ -137,16 +197,23 @@ func (api storageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) {
|
default:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle encoding type.
|
// 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
|
// This implementation of the GET operation returns a list of all buckets
|
||||||
// owned by the authenticated sender of the request.
|
// owned by the authenticated sender of the request.
|
||||||
func (api storageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.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)
|
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
case authTypeSigned, authTypePresigned:
|
||||||
|
if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match {
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
return
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buckets, err := api.Filesystem.ListBuckets()
|
buckets, err := api.Filesystem.ListBuckets()
|
||||||
@ -220,11 +290,6 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content-Length is required and should be non-zero
|
// Content-Length is required and should be non-zero
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||||
if r.ContentLength <= 0 {
|
if r.ContentLength <= 0 {
|
||||||
@ -253,8 +318,19 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if request is presigned.
|
switch getRequestAuthType(r) {
|
||||||
if isRequestPresignedSignatureV4(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()
|
ok, err := auth.DoesPresignedSignatureMatch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", 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)
|
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if isRequestSignatureV4(r) {
|
case authTypeSigned:
|
||||||
// Check if request is signed.
|
// Check if request is signed.
|
||||||
sha := sha256.New()
|
sha := sha256.New()
|
||||||
mdSh := md5.New()
|
mdSh := md5.New()
|
||||||
@ -356,31 +432,20 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
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.
|
// Set http request for signature.
|
||||||
auth := api.Signature.SetHTTPRequestToVerify(r)
|
auth := api.Signature.SetHTTPRequestToVerify(r)
|
||||||
|
switch getRequestAuthType(r) {
|
||||||
if isRequestPresignedSignatureV4(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()
|
ok, err := auth.DoesPresignedSignatureMatch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", 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)
|
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if isRequestSignatureV4(r) {
|
case authTypeSigned:
|
||||||
// Verify signature for the incoming body if any.
|
// Verify signature for the incoming body if any.
|
||||||
locationBytes, e := ioutil.ReadAll(r.Body)
|
locationBytes, e := ioutil.ReadAll(r.Body)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
@ -414,7 +479,7 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make bucket.
|
// Make bucket.
|
||||||
err := api.Filesystem.MakeBucket(bucket, getACLTypeString(aclType))
|
err := api.Filesystem.MakeBucket(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(), "MakeBucket failed.", nil)
|
errorIf(err.Trace(), "MakeBucket failed.", nil)
|
||||||
switch err.ToGoError().(type) {
|
switch err.ToGoError().(type) {
|
||||||
@ -538,87 +603,6 @@ func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
writeSuccessResponse(w, nil)
|
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
|
// HeadBucketHandler - HEAD Bucket
|
||||||
// ----------
|
// ----------
|
||||||
// This operation is useful to determine if a bucket exists.
|
// 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)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) {
|
default:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := api.Filesystem.GetBucketMetadata(bucket)
|
_, err := api.Filesystem.GetBucketMetadata(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(), "GetBucketMetadata failed.", 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)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
|
default:
|
||||||
|
// For all unknown auth types return error.
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
case authTypeAnonymous:
|
||||||
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
if isAllowed, s3Error := enforceBucketPolicy("s3:DeleteBucket", bucket, r.URL); !isAllowed {
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
return
|
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)
|
err := api.Filesystem.DeleteBucket(bucket)
|
||||||
@ -685,5 +677,10 @@ func (api storageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete bucket access policy, if present - ignore any errors.
|
||||||
|
removeBucketPolicy(bucket)
|
||||||
|
|
||||||
|
// Write success response.
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
283
bucket-policy-handlers.go
Normal file
283
bucket-policy-handlers.go
Normal file
@ -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))
|
||||||
|
}
|
147
bucket-policy.go
Normal file
147
bucket-policy.go
Normal file
@ -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
|
||||||
|
}
|
@ -280,7 +280,7 @@ func ignoreNotImplementedObjectResources(req *http.Request) bool {
|
|||||||
|
|
||||||
// List of not implemented bucket queries
|
// List of not implemented bucket queries
|
||||||
var notimplementedBucketResourceNames = map[string]bool{
|
var notimplementedBucketResourceNames = map[string]bool{
|
||||||
"policy": true,
|
"acl": true,
|
||||||
"cors": true,
|
"cors": true,
|
||||||
"lifecycle": true,
|
"lifecycle": true,
|
||||||
"logging": true,
|
"logging": true,
|
||||||
|
@ -61,16 +61,22 @@ func (api storageAPI) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
object = vars["object"]
|
object = vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) {
|
default:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata, err := api.Filesystem.GetObjectMetadata(bucket, object)
|
metadata, err := api.Filesystem.GetObjectMetadata(bucket, object)
|
||||||
@ -225,15 +231,8 @@ func (api storageAPI) HeadObjectHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
object = vars["object"]
|
object = vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
if match, s3Error := isSignV4ReqAuthenticated(api.Signature, r); !match {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) {
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,14 +285,22 @@ func (api storageAPI) CopyObjectHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
|
default:
|
||||||
|
// For all unknown auth types return error.
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
case authTypeAnonymous:
|
||||||
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
if isAllowed, s3Error := enforceBucketPolicy("s3:GetBucketLocation", bucket, r.URL); !isAllowed {
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||||
return
|
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
|
// 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"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
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
|
// get Content-Md5 sent by client and verify if valid
|
||||||
md5 := r.Header.Get("Content-Md5")
|
md5 := r.Header.Get("Content-Md5")
|
||||||
if !isValidMD5(md5) {
|
if !isValidMD5(md5) {
|
||||||
@ -440,11 +440,23 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Set http request for signature.
|
// Set http request for signature.
|
||||||
auth := api.Signature.SetHTTPRequestToVerify(r)
|
auth := api.Signature.SetHTTPRequestToVerify(r)
|
||||||
|
|
||||||
var metadata fs.ObjectMetadata
|
var metadata fs.ObjectMetadata
|
||||||
var err *probe.Error
|
var err *probe.Error
|
||||||
// For presigned requests verify them right here.
|
switch getRequestAuthType(r) {
|
||||||
if isRequestPresignedSignatureV4(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
|
var ok bool
|
||||||
ok, err = auth.DoesPresignedSignatureMatch()
|
ok, err = auth.DoesPresignedSignatureMatch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -458,7 +470,7 @@ func (api storageAPI) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
// Create presigned object.
|
// Create presigned object.
|
||||||
metadata, err = api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, nil)
|
metadata, err = api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, nil)
|
||||||
} else {
|
case authTypeSigned:
|
||||||
// Create object.
|
// Create object.
|
||||||
metadata, err = api.Filesystem.CreateObject(bucket, object, md5, size, r.Body, &auth)
|
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"]
|
bucket = vars["bucket"]
|
||||||
object = vars["object"]
|
object = vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) {
|
case authTypeAnonymous:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadID, err := api.Filesystem.NewMultipartUpload(bucket, object)
|
uploadID, err := api.Filesystem.NewMultipartUpload(bucket, object)
|
||||||
@ -547,13 +561,6 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
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
|
// get Content-Md5 sent by client and verify if valid
|
||||||
md5 := r.Header.Get("Content-Md5")
|
md5 := r.Header.Get("Content-Md5")
|
||||||
if !isValidMD5(md5) {
|
if !isValidMD5(md5) {
|
||||||
@ -585,11 +592,20 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
// Set http request for signature.
|
// Set http request for signature.
|
||||||
auth := api.Signature.SetHTTPRequestToVerify(r)
|
auth := api.Signature.SetHTTPRequestToVerify(r)
|
||||||
|
|
||||||
var partMD5 string
|
var partMD5 string
|
||||||
var err *probe.Error
|
var err *probe.Error
|
||||||
// For presigned requests verify right here.
|
switch getRequestAuthType(r) {
|
||||||
if isRequestPresignedSignatureV4(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
|
var ok bool
|
||||||
ok, err = auth.DoesPresignedSignatureMatch()
|
ok, err = auth.DoesPresignedSignatureMatch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -602,7 +618,7 @@ func (api storageAPI) PutObjectPartHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, nil)
|
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)
|
partMD5, err = api.Filesystem.CreateObjectPart(bucket, object, uploadID, md5, partID, size, r.Body, &auth)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -637,16 +653,18 @@ func (api storageAPI) AbortMultipartUploadHandler(w http.ResponseWriter, r *http
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) {
|
case authTypeAnonymous:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
objectResourcesMetadata := getObjectResources(r.URL.Query())
|
objectResourcesMetadata := getObjectResources(r.URL.Query())
|
||||||
@ -678,16 +696,18 @@ func (api storageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) {
|
case authTypeAnonymous:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
objectResourcesMetadata := getObjectResources(r.URL.Query())
|
objectResourcesMetadata := getObjectResources(r.URL.Query())
|
||||||
@ -724,9 +744,9 @@ func (api storageAPI) ListObjectPartsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
response := generateListPartsResponse(objectResourcesMetadata)
|
response := generateListPartsResponse(objectResourcesMetadata)
|
||||||
encodedSuccessResponse := encodeResponse(response)
|
encodedSuccessResponse := encodeResponse(response)
|
||||||
// write headers.
|
// Write headers.
|
||||||
setCommonHeaders(w)
|
setCommonHeaders(w)
|
||||||
// write success response.
|
// Write success response.
|
||||||
writeSuccessResponse(w, encodedSuccessResponse)
|
writeSuccessResponse(w, encodedSuccessResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,13 +756,6 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
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.
|
// Extract object resources.
|
||||||
objectResourcesMetadata := getObjectResources(r.URL.Query())
|
objectResourcesMetadata := getObjectResources(r.URL.Query())
|
||||||
|
|
||||||
@ -751,8 +764,21 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
var metadata fs.ObjectMetadata
|
var metadata fs.ObjectMetadata
|
||||||
var err *probe.Error
|
var err *probe.Error
|
||||||
// For presigned requests verify right here.
|
switch getRequestAuthType(r) {
|
||||||
if isRequestPresignedSignatureV4(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
|
var ok bool
|
||||||
ok, err = auth.DoesPresignedSignatureMatch()
|
ok, err = auth.DoesPresignedSignatureMatch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -766,7 +792,7 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h
|
|||||||
}
|
}
|
||||||
// Complete multipart upload presigned.
|
// Complete multipart upload presigned.
|
||||||
metadata, err = api.Filesystem.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, r.Body, nil)
|
metadata, err = api.Filesystem.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, r.Body, nil)
|
||||||
} else {
|
case authTypeSigned:
|
||||||
// Complete multipart upload.
|
// Complete multipart upload.
|
||||||
metadata, err = api.Filesystem.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, r.Body, &auth)
|
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"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
|
|
||||||
if isRequestRequiresACLCheck(r) {
|
switch getRequestAuthType(r) {
|
||||||
if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) {
|
default:
|
||||||
writeErrorResponse(w, r, AccessDenied, r.URL.Path)
|
// 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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isSignV4ReqAuthenticated(api.Signature, r) {
|
|
||||||
writeErrorResponse(w, r, SignatureDoesNotMatch, r.URL.Path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := api.Filesystem.DeleteObject(bucket, object)
|
err := api.Filesystem.DeleteObject(bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorIf(err.Trace(), "DeleteObject failed.", nil)
|
errorIf(err.Trace(), "DeleteObject failed.", nil)
|
||||||
|
@ -38,7 +38,6 @@ func APITestSuite(c *check.C, create func() Filesystem) {
|
|||||||
testPaging(c, create)
|
testPaging(c, create)
|
||||||
testObjectOverwriteWorks(c, create)
|
testObjectOverwriteWorks(c, create)
|
||||||
testNonExistantBucketOperations(c, create)
|
testNonExistantBucketOperations(c, create)
|
||||||
testBucketMetadata(c, create)
|
|
||||||
testBucketRecreateFails(c, create)
|
testBucketRecreateFails(c, create)
|
||||||
testPutObjectInSubdir(c, create)
|
testPutObjectInSubdir(c, create)
|
||||||
testListBuckets(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) {
|
func testMakeBucket(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultipartObjectCreation(c *check.C, create func() Filesystem) {
|
func testMultipartObjectCreation(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
||||||
c.Assert(err, check.IsNil)
|
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) {
|
func testMultipartObjectAbort(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
||||||
c.Assert(err, check.IsNil)
|
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) {
|
func testMultipleObjectCreation(c *check.C, create func() Filesystem) {
|
||||||
objects := make(map[string][]byte)
|
objects := make(map[string][]byte)
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
randomPerm := rand.Perm(10)
|
randomPerm := rand.Perm(10)
|
||||||
@ -161,7 +160,7 @@ func testMultipleObjectCreation(c *check.C, create func() Filesystem) {
|
|||||||
|
|
||||||
func testPaging(c *check.C, create func() Filesystem) {
|
func testPaging(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
fs.MakeBucket("bucket", "")
|
fs.MakeBucket("bucket")
|
||||||
result, err := fs.ListObjects("bucket", "", "", "", 0)
|
result, err := fs.ListObjects("bucket", "", "", "", 0)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(result.Objects), check.Equals, 0)
|
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) {
|
func testObjectOverwriteWorks(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
hasher1 := md5.New()
|
hasher1 := md5.New()
|
||||||
@ -292,27 +291,17 @@ func testNonExistantBucketOperations(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(err, check.Not(check.IsNil))
|
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) {
|
func testBucketRecreateFails(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("string", "")
|
err := fs.MakeBucket("string")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = fs.MakeBucket("string", "")
|
err = fs.MakeBucket("string")
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPutObjectInSubdir(c *check.C, create func() Filesystem) {
|
func testPutObjectInSubdir(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
hasher := md5.New()
|
hasher := md5.New()
|
||||||
@ -339,7 +328,7 @@ func testListBuckets(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(len(buckets), check.Equals, 0)
|
c.Assert(len(buckets), check.Equals, 0)
|
||||||
|
|
||||||
// add one and test exists
|
// add one and test exists
|
||||||
err = fs.MakeBucket("bucket1", "")
|
err = fs.MakeBucket("bucket1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
buckets, err = fs.ListBuckets()
|
buckets, err = fs.ListBuckets()
|
||||||
@ -347,7 +336,7 @@ func testListBuckets(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// add two and test exists
|
// add two and test exists
|
||||||
err = fs.MakeBucket("bucket2", "")
|
err = fs.MakeBucket("bucket2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
buckets, err = fs.ListBuckets()
|
buckets, err = fs.ListBuckets()
|
||||||
@ -355,7 +344,7 @@ func testListBuckets(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// add three and test exists + prefix
|
// add three and test exists + prefix
|
||||||
err = fs.MakeBucket("bucket22", "")
|
err = fs.MakeBucket("bucket22")
|
||||||
|
|
||||||
buckets, err = fs.ListBuckets()
|
buckets, err = fs.ListBuckets()
|
||||||
c.Assert(len(buckets), check.Equals, 3)
|
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++ {
|
for i := 0; i < 10; i++ {
|
||||||
fs := create()
|
fs := create()
|
||||||
// add one and test exists
|
// add one and test exists
|
||||||
err := fs.MakeBucket("bucket1", "")
|
err := fs.MakeBucket("bucket1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = fs.MakeBucket("bucket2", "")
|
err = fs.MakeBucket("bucket2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
buckets, err := fs.ListBuckets()
|
buckets, err := fs.ListBuckets()
|
||||||
c.Assert(err, check.IsNil)
|
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) {
|
func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var byteBuffer bytes.Buffer
|
var byteBuffer bytes.Buffer
|
||||||
@ -408,7 +397,7 @@ func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) {
|
|||||||
|
|
||||||
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) {
|
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = fs.CreateObject("bucket", "dir1/dir2/object", "", int64(len("hello world")), bytes.NewBufferString("hello world"), nil)
|
_, 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) {
|
func testDefaultContentType(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// test empty
|
// test empty
|
||||||
@ -455,7 +444,7 @@ func testDefaultContentType(c *check.C, create func() Filesystem) {
|
|||||||
|
|
||||||
func testContentMD5Set(c *check.C, create func() Filesystem) {
|
func testContentMD5Set(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// test md5 invalid
|
// test md5 invalid
|
||||||
|
@ -37,7 +37,6 @@ func APITestSuite(c *check.C, create func() Filesystem) {
|
|||||||
testPaging(c, create)
|
testPaging(c, create)
|
||||||
testObjectOverwriteWorks(c, create)
|
testObjectOverwriteWorks(c, create)
|
||||||
testNonExistantBucketOperations(c, create)
|
testNonExistantBucketOperations(c, create)
|
||||||
testBucketMetadata(c, create)
|
|
||||||
testBucketRecreateFails(c, create)
|
testBucketRecreateFails(c, create)
|
||||||
testPutObjectInSubdir(c, create)
|
testPutObjectInSubdir(c, create)
|
||||||
testListBuckets(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) {
|
func testMakeBucket(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultipartObjectCreation(c *check.C, create func() Filesystem) {
|
func testMultipartObjectCreation(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
||||||
c.Assert(err, check.IsNil)
|
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) {
|
func testMultipartObjectAbort(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
uploadID, err := fs.NewMultipartUpload("bucket", "key")
|
||||||
c.Assert(err, check.IsNil)
|
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) {
|
func testMultipleObjectCreation(c *check.C, create func() Filesystem) {
|
||||||
objects := make(map[string][]byte)
|
objects := make(map[string][]byte)
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
randomPerm := rand.Perm(10)
|
randomPerm := rand.Perm(10)
|
||||||
@ -160,7 +159,7 @@ func testMultipleObjectCreation(c *check.C, create func() Filesystem) {
|
|||||||
|
|
||||||
func testPaging(c *check.C, create func() Filesystem) {
|
func testPaging(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
fs.MakeBucket("bucket", "")
|
fs.MakeBucket("bucket")
|
||||||
result, err := fs.ListObjects("bucket", "", "", "", 0)
|
result, err := fs.ListObjects("bucket", "", "", "", 0)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(result.Objects), check.Equals, 0)
|
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) {
|
func testObjectOverwriteWorks(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
fs.MakeBucket("bucket", "")
|
fs.MakeBucket("bucket")
|
||||||
|
|
||||||
hasher1 := md5.New()
|
hasher1 := md5.New()
|
||||||
hasher1.Write([]byte("one"))
|
hasher1.Write([]byte("one"))
|
||||||
@ -289,27 +288,17 @@ func testNonExistantBucketOperations(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(err, check.Not(check.IsNil))
|
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) {
|
func testBucketRecreateFails(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("string", "private")
|
err := fs.MakeBucket("string")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = fs.MakeBucket("string", "private")
|
err = fs.MakeBucket("string")
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPutObjectInSubdir(c *check.C, create func() Filesystem) {
|
func testPutObjectInSubdir(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "private")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
hasher := md5.New()
|
hasher := md5.New()
|
||||||
@ -336,7 +325,7 @@ func testListBuckets(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(len(buckets), check.Equals, 0)
|
c.Assert(len(buckets), check.Equals, 0)
|
||||||
|
|
||||||
// add one and test exists
|
// add one and test exists
|
||||||
err = fs.MakeBucket("bucket1", "")
|
err = fs.MakeBucket("bucket1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
buckets, err = fs.ListBuckets()
|
buckets, err = fs.ListBuckets()
|
||||||
@ -344,7 +333,7 @@ func testListBuckets(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// add two and test exists
|
// add two and test exists
|
||||||
err = fs.MakeBucket("bucket2", "")
|
err = fs.MakeBucket("bucket2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
buckets, err = fs.ListBuckets()
|
buckets, err = fs.ListBuckets()
|
||||||
@ -352,7 +341,7 @@ func testListBuckets(c *check.C, create func() Filesystem) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// add three and test exists + prefix
|
// add three and test exists + prefix
|
||||||
err = fs.MakeBucket("bucket22", "")
|
err = fs.MakeBucket("bucket22")
|
||||||
|
|
||||||
buckets, err = fs.ListBuckets()
|
buckets, err = fs.ListBuckets()
|
||||||
c.Assert(len(buckets), check.Equals, 3)
|
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++ {
|
for i := 0; i < 10; i++ {
|
||||||
fs := create()
|
fs := create()
|
||||||
// add one and test exists
|
// add one and test exists
|
||||||
err := fs.MakeBucket("bucket1", "")
|
err := fs.MakeBucket("bucket1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = fs.MakeBucket("bucket2", "")
|
err = fs.MakeBucket("bucket2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
buckets, err := fs.ListBuckets()
|
buckets, err := fs.ListBuckets()
|
||||||
c.Assert(err, check.IsNil)
|
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) {
|
func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var byteBuffer bytes.Buffer
|
var byteBuffer bytes.Buffer
|
||||||
@ -409,7 +398,7 @@ func testNonExistantObjectInBucket(c *check.C, create func() Filesystem) {
|
|||||||
|
|
||||||
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) {
|
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = fs.CreateObject("bucket", "dir1/dir2/object", "", int64(len("hello world")), bytes.NewBufferString("hello world"), nil)
|
_, 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) {
|
func testDefaultContentType(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// test empty
|
// test empty
|
||||||
@ -456,7 +445,7 @@ func testDefaultContentType(c *check.C, create func() Filesystem) {
|
|||||||
|
|
||||||
func testContentMD5Set(c *check.C, create func() Filesystem) {
|
func testContentMD5Set(c *check.C, create func() Filesystem) {
|
||||||
fs := create()
|
fs := create()
|
||||||
err := fs.MakeBucket("bucket", "")
|
err := fs.MakeBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// test md5 invalid
|
// test md5 invalid
|
||||||
|
@ -21,12 +21,7 @@ import (
|
|||||||
"github.com/minio/minio/pkg/quick"
|
"github.com/minio/minio/pkg/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
var multipartsMetadataPath, bucketsMetadataPath string
|
var multipartsMetadataPath string
|
||||||
|
|
||||||
// setFSBucketsMetadataPath - set fs buckets metadata path.
|
|
||||||
func setFSBucketsMetadataPath(metadataPath string) {
|
|
||||||
bucketsMetadataPath = metadataPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFSMultipartsMetadataPath - set custom multiparts session
|
// SetFSMultipartsMetadataPath - set custom multiparts session
|
||||||
// metadata path.
|
// metadata path.
|
||||||
@ -46,18 +41,6 @@ func saveMultipartsSession(multiparts Multiparts) *probe.Error {
|
|||||||
return nil
|
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
|
// loadMultipartsSession load multipart session file
|
||||||
func loadMultipartsSession() (*Multiparts, *probe.Error) {
|
func loadMultipartsSession() (*Multiparts, *probe.Error) {
|
||||||
multiparts := &Multiparts{}
|
multiparts := &Multiparts{}
|
||||||
@ -72,18 +55,3 @@ func loadMultipartsSession() (*Multiparts, *probe.Error) {
|
|||||||
}
|
}
|
||||||
return qc.Data().(*Multiparts), nil
|
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
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -52,15 +52,6 @@ func (fs Filesystem) DeleteBucket(bucket string) *probe.Error {
|
|||||||
}
|
}
|
||||||
return probe.NewError(e)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,8 +103,8 @@ func removeDuplicateBuckets(buckets []BucketMetadata) []BucketMetadata {
|
|||||||
return buckets
|
return buckets
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeBucket - PUT Bucket.
|
// MakeBucket - PUT Bucket
|
||||||
func (fs Filesystem) MakeBucket(bucket, acl string) *probe.Error {
|
func (fs Filesystem) MakeBucket(bucket string) *probe.Error {
|
||||||
di, err := disk.GetInfo(fs.path)
|
di, err := disk.GetInfo(fs.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return probe.NewError(err)
|
return probe.NewError(err)
|
||||||
@ -131,12 +122,6 @@ func (fs Filesystem) MakeBucket(bucket, acl string) *probe.Error {
|
|||||||
return probe.NewError(BucketNameInvalid{Bucket: bucket})
|
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)
|
bucket = fs.denormalizeBucket(bucket)
|
||||||
bucketDir := filepath.Join(fs.path, bucket)
|
bucketDir := filepath.Join(fs.path, bucket)
|
||||||
if _, e := os.Stat(bucketDir); e == nil {
|
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 {
|
if e := os.Mkdir(bucketDir, 0700); e != nil {
|
||||||
return probe.NewError(err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +158,6 @@ func (fs Filesystem) GetBucketMetadata(bucket string) (BucketMetadata, *probe.Er
|
|||||||
if !IsValidBucketName(bucket) {
|
if !IsValidBucketName(bucket) {
|
||||||
return BucketMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
return BucketMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket = fs.denormalizeBucket(bucket)
|
bucket = fs.denormalizeBucket(bucket)
|
||||||
// Get bucket path.
|
// Get bucket path.
|
||||||
bucketDir := filepath.Join(fs.path, bucket)
|
bucketDir := filepath.Join(fs.path, bucket)
|
||||||
@ -212,44 +169,8 @@ func (fs Filesystem) GetBucketMetadata(bucket string) (BucketMetadata, *probe.Er
|
|||||||
}
|
}
|
||||||
return BucketMetadata{}, probe.NewError(e)
|
return BucketMetadata{}, probe.NewError(e)
|
||||||
}
|
}
|
||||||
fs.rwLock.RLock()
|
bucketMetadata := BucketMetadata{}
|
||||||
bucketMetadata, ok := fs.buckets.Metadata[bucket]
|
bucketMetadata.Name = fi.Name()
|
||||||
fs.rwLock.RUnlock()
|
bucketMetadata.Created = fi.ModTime()
|
||||||
// If metadata value is not found, get it from disk.
|
return bucketMetadata, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func BenchmarkDeleteBucket(b *testing.B) {
|
|||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
|
||||||
// Create and delete the bucket over and over.
|
// Create and delete the bucket over and over.
|
||||||
err = filesystem.MakeBucket("bucket", "public-read-write")
|
err = filesystem.MakeBucket("bucket")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ func BenchmarkGetBucketMetadata(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Put up a bucket with some metadata.
|
// Put up a bucket with some metadata.
|
||||||
err = filesystem.MakeBucket("bucket", "public-read-write")
|
err = filesystem.MakeBucket("bucket")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
type BucketMetadata struct {
|
type BucketMetadata struct {
|
||||||
Name string
|
Name string
|
||||||
Created time.Time
|
Created time.Time
|
||||||
ACL BucketACL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectMetadata - object key and its relevant metadata
|
// ObjectMetadata - object key and its relevant metadata
|
||||||
|
@ -163,6 +163,13 @@ type GenericBucketError struct {
|
|||||||
Bucket string
|
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
|
// GenericObjectError - generic object error
|
||||||
type GenericObjectError struct {
|
type GenericObjectError struct {
|
||||||
Bucket string
|
Bucket string
|
||||||
@ -183,17 +190,6 @@ type DigestError struct {
|
|||||||
MD5 string
|
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
|
/// Bucket related errors
|
||||||
|
|
||||||
// BucketNameInvalid - bucketname provided is invalid
|
// BucketNameInvalid - bucketname provided is invalid
|
||||||
|
25
pkg/fs/fs.go
25
pkg/fs/fs.go
@ -31,17 +31,10 @@ type Filesystem struct {
|
|||||||
minFreeDisk int64
|
minFreeDisk int64
|
||||||
rwLock *sync.RWMutex
|
rwLock *sync.RWMutex
|
||||||
multiparts *Multiparts
|
multiparts *Multiparts
|
||||||
buckets *Buckets
|
|
||||||
listServiceReqCh chan<- listServiceReq
|
listServiceReqCh chan<- listServiceReq
|
||||||
timeoutReqCh chan<- uint32
|
timeoutReqCh chan<- uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buckets holds acl information
|
|
||||||
type Buckets struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Metadata map[string]*BucketMetadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultipartSession holds active session information
|
// MultipartSession holds active session information
|
||||||
type MultipartSession struct {
|
type MultipartSession struct {
|
||||||
TotalParts int
|
TotalParts int
|
||||||
@ -59,7 +52,6 @@ type Multiparts struct {
|
|||||||
|
|
||||||
// New instantiate a new donut
|
// New instantiate a new donut
|
||||||
func New(rootPath string, minFreeDisk int64) (Filesystem, *probe.Error) {
|
func New(rootPath string, minFreeDisk int64) (Filesystem, *probe.Error) {
|
||||||
setFSBucketsMetadataPath(filepath.Join(rootPath, "$buckets.json"))
|
|
||||||
setFSMultipartsMetadataPath(filepath.Join(rootPath, "$multiparts-session.json"))
|
setFSMultipartsMetadataPath(filepath.Join(rootPath, "$multiparts-session.json"))
|
||||||
|
|
||||||
var err *probe.Error
|
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{
|
fs := Filesystem{
|
||||||
rwLock: &sync.RWMutex{},
|
rwLock: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
fs.path = rootPath
|
fs.path = rootPath
|
||||||
fs.multiparts = multiparts
|
fs.multiparts = multiparts
|
||||||
fs.buckets = buckets
|
|
||||||
/// Defaults
|
/// Defaults
|
||||||
|
|
||||||
// minium free disk required for i/o operations to succeed.
|
// minium free disk required for i/o operations to succeed.
|
||||||
|
33
pkg/s3/access/README.md
Normal file
33
pkg/s3/access/README.md
Normal file
@ -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
|
230
pkg/s3/access/policy.go
Normal file
230
pkg/s3/access/policy.go
Normal file
@ -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
|
||||||
|
}
|
12
routers.go
12
routers.go
@ -137,14 +137,14 @@ func registerAPIHandlers(mux *router.Router, a storageAPI, w *webAPI) {
|
|||||||
|
|
||||||
// GetBucketLocation
|
// GetBucketLocation
|
||||||
bucket.Methods("GET").HandlerFunc(a.GetBucketLocationHandler).Queries("location", "")
|
bucket.Methods("GET").HandlerFunc(a.GetBucketLocationHandler).Queries("location", "")
|
||||||
// GetBucketACL
|
// GetBucketPolicy
|
||||||
bucket.Methods("GET").HandlerFunc(a.GetBucketACLHandler).Queries("acl", "")
|
bucket.Methods("GET").HandlerFunc(a.GetBucketPolicyHandler).Queries("policy", "")
|
||||||
// ListMultipartUploads
|
// ListMultipartUploads
|
||||||
bucket.Methods("GET").HandlerFunc(a.ListMultipartUploadsHandler).Queries("uploads", "")
|
bucket.Methods("GET").HandlerFunc(a.ListMultipartUploadsHandler).Queries("uploads", "")
|
||||||
// ListObjects
|
// ListObjects
|
||||||
bucket.Methods("GET").HandlerFunc(a.ListObjectsHandler)
|
bucket.Methods("GET").HandlerFunc(a.ListObjectsHandler)
|
||||||
// PutBucketACL
|
// PutBucketPolicy
|
||||||
bucket.Methods("PUT").HandlerFunc(a.PutBucketACLHandler).Queries("acl", "")
|
bucket.Methods("PUT").HandlerFunc(a.PutBucketPolicyHandler).Queries("policy", "")
|
||||||
// PutBucket
|
// PutBucket
|
||||||
bucket.Methods("PUT").HandlerFunc(a.PutBucketHandler)
|
bucket.Methods("PUT").HandlerFunc(a.PutBucketHandler)
|
||||||
// HeadBucket
|
// HeadBucket
|
||||||
@ -152,7 +152,9 @@ func registerAPIHandlers(mux *router.Router, a storageAPI, w *webAPI) {
|
|||||||
// DeleteMultipleObjects
|
// DeleteMultipleObjects
|
||||||
bucket.Methods("POST").HandlerFunc(a.DeleteMultipleObjectsHandler)
|
bucket.Methods("POST").HandlerFunc(a.DeleteMultipleObjectsHandler)
|
||||||
// PostPolicy
|
// 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
|
// DeleteBucket
|
||||||
bucket.Methods("DELETE").HandlerFunc(a.DeleteBucketHandler)
|
bucket.Methods("DELETE").HandlerFunc(a.DeleteBucketHandler)
|
||||||
|
|
||||||
|
@ -154,8 +154,8 @@ func createConfigPath() *probe.Error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Trace()
|
return err.Trace()
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(configPath, 0700); err != nil {
|
if e := os.MkdirAll(configPath, 0700); e != nil {
|
||||||
return probe.NewError(err)
|
return probe.NewError(e)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -187,6 +187,9 @@ func getConfig() (*configV2, *probe.Error) {
|
|||||||
if err := createConfigPath(); err != nil {
|
if err := createConfigPath(); err != nil {
|
||||||
return nil, err.Trace()
|
return nil, err.Trace()
|
||||||
}
|
}
|
||||||
|
if err := createBucketsConfigPath(); err != nil {
|
||||||
|
return nil, err.Trace()
|
||||||
|
}
|
||||||
config, err := loadConfigV2()
|
config, err := loadConfigV2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err.ToGoError()) {
|
if os.IsNotExist(err.ToGoError()) {
|
||||||
|
@ -524,7 +524,6 @@ func (s *MyAPIFSCacheSuite) TestHeader(c *C) {
|
|||||||
func (s *MyAPIFSCacheSuite) TestPutBucket(c *C) {
|
func (s *MyAPIFSCacheSuite) TestPutBucket(c *C) {
|
||||||
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-bucket", 0, nil)
|
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-bucket", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
response, err := client.Do(request)
|
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)
|
request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-bucket-slash/", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client = http.Client{}
|
client = http.Client{}
|
||||||
response, err = client.Do(request)
|
response, err = client.Do(request)
|
||||||
@ -582,7 +580,6 @@ func (s *MyAPIFSCacheSuite) TestCopyObject(c *C) {
|
|||||||
func (s *MyAPIFSCacheSuite) TestPutObject(c *C) {
|
func (s *MyAPIFSCacheSuite) TestPutObject(c *C) {
|
||||||
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-object", 0, nil)
|
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/put-object", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
response, err := client.Do(request)
|
response, err := client.Do(request)
|
||||||
@ -627,7 +624,6 @@ func (s *MyAPIFSCacheSuite) TestNotBeAbleToCreateObjectInNonexistantBucket(c *C)
|
|||||||
func (s *MyAPIFSCacheSuite) TestHeadOnObject(c *C) {
|
func (s *MyAPIFSCacheSuite) TestHeadOnObject(c *C) {
|
||||||
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/headonobject", 0, nil)
|
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/headonobject", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
response, err := client.Do(request)
|
response, err := client.Do(request)
|
||||||
@ -671,7 +667,6 @@ func (s *MyAPIFSCacheSuite) TestHeadOnObject(c *C) {
|
|||||||
func (s *MyAPIFSCacheSuite) TestHeadOnBucket(c *C) {
|
func (s *MyAPIFSCacheSuite) TestHeadOnBucket(c *C) {
|
||||||
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/headonbucket", 0, nil)
|
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/headonbucket", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
response, err := client.Do(request)
|
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)
|
request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/objecthandlererrors", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client = http.Client{}
|
client = http.Client{}
|
||||||
response, err = client.Do(request)
|
response, err = client.Do(request)
|
||||||
@ -855,7 +849,6 @@ func (s *MyAPIFSCacheSuite) TestListObjectsHandlerErrors(c *C) {
|
|||||||
func (s *MyAPIFSCacheSuite) TestPutBucketErrors(c *C) {
|
func (s *MyAPIFSCacheSuite) TestPutBucketErrors(c *C) {
|
||||||
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket-.", 0, nil)
|
request, err := s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket-.", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
response, err := client.Do(request)
|
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)
|
request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
client = http.Client{}
|
client = http.Client{}
|
||||||
response, err = client.Do(request)
|
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)
|
request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "private")
|
|
||||||
|
|
||||||
response, err = client.Do(request)
|
response, err = client.Do(request)
|
||||||
c.Assert(err, IsNil)
|
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)
|
request, err = s.newRequest("PUT", testAPIFSCacheServer.URL+"/putbucket?acl", 0, nil)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
request.Header.Add("x-amz-acl", "unknown")
|
|
||||||
|
|
||||||
response, err = client.Do(request)
|
response, err = client.Do(request)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
Loading…
Reference in New Issue
Block a user