diff --git a/api-resources.go b/api-resources.go index 41a930530..e76963b91 100644 --- a/api-resources.go +++ b/api-resources.go @@ -52,9 +52,3 @@ func getObjectResources(values url.Values) (v fs.ObjectResourcesMetadata) { v.EncodingType = values.Get("encoding-type") return } - -// check if req quere values carry uploads resource -func isRequestUploads(values url.Values) bool { - _, ok := values["uploads"] - return ok -} diff --git a/api-signature.go b/api-signature.go index c94e81f9a..66f171d05 100644 --- a/api-signature.go +++ b/api-signature.go @@ -26,8 +26,8 @@ import ( "strings" "time" - "github.com/minio/minio/pkg/fs" "github.com/minio/minio-xl/pkg/probe" + "github.com/minio/minio/pkg/fs" ) const ( diff --git a/bucket-handlers.go b/bucket-handlers.go index c2ed8d9c7..f5da4f605 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -31,10 +31,23 @@ import ( // ------------------------- // This operation lists in-progress multipart uploads. An in-progress // multipart upload is a multipart upload that has been initiated, -// using the Initiate Multipart Upload request, but has not yet been completed or aborted. -// This operation returns at most 1,000 multipart uploads in the response. +// using the Initiate Multipart Upload request, but has not yet been +// completed or aborted. This operation returns at most 1,000 multipart +// uploads in the response. // func (api API) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } + resources := getBucketMultipartResources(req.URL.Query()) if resources.MaxUploads < 0 { writeErrorResponse(w, req, InvalidMaxUploads, req.URL.Path) @@ -44,9 +57,6 @@ func (api API) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Requ resources.MaxUploads = maxObjectList } - vars := mux.Vars(req) - bucket := vars["bucket"] - resources, err := api.Filesystem.ListMultipartUploads(bucket, resources) if err != nil { errorIf(err.Trace(), "ListMultipartUploads failed.", nil) @@ -74,11 +84,17 @@ func (api API) ListMultipartUploadsHandler(w http.ResponseWriter, req *http.Requ // criteria to return a subset of the objects in a bucket. // func (api API) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { - if isRequestUploads(req.URL.Query()) { - api.ListMultipartUploadsHandler(w, req) - return - } + vars := mux.Vars(req) + bucket := vars["bucket"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } resources := getBucketResources(req.URL.Query()) if resources.Maxkeys < 0 { writeErrorResponse(w, req, InvalidMaxKeys, req.URL.Path) @@ -88,9 +104,6 @@ func (api API) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { resources.Maxkeys = maxObjectList } - vars := mux.Vars(req) - bucket := vars["bucket"] - objects, resources, err := api.Filesystem.ListObjects(bucket, resources) if err == nil { // generate response @@ -122,6 +135,12 @@ func (api API) ListObjectsHandler(w http.ResponseWriter, req *http.Request) { // This implementation of the GET operation returns a list of all buckets // owned by the authenticated sender of the request. func (api API) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } buckets, err := api.Filesystem.ListBuckets() if err == nil { // generate response @@ -141,6 +160,16 @@ func (api API) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { // ---------- // This implementation of the PUT operation creates a new bucket for authenticated request func (api API) PutBucketHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + // read from 'x-amz-acl' aclType := getACLType(req) if aclType == unsupportedACLType { @@ -148,13 +177,10 @@ func (api API) PutBucketHandler(w http.ResponseWriter, req *http.Request) { return } - vars := mux.Vars(req) - bucket := vars["bucket"] - var signature *fs.Signature if !api.Anonymous { - if _, ok := req.Header["Authorization"]; ok { - // Init signature V4 verification + // Init signature V4 verification + if isRequestSignatureV4(req) { var err *probe.Error signature, err = initSignatureV4(req) if err != nil { @@ -295,16 +321,22 @@ func (api API) PostPolicyBucketHandler(w http.ResponseWriter, req *http.Request) // ---------- // This implementation of the PUT operation modifies the bucketACL for authenticated request func (api API) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + // read from 'x-amz-acl' aclType := getACLType(req) if aclType == unsupportedACLType { writeErrorResponse(w, req, NotImplemented, req.URL.Path) return } - - vars := mux.Vars(req) - bucket := vars["bucket"] - err := api.Filesystem.SetBucketMetadata(bucket, map[string]string{"acl": getACLTypeString(aclType)}) if err != nil { errorIf(err.Trace(), "PutBucketACL failed.", nil) @@ -331,6 +363,13 @@ func (api API) GetBucketACLHandler(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) bucket := vars["bucket"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + bucketMetadata, err := api.Filesystem.GetBucketMetadata(bucket) if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) @@ -363,6 +402,15 @@ func (api API) HeadBucketHandler(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) bucket := vars["bucket"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } + _, err := api.Filesystem.GetBucketMetadata(bucket) if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) @@ -384,6 +432,13 @@ func (api API) DeleteBucketHandler(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) bucket := vars["bucket"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + err := api.Filesystem.DeleteBucket(bucket) if err != nil { errorIf(err.Trace(), "DeleteBucket failed.", nil) diff --git a/httprange.go b/httprange.go index d8e98fa48..7d6ddae94 100644 --- a/httprange.go +++ b/httprange.go @@ -22,8 +22,8 @@ import ( "strconv" "strings" - "github.com/minio/minio/pkg/fs" "github.com/minio/minio-xl/pkg/probe" + "github.com/minio/minio/pkg/fs" ) const ( diff --git a/object-handlers.go b/object-handlers.go index a311313c0..081ae9e2c 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -21,8 +21,8 @@ import ( "strconv" "github.com/gorilla/mux" - "github.com/minio/minio/pkg/fs" "github.com/minio/minio-xl/pkg/probe" + "github.com/minio/minio/pkg/fs" ) const ( @@ -39,6 +39,15 @@ func (api API) GetObjectHandler(w http.ResponseWriter, req *http.Request) { bucket = vars["bucket"] object = vars["object"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } + metadata, err := api.Filesystem.GetObjectMetadata(bucket, object) if err != nil { errorIf(err.Trace(), "GetObject failed.", nil) @@ -78,6 +87,15 @@ func (api API) HeadObjectHandler(w http.ResponseWriter, req *http.Request) { bucket = vars["bucket"] object = vars["object"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } + metadata, err := api.Filesystem.GetObjectMetadata(bucket, object) if err != nil { switch err.ToGoError().(type) { @@ -107,6 +125,15 @@ func (api API) PutObjectHandler(w http.ResponseWriter, req *http.Request) { bucket = vars["bucket"] object = vars["object"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } + // get Content-MD5 sent by client and verify if valid md5 := req.Header.Get("Content-MD5") if !isValidMD5(md5) { @@ -136,7 +163,7 @@ func (api API) PutObjectHandler(w http.ResponseWriter, req *http.Request) { var signature *fs.Signature if !api.Anonymous { - if _, ok := req.Header["Authorization"]; ok { + if isRequestSignatureV4(req) { // Init signature V4 verification var err *probe.Error signature, err = initSignatureV4(req) @@ -181,16 +208,19 @@ func (api API) PutObjectHandler(w http.ResponseWriter, req *http.Request) { // NewMultipartUploadHandler - New multipart upload func (api API) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Request) { - if !isRequestUploads(req.URL.Query()) { - writeErrorResponse(w, req, MethodNotAllowed, req.URL.Path) - return - } - var object, bucket string vars := mux.Vars(req) bucket = vars["bucket"] object = vars["object"] + if !api.Anonymous { + // Unauthorized multipart uploads are not supported + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + uploadID, err := api.Filesystem.NewMultipartUpload(bucket, object) if err != nil { errorIf(err.Trace(), "NewMultipartUpload failed.", nil) @@ -219,11 +249,15 @@ func (api API) NewMultipartUploadHandler(w http.ResponseWriter, req *http.Reques // PutObjectPartHandler - Upload part func (api API) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { - // get Content-MD5 sent by client and verify if valid - md5 := req.Header.Get("Content-MD5") - if !isValidMD5(md5) { - writeErrorResponse(w, req, InvalidDigest, req.URL.Path) - return + vars := mux.Vars(req) + bucket := vars["bucket"] + object := vars["object"] + + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } } /// if Content-Length missing, throw away @@ -233,6 +267,13 @@ func (api API) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { return } + // get Content-MD5 sent by client and verify if valid + md5 := req.Header.Get("Content-MD5") + if !isValidMD5(md5) { + writeErrorResponse(w, req, InvalidDigest, req.URL.Path) + return + } + /// maximum Upload size for multipart objects in a single operation if isMaxObjectSize(size) { writeErrorResponse(w, req, EntityTooLarge, req.URL.Path) @@ -249,10 +290,6 @@ func (api API) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { } } - vars := mux.Vars(req) - bucket := vars["bucket"] - object := vars["object"] - uploadID := req.URL.Query().Get("uploadId") partIDString := req.URL.Query().Get("partNumber") @@ -268,7 +305,7 @@ func (api API) PutObjectPartHandler(w http.ResponseWriter, req *http.Request) { var signature *fs.Signature if !api.Anonymous { - if _, ok := req.Header["Authorization"]; ok { + if isRequestSignatureV4(req) { // Init signature V4 verification var err *probe.Error signature, err = initSignatureV4(req) @@ -311,6 +348,13 @@ func (api API) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Requ bucket := vars["bucket"] object := vars["object"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + objectResourcesMetadata := getObjectResources(req.URL.Query()) err := api.Filesystem.AbortMultipartUpload(bucket, object, objectResourcesMetadata.UploadID) @@ -338,6 +382,17 @@ func (api API) AbortMultipartUploadHandler(w http.ResponseWriter, req *http.Requ // ListObjectPartsHandler - List object parts func (api API) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + bucket := vars["bucket"] + object := vars["object"] + + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + objectResourcesMetadata := getObjectResources(req.URL.Query()) if objectResourcesMetadata.PartNumberMarker < 0 { writeErrorResponse(w, req, InvalidPartNumberMarker, req.URL.Path) @@ -351,10 +406,6 @@ func (api API) ListObjectPartsHandler(w http.ResponseWriter, req *http.Request) objectResourcesMetadata.MaxParts = maxPartsList } - vars := mux.Vars(req) - bucket := vars["bucket"] - object := vars["object"] - objectResourcesMetadata, err := api.Filesystem.ListObjectParts(bucket, object, objectResourcesMetadata) if err != nil { errorIf(err.Trace(), "ListObjectParts failed.", nil) @@ -388,11 +439,17 @@ func (api API) CompleteMultipartUploadHandler(w http.ResponseWriter, req *http.R bucket := vars["bucket"] object := vars["object"] - objectResourcesMetadata := getObjectResources(req.URL.Query()) + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + objectResourcesMetadata := getObjectResources(req.URL.Query()) var signature *fs.Signature if !api.Anonymous { - if _, ok := req.Header["Authorization"]; ok { + if isRequestSignatureV4(req) { // Init signature V4 verification var err *probe.Error signature, err = initSignatureV4(req) @@ -449,6 +506,15 @@ func (api API) DeleteObjectHandler(w http.ResponseWriter, req *http.Request) { bucket := vars["bucket"] object := vars["object"] + if !api.Anonymous { + if isRequestRequiresACLCheck(req) { + if api.Filesystem.IsPrivateBucket(bucket) { + writeErrorResponse(w, req, AccessDenied, req.URL.Path) + return + } + } + } + err := api.Filesystem.DeleteObject(bucket, object) if err != nil { errorIf(err.Trace(), "DeleteObject failed.", nil) diff --git a/pkg/fs/acl.go b/pkg/fs/acl.go new file mode 100644 index 000000000..552782c03 --- /dev/null +++ b/pkg/fs/acl.go @@ -0,0 +1,45 @@ +package fs + +import ( + "os" + "path/filepath" +) + +// IsPrivateBucket - is private bucket +func (fs API) IsPrivateBucket(bucket string) bool { + fs.lock.Lock() + defer fs.lock.Unlock() + // get bucket path + bucketDir := filepath.Join(fs.path, bucket) + fi, err := os.Stat(bucketDir) + if err != nil { + return true + } + return permToACL(fi.Mode()).IsPrivate() +} + +// IsPublicBucket - is public bucket +func (fs API) IsPublicBucket(bucket string) bool { + fs.lock.Lock() + defer fs.lock.Unlock() + // get bucket path + bucketDir := filepath.Join(fs.path, bucket) + fi, err := os.Stat(bucketDir) + if err != nil { + return true + } + return permToACL(fi.Mode()).IsPublicReadWrite() +} + +// IsReadOnlyBucket - is read only bucket +func (fs API) IsReadOnlyBucket(bucket string) bool { + fs.lock.Lock() + defer fs.lock.Unlock() + // get bucket path + bucketDir := filepath.Join(fs.path, bucket) + fi, err := os.Stat(bucketDir) + if err != nil { + return true + } + return permToACL(fi.Mode()).IsPublicRead() +} diff --git a/pkg/fs/fs-filter.go b/pkg/fs/fs-filter.go index 4bd72f2d6..66c1e60f2 100644 --- a/pkg/fs/fs-filter.go +++ b/pkg/fs/fs-filter.go @@ -48,12 +48,10 @@ func (fs API) filterObjects(bucket string, content contentInfo, resources Bucket } if metadata.Mode.IsDir() { resources.CommonPrefixes = append(resources.CommonPrefixes, name+resources.Delimiter) - sortUnique(resources.CommonPrefixes) return ObjectMetadata{}, resources, nil } case delimitedName != "": resources.CommonPrefixes = append(resources.CommonPrefixes, resources.Prefix+delimitedName) - sortUnique(resources.CommonPrefixes) } } // Delimiter present and Prefix is absent @@ -72,12 +70,10 @@ func (fs API) filterObjects(bucket string, content contentInfo, resources Bucket } if metadata.Mode.IsDir() { resources.CommonPrefixes = append(resources.CommonPrefixes, name+resources.Delimiter) - sortUnique(resources.CommonPrefixes) return ObjectMetadata{}, resources, nil } case delimitedName != "": resources.CommonPrefixes = append(resources.CommonPrefixes, delimitedName) - sortUnique(resources.CommonPrefixes) } // Delimiter is absent and only Prefix is present case resources.Delimiter == "" && resources.Prefix != "": @@ -94,6 +90,6 @@ func (fs API) filterObjects(bucket string, content contentInfo, resources Bucket return ObjectMetadata{}, resources, err.Trace() } } - + sortUnique(resources.CommonPrefixes) return metadata, resources, nil } diff --git a/pkg/fs/interfaces.go b/pkg/fs/interfaces.go index 8189ab921..3968dcf5e 100644 --- a/pkg/fs/interfaces.go +++ b/pkg/fs/interfaces.go @@ -41,7 +41,11 @@ type CloudStorage interface { CreateObject(bucket, object, md5sum string, size int64, data io.Reader, signature *Signature) (ObjectMetadata, *probe.Error) DeleteObject(bucket, object string) *probe.Error + // Multipart API Multipart + + // ACL API + ACL } // Multipart API @@ -53,3 +57,10 @@ type Multipart interface { ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) ListObjectParts(bucket, object string, objectResources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) } + +// ACL API +type ACL interface { + IsPublicBucket(bucket string) bool + IsPrivateBucket(bucket string) bool + IsReadOnlyBucket(bucket string) bool +} diff --git a/routers.go b/routers.go index ddb350af6..a92069516 100644 --- a/routers.go +++ b/routers.go @@ -27,6 +27,7 @@ import ( func registerAPI(mux *router.Router, a API) { mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET") mux.HandleFunc("/{bucket}", a.GetBucketACLHandler).Queries("acl", "").Methods("GET") + mux.HandleFunc("/{bucket}", a.ListMultipartUploadsHandler).Queries("uploads", "").Methods("GET") mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET") mux.HandleFunc("/{bucket}", a.PutBucketACLHandler).Queries("acl", "").Methods("PUT") mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT") @@ -36,15 +37,12 @@ func registerAPI(mux *router.Router, a API) { mux.HandleFunc("/{bucket}/{object:.*}", a.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}").Methods("PUT") mux.HandleFunc("/{bucket}/{object:.*}", a.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}").Methods("GET") mux.HandleFunc("/{bucket}/{object:.*}", a.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}").Methods("POST") - mux.HandleFunc("/{bucket}/{object:.*}", a.NewMultipartUploadHandler).Methods("POST") + mux.HandleFunc("/{bucket}/{object:.*}", a.NewMultipartUploadHandler).Queries("uploads", "").Methods("POST") mux.HandleFunc("/{bucket}/{object:.*}", a.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}").Methods("DELETE") mux.HandleFunc("/{bucket}/{object:.*}", a.GetObjectHandler).Methods("GET") mux.HandleFunc("/{bucket}/{object:.*}", a.PutObjectHandler).Methods("PUT") - // not implemented yet mux.HandleFunc("/{bucket}", a.DeleteBucketHandler).Methods("DELETE") - - // unsupported API mux.HandleFunc("/{bucket}/{object:.*}", a.DeleteObjectHandler).Methods("DELETE") } diff --git a/signature-handler.go b/signature-handler.go index 8a7fdede7..b809fbdc1 100644 --- a/signature-handler.go +++ b/signature-handler.go @@ -21,9 +21,9 @@ import ( "net/http" "strings" - "github.com/minio/minio/pkg/fs" "github.com/minio/minio-xl/pkg/crypto/sha256" "github.com/minio/minio-xl/pkg/probe" + "github.com/minio/minio/pkg/fs" ) type signatureHandler struct { @@ -58,6 +58,13 @@ func isRequestPostPolicySignatureV4(req *http.Request) bool { return false } +func isRequestRequiresACLCheck(req *http.Request) bool { + if isRequestSignatureV4(req) || isRequestPresignedSignatureV4(req) || isRequestPostPolicySignatureV4(req) { + return false + } + return true +} + func (s signatureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if isRequestPostPolicySignatureV4(r) && r.Method == "POST" { s.handler.ServeHTTP(w, r) @@ -129,5 +136,6 @@ func (s signatureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.handler.ServeHTTP(w, r) return } - writeErrorResponse(w, r, AccessDenied, r.URL.Path) + // call goes up from here, let ACL's verify the validity of the request + s.handler.ServeHTTP(w, r) }