Enhance policy handling to support SSE and WORM (#5790)

- remove old bucket policy handling
- add new policy handling
- add new policy handling unit tests

This patch brings support to bucket policy to have more control not
limiting to anonymous.  Bucket owner controls to allow/deny any rest
API.

For example server side encryption can be controlled by allowing
PUT/GET objects with encryptions including bucket owner.
This commit is contained in:
Bala FA
2018-04-25 04:23:30 +05:30
committed by kannappanr
parent 21a3c0f482
commit 0d52126023
77 changed files with 9811 additions and 2633 deletions

View File

@@ -181,6 +181,9 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) {
return nil, err
}
// Create new policy system.
globalPolicySys = NewPolicySys()
// Setup admin mgmt REST API handlers.
adminRouter := mux.NewRouter()
registerAdminRouter(adminRouter)

View File

@@ -953,8 +953,6 @@ func toAPIErrorCode(err error) (apiErr APIErrorCode) {
apiErr = ErrEntityTooSmall
case NotImplemented:
apiErr = ErrNotImplemented
case PolicyNotFound:
apiErr = ErrNoSuchBucketPolicy
case PartTooBig:
apiErr = ErrEntityTooLarge
case UnsupportedMetadata:

View File

@@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
* Minio Cloud Storage, (C) 2015-2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import (
"strings"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/policy"
)
// Verify if request has JWT.
@@ -123,28 +123,84 @@ func checkAdminRequestAuthType(r *http.Request, region string) APIErrorCode {
return s3Err
}
func checkRequestAuthType(ctx context.Context, r *http.Request, bucket, policyAction, region string) APIErrorCode {
reqAuthType := getRequestAuthType(r)
func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) APIErrorCode {
isOwner := true
accountName := globalServerConfig.GetCredential().AccessKey
switch reqAuthType {
switch getRequestAuthType(r) {
case authTypeUnknown:
return ErrAccessDenied
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
return isReqAuthenticatedV2(r)
case authTypeSigned, authTypePresigned:
return isReqAuthenticated(r, region)
}
if reqAuthType == authTypeAnonymous && policyAction != "" {
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
resource, err := getResource(r.URL.Path, r.Host, globalDomainName)
if err != nil {
return ErrInternalError
if errorCode := isReqAuthenticatedV2(r); errorCode != ErrNone {
return errorCode
}
return enforceBucketPolicy(ctx, bucket, policyAction, resource,
r.Referer(), handlers.GetSourceIP(r), r.URL.Query())
case authTypeSigned, authTypePresigned:
region := globalServerConfig.GetRegion()
switch action {
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
region = ""
}
if errorCode := isReqAuthenticated(r, region); errorCode != ErrNone {
return errorCode
}
default:
isOwner = false
accountName = ""
}
// LocationConstraint is valid only for CreateBucketAction.
var locationConstraint string
if action == policy.CreateBucketAction {
// To extract region from XML in request body, get copy of request body.
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
logger.LogIf(ctx, err)
return ErrAccessDenied
}
// Populate payload to extract location constraint.
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
var s3Error APIErrorCode
locationConstraint, s3Error = parseLocationConstraint(r)
if s3Error != ErrNone {
return ErrAccessDenied
}
// Populate payload again to handle it in HTTP handler.
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
}
if globalPolicySys.IsAllowed(policy.Args{
AccountName: accountName,
Action: action,
BucketName: bucketName,
ConditionValues: getConditionValues(r, locationConstraint),
IsOwner: isOwner,
ObjectName: objectName,
}) {
return ErrNone
}
// As policy.ListBucketAction and policy.ListObjectsAction are same but different names,
// policy.ListBucketAction is used across the code but user may used policy.ListObjectsAction
// in bucket policy to denote the same. In below try again with policy.ListObjectsAction.
if action != policy.ListBucketAction {
return ErrAccessDenied
}
if globalPolicySys.IsAllowed(policy.Args{
AccountName: accountName,
Action: policy.ListObjectsAction,
BucketName: bucketName,
ConditionValues: getConditionValues(r, locationConstraint),
IsOwner: isOwner,
ObjectName: objectName,
}) {
return ErrNone
}
// By default return ErrAccessDenied
return ErrAccessDenied
}

View File

@@ -20,6 +20,7 @@ import (
"net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/pkg/policy"
)
// Validate all the ListObjects query arguments, returns an APIErrorCode
@@ -64,7 +65,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:ListBucket", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -134,7 +135,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:ListBucket", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}

View File

@@ -17,7 +17,6 @@
package cmd
import (
"context"
"encoding/base64"
"encoding/xml"
"io"
@@ -26,86 +25,16 @@ import (
"net/url"
"path"
"path/filepath"
"reflect"
"strings"
"sync"
"github.com/gorilla/mux"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
)
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
// Enforces bucket policies for a bucket for a given tatusaction.
func enforceBucketPolicy(ctx context.Context, bucket, action, resource, referer, sourceIP string, queryParams url.Values) (s3Error APIErrorCode) {
// Verify if bucket actually exists
objAPI := newObjectLayerFn()
if err := checkBucketExist(ctx, bucket, objAPI); err != nil {
switch err.(type) {
case BucketNameInvalid:
// Return error for invalid bucket name.
return ErrInvalidBucketName
case BucketNotFound:
// For no bucket found we return NoSuchBucket instead.
return ErrNoSuchBucket
}
// Return internal error for any other errors so that we can investigate.
return ErrInternalError
}
// Fetch bucket policy, if policy is not set return access denied.
p, err := objAPI.GetBucketPolicy(ctx, bucket)
if err != nil {
return ErrAccessDenied
}
if reflect.DeepEqual(p, emptyBucketPolicy) {
return ErrAccessDenied
}
// Construct resource in 'arn:aws:s3:::examplebucket/object' format.
arn := bucketARNPrefix + strings.TrimSuffix(strings.TrimPrefix(resource, "/"), "/")
// Get conditions for policy verification.
conditionKeyMap := make(policy.ConditionKeyMap)
for queryParam := range queryParams {
conditionKeyMap[queryParam] = set.CreateStringSet(queryParams.Get(queryParam))
}
// Add request referer to conditionKeyMap if present.
if referer != "" {
conditionKeyMap["referer"] = set.CreateStringSet(referer)
}
// Add request source Ip to conditionKeyMap.
conditionKeyMap["ip"] = set.CreateStringSet(sourceIP)
// Validate action, resource and conditions with current policy statements.
if !bucketPolicyEvalStatements(action, arn, conditionKeyMap, p.Statements) {
return ErrAccessDenied
}
return ErrNone
}
// Check if the action is allowed on the bucket/prefix.
func isBucketActionAllowed(action, bucket, prefix string, objectAPI ObjectLayer) bool {
reqInfo := &logger.ReqInfo{BucketName: bucket}
reqInfo.AppendTags("prefix", prefix)
ctx := logger.SetReqInfo(context.Background(), reqInfo)
bp, err := objectAPI.GetBucketPolicy(ctx, bucket)
if err != nil {
return false
}
if reflect.DeepEqual(bp, emptyBucketPolicy) {
return false
}
resource := bucketARNPrefix + path.Join(bucket, prefix)
var conditionKeyMap map[string]set.StringSet
// Validate action, resource and conditions with current policy statements.
return bucketPolicyEvalStatements(action, resource, conditionKeyMap, bp.Statements)
}
// GetBucketLocationHandler - GET Bucket location.
// -------------------------
// This operation returns bucket location.
@@ -121,12 +50,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
return
}
s3Error := checkRequestAuthType(ctx, r, bucket, "s3:GetBucketLocation", globalMinioDefaultRegion)
if s3Error == ErrInvalidRegion {
// Clients like boto3 send getBucketLocation() call signed with region that is configured.
s3Error = checkRequestAuthType(ctx, r, "", "s3:GetBucketLocation", globalServerConfig.GetRegion())
}
if s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLocationAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -180,7 +104,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:ListBucketMultipartUploads", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketMultipartUploadsAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -228,16 +152,12 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
if api.CacheAPI() != nil {
listBuckets = api.CacheAPI().ListBuckets
}
// ListBuckets does not have any bucket action.
s3Error := checkRequestAuthType(ctx, r, "", "", globalMinioDefaultRegion)
if s3Error == ErrInvalidRegion {
// Clients like boto3 send listBuckets() call signed with region that is configured.
s3Error = checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion())
}
if s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.ListAllMyBucketsAction, "", ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
// Invoke the list buckets.
bucketsInfo, err := listBuckets(ctx)
if err != nil {
@@ -266,12 +186,12 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
return
}
var authError APIErrorCode
if authError = checkRequestAuthType(ctx, r, bucket, "s3:DeleteObject", globalServerConfig.GetRegion()); authError != ErrNone {
var s3Error APIErrorCode
if s3Error = checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, ""); s3Error != ErrNone {
// In the event access is denied, a 200 response should still be returned
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if authError != ErrAccessDenied {
writeErrorResponse(w, authError, r.URL)
if s3Error != ErrAccessDenied {
writeErrorResponse(w, s3Error, r.URL)
return
}
}
@@ -326,7 +246,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
defer wg.Done()
// If the request is denied access, each item
// should be marked as 'AccessDenied'
if authError == ErrAccessDenied {
if s3Error == ErrAccessDenied {
dErrs[i] = PrefixAccessDenied{
Bucket: bucket,
Object: obj.ObjectName,
@@ -411,16 +331,14 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
return
}
// PutBucket does not have any bucket action.
s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion())
if s3Error != ErrNone {
vars := mux.Vars(r)
bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.CreateBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// Parse incoming location constraint.
location, s3Error := parseLocationConstraint(r)
if s3Error != ErrNone {
@@ -690,10 +608,11 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:ListBucket", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponseHeadersOnly(w, s3Error)
return
}
getBucketInfo := objectAPI.GetBucketInfo
if api.CacheAPI() != nil {
getBucketInfo = api.CacheAPI().GetBucketInfo
@@ -710,20 +629,20 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, "DeleteBucket")
vars := mux.Vars(r)
bucket := vars["bucket"]
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL)
return
}
// DeleteBucket does not have any bucket action.
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
deleteBucket := objectAPI.DeleteBucket
if api.CacheAPI() != nil {
deleteBucket = api.CacheAPI().DeleteBucket
@@ -734,13 +653,8 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
return
}
// Notify all peers (including self) to update in-memory state
for addr, err := range globalNotificationSys.UpdateBucketPolicy(bucket) {
logger.GetReqInfo(ctx).AppendTags("remotePeer", addr.Name)
logger.LogIf(ctx, err)
}
globalNotificationSys.RemoveNotification(bucket)
globalPolicySys.Remove(bucket)
for addr, err := range globalNotificationSys.DeleteBucket(bucket) {
logger.GetReqInfo(ctx).AppendTags("remotePeer", addr.Name)
logger.LogIf(ctx, err)

View File

@@ -155,7 +155,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestGetBucketLocationHandler", bucketName, "", instanceType, apiRouter, anonReq, getReadOnlyBucketStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestGetBucketLocationHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -260,7 +260,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestHeadBucketHandler", bucketName, "", instanceType, apiRouter, anonReq, getReadOnlyBucketStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestHeadBucketHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -494,7 +494,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyBucketStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestListMultipartUploadsHandler", bucketName, "", instanceType, apiRouter, anonReq, getWriteOnlyBucketStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestListMultipartUploadsHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy(bucketName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -592,7 +592,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "ListBucketsHandler", "", "", instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "ListBucketsHandler", "", "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy("*"))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -793,31 +793,3 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
}
func TestIsBucketActionAllowed(t *testing.T) {
ExecObjectLayerAPITest(t, testIsBucketActionAllowedHandler, []string{"BucketLocation"})
}
func testIsBucketActionAllowedHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials auth.Credentials, t *testing.T) {
testCases := []struct {
// input.
action string
bucket string
prefix string
isGlobalPoliciesNil bool
// flag indicating whether the test should pass.
shouldPass bool
}{
{"s3:GetBucketLocation", "mybucket", "abc", true, false},
{"s3:ListObject", "mybucket", "abc", false, false},
}
for i, testCase := range testCases {
isAllowed := isBucketActionAllowed(testCase.action, testCase.bucket, testCase.prefix, obj)
if isAllowed != testCase.shouldPass {
t.Errorf("Case %d: Expected the response status to be `%t`, but instead found `%t`", i+1, testCase.shouldPass, isAllowed)
}
}
}

View File

@@ -27,6 +27,7 @@ import (
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/event/target"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
)
const (
@@ -43,6 +44,9 @@ var errNoSuchNotifications = errors.New("The specified bucket does not have buck
func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, "GetBucketNotification")
vars := mux.Vars(r)
bucketName := vars["bucket"]
objAPI := api.ObjectAPI()
if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL)
@@ -53,14 +57,12 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
writeErrorResponse(w, ErrNotImplemented, r.URL)
return
}
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucketName := vars["bucket"]
_, err := objAPI.GetBucketInfo(ctx, bucketName)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -104,14 +106,15 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
writeErrorResponse(w, ErrNotImplemented, r.URL)
return
}
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucketName := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
_, err := objectAPI.GetBucketInfo(ctx, bucketName)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -166,14 +169,15 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
writeErrorResponse(w, ErrNotImplemented, r.URL)
return
}
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucketName := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.ListenBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
values := r.URL.Query()
var prefix string

View File

@@ -19,206 +19,24 @@ package cmd
import (
"encoding/json"
"io"
"io/ioutil"
"net"
"net/http"
"runtime"
"strings"
humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/wildcard"
"github.com/minio/minio/pkg/policy"
)
// maximum supported access policy size.
const maxAccessPolicySize = 20 * humanize.KiByte
const (
// As per AWS S3 specification, 20KiB policy JSON data is allowed.
maxBucketPolicySize = 20 * humanize.KiByte
// 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 policy.ConditionKeyMap,
statements []policy.Statement) bool {
for _, statement := range statements {
if bucketPolicyMatchStatement(action, resource, conditions, statement) {
if statement.Effect == "Allow" {
return true
}
// Do not uncomment kept here for readability.
// else statement.Effect == "Deny"
return false
}
}
// None match so deny.
return false
}
// Policy configuration file.
bucketPolicyConfig = "policy.json"
)
// Verify if action, resource and conditions match input policy statement.
func bucketPolicyMatchStatement(action string, resource string, conditions policy.ConditionKeyMap,
statement policy.Statement) bool {
// Verify if action, resource and condition match in given statement.
return (bucketPolicyActionMatch(action, statement) &&
bucketPolicyResourceMatch(resource, statement) &&
bucketPolicyConditionMatch(conditions, statement))
}
// Verify if given action matches with policy statement.
func bucketPolicyActionMatch(action string, statement policy.Statement) bool {
return !statement.Actions.FuncMatch(actionMatch, action).IsEmpty()
}
// Match function matches wild cards in 'pattern' for resource.
func resourceMatch(pattern, resource string) bool {
if runtime.GOOS == "windows" {
// For windows specifically make sure we are case insensitive.
return wildcard.Match(strings.ToLower(pattern), strings.ToLower(resource))
}
return wildcard.Match(pattern, resource)
}
// Match function matches wild cards in 'pattern' for action.
func actionMatch(pattern, action string) bool {
return wildcard.MatchSimple(pattern, action)
}
func refererMatch(pattern, referer string) bool {
return wildcard.MatchSimple(pattern, referer)
}
// isIPInCIDR - checks if a given a IP address is a member of the given subnet.
func isIPInCIDR(cidr, ip string) bool {
// AWS S3 spec says IPs must use standard CIDR notation.
// http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html#example-bucket-policies-use-case-3.
_, cidrNet, err := net.ParseCIDR(cidr)
if err != nil {
return false // If provided CIDR can't be parsed no IP will be in the subnet.
}
addr := net.ParseIP(ip)
return cidrNet.Contains(addr)
}
// Verify if given resource matches with policy statement.
func bucketPolicyResourceMatch(resource string, statement policy.Statement) bool {
// the resource rule for object could contain "*" wild card.
// the requested object can be given access based on the already set bucket policy if
// the match is successful.
// More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html.
return !statement.Resources.FuncMatch(resourceMatch, resource).IsEmpty()
}
// Verify if given condition matches with policy statement.
func bucketPolicyConditionMatch(conditions policy.ConditionKeyMap, statement policy.Statement) bool {
// Supports following conditions.
// - StringEquals
// - StringNotEquals
// - StringLike
// - StringNotLike
// - IpAddress
// - NotIpAddress
//
// Supported applicable condition keys for each conditions.
// - s3:prefix
// - s3:max-keys
// - s3:aws-Referer
// - s3:aws-SourceIp
// The following loop evaluates the logical AND of all the
// conditions in the statement. Note: we can break out of the
// loop if and only if a condition evaluates to false.
for condition, conditionKeyVal := range statement.Conditions {
prefixConditon := conditionKeyVal["s3:prefix"]
maxKeyCondition := conditionKeyVal["s3:max-keys"]
if condition == "StringEquals" {
// If there is no condition with "s3:prefix" or "s3:max-keys" condition key
// then there is nothing to check condition against.
if !prefixConditon.IsEmpty() && !prefixConditon.Equals(conditions["prefix"]) {
return false
}
if !maxKeyCondition.IsEmpty() && !maxKeyCondition.Equals(conditions["max-keys"]) {
return false
}
} else if condition == "StringNotEquals" {
// If there is no condition with "s3:prefix" or "s3:max-keys" condition key
// then there is nothing to check condition against.
if !prefixConditon.IsEmpty() && prefixConditon.Equals(conditions["prefix"]) {
return false
}
if !maxKeyCondition.IsEmpty() && maxKeyCondition.Equals(conditions["max-keys"]) {
return false
}
} else if condition == "StringLike" {
awsReferers := conditionKeyVal["aws:Referer"]
// Skip empty condition, it is trivially satisfied.
if awsReferers.IsEmpty() {
continue
}
// wildcard match of referer in statement was not empty.
// StringLike has a match, i.e, condition evaluates to true.
refererFound := false
for referer := range conditions["referer"] {
if !awsReferers.FuncMatch(refererMatch, referer).IsEmpty() {
refererFound = true
break
}
}
// No matching referer found, so the condition is false.
if !refererFound {
return false
}
} else if condition == "StringNotLike" {
awsReferers := conditionKeyVal["aws:Referer"]
// Skip empty condition, it is trivially satisfied.
if awsReferers.IsEmpty() {
continue
}
// wildcard match of referer in statement was not empty.
// StringNotLike has a match, i.e, condition evaluates to false.
for referer := range conditions["referer"] {
if !awsReferers.FuncMatch(refererMatch, referer).IsEmpty() {
return false
}
}
} else if condition == "IpAddress" {
awsIps := conditionKeyVal["aws:SourceIp"]
// Skip empty condition, it is trivially satisfied.
if awsIps.IsEmpty() {
continue
}
// wildcard match of ip if statement was not empty.
// Find a valid ip.
ipFound := false
for ip := range conditions["ip"] {
if !awsIps.FuncMatch(isIPInCIDR, ip).IsEmpty() {
ipFound = true
break
}
}
if !ipFound {
return false
}
} else if condition == "NotIpAddress" {
awsIps := conditionKeyVal["aws:SourceIp"]
// Skip empty condition, it is trivially satisfied.
if awsIps.IsEmpty() {
continue
}
// wildcard match of ip if statement was not empty.
// Find if nothing matches.
for ip := range conditions["ip"] {
if !awsIps.FuncMatch(isIPInCIDR, ip).IsEmpty() {
return false
}
}
}
}
return true
}
// PutBucketPolicyHandler - PUT Bucket policy
// -----------------
// This implementation of the PUT operation uses the policy
// subresource to add to or replace a policy on a bucket
// PutBucketPolicyHandler - This HTTP handler stores given bucket policy configuration as per
// https://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, "PutBucketPolicy")
@@ -228,65 +46,46 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
return
}
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
vars := mux.Vars(r)
bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// Before proceeding validate if bucket exists.
_, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil {
// Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
// If Content-Length is unknown or zero, deny the
// request. PutBucketPolicy always needs a Content-Length.
if r.ContentLength == -1 || r.ContentLength == 0 {
// Error out if Content-Length is missing.
// PutBucketPolicy always needs Content-Length.
if r.ContentLength <= 0 {
writeErrorResponse(w, ErrMissingContentLength, r.URL)
return
}
// If Content-Length is greater than maximum allowed policy size.
if r.ContentLength > maxAccessPolicySize {
// Error out if Content-Length is beyond allowed size.
if r.ContentLength > maxBucketPolicySize {
writeErrorResponse(w, ErrEntityTooLarge, r.URL)
return
}
// Read access policy up to maxAccessPolicySize.
// http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
// bucket policies are limited to 20KB in size, using a limit reader.
policyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize))
bucketPolicy, err := policy.ParseConfig(io.LimitReader(r.Body, r.ContentLength), bucket)
if err != nil {
logger.LogIf(ctx, err)
writeErrorResponse(w, ErrMalformedPolicy, r.URL)
return
}
if err = objAPI.SetBucketPolicy(ctx, bucket, bucketPolicy); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
policyInfo := policy.BucketAccessPolicy{}
if err = json.Unmarshal(policyBytes, &policyInfo); err != nil {
writeErrorResponse(w, ErrInvalidPolicyDocument, r.URL)
return
}
// Parse check bucket policy.
if s3Error := checkBucketPolicyResources(bucket, policyInfo); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
if err = objAPI.SetBucketPolicy(ctx, bucket, policyInfo); err != nil {
switch err.(type) {
case NotImplemented:
// Return error for invalid bucket name.
writeErrorResponse(w, ErrNotImplemented, r.URL)
default:
writeErrorResponse(w, ErrMalformedPolicy, r.URL)
}
return
}
for addr, err := range globalNotificationSys.UpdateBucketPolicy(bucket) {
globalPolicySys.Set(bucket, *bucketPolicy)
for addr, err := range globalNotificationSys.SetBucketPolicy(bucket, bucketPolicy) {
logger.GetReqInfo(ctx).AppendTags("remotePeer", addr.Name)
logger.LogIf(ctx, err)
}
@@ -295,10 +94,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
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.
// DeleteBucketPolicyHandler - This HTTP handler removes bucket policy configuration.
func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, "DeleteBucketPolicy")
@@ -308,29 +104,27 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
return
}
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
vars := mux.Vars(r)
bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// Before proceeding validate if bucket exists.
_, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil {
// Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
// Delete bucket access policy, by passing an empty policy
// struct.
if err := objAPI.DeleteBucketPolicy(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
for addr, err := range globalNotificationSys.UpdateBucketPolicy(bucket) {
globalPolicySys.Remove(bucket)
for addr, err := range globalNotificationSys.RemoveBucketPolicy(bucket) {
logger.GetReqInfo(ctx).AppendTags("remotePeer", addr.Name)
logger.LogIf(ctx, err)
}
@@ -339,10 +133,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
writeSuccessNoContent(w)
}
// GetBucketPolicyHandler - GET Bucket policy
// -----------------
// This operation uses the policy
// subresource to return the policy of a specified bucket.
// GetBucketPolicyHandler - This HTTP handler returns bucket policy configuration.
func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, "GetBucketPolicy")
@@ -352,29 +143,28 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
return
}
if s3Error := checkRequestAuthType(ctx, r, "", "", globalServerConfig.GetRegion()); s3Error != ErrNone {
vars := mux.Vars(r)
bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// Before proceeding validate if bucket exists.
_, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil {
// Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
// Read bucket access policy.
policy, err := objAPI.GetBucketPolicy(ctx, bucket)
bucketPolicy, err := objAPI.GetBucketPolicy(ctx, bucket)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return
}
policyBytes, err := json.Marshal(&policy)
policyData, err := json.Marshal(bucketPolicy)
if err != nil {
logger.LogIf(ctx, err)
writeErrorResponse(w, toAPIErrorCode(err), r.URL)
@@ -382,5 +172,5 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
}
// Write to client.
w.Write(policyBytes)
w.Write(policyData)
}

View File

@@ -24,222 +24,72 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
)
// Tests validate Bucket policy resource matcher.
func TestBucketPolicyResourceMatch(t *testing.T) {
// generates statement with given resource..
generateStatement := func(resource string) policy.Statement {
statement := policy.Statement{}
statement.Resources = set.CreateStringSet([]string{resource}...)
return statement
}
// generates resource prefix.
generateResource := func(bucketName, objectName string) string {
return bucketARNPrefix + bucketName + "/" + objectName
}
testCases := []struct {
resourceToMatch string
statement policy.Statement
expectedResourceMatch bool
}{
// Test case 1-4.
// Policy with resource ending with bucket/* allows access to all objects inside the given bucket.
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/*")), true},
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/*")), true},
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/*")), true},
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/*")), true},
// Test case - 5.
// Policy with resource ending with bucket/oo* should not allow access to bucket/output.txt.
{generateResource("minio-bucket", "output.txt"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/oo*")), false},
// Test case - 6.
// Policy with resource ending with bucket/oo* should allow access to bucket/ootput.txt.
{generateResource("minio-bucket", "ootput.txt"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/oo*")), true},
// Test case - 7.
// Policy with resource ending with bucket/oo* allows access to all sub-dirs starting with "oo" inside given bucket.
{generateResource("minio-bucket", "oop-bucket/my-file"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/oo*")), true},
// Test case - 8.
{generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/Asia/Japan/*")), false},
// Test case - 9.
{generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/Asia/Japan/*")), false},
// Test case - 10.
// Proves that the name space is flat.
{generateResource("minio-bucket", "Africa/Bihar/India/design_info.doc/Bihar"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix,
"minio-bucket"+"/*/India/*/Bihar")), true},
// Test case - 11.
// Proves that the name space is flat.
{generateResource("minio-bucket", "Asia/China/India/States/Bihar/output.txt"), generateStatement(fmt.Sprintf("%s%s", bucketARNPrefix,
"minio-bucket"+"/*/India/*/Bihar/*")), true},
}
for i, testCase := range testCases {
actualResourceMatch := bucketPolicyResourceMatch(testCase.resourceToMatch, testCase.statement)
if testCase.expectedResourceMatch != actualResourceMatch {
t.Errorf("Test %d: Expected Resource match to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResourceMatch, actualResourceMatch)
}
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.Policy {
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
policy.NewResourceSet(policy.NewResource(bucketName, "")),
condition.NewFunctions(),
)},
}
}
// TestBucketPolicyActionMatch - Test validates whether given action on the
// bucket/object matches the allowed actions in policy.Statement.
// This test preserves the allowed actions for all 3 sets of policies, that is read-write,read-only, write-only.
// The intention of the test is to catch any changes made to allowed action for on eof the above 3 major policy groups mentioned.
func TestBucketPolicyActionMatch(t *testing.T) {
bucketName := getRandomBucketName()
objectPrefix := "test-object"
testCases := []struct {
action string
statement policy.Statement
expectedResult bool
}{
// s3:GetBucketLocation is the action necessary to be present in the bucket policy to allow
// fetching of bucket location on an Anonymous/unsigned request.
//r ead-write bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 1).
{"s3:GetBucketLocation", getReadWriteBucketStatement(bucketName, objectPrefix), true},
// write-only bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 2).
{"s3:GetBucketLocation", getWriteOnlyBucketStatement(bucketName, objectPrefix), true},
// read-only bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 3).
{"s3:GetBucketLocation", getReadOnlyBucketStatement(bucketName, objectPrefix), true},
// Any of the Object level access permissions shouldn't allow for GetBucketLocation operation on an Anonymous/unsigned request (Test cases 4-6).
{"s3:GetBucketLocation", getReadWriteObjectStatement(bucketName, objectPrefix), false},
{"s3:GetBucketLocation", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
{"s3:GetBucketLocation", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
// s3:ListBucketMultipartUploads is the action necessary to be present in the bucket policy to allow
// Listing of multipart uploads in a given bucket for an Anonymous/unsigned request.
//read-write bucket policy is expected to allow ListBucketMultipartUploads operation on an anonymous request (Test case 7).
{"s3:ListBucketMultipartUploads", getReadWriteBucketStatement(bucketName, objectPrefix), true},
// write-only bucket policy is expected to allow ListBucketMultipartUploads operation on an anonymous request (Test case 8).
{"s3:ListBucketMultipartUploads", getWriteOnlyBucketStatement(bucketName, objectPrefix), true},
// read-only bucket policy is expected to not allow ListBucketMultipartUploads operation on an anonymous request (Test case 9).
// the allowed actions in read-only bucket statement are "s3:GetBucketLocation","s3:ListBucket",
// this should not allow for ListBucketMultipartUploads operations.
{"s3:ListBucketMultipartUploads", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
// Any of the object level policy will not allow for s3:ListBucketMultipartUploads (Test cases 10-12).
{"s3:ListBucketMultipartUploads", getReadWriteObjectStatement(bucketName, objectPrefix), false},
{"s3:ListBucketMultipartUploads", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
{"s3:ListBucketMultipartUploads", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
// s3:ListBucket is the action necessary to be present in the bucket policy to allow
// listing of all objects inside a given bucket on an Anonymous/unsigned request.
// Cases for testing ListBucket access for different Bucket level access permissions.
// read-only bucket policy is expected to allow ListBucket operation on an anonymous request (Test case 13).
{"s3:ListBucket", getReadOnlyBucketStatement(bucketName, objectPrefix), true},
// read-write bucket policy is expected to allow ListBucket operation on an anonymous request (Test case 14).
{"s3:ListBucket", getReadWriteBucketStatement(bucketName, objectPrefix), true},
// write-only bucket policy is expected to not allow ListBucket operation on an anonymous request (Test case 15).
// the allowed actions in write-only bucket statement are "s3:GetBucketLocation", "s3:ListBucketMultipartUploads",
// this should not allow for ListBucket operations.
{"s3:ListBucket", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
// Cases for testing ListBucket access for different Object level access permissions (Test cases 16-18).
// Any of the Object level access permissions shouldn't allow for ListBucket operation on an Anonymous/unsigned request.
{"s3:ListBucket", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
{"s3:ListBucket", getReadWriteObjectStatement(bucketName, objectPrefix), false},
{"s3:ListBucket", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
// s3:DeleteObject is the action necessary to be present in the bucket policy to allow
// deleting/removal of objects inside a given bucket for an Anonymous/unsigned request.
// Cases for testing DeleteObject access for different Bucket level access permissions (Test cases 19-21).
// Any of the Bucket level access permissions shouldn't allow for DeleteObject operation on an Anonymous/unsigned request.
{"s3:DeleteObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
{"s3:DeleteObject", getReadWriteBucketStatement(bucketName, objectPrefix), false},
{"s3:DeleteObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
// Cases for testing DeleteObject access for different Object level access permissions (Test cases 22).
// read-only bucket policy is expected to not allow Delete Object operation on an anonymous request.
{"s3:DeleteObject", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
// read-write bucket policy is expected to allow Delete Bucket operation on an anonymous request (Test cases 23).
{"s3:DeleteObject", getReadWriteObjectStatement(bucketName, objectPrefix), true},
// write-only bucket policy is expected to allow Delete Object operation on an anonymous request (Test cases 24).
{"s3:DeleteObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
// s3:AbortMultipartUpload is the action necessary to be present in the bucket policy to allow
// cancelling or abortion of an already initiated multipart upload operation for an Anonymous/unsigned request.
// Cases for testing AbortMultipartUpload access for different Bucket level access permissions (Test cases 25-27).
// Any of the Bucket level access permissions shouldn't allow for AbortMultipartUpload operation on an Anonymous/unsigned request.
{"s3:AbortMultipartUpload", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
{"s3:AbortMultipartUpload", getReadWriteBucketStatement(bucketName, objectPrefix), false},
{"s3:AbortMultipartUpload", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
// Cases for testing AbortMultipartUpload access for different Object level access permissions.
// read-only object policy is expected to not allow AbortMultipartUpload operation on an anonymous request (Test case 28).
{"s3:AbortMultipartUpload", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
// read-write object policy is expected to allow AbortMultipartUpload operation on an anonymous request (Test case 29).
{"s3:AbortMultipartUpload", getReadWriteObjectStatement(bucketName, objectPrefix), true},
// write-only object policy is expected to allow AbortMultipartUpload operation on an anonymous request (Test case 30).
{"s3:AbortMultipartUpload", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
// s3:PutObject is the action necessary to be present in the bucket policy to allow
// uploading of an object for an Anonymous/unsigned request.
// Cases for testing PutObject access for different Bucket level access permissions (Test cases 31-33).
// Any of the Bucket level access permissions shouldn't allow for PutObject operation on an Anonymous/unsigned request.
{"s3:PutObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
{"s3:PutObject", getReadWriteBucketStatement(bucketName, objectPrefix), false},
{"s3:PutObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
// Cases for testing PutObject access for different Object level access permissions.
// read-only object policy is expected to not allow PutObject operation on an anonymous request (Test case 34).
{"s3:PutObject", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
// read-write object policy is expected to allow PutObject operation on an anonymous request (Test case 35).
{"s3:PutObject", getReadWriteObjectStatement(bucketName, objectPrefix), true},
// write-only object policy is expected to allow PutObject operation on an anonymous request (Test case 36).
{"s3:PutObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
// s3:GetObject is the action necessary to be present in the bucket policy to allow
// downloading of an object for an Anonymous/unsigned request.
// Cases for testing GetObject access for different Bucket level access permissions (Test cases 37-39).
// Any of the Bucket level access permissions shouldn't allow for GetObject operation on an Anonymous/unsigned request.
{"s3:GetObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
{"s3:GetObject", getReadWriteBucketStatement(bucketName, objectPrefix), false},
{"s3:GetObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
// Cases for testing GetObject access for different Object level access permissions.
// read-only bucket policy is expected to allow downloading of an Object on an anonymous request (Test case 40).
{"s3:GetObject", getReadOnlyObjectStatement(bucketName, objectPrefix), true},
// read-write bucket policy is expected to allow downloading of an Object on an anonymous request (Test case 41).
{"s3:GetObject", getReadWriteObjectStatement(bucketName, objectPrefix), true},
// write-only bucket policy is expected to not allow downloading of an Object on an anonymous request (Test case 42).
{"s3:GetObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), false},
// s3:ListMultipartUploadParts is the action necessary to be present in the bucket policy to allow
// Listing of uploaded parts for an Anonymous/unsigned request.
// Any of the Bucket level access permissions shouldn't allow for ListMultipartUploadParts operation on an Anonymous/unsigned request.
// read-only bucket policy is expected to not allow ListMultipartUploadParts operation on an anonymous request (Test cases 43-45).
{"s3:ListMultipartUploadParts", getReadOnlyBucketStatement(bucketName, objectPrefix), false},
{"s3:ListMultipartUploadParts", getReadWriteBucketStatement(bucketName, objectPrefix), false},
{"s3:ListMultipartUploadParts", getWriteOnlyBucketStatement(bucketName, objectPrefix), false},
// read-only object policy is expected to not allow ListMultipartUploadParts operation on an anonymous request (Test case 46).
{"s3:ListMultipartUploadParts", getReadOnlyObjectStatement(bucketName, objectPrefix), false},
// read-write object policy is expected to allow ListMultipartUploadParts operation on an anonymous request (Test case 47).
{"s3:ListMultipartUploadParts", getReadWriteObjectStatement(bucketName, objectPrefix), true},
// write-only object policy is expected to allow ListMultipartUploadParts operation on an anonymous request (Test case 48).
{"s3:ListMultipartUploadParts", getWriteOnlyObjectStatement(bucketName, objectPrefix), true},
func getAnonWriteOnlyBucketPolicy(bucketName string) *policy.Policy {
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(
policy.GetBucketLocationAction,
policy.ListBucketMultipartUploadsAction,
),
policy.NewResourceSet(policy.NewResource(bucketName, "")),
condition.NewFunctions(),
)},
}
for i, testCase := range testCases {
actualResult := bucketPolicyActionMatch(testCase.action, testCase.statement)
if testCase.expectedResult != actualResult {
t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResult, actualResult)
}
}
func getAnonReadOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource(bucketName, prefix)),
condition.NewFunctions(),
)},
}
}
func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(
policy.AbortMultipartUploadAction,
policy.DeleteObjectAction,
policy.ListMultipartUploadPartsAction,
policy.PutObjectAction,
),
policy.NewResourceSet(policy.NewResource(bucketName, prefix)),
condition.NewFunctions(),
)},
}
}
@@ -290,7 +140,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
bucketName: bucketName,
bucketPolicyReader: bytes.NewReader([]byte(fmt.Sprintf(bucketPolicyTemplate, bucketName, bucketName))),
policyLen: maxAccessPolicySize + 1,
policyLen: maxBucketPolicySize + 1,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
@@ -430,7 +280,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "PutBucketPolicyHandler", bucketName, "", instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "PutBucketPolicyHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy(bucketName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -459,7 +309,7 @@ func TestGetBucketPolicyHandler(t *testing.T) {
func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials auth.Credentials, t *testing.T) {
// template for constructing HTTP request body for PUT bucket policy.
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"],"Sid":""}]}`
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"]},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"]}]}`
// Writing bucket policy before running test on GetBucketPolicy.
putTestPolicies := []struct {
@@ -572,7 +422,16 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
if recV4.Code != testCase.expectedRespStatus {
// Verify whether the bucket policy fetched is same as the one inserted.
if expectedBucketPolicyStr != string(bucketPolicyReadBuf) {
expectedPolicy, err := policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
if err != nil {
t.Fatalf("unexpected error. %v", err)
}
gotPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
if err != nil {
t.Fatalf("unexpected error. %v", err)
}
if !reflect.DeepEqual(expectedPolicy, gotPolicy) {
t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType)
}
}
@@ -598,7 +457,16 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
}
if recV2.Code == http.StatusOK {
// Verify whether the bucket policy fetched is same as the one inserted.
if expectedBucketPolicyStr != string(bucketPolicyReadBuf) {
expectedPolicy, err := policy.ParseConfig(strings.NewReader(expectedBucketPolicyStr), testCase.bucketName)
if err != nil {
t.Fatalf("unexpected error. %v", err)
}
gotPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), testCase.bucketName)
if err != nil {
t.Fatalf("unexpected error. %v", err)
}
if !reflect.DeepEqual(expectedPolicy, gotPolicy) {
t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType)
}
}
@@ -617,7 +485,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "GetBucketPolicyHandler", bucketName, "", instanceType, apiRouter, anonReq, getReadOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "GetBucketPolicyHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -820,7 +688,7 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "DeleteBucketPolicyHandler", bucketName, "", instanceType, apiRouter, anonReq, getReadOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "DeleteBucketPolicyHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy(bucketName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -838,174 +706,3 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName str
// `ExecObjectLayerAPINilTest` manages the operation.
ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq)
}
// TestBucketPolicyConditionMatch - Tests to validate whether bucket policy conditions match.
func TestBucketPolicyConditionMatch(t *testing.T) {
// obtain the inner map[string]set.StringSet for policy.Statement.Conditions.
getInnerMap := func(key2, value string) map[string]set.StringSet {
innerMap := make(map[string]set.StringSet)
innerMap[key2] = set.CreateStringSet(value)
return innerMap
}
// obtain policy.Statement with Conditions set.
getStatementWithCondition := func(key1, key2, value string) policy.Statement {
innerMap := getInnerMap(key2, value)
// to set policyStatment.Conditions .
conditions := make(policy.ConditionMap)
conditions[key1] = innerMap
// new policy statement.
statement := policy.Statement{}
// set the condition.
statement.Conditions = conditions
return statement
}
testCases := []struct {
statementCondition policy.Statement
condition map[string]set.StringSet
expectedMatch bool
}{
// Test case - 1.
// StringEquals condition matches.
{
statementCondition: getStatementWithCondition("StringEquals", "s3:prefix", "Asia/"),
condition: getInnerMap("prefix", "Asia/"),
expectedMatch: true,
},
// Test case - 2.
// StringEquals condition doesn't match.
{
statementCondition: getStatementWithCondition("StringEquals", "s3:prefix", "Asia/"),
condition: getInnerMap("prefix", "Africa/"),
expectedMatch: false,
},
// Test case - 3.
// StringEquals condition matches.
{
statementCondition: getStatementWithCondition("StringEquals", "s3:max-keys", "Asia/"),
condition: getInnerMap("max-keys", "Asia/"),
expectedMatch: true,
},
// Test case - 4.
// StringEquals condition doesn't match.
{
statementCondition: getStatementWithCondition("StringEquals", "s3:max-keys", "Asia/"),
condition: getInnerMap("max-keys", "Africa/"),
expectedMatch: false,
},
// Test case - 5.
// StringNotEquals condition matches.
{
statementCondition: getStatementWithCondition("StringNotEquals", "s3:prefix", "Asia/"),
condition: getInnerMap("prefix", "Asia/"),
expectedMatch: false,
},
// Test case - 6.
// StringNotEquals condition doesn't match.
{
statementCondition: getStatementWithCondition("StringNotEquals", "s3:prefix", "Asia/"),
condition: getInnerMap("prefix", "Africa/"),
expectedMatch: true,
},
// Test case - 7.
// StringNotEquals condition matches.
{
statementCondition: getStatementWithCondition("StringNotEquals", "s3:max-keys", "Asia/"),
condition: getInnerMap("max-keys", "Asia/"),
expectedMatch: false,
},
// Test case - 8.
// StringNotEquals condition doesn't match.
{
statementCondition: getStatementWithCondition("StringNotEquals", "s3:max-keys", "Asia/"),
condition: getInnerMap("max-keys", "Africa/"),
expectedMatch: true,
},
// Test case - 9.
// StringLike condition matches.
{
statementCondition: getStatementWithCondition("StringLike", "aws:Referer", "http://www.example.com/"),
condition: getInnerMap("referer", "http://www.example.com/"),
expectedMatch: true,
},
// Test case - 10.
// StringLike condition doesn't match.
{
statementCondition: getStatementWithCondition("StringLike", "aws:Referer", "http://www.example.com/"),
condition: getInnerMap("referer", "www.somethingelse.com"),
expectedMatch: false,
},
// Test case - 11.
// StringNotLike condition evaluates to false.
{
statementCondition: getStatementWithCondition("StringNotLike", "aws:Referer", "http://www.example.com/"),
condition: getInnerMap("referer", "http://www.example.com/"),
expectedMatch: false,
},
// Test case - 12.
// StringNotLike condition evaluates to true.
{
statementCondition: getStatementWithCondition("StringNotLike", "aws:Referer", "http://www.example.com/"),
condition: getInnerMap("referer", "http://somethingelse.com/"),
expectedMatch: true,
},
// Test case 13.
// IpAddress condition evaluates to true.
{
statementCondition: getStatementWithCondition("IpAddress", "aws:SourceIp", "54.240.143.0/24"),
condition: getInnerMap("ip", "54.240.143.2"),
expectedMatch: true,
},
// Test case 14.
// IpAddress condition evaluates to false.
{
statementCondition: getStatementWithCondition("IpAddress", "aws:SourceIp", "54.240.143.0/24"),
condition: getInnerMap("ip", "127.240.143.224"),
expectedMatch: false,
},
// Test case 15.
// NotIpAddress condition evaluates to true.
{
statementCondition: getStatementWithCondition("NotIpAddress", "aws:SourceIp", "54.240.143.0/24"),
condition: getInnerMap("ip", "54.240.144.188"),
expectedMatch: true,
},
// Test case 16.
// NotIpAddress condition evaluates to false.
{
statementCondition: getStatementWithCondition("NotIpAddress", "aws:SourceIp", "54.240.143.0/24"),
condition: getInnerMap("ip", "54.240.143.243"),
expectedMatch: false,
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("Case %d", i+1), func(t *testing.T) {
// call the function under test and assert the result with the expected result.
doesMatch := bucketPolicyConditionMatch(tc.condition, tc.statementCondition)
if tc.expectedMatch != doesMatch {
t.Errorf("Expected the match to be `%v`; got `%v` - %v %v.",
tc.expectedMatch, doesMatch, tc.condition, tc.statementCondition)
}
})
}
}

View File

@@ -1,295 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package cmd This file implements AWS Access Policy Language parser in
// accordance with http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
package cmd
import (
"encoding/json"
"errors"
"fmt"
"io"
"sort"
"strings"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/set"
)
var emptyBucketPolicy = policy.BucketAccessPolicy{}
var conditionKeyActionMap = policy.ConditionKeyMap{
"s3:prefix": set.CreateStringSet("s3:ListBucket", "s3:ListBucketMultipartUploads"),
"s3:max-keys": set.CreateStringSet("s3:ListBucket", "s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts"),
}
// supportedActionMap - lists all the actions supported by minio.
var supportedActionMap = set.CreateStringSet("*", "s3:*", "s3:GetObject",
"s3:ListBucket", "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject",
"s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts")
// supported Conditions type.
var supportedConditionsType = set.CreateStringSet("StringEquals", "StringNotEquals", "StringLike", "StringNotLike", "IpAddress", "NotIpAddress")
// Validate s3:prefix, s3:max-keys are present if not
// supported keys for the conditions.
var supportedConditionsKey = set.CreateStringSet("s3:prefix", "s3:max-keys", "aws:Referer", "aws:SourceIp")
// supportedEffectMap - supported effects.
var supportedEffectMap = set.CreateStringSet("Allow", "Deny")
// isValidActions - are actions valid.
func isValidActions(actions set.StringSet) (err error) {
// Statement actions cannot be empty.
if actions.IsEmpty() {
err = errors.New("Action list cannot be empty")
return err
}
if unsupportedActions := actions.Difference(supportedActionMap); !unsupportedActions.IsEmpty() {
err = fmt.Errorf("Unsupported actions found: %#v, please validate your policy document",
unsupportedActions)
return err
}
return nil
}
// isValidEffect - is effect valid.
func isValidEffect(effect string) (err error) {
// Statement effect cannot be empty.
if effect == "" {
err = errors.New("Policy effect cannot be empty")
return err
}
if !supportedEffectMap.Contains(effect) {
err = errors.New("Unsupported Effect found: " + effect + ", please validate your policy document")
return err
}
return nil
}
// isValidResources - are valid resources.
func isValidResources(resources set.StringSet) (err error) {
// Statement resources cannot be empty.
if resources.IsEmpty() {
err = errors.New("Resource list cannot be empty")
return err
}
for resource := range resources {
if !hasPrefix(resource, bucketARNPrefix) {
err = errors.New("Unsupported resource style found: " + resource + ", please validate your policy document")
return err
}
resourceSuffix := strings.SplitAfter(resource, bucketARNPrefix)[1]
if len(resourceSuffix) == 0 || 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(principal policy.User) (err error) {
if principal.AWS.IsEmpty() {
return errors.New("Principal cannot be empty")
}
if diff := principal.AWS.Difference(set.CreateStringSet("*")); !diff.IsEmpty() {
// Minio does not support or implement IAM, "*" is the only valid value.
// Amazon s3 doc on principal:
// http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Principal
err = fmt.Errorf("Unsupported principals found: %#v, please validate your policy document",
diff)
return err
}
return nil
}
// isValidConditions - returns nil if the given conditions valid and
// corresponding error otherwise.
func isValidConditions(actions set.StringSet, conditions policy.ConditionMap) (err error) {
// Verify conditions should be valid. Validate if only
// supported condition keys are present and return error
// otherwise.
conditionKeyVal := make(map[string]set.StringSet)
for conditionType := range conditions {
if !supportedConditionsType.Contains(conditionType) {
err = fmt.Errorf("Unsupported condition type '%s', please validate your policy document", conditionType)
return err
}
for key, value := range conditions[conditionType] {
if !supportedConditionsKey.Contains(key) {
err = fmt.Errorf("Unsupported condition key '%s', please validate your policy document", conditionType)
return err
}
compatibleActions := conditionKeyActionMap[key]
if !compatibleActions.IsEmpty() &&
compatibleActions.Intersection(actions).IsEmpty() {
err = fmt.Errorf("Unsupported condition key %s for the given actions %s, "+
"please validate your policy document", key, actions)
return err
}
conditionVal, ok := conditionKeyVal[key]
if ok && !value.Intersection(conditionVal).IsEmpty() {
err = fmt.Errorf("Ambigious condition values for key '%s', please validate your policy document", key)
return err
}
conditionKeyVal[key] = value
}
}
return nil
}
// List of actions for which prefixes are not allowed.
var invalidPrefixActions = set.StringSet{
"s3:GetBucketLocation": {},
"s3:ListBucket": {},
"s3:ListBucketMultipartUploads": {},
// Add actions which do not honor prefixes.
}
// resourcePrefix - provides the prefix removing any wildcards.
func resourcePrefix(resource string) string {
if strings.HasSuffix(resource, "*") {
resource = strings.TrimSuffix(resource, "*")
}
return resource
}
// checkBucketPolicyResources validates Resources in unmarshalled bucket policy structure.
// - Resources are validated against the given set of Actions.
// -
func checkBucketPolicyResources(bucket string, bucketPolicy policy.BucketAccessPolicy) APIErrorCode {
// Validate statements for special actions and collect resources
// for others to validate nesting.
var resourceMap = set.NewStringSet()
for _, statement := range bucketPolicy.Statements {
for action := range statement.Actions {
for resource := range statement.Resources {
resourcePrefix := strings.SplitAfter(resource, bucketARNPrefix)[1]
if _, ok := invalidPrefixActions[action]; ok {
// Resource prefix is not equal to bucket for
// prefix invalid actions, reject them.
if resourcePrefix != bucket {
return ErrMalformedPolicy
}
} else {
// For all other actions validate if resourcePrefix begins
// with bucket name, if not reject them.
if strings.Split(resourcePrefix, "/")[0] != bucket {
return ErrMalformedPolicy
}
// All valid resources collect them separately to verify nesting.
resourceMap.Add(resourcePrefix)
}
}
}
}
var resources []string
for resource := range resourceMap {
resources = append(resources, resourcePrefix(resource))
}
// Sort strings as shorter first.
sort.Strings(resources)
for len(resources) > 1 {
var resource string
resource, resources = resources[0], resources[1:]
// Loop through all resources, if one of them matches with
// previous shorter one, it means we have detected
// nesting. Reject such rules.
for _, otherResource := range resources {
// Common prefix reject such rules.
if hasPrefix(otherResource, resource) {
return ErrPolicyNesting
}
}
}
// No errors found.
return ErrNone
}
// parseBucketPolicy - parses and validates if bucket policy is of
// proper JSON and follows allowed restrictions with policy standards.
func parseBucketPolicy(bucketPolicyReader io.Reader, bktPolicy *policy.BucketAccessPolicy) (err error) {
// Parse bucket policy reader.
decoder := json.NewDecoder(bucketPolicyReader)
if err = decoder.Decode(bktPolicy); err != nil {
return err
}
// Policy version cannot be empty.
if len(bktPolicy.Version) == 0 {
err = errors.New("Policy version cannot be empty")
return err
}
// Policy statements cannot be empty.
if len(bktPolicy.Statements) == 0 {
err = errors.New("Policy statement cannot be empty")
return err
}
// Loop through all policy statements and validate entries.
for _, statement := range bktPolicy.Statements {
// Statement effect should be valid.
if err := isValidEffect(statement.Effect); err != nil {
return err
}
// Statement principal should be supported format.
if err := isValidPrincipals(statement.Principal); err != nil {
return err
}
// Statement actions should be valid.
if err := isValidActions(statement.Actions); err != nil {
return err
}
// Statement resources should be valid.
if err := isValidResources(statement.Resources); err != nil {
return err
}
// Statement conditions should be valid.
if err := isValidConditions(statement.Actions, statement.Conditions); err != nil {
return err
}
}
// Separate deny and allow statements, so that we can apply deny
// statements in the beginning followed by Allow statements.
var denyStatements []policy.Statement
var allowStatements []policy.Statement
for _, statement := range bktPolicy.Statements {
if statement.Effect == "Deny" {
denyStatements = append(denyStatements, statement)
continue
}
// else if statement.Effect == "Allow"
allowStatements = append(allowStatements, statement)
}
// Deny statements are enforced first once matched.
bktPolicy.Statements = append(denyStatements, allowStatements...)
// Return successfully parsed policy structure.
return nil
}

View File

@@ -1,867 +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 cmd
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/set"
)
// Common bucket actions for both read and write policies.
var (
readWriteBucketActions = []string{
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
// Add more bucket level read-write actions here.
}
readWriteObjectActions = []string{
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject",
// Add more object level read-write actions here.
}
)
// Write only actions.
var (
writeOnlyBucketActions = []string{
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads",
// Add more bucket level write actions here.
}
writeOnlyObjectActions = []string{
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:ListMultipartUploadParts",
"s3:PutObject",
// Add more object level write actions here.
}
)
// Read only actions.
var (
readOnlyBucketActions = []string{
"s3:GetBucketLocation",
"s3:ListBucket",
// Add more bucket level read actions here.
}
readOnlyObjectActions = []string{
"s3:GetObject",
// Add more object level read actions here.
}
)
// Obtain bucket statement for read-write bucketPolicy.
func getReadWriteObjectStatement(bucketName, objectPrefix string) policy.Statement {
objectResourceStatement := policy.Statement{}
objectResourceStatement.Effect = "Allow"
objectResourceStatement.Principal = policy.User{
AWS: set.StringSet{"*": struct{}{}},
}
objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", bucketARNPrefix, bucketName+"/"+objectPrefix+"*")}...)
objectResourceStatement.Actions = set.CreateStringSet(readWriteObjectActions...)
return objectResourceStatement
}
// Obtain object statement for read-write bucketPolicy.
func getReadWriteBucketStatement(bucketName, objectPrefix string) policy.Statement {
bucketResourceStatement := policy.Statement{}
bucketResourceStatement.Effect = "Allow"
bucketResourceStatement.Principal = policy.User{
AWS: set.StringSet{"*": struct{}{}},
}
bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", bucketARNPrefix, bucketName)}...)
bucketResourceStatement.Actions = set.CreateStringSet(readWriteBucketActions...)
return bucketResourceStatement
}
// Obtain statements for read-write bucketPolicy.
func getReadWriteStatement(bucketName, objectPrefix string) []policy.Statement {
statements := []policy.Statement{}
// Save the read write policy.
statements = append(statements, getReadWriteBucketStatement(bucketName, objectPrefix), getReadWriteObjectStatement(bucketName, objectPrefix))
return statements
}
// Obtain bucket statement for read only bucketPolicy.
func getReadOnlyBucketStatement(bucketName, objectPrefix string) policy.Statement {
bucketResourceStatement := policy.Statement{}
bucketResourceStatement.Effect = "Allow"
bucketResourceStatement.Principal = policy.User{
AWS: set.StringSet{"*": struct{}{}},
}
bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", bucketARNPrefix, bucketName)}...)
bucketResourceStatement.Actions = set.CreateStringSet(readOnlyBucketActions...)
return bucketResourceStatement
}
// Obtain object statement for read only bucketPolicy.
func getReadOnlyObjectStatement(bucketName, objectPrefix string) policy.Statement {
objectResourceStatement := policy.Statement{}
objectResourceStatement.Effect = "Allow"
objectResourceStatement.Principal = policy.User{
AWS: set.StringSet{"*": struct{}{}},
}
objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", bucketARNPrefix, bucketName+"/"+objectPrefix+"*")}...)
objectResourceStatement.Actions = set.CreateStringSet(readOnlyObjectActions...)
return objectResourceStatement
}
// Obtain statements for read only bucketPolicy.
func getReadOnlyStatement(bucketName, objectPrefix string) []policy.Statement {
statements := []policy.Statement{}
// Save the read only policy.
statements = append(statements, getReadOnlyBucketStatement(bucketName, objectPrefix), getReadOnlyObjectStatement(bucketName, objectPrefix))
return statements
}
// Obtain bucket statements for write only bucketPolicy.
func getWriteOnlyBucketStatement(bucketName, objectPrefix string) policy.Statement {
bucketResourceStatement := policy.Statement{}
bucketResourceStatement.Effect = "Allow"
bucketResourceStatement.Principal = policy.User{
AWS: set.StringSet{"*": struct{}{}},
}
bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", bucketARNPrefix, bucketName)}...)
bucketResourceStatement.Actions = set.CreateStringSet(writeOnlyBucketActions...)
return bucketResourceStatement
}
// Obtain object statements for write only bucketPolicy.
func getWriteOnlyObjectStatement(bucketName, objectPrefix string) policy.Statement {
objectResourceStatement := policy.Statement{}
objectResourceStatement.Effect = "Allow"
objectResourceStatement.Principal = policy.User{
AWS: set.StringSet{"*": struct{}{}},
}
objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", bucketARNPrefix, bucketName+"/"+objectPrefix+"*")}...)
objectResourceStatement.Actions = set.CreateStringSet(writeOnlyObjectActions...)
return objectResourceStatement
}
// Obtain statements for write only bucketPolicy.
func getWriteOnlyStatement(bucketName, objectPrefix string) []policy.Statement {
statements := []policy.Statement{}
// Write only policy.
// Save the write only policy.
statements = append(statements, getWriteOnlyBucketStatement(bucketName, objectPrefix), getWriteOnlyBucketStatement(bucketName, objectPrefix))
return statements
}
// Tests validate Action validator.
func TestIsValidActions(t *testing.T) {
testCases := []struct {
// input.
actions set.StringSet
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Action.
// Test case - 1.
// "s3:ListObject" is an invalid Action.
{set.CreateStringSet([]string{"s3:GetObject", "s3:ListObject", "s3:RemoveObject"}...),
errors.New("Unsupported actions found: set.StringSet{\"s3:RemoveObject\":struct {}{}, \"s3:ListObject\":struct {}{}}, please validate your policy document"), false},
// Test case - 2.
// Empty Actions.
{set.CreateStringSet([]string{}...), errors.New("Action list cannot be empty"), false},
// Test case - 3.
// "s3:DeleteEverything"" is an invalid Action.
{set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...),
errors.New("Unsupported actions found: set.StringSet{\"s3:DeleteEverything\":struct {}{}}, please validate your policy document"), false},
// Inputs with valid Action.
// Test Case - 4.
{set.CreateStringSet([]string{
"s3:*", "*", "s3:GetObject", "s3:ListBucket",
"s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject",
"s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts"}...), nil, true},
}
for i, testCase := range testCases {
err := isValidActions(testCase.actions)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
}
}
// Tests validate Effect validator.
func TestIsValidEffect(t *testing.T) {
testCases := []struct {
// input.
effect string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Effect.
// Test case - 1.
{"", errors.New("Policy effect cannot be empty"), false},
// Test case - 2.
{"DontAllow", errors.New("Unsupported Effect found: DontAllow, please validate your policy document"), false},
// Test case - 3.
{"NeverAllow", errors.New("Unsupported Effect found: NeverAllow, please validate your policy document"), false},
// Test case - 4.
{"AllowAlways", errors.New("Unsupported Effect found: AllowAlways, please validate your policy document"), false},
// Inputs with valid Effect.
// Test Case - 5.
{"Allow", nil, true},
// Test Case - 6.
{"Deny", nil, true},
}
for i, testCase := range testCases {
err := isValidEffect(testCase.effect)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// Tests validate Resources validator.
func TestIsValidResources(t *testing.T) {
testCases := []struct {
// input.
resources []string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Action.
// Test case - 1.
// Empty Resources.
{[]string{}, errors.New("Resource list cannot be empty"), false},
// Test case - 2.
// A valid resource should have prefix bucketARNPrefix.
{[]string{"my-resource"}, errors.New("Unsupported resource style found: my-resource, please validate your policy document"), false},
// Test case - 3.
// A Valid resource should have bucket name followed by bucketARNPrefix.
{[]string{bucketARNPrefix}, errors.New("Invalid resource style found: arn:aws:s3:::, please validate your policy document"), false},
// Test Case - 4.
// Valid resource shouldn't have slash('/') followed by bucketARNPrefix.
{[]string{bucketARNPrefix + "/"}, errors.New("Invalid resource style found: arn:aws:s3:::/, please validate your policy document"), false},
// Test cases with valid Resources.
{[]string{bucketARNPrefix + "my-bucket"}, nil, true},
{[]string{bucketARNPrefix + "my-bucket/Asia/*"}, nil, true},
{[]string{bucketARNPrefix + "my-bucket/Asia/India/*"}, nil, true},
}
for i, testCase := range testCases {
err := isValidResources(set.CreateStringSet(testCase.resources...))
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// Tests validate principals validator.
func TestIsValidPrincipals(t *testing.T) {
testCases := []struct {
// input.
principals []string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Principals.
// Test case - 1.
// Empty Principals list.
{[]string{}, errors.New("Principal cannot be empty"), false},
// Test case - 2.
// "*" is the only valid principal.
{[]string{"my-principal"}, errors.New("Unsupported principals found: set.StringSet{\"my-principal\":struct {}{}}, please validate your policy document"), false},
// Test case - 3.
{[]string{"*", "111122233"}, errors.New("Unsupported principals found: set.StringSet{\"111122233\":struct {}{}}, please validate your policy document"), false},
// Test case - 4.
// Test case with valid principal value.
{[]string{"*"}, nil, true},
}
for i, testCase := range testCases {
u := policy.User{
AWS: set.CreateStringSet(testCase.principals...),
}
err := isValidPrincipals(u)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// getEmptyConditionMap - returns a function that generates a
// condition key map for a given key.
func getEmptyConditionMap(conditionKey string) func() policy.ConditionMap {
emptyConditonGenerator := func() policy.ConditionMap {
emptyMap := make(policy.ConditionKeyMap)
conditions := make(policy.ConditionMap)
conditions[conditionKey] = emptyMap
return conditions
}
return emptyConditonGenerator
}
// Tests validate policy.Statement condition validator.
func TestIsValidConditions(t *testing.T) {
// returns empty conditions map.
setEmptyConditions := func() policy.ConditionMap {
return make(policy.ConditionMap)
}
// returns map with the "StringEquals" set to empty map.
setEmptyStringEquals := getEmptyConditionMap("StringEquals")
// returns map with the "StringNotEquals" set to empty map.
setEmptyStringNotEquals := getEmptyConditionMap("StringNotEquals")
// returns map with the "StringLike" set to empty map.
setEmptyStringLike := getEmptyConditionMap("StringLike")
// returns map with the "StringNotLike" set to empty map.
setEmptyStringNotLike := getEmptyConditionMap("StringNotLike")
// returns map with the "IpAddress" set to empty map.
setEmptyIPAddress := getEmptyConditionMap("IpAddress")
// returns map with "NotIpAddress" set to empty map.
setEmptyNotIPAddress := getEmptyConditionMap("NotIpAddress")
// Generate conditions.
generateConditions := func(key1, key2, value string) policy.ConditionMap {
innerMap := make(policy.ConditionKeyMap)
innerMap[key2] = set.CreateStringSet(value)
conditions := make(policy.ConditionMap)
conditions[key1] = innerMap
return conditions
}
// generate ambigious conditions.
generateAmbigiousConditions := func() policy.ConditionMap {
prefixMap := make(policy.ConditionKeyMap)
prefixMap["s3:prefix"] = set.CreateStringSet("Asia/")
conditions := make(policy.ConditionMap)
conditions["StringEquals"] = prefixMap
conditions["StringNotEquals"] = prefixMap
return conditions
}
// generate valid and non valid type in the condition map.
generateValidInvalidConditions := func() policy.ConditionMap {
innerMap := make(policy.ConditionKeyMap)
innerMap["s3:prefix"] = set.CreateStringSet("Asia/")
conditions := make(policy.ConditionMap)
conditions["StringEquals"] = innerMap
conditions["InvalidType"] = innerMap
return conditions
}
// generate valid and invalid keys for valid types in the same condition map.
generateValidInvalidConditionKeys := func() policy.ConditionMap {
innerMapValid := make(policy.ConditionKeyMap)
innerMapValid["s3:prefix"] = set.CreateStringSet("Asia/")
innerMapInValid := make(map[string]set.StringSet)
innerMapInValid["s3:invalid"] = set.CreateStringSet("Asia/")
conditions := make(policy.ConditionMap)
conditions["StringEquals"] = innerMapValid
conditions["StringEquals"] = innerMapInValid
return conditions
}
// List of Conditions used for test cases.
testConditions := []policy.ConditionMap{
generateConditions("StringValues", "s3:max-keys", "100"),
generateConditions("StringEquals", "s3:Object", "100"),
generateAmbigiousConditions(),
generateValidInvalidConditions(),
generateValidInvalidConditionKeys(),
setEmptyConditions(),
setEmptyStringEquals(),
setEmptyStringNotEquals(),
setEmptyStringLike(),
setEmptyStringNotLike(),
setEmptyIPAddress(),
setEmptyNotIPAddress(),
generateConditions("StringEquals", "s3:prefix", "Asia/"),
generateConditions("StringEquals", "s3:max-keys", "100"),
generateConditions("StringNotEquals", "s3:prefix", "Asia/"),
generateConditions("StringNotEquals", "s3:max-keys", "100"),
}
getObjectActionSet := set.CreateStringSet("s3:GetObject")
roBucketActionSet := set.CreateStringSet(readOnlyBucketActions...)
maxKeysConditionErr := fmt.Errorf("Unsupported condition key %s for the given actions %s, "+
"please validate your policy document", "s3:max-keys", getObjectActionSet)
testCases := []struct {
inputActions set.StringSet
inputCondition policy.ConditionMap
// expected result.
expectedErr error
// flag indicating whether test should pass.
shouldPass bool
}{
// Malformed conditions.
// Test case - 1.
// "StringValues" is an invalid type.
{roBucketActionSet, testConditions[0], fmt.Errorf("Unsupported condition type 'StringValues', " +
"please validate your policy document"), false},
// Test case - 2.
// "s3:Object" is an invalid key.
{roBucketActionSet, testConditions[1], fmt.Errorf("Unsupported condition key " +
"'StringEquals', please validate your policy document"), false},
// Test case - 3.
// Test case with Ambigious conditions set.
{roBucketActionSet, testConditions[2], fmt.Errorf("Ambigious condition values for key 's3:prefix', " +
"please validate your policy document"), false},
// Test case - 4.
// Test case with valid and invalid condition types.
{roBucketActionSet, testConditions[3], fmt.Errorf("Unsupported condition type 'InvalidType', " +
"please validate your policy document"), false},
// Test case - 5.
// Test case with valid and invalid condition keys.
{roBucketActionSet, testConditions[4], fmt.Errorf("Unsupported condition key 'StringEquals', " +
"please validate your policy document"), false},
// Test cases with valid conditions.
// Test case - 6.
{roBucketActionSet, testConditions[5], nil, true},
// Test case - 7.
{roBucketActionSet, testConditions[6], nil, true},
// Test case - 8.
{roBucketActionSet, testConditions[7], nil, true},
// Test case - 9.
{roBucketActionSet, testConditions[8], nil, true},
// Test case - 10.
{roBucketActionSet, testConditions[9], nil, true},
// Test case - 11.
{roBucketActionSet, testConditions[10], nil, true},
// Test case - 12.
{roBucketActionSet, testConditions[11], nil, true},
// Test case - 13.
{roBucketActionSet, testConditions[12], nil, true},
// Test case - 11.
{roBucketActionSet, testConditions[13], nil, true},
// Test case - 12.
{roBucketActionSet, testConditions[14], nil, true},
// Test case - 13.
{getObjectActionSet, testConditions[15], maxKeysConditionErr, false},
}
for i, testCase := range testCases {
actualErr := isValidConditions(testCase.inputActions, testCase.inputCondition)
if actualErr != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, actualErr.Error())
}
if actualErr == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.expectedErr.Error())
}
// Failed as expected, but does it fail for the expected reason.
if actualErr != nil && !testCase.shouldPass {
if actualErr.Error() != testCase.expectedErr.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.expectedErr.Error(), actualErr.Error())
}
}
}
}
// Tests validate Policy Action and Resource fields.
func TestCheckbucketPolicyResources(t *testing.T) {
// constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
setValidPrefixActions := func(statements []policy.Statement) []policy.Statement {
statements[0].Actions = set.CreateStringSet([]string{"s3:DeleteObject", "s3:PutObject"}...)
return statements
}
// contracting policy statement with recursive resources.
// should result in ErrMalformedPolicy
setRecurseResource := func(statements []policy.Statement) []policy.Statement {
statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/Asia/*", "arn:aws:s3:::minio-bucket/Asia/India/*"}...)
return statements
}
// constructing policy statement with lexically close characters.
// should not result in ErrMalformedPolicy
setResourceLexical := func(statements []policy.Statement) []policy.Statement {
statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}...)
return statements
}
// List of bucketPolicy used for tests.
bucketAccessPolicies := []policy.BucketAccessPolicy{
// bucketPolicy - 1.
// Contains valid read only policy statement.
{Version: "1.0", Statements: getReadOnlyStatement("minio-bucket", "")},
// bucketPolicy - 2.
// Contains valid read-write only policy statement.
{Version: "1.0", Statements: getReadWriteStatement("minio-bucket", "Asia/")},
// bucketPolicy - 3.
// Contains valid write only policy statement.
{Version: "1.0", Statements: getWriteOnlyStatement("minio-bucket", "Asia/India/")},
// bucketPolicy - 4.
// Contains invalidPrefixActions.
// Since resourcePrefix is not to the bucket-name, it return ErrMalformedPolicy.
{Version: "1.0", Statements: getReadOnlyStatement("minio-bucket-fail", "Asia/India/")},
// bucketPolicy - 5.
// constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
// but bucket part of the resource is not equal to the bucket name.
// this results in return of ErrMalformedPolicy.
{Version: "1.0", Statements: setValidPrefixActions(getWriteOnlyStatement("minio-bucket-fail", "Asia/India/"))},
// bucketPolicy - 6.
// contracting policy statement with recursive resources.
// should result in ErrMalformedPolicy
{Version: "1.0", Statements: setRecurseResource(setValidPrefixActions(getWriteOnlyStatement("minio-bucket", "")))},
// BucketPolciy - 7.
// constructing policy statement with non recursive but
// lexically close resources.
// should result in ErrNone.
{Version: "1.0", Statements: setResourceLexical(setValidPrefixActions(getWriteOnlyStatement("minio-bucket", "oo")))},
}
testCases := []struct {
inputPolicy policy.BucketAccessPolicy
// expected results.
apiErrCode APIErrorCode
// Flag indicating whether the test should pass.
shouldPass bool
}{
// Test case - 1.
{bucketAccessPolicies[0], ErrNone, true},
// Test case - 2.
{bucketAccessPolicies[1], ErrNone, true},
// Test case - 3.
{bucketAccessPolicies[2], ErrNone, true},
// Test case - 4.
// contains invalidPrefixActions (check bucket-policy-parser.go).
// Resource prefix will not be equal to the bucket name in this case.
{bucketAccessPolicies[3], ErrMalformedPolicy, false},
// Test case - 5.
// actions contain invalidPrefixActions (check bucket-policy-parser.go).
// Resource prefix bucket part is not equal to the bucket name in this case.
{bucketAccessPolicies[4], ErrMalformedPolicy, false},
// Test case - 6.
// contracting policy statement with recursive resources.
// should result in ErrPolicyNesting.
{bucketAccessPolicies[5], ErrPolicyNesting, false},
// Test case - 7.
// constructing policy statement with lexically close
// characters.
// should result in ErrNone.
{bucketAccessPolicies[6], ErrNone, true},
}
for i, testCase := range testCases {
apiErrCode := checkBucketPolicyResources("minio-bucket", testCase.inputPolicy)
if apiErrCode != ErrNone && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with Errocode %v", i+1, apiErrCode)
}
if apiErrCode == ErrNone && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with ErrCode %v, but passed instead", i+1, testCase.apiErrCode)
}
// Failed as expected, but does it fail for the expected reason.
if apiErrCode != ErrNone && !testCase.shouldPass {
if testCase.apiErrCode != apiErrCode {
t.Errorf("Test %d: Expected to fail with error code %v, but instead failed with error code %v", i+1, testCase.apiErrCode, apiErrCode)
}
}
}
}
// Tests validate parsing of BucketAccessPolicy.
func TestParseBucketPolicy(t *testing.T) {
// set Unsupported Actions.
setUnsupportedActions := func(statements []policy.Statement) []policy.Statement {
// "s3:DeleteEverything"" is an Unsupported Action.
statements[0].Actions = set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...)
return statements
}
// set unsupported Effect.
setUnsupportedEffect := func(statements []policy.Statement) []policy.Statement {
// Effect "Don't allow" is Unsupported.
statements[0].Effect = "DontAllow"
return statements
}
// set unsupported principals.
setUnsupportedPrincipals := func(statements []policy.Statement) []policy.Statement {
// "User1111"" is an Unsupported Principal.
statements[0].Principal = policy.User{
AWS: set.CreateStringSet([]string{"*", "User1111"}...),
}
return statements
}
// set unsupported Resources.
setUnsupportedResources := func(statements []policy.Statement) []policy.Statement {
// "s3:DeleteEverything"" is an Unsupported Action.
statements[0].Resources = set.CreateStringSet([]string{"my-resource"}...)
return statements
}
// List of bucketPolicy used for test cases.
bucketAccesPolicies := []policy.BucketAccessPolicy{
// bucketPolicy - 0.
// bucketPolicy statement empty.
{Version: "1.0"},
// bucketPolicy - 1.
// bucketPolicy version empty.
{Version: "", Statements: []policy.Statement{}},
// bucketPolicy - 2.
// Readonly bucketPolicy.
{Version: "1.0", Statements: getReadOnlyStatement("minio-bucket", "")},
// bucketPolicy - 3.
// Read-Write bucket policy.
{Version: "1.0", Statements: getReadWriteStatement("minio-bucket", "Asia/")},
// bucketPolicy - 4.
// Write only bucket policy.
{Version: "1.0", Statements: getWriteOnlyStatement("minio-bucket", "Asia/India/")},
// bucketPolicy - 5.
// bucketPolicy statement contains unsupported action.
{Version: "1.0", Statements: setUnsupportedActions(getReadOnlyStatement("minio-bucket", ""))},
// bucketPolicy - 6.
// bucketPolicy statement contains unsupported Effect.
{Version: "1.0", Statements: setUnsupportedEffect(getReadWriteStatement("minio-bucket", "Asia/"))},
// bucketPolicy - 7.
// bucketPolicy statement contains unsupported Principal.
{Version: "1.0", Statements: setUnsupportedPrincipals(getWriteOnlyStatement("minio-bucket", "Asia/India/"))},
// bucketPolicy - 8.
// bucketPolicy statement contains unsupported Resource.
{Version: "1.0", Statements: setUnsupportedResources(getWriteOnlyStatement("minio-bucket", "Asia/India/"))},
}
testCases := []struct {
inputPolicy policy.BucketAccessPolicy
// expected results.
expectedPolicy policy.BucketAccessPolicy
err error
// Flag indicating whether the test should pass.
shouldPass bool
}{
// Test case - 1.
// bucketPolicy statement empty.
{bucketAccesPolicies[0], policy.BucketAccessPolicy{}, errors.New("Policy statement cannot be empty"), false},
// Test case - 2.
// bucketPolicy version empty.
{bucketAccesPolicies[1], policy.BucketAccessPolicy{}, errors.New("Policy version cannot be empty"), false},
// Test case - 3.
// Readonly bucketPolicy.
{bucketAccesPolicies[2], bucketAccesPolicies[2], nil, true},
// Test case - 4.
// Read-Write bucket policy.
{bucketAccesPolicies[3], bucketAccesPolicies[3], nil, true},
// Test case - 5.
// Write only bucket policy.
{bucketAccesPolicies[4], bucketAccesPolicies[4], nil, true},
// Test case - 6.
// bucketPolicy statement contains unsupported action.
{bucketAccesPolicies[5], bucketAccesPolicies[5], fmt.Errorf("Unsupported actions found: set.StringSet{\"s3:DeleteEverything\":struct {}{}}, please validate your policy document"), false},
// Test case - 7.
// bucketPolicy statement contains unsupported Effect.
{bucketAccesPolicies[6], bucketAccesPolicies[6], fmt.Errorf("Unsupported Effect found: DontAllow, please validate your policy document"), false},
// Test case - 8.
// bucketPolicy statement contains unsupported Principal.
{bucketAccesPolicies[7], bucketAccesPolicies[7], fmt.Errorf("Unsupported principals found: set.StringSet{\"User1111\":struct {}{}}, please validate your policy document"), false},
// Test case - 9.
// bucketPolicy statement contains unsupported Resource.
{bucketAccesPolicies[8], bucketAccesPolicies[8], fmt.Errorf("Unsupported resource style found: my-resource, please validate your policy document"), false},
}
for i, testCase := range testCases {
var buffer bytes.Buffer
encoder := json.NewEncoder(&buffer)
err := encoder.Encode(testCase.inputPolicy)
if err != nil {
t.Fatalf("Test %d: Couldn't Marshal bucket policy %s", i+1, err)
}
var actualAccessPolicy = policy.BucketAccessPolicy{}
err = parseBucketPolicy(&buffer, &actualAccessPolicy)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
// Test passes as expected, but the output values are verified for correctness here.
if err == nil && testCase.shouldPass {
if !reflect.DeepEqual(testCase.expectedPolicy, actualAccessPolicy) {
t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1)
}
}
}
}
func TestAWSRefererCondition(t *testing.T) {
resource := set.CreateStringSet([]string{
fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/"+"Asia"+"*"),
}...)
conditionsKeyMap := make(policy.ConditionKeyMap)
conditionsKeyMap.Add("aws:Referer",
set.CreateStringSet("www.example.com",
"http://www.example.com"))
requestConditionMap := make(policy.ConditionKeyMap)
requestConditionMap["referer"] = set.CreateStringSet("www.example.com")
testCases := []struct {
effect string
conditionKey string
match bool
}{
{
effect: "Allow",
conditionKey: "StringLike",
match: true,
},
{
effect: "Allow",
conditionKey: "StringNotLike",
match: false,
},
{
effect: "Deny",
conditionKey: "StringLike",
match: true,
},
{
effect: "Deny",
conditionKey: "StringNotLike",
match: false,
},
}
for i, test := range testCases {
conditions := make(policy.ConditionMap)
conditions[test.conditionKey] = conditionsKeyMap
allowStatement := policy.Statement{
Sid: "Testing AWS referer condition",
Effect: test.effect,
Principal: policy.User{
AWS: set.CreateStringSet("*"),
},
Resources: resource,
Conditions: conditions,
}
if result := bucketPolicyConditionMatch(requestConditionMap, allowStatement); result != test.match {
t.Errorf("Test %d - Expected conditons to evaluate to %v but got %v",
i+1, test.match, result)
}
}
}
func TestAWSSourceIPCondition(t *testing.T) {
resource := set.CreateStringSet([]string{
fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/"+"Asia"+"*"),
}...)
conditionsKeyMap := make(policy.ConditionKeyMap)
// Test both IPv4 and IPv6 addresses.
conditionsKeyMap.Add("aws:SourceIp",
set.CreateStringSet("54.240.143.0/24",
"2001:DB8:1234:5678::/64"))
requestConditionMap := make(policy.ConditionKeyMap)
requestConditionMap["ip"] = set.CreateStringSet("54.240.143.2")
testCases := []struct {
effect string
conditionKey string
match bool
}{
{
effect: "Allow",
conditionKey: "IpAddress",
match: true,
},
{
effect: "Allow",
conditionKey: "NotIpAddress",
match: false,
},
{
effect: "Deny",
conditionKey: "IpAddress",
match: true,
},
{
effect: "Deny",
conditionKey: "NotIpAddress",
match: false,
},
}
for i, test := range testCases {
conditions := make(policy.ConditionMap)
conditions[test.conditionKey] = conditionsKeyMap
allowStatement := policy.Statement{
Sid: "Testing AWS referer condition",
Effect: test.effect,
Principal: policy.User{
AWS: set.CreateStringSet("*"),
},
Resources: resource,
Conditions: conditions,
}
if result := bucketPolicyConditionMatch(requestConditionMap, allowStatement); result != test.match {
t.Errorf("Test %d - Expected conditons to evaluate to %v but got %v",
i+1, test.match, result)
}
}
}

View File

@@ -1,206 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015, 2016, 2017, 2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"bytes"
"context"
"encoding/json"
"io"
"reflect"
"sync"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash"
)
const (
// Static prefix to be used while constructing bucket ARN.
// refer to S3 docs for more info.
bucketARNPrefix = "arn:aws:s3:::"
// Bucket policy config name.
bucketPolicyConfig = "policy.json"
)
// Global bucket policies list, policies are enforced on each bucket looking
// through the policies here.
type bucketPolicies struct {
rwMutex *sync.RWMutex
// Collection of 'bucket' policies.
bucketPolicyConfigs map[string]policy.BucketAccessPolicy
}
// Fetch bucket policy for a given bucket.
func (bp bucketPolicies) GetBucketPolicy(bucket string) policy.BucketAccessPolicy {
bp.rwMutex.RLock()
defer bp.rwMutex.RUnlock()
return bp.bucketPolicyConfigs[bucket]
}
// Set a new bucket policy for a bucket, this operation will overwrite
// any previous bucket policies for the bucket.
func (bp *bucketPolicies) SetBucketPolicy(bucket string, newpolicy policy.BucketAccessPolicy) error {
bp.rwMutex.Lock()
defer bp.rwMutex.Unlock()
if reflect.DeepEqual(newpolicy, emptyBucketPolicy) {
return errInvalidArgument
}
bp.bucketPolicyConfigs[bucket] = newpolicy
return nil
}
// Delete bucket policy from struct for a given bucket.
func (bp *bucketPolicies) DeleteBucketPolicy(bucket string) error {
bp.rwMutex.Lock()
defer bp.rwMutex.Unlock()
delete(bp.bucketPolicyConfigs, bucket)
return nil
}
// Intialize all bucket policies.
func initBucketPolicies(objAPI ObjectLayer) (*bucketPolicies, error) {
if objAPI == nil {
return nil, errInvalidArgument
}
// List buckets to proceed loading all notification configuration.
buckets, err := objAPI.ListBuckets(context.Background())
if err != nil {
return nil, err
}
policies := make(map[string]policy.BucketAccessPolicy)
// Loads bucket policy.
for _, bucket := range buckets {
bp, pErr := ReadBucketPolicy(bucket.Name, objAPI)
if pErr != nil {
// net.Dial fails for rpc client or any
// other unexpected errors during net.Dial.
if !IsErrIgnored(pErr, errDiskNotFound) {
if !isErrBucketPolicyNotFound(pErr) {
return nil, pErr
}
}
// Continue to load other bucket policies if possible.
continue
}
policies[bucket.Name] = bp
}
// Return all bucket policies.
return &bucketPolicies{
rwMutex: &sync.RWMutex{},
bucketPolicyConfigs: policies,
}, nil
}
// readBucketPolicyJSON - reads bucket policy for an input bucket, returns BucketPolicyNotFound
// if bucket policy is not found.
func readBucketPolicyJSON(bucket string, objAPI ObjectLayer) (bucketPolicyReader io.Reader, err error) {
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
var buffer bytes.Buffer
ctx := logger.SetReqInfo(context.Background(), &logger.ReqInfo{BucketName: bucket})
err = objAPI.GetObject(ctx, minioMetaBucket, policyPath, 0, -1, &buffer, "")
if err != nil {
if isErrObjectNotFound(err) || isErrIncompleteBody(err) {
return nil, PolicyNotFound{Bucket: bucket}
}
return nil, err
}
return &buffer, nil
}
// ReadBucketPolicy - reads bucket policy for an input bucket, returns BucketPolicyNotFound
// if bucket policy is not found. This function also parses the bucket policy into an object.
func ReadBucketPolicy(bucket string, objAPI ObjectLayer) (policy.BucketAccessPolicy, error) {
// Read bucket policy JSON.
bucketPolicyReader, err := readBucketPolicyJSON(bucket, objAPI)
if err != nil {
return emptyBucketPolicy, err
}
// Parse the saved policy.
var bp policy.BucketAccessPolicy
if err = parseBucketPolicy(bucketPolicyReader, &bp); err != nil {
return emptyBucketPolicy, err
}
return bp, nil
}
// removeBucketPolicy - removes any previously written bucket policy. Returns BucketPolicyNotFound
// if no policies are found.
func removeBucketPolicy(ctx context.Context, bucket string, objAPI ObjectLayer) error {
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
err := objAPI.DeleteObject(ctx, minioMetaBucket, policyPath)
if err != nil {
if _, ok := err.(ObjectNotFound); ok {
return BucketPolicyNotFound{Bucket: bucket}
}
return err
}
return nil
}
// writeBucketPolicy - save a bucket policy that is assumed to be validated.
func writeBucketPolicy(ctx context.Context, bucket string, objAPI ObjectLayer, bpy policy.BucketAccessPolicy) error {
buf, err := json.Marshal(bpy)
if err != nil {
logger.LogIf(ctx, err)
return err
}
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
hashReader, err := hash.NewReader(bytes.NewReader(buf), int64(len(buf)), "", getSHA256Hash(buf))
if err != nil {
logger.LogIf(ctx, err)
return err
}
if _, err = objAPI.PutObject(ctx, minioMetaBucket, policyPath, hashReader, nil); err != nil {
return err
}
return nil
}
// persistAndNotifyBucketPolicyChange - takes a policyChange argument,
// persists it to storage, and notify nodes in the cluster about the
// change. In-memory state is updated in response to the notification.
func persistAndNotifyBucketPolicyChange(ctx context.Context, bucket string, isRemove bool, bktPolicy policy.BucketAccessPolicy, objAPI ObjectLayer) error {
if isRemove {
err := removeBucketPolicy(ctx, bucket, objAPI)
if err != nil {
return err
}
} else {
if reflect.DeepEqual(bktPolicy, emptyBucketPolicy) {
return errInvalidArgument
}
if err := writeBucketPolicy(ctx, bucket, objAPI, bktPolicy); err != nil {
return err
}
}
return nil
}

View File

@@ -24,16 +24,15 @@ import (
"io/ioutil"
"os"
"path"
"reflect"
"sort"
"sync"
"time"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/lock"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/policy"
)
// FSObjects - Implements fs object layer.
@@ -60,9 +59,6 @@ type FSObjects struct {
// To manage the appendRoutine go-routines
nsMutex *nsLockMap
// Variable represents bucket policies in memory.
bucketPolicies *bucketPolicies
}
// Represents the background append file.
@@ -139,15 +135,14 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) {
// or cause changes on backend format.
fs.fsFormatRlk = rlk
// Initialize and load bucket policies.
fs.bucketPolicies, err = initBucketPolicies(fs)
if err != nil {
return nil, fmt.Errorf("Unable to load all bucket policies. %s", err)
}
// Initialize notification system.
if err = globalNotificationSys.Init(fs); err != nil {
return nil, fmt.Errorf("Unable to initialize event notification. %s", err)
return nil, fmt.Errorf("Unable to initialize notification system. %v", err)
}
// Initialize policy system.
if err = globalPolicySys.Init(fs); err != nil {
return nil, fmt.Errorf("Unable to initialize policy system. %v", err)
}
go fs.cleanupStaleMultipartUploads(ctx, globalMultipartCleanupInterval, globalMultipartExpiry, globalServiceDoneCh)
@@ -1058,22 +1053,18 @@ func (fs *FSObjects) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error)
}
// SetBucketPolicy sets policy on bucket
func (fs *FSObjects) SetBucketPolicy(ctx context.Context, bucket string, policy policy.BucketAccessPolicy) error {
return persistAndNotifyBucketPolicyChange(ctx, bucket, false, policy, fs)
func (fs *FSObjects) SetBucketPolicy(ctx context.Context, bucket string, policy *policy.Policy) error {
return savePolicyConfig(fs, bucket, policy)
}
// GetBucketPolicy will get policy on bucket
func (fs *FSObjects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
policy := fs.bucketPolicies.GetBucketPolicy(bucket)
if reflect.DeepEqual(policy, emptyBucketPolicy) {
return ReadBucketPolicy(bucket, fs)
}
return policy, nil
func (fs *FSObjects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
return GetPolicyConfig(fs, bucket)
}
// DeleteBucketPolicy deletes all policies on bucket
func (fs *FSObjects) DeleteBucketPolicy(ctx context.Context, bucket string) error {
return persistAndNotifyBucketPolicyChange(ctx, bucket, true, emptyBucketPolicy, fs)
return removePolicyConfig(ctx, fs, bucket)
}
// ListObjectsV2 lists all blobs in bucket filtered by prefix
@@ -1093,19 +1084,6 @@ func (fs *FSObjects) ListObjectsV2(ctx context.Context, bucket, prefix, continua
return listObjectsV2Info, err
}
// RefreshBucketPolicy refreshes cache policy with what's on disk.
func (fs *FSObjects) RefreshBucketPolicy(ctx context.Context, bucket string) error {
policy, err := ReadBucketPolicy(bucket, fs)
if err != nil {
if reflect.DeepEqual(policy, emptyBucketPolicy) {
return fs.bucketPolicies.DeleteBucketPolicy(bucket)
}
return err
}
return fs.bucketPolicies.SetBucketPolicy(bucket, policy)
}
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
func (fs *FSObjects) IsNotificationSupported() bool {
return true

View File

@@ -281,7 +281,7 @@ func ErrorRespToObjectError(err error, params ...string) error {
case "BucketNotEmpty":
err = BucketNotEmpty{}
case "NoSuchBucketPolicy":
err = PolicyNotFound{}
err = BucketPolicyNotFound{}
case "InvalidBucketName":
err = BucketNameInvalid{Bucket: bucket}
case "NoSuchBucket":

View File

@@ -167,9 +167,12 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
initNSLock(false) // Enable local namespace lock.
// Initialize notification system.
// Create new notification system.
globalNotificationSys, err = NewNotificationSys(globalServerConfig, EndpointList{})
logger.FatalIf(err, "Unable to initialize notification system.")
logger.FatalIf(err, "Unable to create new notification system.")
// Create new policy system.
globalPolicySys = NewPolicySys()
newObject, err := gw.NewGatewayLayer(globalServerConfig.GetCredential())
logger.FatalIf(err, "Unable to initialize gateway layer")

View File

@@ -20,10 +20,10 @@ import (
"context"
"time"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/policy"
)
// GatewayUnsupported list of unsupported call stubs for gateway.
@@ -72,15 +72,15 @@ func (a GatewayUnsupported) CompleteMultipartUpload(ctx context.Context, bucket
}
// SetBucketPolicy sets policy on bucket
func (a GatewayUnsupported) SetBucketPolicy(ctx context.Context, bucket string, policyInfo policy.BucketAccessPolicy) error {
func (a GatewayUnsupported) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
logger.LogIf(ctx, NotImplemented{})
return NotImplemented{}
}
// GetBucketPolicy will get policy on bucket
func (a GatewayUnsupported) GetBucketPolicy(ctx context.Context, bucket string) (bal policy.BucketAccessPolicy, err error) {
func (a GatewayUnsupported) GetBucketPolicy(ctx context.Context, bucket string) (bucketPolicy *policy.Policy, err error) {
logger.LogIf(ctx, NotImplemented{})
return bal, NotImplemented{}
return nil, NotImplemented{}
}
// DeleteBucketPolicy deletes all policies on bucket

View File

@@ -34,10 +34,12 @@ import (
"github.com/Azure/azure-sdk-for-go/storage"
humanize "github.com/dustin/go-humanize"
"github.com/minio/cli"
"github.com/minio/minio-go/pkg/policy"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
sha256 "github.com/minio/sha256-simd"
minio "github.com/minio/minio/cmd"
@@ -1020,10 +1022,16 @@ func (a *azureObjects) CompleteMultipartUpload(ctx context.Context, bucket, obje
// storage.ContainerAccessTypePrivate - none in minio terminology
// As the common denominator for minio and azure is readonly and none, we support
// these two policies at the bucket level.
func (a *azureObjects) SetBucketPolicy(ctx context.Context, bucket string, policyInfo policy.BucketAccessPolicy) error {
var policies []minio.BucketAccessPolicy
func (a *azureObjects) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
policyInfo, err := minio.PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
logger.LogIf(ctx, err)
return azureToObjectError(err, bucket)
}
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket, "") {
var policies []minio.BucketAccessPolicy
for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, bucket, "") {
policies = append(policies, minio.BucketAccessPolicy{
Prefix: prefix,
Policy: policy,
@@ -1038,7 +1046,7 @@ func (a *azureObjects) SetBucketPolicy(ctx context.Context, bucket string, polic
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
if policies[0].Policy != policy.BucketPolicyReadOnly {
if policies[0].Policy != miniogopolicy.BucketPolicyReadOnly {
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
@@ -1047,31 +1055,47 @@ func (a *azureObjects) SetBucketPolicy(ctx context.Context, bucket string, polic
AccessPolicies: nil,
}
container := a.client.GetContainerReference(bucket)
err := container.SetPermissions(perm, nil)
err = container.SetPermissions(perm, nil)
logger.LogIf(ctx, err)
return azureToObjectError(err, bucket)
}
// GetBucketPolicy - Get the container ACL and convert it to canonical []bucketAccessPolicy
func (a *azureObjects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"}
func (a *azureObjects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
container := a.client.GetContainerReference(bucket)
perm, err := container.GetPermissions(nil)
if err != nil {
logger.LogIf(ctx, err)
return policy.BucketAccessPolicy{}, azureToObjectError(err, bucket)
return nil, azureToObjectError(err, bucket)
}
switch perm.AccessType {
case storage.ContainerAccessTypePrivate:
logger.LogIf(ctx, minio.PolicyNotFound{Bucket: bucket})
return policy.BucketAccessPolicy{}, minio.PolicyNotFound{Bucket: bucket}
case storage.ContainerAccessTypeContainer:
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadOnly, bucket, "")
default:
if perm.AccessType == storage.ContainerAccessTypePrivate {
logger.LogIf(ctx, minio.BucketPolicyNotFound{Bucket: bucket})
return nil, minio.BucketPolicyNotFound{Bucket: bucket}
} else if perm.AccessType != storage.ContainerAccessTypeContainer {
logger.LogIf(ctx, minio.NotImplemented{})
return policy.BucketAccessPolicy{}, azureToObjectError(minio.NotImplemented{})
return nil, azureToObjectError(minio.NotImplemented{})
}
return policyInfo, nil
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(
policy.GetBucketLocationAction,
policy.ListBucketAction,
policy.GetObjectAction,
),
policy.NewResourceSet(
policy.NewResource(bucket, ""),
policy.NewResource(bucket, "*"),
),
condition.NewFunctions(),
),
},
}, nil
}
// DeleteBucketPolicy - Set the container ACL to "private"

View File

@@ -30,10 +30,12 @@ import (
b2 "github.com/minio/blazer/base"
"github.com/minio/cli"
"github.com/minio/minio-go/pkg/policy"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
h2 "github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
minio "github.com/minio/minio/cmd"
)
@@ -722,10 +724,15 @@ func (l *b2Objects) CompleteMultipartUpload(ctx context.Context, bucket string,
// bucketType.AllPublic - bucketTypeReadOnly means that anybody can download the files is the bucket;
// bucketType.AllPrivate - bucketTypePrivate means that you need an authorization token to download them.
// Default is AllPrivate for all buckets.
func (l *b2Objects) SetBucketPolicy(ctx context.Context, bucket string, policyInfo policy.BucketAccessPolicy) error {
var policies []minio.BucketAccessPolicy
func (l *b2Objects) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
policyInfo, err := minio.PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
return b2ToObjectError(err, bucket)
}
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket, "") {
var policies []minio.BucketAccessPolicy
for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, bucket, "") {
policies = append(policies, minio.BucketAccessPolicy{
Prefix: prefix,
Policy: policy,
@@ -740,7 +747,7 @@ func (l *b2Objects) SetBucketPolicy(ctx context.Context, bucket string, policyIn
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
if policies[0].Policy != policy.BucketPolicyReadOnly {
if policies[0].Policy != miniogopolicy.BucketPolicyReadOnly {
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
@@ -756,21 +763,39 @@ func (l *b2Objects) SetBucketPolicy(ctx context.Context, bucket string, policyIn
// GetBucketPolicy, returns the current bucketType from B2 backend and convert
// it into S3 compatible bucket policy info.
func (l *b2Objects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"}
func (l *b2Objects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return policyInfo, err
}
if bkt.Type == bucketTypeReadOnly {
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadOnly, bucket, "")
return policyInfo, nil
return nil, err
}
// bkt.Type can also be snapshot, but it is only allowed through B2 browser console,
// just return back as policy not found for all cases.
// CreateBucket always sets the value to allPrivate by default.
logger.LogIf(ctx, minio.PolicyNotFound{Bucket: bucket})
return policy.BucketAccessPolicy{}, minio.PolicyNotFound{Bucket: bucket}
if bkt.Type != bucketTypeReadOnly {
logger.LogIf(ctx, minio.BucketPolicyNotFound{Bucket: bucket})
return nil, minio.BucketPolicyNotFound{Bucket: bucket}
}
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(
policy.GetBucketLocationAction,
policy.ListBucketAction,
policy.GetObjectAction,
),
policy.NewResourceSet(
policy.NewResource(bucket, ""),
policy.NewResource(bucket, "*"),
),
condition.NewFunctions(),
),
},
}, nil
}
// DeleteBucketPolicy - resets the bucketType of bucket on B2 to 'allPrivate'.

View File

@@ -33,10 +33,12 @@ import (
"cloud.google.com/go/storage"
humanize "github.com/dustin/go-humanize"
"github.com/minio/cli"
"github.com/minio/minio-go/pkg/policy"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
"google.golang.org/api/googleapi"
"google.golang.org/api/iterator"
@@ -1132,10 +1134,16 @@ func (l *gcsGateway) CompleteMultipartUpload(ctx context.Context, bucket string,
}
// SetBucketPolicy - Set policy on bucket
func (l *gcsGateway) SetBucketPolicy(ctx context.Context, bucket string, policyInfo policy.BucketAccessPolicy) error {
var policies []minio.BucketAccessPolicy
func (l *gcsGateway) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
policyInfo, err := minio.PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
logger.LogIf(ctx, err)
return gcsToObjectError(err, bucket)
}
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, bucket, "") {
var policies []minio.BucketAccessPolicy
for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, bucket, "") {
policies = append(policies, minio.BucketAccessPolicy{
Prefix: prefix,
Policy: policy,
@@ -1154,7 +1162,7 @@ func (l *gcsGateway) SetBucketPolicy(ctx context.Context, bucket string, policyI
}
acl := l.client.Bucket(bucket).ACL()
if policies[0].Policy == policy.BucketPolicyNone {
if policies[0].Policy == miniogopolicy.BucketPolicyNone {
if err := acl.Delete(l.ctx, storage.AllUsers); err != nil {
logger.LogIf(ctx, err)
return gcsToObjectError(err, bucket)
@@ -1164,9 +1172,9 @@ func (l *gcsGateway) SetBucketPolicy(ctx context.Context, bucket string, policyI
var role storage.ACLRole
switch policies[0].Policy {
case policy.BucketPolicyReadOnly:
case miniogopolicy.BucketPolicyReadOnly:
role = storage.RoleReader
case policy.BucketPolicyWriteOnly:
case miniogopolicy.BucketPolicyWriteOnly:
role = storage.RoleWriter
default:
logger.LogIf(ctx, minio.NotImplemented{})
@@ -1182,30 +1190,63 @@ func (l *gcsGateway) SetBucketPolicy(ctx context.Context, bucket string, policyI
}
// GetBucketPolicy - Get policy on bucket
func (l *gcsGateway) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
func (l *gcsGateway) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
rules, err := l.client.Bucket(bucket).ACL().List(l.ctx)
if err != nil {
logger.LogIf(ctx, err)
return policy.BucketAccessPolicy{}, gcsToObjectError(err, bucket)
return nil, gcsToObjectError(err, bucket)
}
policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"}
var readOnly, writeOnly bool
for _, r := range rules {
if r.Entity != storage.AllUsers || r.Role == storage.RoleOwner {
continue
}
switch r.Role {
case storage.RoleReader:
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadOnly, bucket, "")
readOnly = true
case storage.RoleWriter:
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyWriteOnly, bucket, "")
writeOnly = true
}
}
// Return NoSuchBucketPolicy error, when policy is not set
if len(policyInfo.Statements) == 0 {
logger.LogIf(ctx, minio.PolicyNotFound{})
return policy.BucketAccessPolicy{}, gcsToObjectError(minio.PolicyNotFound{}, bucket)
actionSet := policy.NewActionSet()
if readOnly {
actionSet.Add(policy.GetBucketLocationAction)
actionSet.Add(policy.ListBucketAction)
actionSet.Add(policy.GetObjectAction)
}
return policyInfo, nil
if writeOnly {
actionSet.Add(policy.GetBucketLocationAction)
actionSet.Add(policy.ListBucketMultipartUploadsAction)
actionSet.Add(policy.AbortMultipartUploadAction)
actionSet.Add(policy.DeleteObjectAction)
actionSet.Add(policy.ListMultipartUploadPartsAction)
actionSet.Add(policy.PutObjectAction)
}
// Return NoSuchBucketPolicy error, when policy is not set
if len(actionSet) == 0 {
logger.LogIf(ctx, minio.BucketPolicyNotFound{})
return nil, gcsToObjectError(minio.BucketPolicyNotFound{}, bucket)
}
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
actionSet,
policy.NewResourceSet(
policy.NewResource(bucket, ""),
policy.NewResource(bucket, "*"),
),
condition.NewFunctions(),
),
},
}, nil
}
// DeleteBucketPolicy - Delete all policies on bucket

View File

@@ -20,9 +20,9 @@ import (
"context"
"github.com/minio/cli"
"github.com/minio/minio-go/pkg/policy"
minio "github.com/minio/minio/cmd"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/policy"
)
const (
@@ -132,6 +132,6 @@ func (l *nasObjects) IsNotificationSupported() bool {
}
// GetBucketPolicy will get policy on bucket
func (l *nasObjects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
return minio.ReadBucketPolicy(bucket, l)
func (l *nasObjects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
return minio.GetPolicyConfig(l, bucket)
}

View File

@@ -30,11 +30,13 @@ import (
"github.com/dustin/go-humanize"
"github.com/minio/cli"
"github.com/minio/minio-go/pkg/policy"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
minio "github.com/minio/minio/cmd"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
)
const (
@@ -962,8 +964,14 @@ func (l *ossObjects) CompleteMultipartUpload(ctx context.Context, bucket, object
// oss.ACLPublicReadWrite: readwrite in minio terminology
// oss.ACLPublicRead: readonly in minio terminology
// oss.ACLPrivate: none in minio terminology
func (l *ossObjects) SetBucketPolicy(ctx context.Context, bucket string, policyInfo policy.BucketAccessPolicy) error {
bucketPolicies := policy.GetPolicies(policyInfo.Statements, bucket, "")
func (l *ossObjects) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
policyInfo, err := minio.PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
return ossToObjectError(err, bucket)
}
bucketPolicies := miniogopolicy.GetPolicies(policyInfo.Statements, bucket, "")
if len(bucketPolicies) != 1 {
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
@@ -978,11 +986,11 @@ func (l *ossObjects) SetBucketPolicy(ctx context.Context, bucket string, policyI
var acl oss.ACLType
switch bucketPolicy {
case policy.BucketPolicyNone:
case miniogopolicy.BucketPolicyNone:
acl = oss.ACLPrivate
case policy.BucketPolicyReadOnly:
case miniogopolicy.BucketPolicyReadOnly:
acl = oss.ACLPublicRead
case policy.BucketPolicyReadWrite:
case miniogopolicy.BucketPolicyReadWrite:
acl = oss.ACLPublicReadWrite
default:
logger.LogIf(ctx, minio.NotImplemented{})
@@ -1000,29 +1008,60 @@ func (l *ossObjects) SetBucketPolicy(ctx context.Context, bucket string, policyI
}
// GetBucketPolicy will get policy on bucket.
func (l *ossObjects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
func (l *ossObjects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
result, err := l.Client.GetBucketACL(bucket)
if err != nil {
logger.LogIf(ctx, err)
return policy.BucketAccessPolicy{}, ossToObjectError(err)
return nil, ossToObjectError(err)
}
policyInfo := policy.BucketAccessPolicy{Version: "2012-10-17"}
var readOnly, readWrite bool
switch result.ACL {
case string(oss.ACLPrivate):
// By default, all buckets starts with a "private" policy.
logger.LogIf(ctx, minio.PolicyNotFound{})
return policy.BucketAccessPolicy{}, ossToObjectError(minio.PolicyNotFound{}, bucket)
logger.LogIf(ctx, minio.BucketPolicyNotFound{})
return nil, ossToObjectError(minio.BucketPolicyNotFound{}, bucket)
case string(oss.ACLPublicRead):
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadOnly, bucket, "")
readOnly = true
case string(oss.ACLPublicReadWrite):
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, policy.BucketPolicyReadWrite, bucket, "")
readWrite = true
default:
logger.LogIf(ctx, minio.NotImplemented{})
return policy.BucketAccessPolicy{}, minio.NotImplemented{}
return nil, minio.NotImplemented{}
}
return policyInfo, nil
actionSet := policy.NewActionSet()
if readOnly {
actionSet.Add(policy.GetBucketLocationAction)
actionSet.Add(policy.ListBucketAction)
actionSet.Add(policy.GetObjectAction)
}
if readWrite {
actionSet.Add(policy.GetBucketLocationAction)
actionSet.Add(policy.ListBucketAction)
actionSet.Add(policy.GetObjectAction)
actionSet.Add(policy.ListBucketMultipartUploadsAction)
actionSet.Add(policy.AbortMultipartUploadAction)
actionSet.Add(policy.DeleteObjectAction)
actionSet.Add(policy.ListMultipartUploadPartsAction)
actionSet.Add(policy.PutObjectAction)
}
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
actionSet,
policy.NewResourceSet(
policy.NewResource(bucket, ""),
policy.NewResource(bucket, "*"),
),
condition.NewFunctions(),
),
},
}, nil
}
// DeleteBucketPolicy deletes all policies on bucket.

View File

@@ -20,14 +20,15 @@ import (
"context"
"encoding/json"
"io"
"strings"
"github.com/minio/cli"
miniogo "github.com/minio/minio-go"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/s3utils"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
minio "github.com/minio/minio/cmd"
)
@@ -428,31 +429,32 @@ func (l *s3Objects) CompleteMultipartUpload(ctx context.Context, bucket string,
}
// SetBucketPolicy sets policy on bucket
func (l *s3Objects) SetBucketPolicy(ctx context.Context, bucket string, policyInfo policy.BucketAccessPolicy) error {
data, err := json.Marshal(&policyInfo)
func (l *s3Objects) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
data, err := json.Marshal(bucketPolicy)
if err != nil {
return err
// This should not happen.
logger.LogIf(ctx, err)
return minio.ErrorRespToObjectError(err, bucket)
}
if err := l.Client.SetBucketPolicy(bucket, string(data)); err != nil {
logger.LogIf(ctx, err)
return minio.ErrorRespToObjectError(err, bucket, "")
return minio.ErrorRespToObjectError(err, bucket)
}
return nil
}
// GetBucketPolicy will get policy on bucket
func (l *s3Objects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
func (l *s3Objects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
data, err := l.Client.GetBucketPolicy(bucket)
if err != nil {
logger.LogIf(ctx, err)
return policy.BucketAccessPolicy{}, minio.ErrorRespToObjectError(err, bucket, "")
return nil, minio.ErrorRespToObjectError(err, bucket)
}
var policyInfo policy.BucketAccessPolicy
if err = json.Unmarshal([]byte(data), &policyInfo); err != nil {
return policyInfo, err
}
return policyInfo, nil
bucketPolicy, err := policy.ParseConfig(strings.NewReader(data), bucket)
return bucketPolicy, minio.ErrorRespToObjectError(err, bucket)
}
// DeleteBucketPolicy deletes all policies on bucket

View File

@@ -52,7 +52,7 @@ func TestS3ToObjectError(t *testing.T) {
},
{
inputErr: errResponse("NoSuchBucketPolicy"),
expectedErr: minio.PolicyNotFound{},
expectedErr: minio.BucketPolicyNotFound{},
},
{
inputErr: errResponse("NoSuchBucket"),

View File

@@ -120,6 +120,7 @@ var (
globalMinioHost = ""
globalNotificationSys *NotificationSys
globalPolicySys *PolicySys
// CA root certificates, a nil value means system certs pool will be used
globalRootCAs *x509.CertPool

View File

@@ -21,6 +21,7 @@ import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"net/url"
"path"
@@ -30,6 +31,7 @@ import (
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/hash"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
)
// NotificationSys - notification system.
@@ -75,15 +77,33 @@ func (sys *NotificationSys) DeleteBucket(bucketName string) map[xnet.Host]error
return errors
}
// UpdateBucketPolicy - calls UpdateBucketPolicy RPC call on all peers.
func (sys *NotificationSys) UpdateBucketPolicy(bucketName string) map[xnet.Host]error {
// SetBucketPolicy - calls SetBucketPolicy RPC call on all peers.
func (sys *NotificationSys) SetBucketPolicy(bucketName string, bucketPolicy *policy.Policy) map[xnet.Host]error {
errors := make(map[xnet.Host]error)
var wg sync.WaitGroup
for addr, client := range sys.peerRPCClientMap {
wg.Add(1)
go func(addr xnet.Host, client *PeerRPCClient) {
defer wg.Done()
if err := client.UpdateBucketPolicy(bucketName); err != nil {
if err := client.SetBucketPolicy(bucketName, bucketPolicy); err != nil {
errors[addr] = err
}
}(addr, client)
}
wg.Wait()
return errors
}
// RemoveBucketPolicy - calls RemoveBucketPolicy RPC call on all peers.
func (sys *NotificationSys) RemoveBucketPolicy(bucketName string) map[xnet.Host]error {
errors := make(map[xnet.Host]error)
var wg sync.WaitGroup
for addr, client := range sys.peerRPCClientMap {
wg.Add(1)
go func(addr xnet.Host, client *PeerRPCClient) {
defer wg.Done()
if err := client.RemoveBucketPolicy(bucketName); err != nil {
errors[addr] = err
}
}(addr, client)
@@ -182,7 +202,7 @@ func (sys *NotificationSys) initListeners(ctx context.Context, objAPI ObjectLaye
defer objLock.Unlock()
reader, e := readConfig(ctx, objAPI, configFile)
if e != nil && !IsErrIgnored(e, errDiskNotFound, errNoSuchNotifications) {
if e != nil && !IsErrIgnored(e, errDiskNotFound, errConfigNotFound) {
return e
}
@@ -433,7 +453,7 @@ func (args eventArgs) ToEvent() event.Event {
Bucket: event.Bucket{
Name: args.BucketName,
OwnerIdentity: event.Identity{creds.AccessKey},
ARN: bucketARNPrefix + args.BucketName,
ARN: policy.ResourceARNPrefix + args.BucketName,
},
Object: event.Object{
Key: url.QueryEscape(args.Object.Name),
@@ -483,6 +503,8 @@ func saveConfig(objAPI ObjectLayer, configFile string, data []byte) error {
return err
}
var errConfigNotFound = errors.New("config file not found")
func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) (*bytes.Buffer, error) {
var buffer bytes.Buffer
// Read entire content by setting size to -1
@@ -490,8 +512,9 @@ func readConfig(ctx context.Context, objAPI ObjectLayer, configFile string) (*by
if err != nil {
// Ignore if err is ObjectNotFound or IncompleteBody when bucket is not configured with notification
if isErrObjectNotFound(err) || isErrIncompleteBody(err) {
return nil, errNoSuchNotifications
return nil, errConfigNotFound
}
logger.GetReqInfo(ctx).AppendTags("configFile", configFile)
logger.LogIf(ctx, err)
return nil, err
@@ -510,6 +533,10 @@ func readNotificationConfig(ctx context.Context, objAPI ObjectLayer, bucketName
configFile := path.Join(bucketConfigPrefix, bucketName, bucketNotificationConfig)
reader, err := readConfig(ctx, objAPI, configFile)
if err != nil {
if err == errConfigNotFound {
err = errNoSuchNotifications
}
return nil, err
}
@@ -551,7 +578,7 @@ func SaveListener(objAPI ObjectLayer, bucketName string, eventNames []event.Name
defer objLock.Unlock()
reader, err := readConfig(ctx, objAPI, configFile)
if err != nil && !IsErrIgnored(err, errDiskNotFound, errNoSuchNotifications) {
if err != nil && !IsErrIgnored(err, errDiskNotFound, errConfigNotFound) {
return err
}
@@ -602,7 +629,7 @@ func RemoveListener(objAPI ObjectLayer, bucketName string, targetID event.Target
defer objLock.Unlock()
reader, err := readConfig(ctx, objAPI, configFile)
if err != nil && !IsErrIgnored(err, errDiskNotFound, errNoSuchNotifications) {
if err != nil && !IsErrIgnored(err, errDiskNotFound, errConfigNotFound) {
return err
}

View File

@@ -85,7 +85,7 @@ func dirObjectInfo(bucket, object string, size int64, metadata map[string]string
func deleteBucketMetadata(ctx context.Context, bucket string, objAPI ObjectLayer) {
// Delete bucket access policy, if present - ignore any errors.
removeBucketPolicy(ctx, bucket, objAPI)
removePolicyConfig(ctx, objAPI, bucket)
// Delete notification config, if present - ignore any errors.
removeNotificationConfig(ctx, objAPI, bucket)

View File

@@ -366,13 +366,6 @@ func (e PolicyNesting) Error() string {
return "New bucket policy conflicts with an existing policy. Please try again with new prefix."
}
// PolicyNotFound - policy not found
type PolicyNotFound GenericError
func (e PolicyNotFound) Error() string {
return "Policy not found"
}
// UnsupportedMetadata - unsupported metadata
type UnsupportedMetadata struct{}
@@ -396,15 +389,6 @@ func isErrIncompleteBody(err error) bool {
return false
}
// isErrBucketPolicyNotFound - Check if error type is BucketPolicyNotFound.
func isErrBucketPolicyNotFound(err error) bool {
switch err.(type) {
case PolicyNotFound:
return true
}
return false
}
// isErrObjectNotFound - Check if error type is ObjectNotFound.
func isErrObjectNotFound(err error) bool {
switch err.(type) {

View File

@@ -21,9 +21,9 @@ import (
"io"
"time"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/policy"
)
// ObjectLayer implements primitives for object API layer.
@@ -70,9 +70,8 @@ type ObjectLayer interface {
ClearLocks(context.Context, []VolumeLockInfo) error
// Policy operations
SetBucketPolicy(context.Context, string, policy.BucketAccessPolicy) error
GetBucketPolicy(context.Context, string) (policy.BucketAccessPolicy, error)
RefreshBucketPolicy(context.Context, string) error
SetBucketPolicy(context.Context, string, *policy.Policy) error
GetBucketPolicy(context.Context, string) (*policy.Policy, error)
DeleteBucketPolicy(context.Context, string) error
// Supported operations check

View File

@@ -34,9 +34,9 @@ import (
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/ioutil"
"github.com/minio/minio/pkg/policy"
sha256 "github.com/minio/sha256-simd"
"github.com/minio/sio"
)
@@ -60,23 +60,6 @@ func setHeadGetRespHeaders(w http.ResponseWriter, reqParams url.Values) {
}
}
// errAllowableNotFound - For an anon user, return 404 if have ListBucket, 403 otherwise
// this is in keeping with the permissions sections of the docs of both:
// HEAD Object: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
// GET Object: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
func errAllowableObjectNotFound(ctx context.Context, bucket string, r *http.Request) APIErrorCode {
if getRequestAuthType(r) == authTypeAnonymous {
// We care about the bucket as a whole, not a particular resource.
resource := "/" + bucket
sourceIP := handlers.GetSourceIP(r)
if s3Error := enforceBucketPolicy(ctx, bucket, "s3:ListBucket", resource,
r.Referer(), sourceIP, r.URL.Query()); s3Error != ErrNone {
return ErrAccessDenied
}
}
return ErrNoSuchKey
}
// GetObjectHandler - GET Object
// ----------
// This implementation of the GET operation retrieves object. To use GET,
@@ -96,7 +79,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:GetObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -109,9 +92,21 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
objInfo, err := getObjectInfo(ctx, bucket, object)
if err != nil {
apiErr := toAPIErrorCode(err)
if apiErr == ErrNoSuchKey {
apiErr = errAllowableObjectNotFound(ctx, bucket, r)
if apiErr == ErrNoSuchKey && getRequestAuthType(r) == authTypeAnonymous {
// As per "Permission" section in https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
// If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.
// * If you have the s3:ListBucket permission on the bucket, Amazon S3 will return an HTTP status code 404 ("no such key") error.
// * if you dont have the s3:ListBucket permission, Amazon S3 will return an HTTP status code 403 ("access denied") error.`
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
}) {
apiErr = ErrAccessDenied
}
}
writeErrorResponse(w, apiErr, r.URL)
return
}
@@ -232,7 +227,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:GetObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone {
writeErrorResponseHeadersOnly(w, s3Error)
return
}
@@ -245,9 +240,21 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
objInfo, err := getObjectInfo(ctx, bucket, object)
if err != nil {
apiErr := toAPIErrorCode(err)
if apiErr == ErrNoSuchKey {
apiErr = errAllowableObjectNotFound(ctx, bucket, r)
if apiErr == ErrNoSuchKey && getRequestAuthType(r) == authTypeAnonymous {
// As per "Permission" section in https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
// If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.
// * If you have the s3:ListBucket permission on the bucket, Amazon S3 will return an HTTP status code 404 ("no such key") error.
// * if you dont have the s3:ListBucket permission, Amazon S3 will return an HTTP status code 403 ("access denied") error.`
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
}) {
apiErr = ErrAccessDenied
}
}
writeErrorResponseHeadersOnly(w, apiErr)
return
}
@@ -340,7 +347,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return
}
if s3Error := checkRequestAuthType(ctx, r, dstBucket, "s3:PutObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, dstBucket, dstObject); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -666,10 +673,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(w, ErrAccessDenied, r.URL)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
sourceIP := handlers.GetSourceIP(r)
if s3Err = enforceBucketPolicy(ctx, bucket, "s3:PutObject", r.URL.Path, r.Referer(), sourceIP, r.URL.Query()); s3Err != ErrNone {
writeErrorResponse(w, s3Err, r.URL)
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: object,
}) {
writeErrorResponse(w, ErrAccessDenied, r.URL)
return
}
case authTypeStreamingSigned:
@@ -781,7 +792,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:PutObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, bucket, object); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -867,7 +878,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
return
}
if s3Error := checkRequestAuthType(ctx, r, dstBucket, "s3:PutObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, dstBucket, dstObject); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -1133,10 +1144,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
writeErrorResponse(w, ErrAccessDenied, r.URL)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
if s3Error := enforceBucketPolicy(ctx, bucket, "s3:PutObject", r.URL.Path,
r.Referer(), handlers.GetSourceIP(r), r.URL.Query()); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: object,
}) {
writeErrorResponse(w, ErrAccessDenied, r.URL)
return
}
case authTypeStreamingSigned:
@@ -1262,7 +1277,8 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
if api.CacheAPI() != nil {
abortMultipartUpload = api.CacheAPI().AbortMultipartUpload
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:AbortMultipartUpload", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.AbortMultipartUploadAction, bucket, object); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -1297,7 +1313,7 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:ListMultipartUploadParts", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.ListMultipartUploadPartsAction, bucket, object); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -1337,7 +1353,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:PutObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, bucket, object); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}
@@ -1446,7 +1462,7 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
return
}
if s3Error := checkRequestAuthType(ctx, r, bucket, "s3:DeleteObject", globalServerConfig.GetRegion()); s3Error != ErrNone {
if s3Error := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}

View File

@@ -172,7 +172,7 @@ func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string,
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIHeadObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getReadOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIHeadObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonReadOnlyObjectPolicy(bucketName, objectName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -445,7 +445,7 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIGetObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getReadOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIGetObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonReadOnlyObjectPolicy(bucketName, objectName))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -1001,7 +1001,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIPutObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIPutObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
@@ -1847,7 +1847,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPICopyObjectHandler", bucketName, newCopyAnonObject, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPICopyObjectHandler", bucketName, newCopyAnonObject, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, newCopyAnonObject))
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
@@ -1998,7 +1998,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPINewMultipartHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPINewMultipartHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
@@ -2409,7 +2409,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPICompleteMultipartHandler", bucketName, objectName, instanceType,
apiRouter, anonReq, getWriteOnlyObjectStatement)
apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
@@ -2572,7 +2572,7 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIAbortMultipartHandler", bucketName, objectName, instanceType,
apiRouter, anonReq, getWriteOnlyObjectStatement)
apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
@@ -2734,7 +2734,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIDeleteObjectHandler", bucketName, anonObjectName, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIDeleteObjectHandler", bucketName, anonObjectName, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, anonObjectName))
// HTTP request to test the case of `objectLayer` being set to `nil`.
// There is no need to use an existing bucket or valid input for creating the request,
@@ -3205,7 +3205,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIPutObjectPartHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIPutObjectPartHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, testObject))
// HTTP request for testing when `ObjectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request
@@ -3508,7 +3508,7 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
// unsigned request goes through and its validated again.
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIListObjectPartsHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement)
ExecObjectLayerAPIAnonTest(t, obj, "TestAPIListObjectPartsHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, testObject))
// HTTP request for testing when `objectLayer` is set to `nil`.
// There is no need to use an existing bucket and valid input for creating the request

View File

@@ -25,6 +25,7 @@ import (
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/event"
xnet "github.com/minio/minio/pkg/net"
"github.com/minio/minio/pkg/policy"
)
const s3Path = "/s3/remote"
@@ -43,23 +44,33 @@ type DeleteBucketArgs struct {
// DeleteBucket - handles delete bucket RPC call which removes all values of given bucket in global NotificationSys object.
func (receiver *PeerRPCReceiver) DeleteBucket(args *DeleteBucketArgs, reply *AuthRPCArgs) error {
globalNotificationSys.RemoveNotification(args.BucketName)
globalPolicySys.Remove(args.BucketName)
return nil
}
// UpdateBucketPolicyArgs - update bucket policy RPC arguments.
type UpdateBucketPolicyArgs struct {
// SetBucketPolicyArgs - set bucket policy RPC arguments.
type SetBucketPolicyArgs struct {
AuthRPCArgs
BucketName string
Policy policy.Policy
}
// SetBucketPolicy - handles set bucket policy RPC call which adds bucket policy to globalPolicySys.
func (receiver *PeerRPCReceiver) SetBucketPolicy(args *SetBucketPolicyArgs, reply *AuthRPCArgs) error {
globalPolicySys.Set(args.BucketName, args.Policy)
return nil
}
// RemoveBucketPolicyArgs - delete bucket policy RPC arguments.
type RemoveBucketPolicyArgs struct {
AuthRPCArgs
BucketName string
}
// UpdateBucketPolicy - handles update bucket policy RPC call which sets bucket policies to given bucket in global BucketPolicies object.
func (receiver *PeerRPCReceiver) UpdateBucketPolicy(args *UpdateBucketPolicyArgs, reply *AuthRPCArgs) error {
objectAPI := newObjectLayerFn()
if objectAPI == nil {
// If the object layer is just coming up then it will load the policy from the disk.
return nil
}
return objectAPI.RefreshBucketPolicy(context.Background(), args.BucketName)
// RemoveBucketPolicy - handles delete bucket policy RPC call which removes bucket policy to globalPolicySys.
func (receiver *PeerRPCReceiver) RemoveBucketPolicy(args *RemoveBucketPolicyArgs, reply *AuthRPCArgs) error {
globalPolicySys.Remove(args.BucketName)
return nil
}
// PutBucketNotificationArgs - put bucket notification RPC arguments.
@@ -195,13 +206,23 @@ func (rpcClient *PeerRPCClient) DeleteBucket(bucketName string) error {
return rpcClient.Call("Peer.DeleteBucket", &args, &reply)
}
// UpdateBucketPolicy - calls update bucket policy RPC.
func (rpcClient *PeerRPCClient) UpdateBucketPolicy(bucketName string) error {
args := UpdateBucketPolicyArgs{
// SetBucketPolicy - calls set bucket policy RPC.
func (rpcClient *PeerRPCClient) SetBucketPolicy(bucketName string, bucketPolicy *policy.Policy) error {
args := SetBucketPolicyArgs{
BucketName: bucketName,
Policy: *bucketPolicy,
}
reply := AuthRPCReply{}
return rpcClient.Call("Peer.SetBucketPolicy", &args, &reply)
}
// RemoveBucketPolicy - calls remove bucket policy RPC.
func (rpcClient *PeerRPCClient) RemoveBucketPolicy(bucketName string) error {
args := RemoveBucketPolicyArgs{
BucketName: bucketName,
}
reply := AuthRPCReply{}
return rpcClient.Call("Peer.UpdateBucketPolicy", &args, &reply)
return rpcClient.Call("Peer.RemoveBucketPolicy", &args, &reply)
}
// PutBucketNotification - calls put bukcet notification RPC.

218
cmd/policy.go Normal file
View File

@@ -0,0 +1,218 @@
/*
* Minio Cloud Storage, (C) 2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"context"
"encoding/json"
"net/http"
"path"
"sync"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/policy"
)
// PolicySys - policy system.
type PolicySys struct {
sync.RWMutex
bucketPolicyMap map[string]policy.Policy
}
// Set - sets policy to given bucket name. If policy is empty, existing policy is removed.
func (sys *PolicySys) Set(bucketName string, policy policy.Policy) {
sys.Lock()
defer sys.Unlock()
if policy.IsEmpty() {
delete(sys.bucketPolicyMap, bucketName)
} else {
sys.bucketPolicyMap[bucketName] = policy
}
}
// Remove - removes policy for given bucket name.
func (sys *PolicySys) Remove(bucketName string) {
sys.Lock()
defer sys.Unlock()
delete(sys.bucketPolicyMap, bucketName)
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (sys *PolicySys) IsAllowed(args policy.Args) bool {
sys.RLock()
defer sys.RUnlock()
// If policy is available for given bucket, check the policy.
if p, found := sys.bucketPolicyMap[args.BucketName]; found {
return p.IsAllowed(args)
}
// As policy is not available for given bucket name, returns IsOwner i.e.
// operation is allowed only for owner.
return args.IsOwner
}
// Init - initializes policy system from policy.json of all buckets.
func (sys *PolicySys) Init(objAPI ObjectLayer) error {
if objAPI == nil {
return errInvalidArgument
}
buckets, err := objAPI.ListBuckets(context.Background())
if err != nil {
return err
}
for _, bucket := range buckets {
config, err := GetPolicyConfig(objAPI, bucket.Name)
if err != nil {
if _, ok := err.(BucketPolicyNotFound); !ok {
return err
}
} else {
sys.Set(bucket.Name, *config)
}
}
return nil
}
// NewPolicySys - creates new policy system.
func NewPolicySys() *PolicySys {
return &PolicySys{
bucketPolicyMap: make(map[string]policy.Policy),
}
}
func getConditionValues(request *http.Request, locationConstraint string) map[string][]string {
args := make(map[string][]string)
for key, values := range request.Header {
if existingValues, found := args[key]; found {
args[key] = append(existingValues, values...)
} else {
args[key] = values
}
}
for key, values := range request.URL.Query() {
if existingValues, found := args[key]; found {
args[key] = append(existingValues, values...)
} else {
args[key] = values
}
}
args["SourceIp"] = []string{handlers.GetSourceIP(request)}
if locationConstraint != "" {
args["LocationConstraint"] = []string{locationConstraint}
}
return args
}
// GetPolicyConfig - get policy config for given bucket name.
func GetPolicyConfig(objAPI ObjectLayer, bucketName string) (*policy.Policy, error) {
// Construct path to policy.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, bucketName, bucketPolicyConfig)
reader, err := readConfig(context.Background(), objAPI, configFile)
if err != nil {
if err == errConfigNotFound {
err = BucketPolicyNotFound{Bucket: bucketName}
}
return nil, err
}
bucketPolicy, err := policy.ParseConfig(reader, bucketName)
if err != nil {
return nil, err
}
return bucketPolicy, nil
}
func savePolicyConfig(objAPI ObjectLayer, bucketName string, bucketPolicy *policy.Policy) error {
data, err := json.Marshal(bucketPolicy)
if err != nil {
return err
}
// Construct path to policy.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, bucketName, bucketPolicyConfig)
return saveConfig(objAPI, configFile, data)
}
func removePolicyConfig(ctx context.Context, objAPI ObjectLayer, bucketName string) error {
// Construct path to policy.json for the given bucket.
configFile := path.Join(bucketConfigPrefix, bucketName, bucketPolicyConfig)
if err := objAPI.DeleteObject(ctx, minioMetaBucket, configFile); err != nil {
if _, ok := err.(ObjectNotFound); ok {
return BucketPolicyNotFound{Bucket: bucketName}
}
return err
}
return nil
}
// PolicyToBucketAccessPolicy - converts policy.Policy to minio-go/policy.BucketAccessPolicy.
func PolicyToBucketAccessPolicy(bucketPolicy *policy.Policy) (*miniogopolicy.BucketAccessPolicy, error) {
// Return empty BucketAccessPolicy for empty bucket policy.
if bucketPolicy == nil {
return &miniogopolicy.BucketAccessPolicy{Version: policy.DefaultVersion}, nil
}
data, err := json.Marshal(bucketPolicy)
if err != nil {
// This should not happen because bucketPolicy is valid to convert to JSON data.
return nil, err
}
var policyInfo miniogopolicy.BucketAccessPolicy
if err = json.Unmarshal(data, &policyInfo); err != nil {
// This should not happen because data is valid to JSON data.
return nil, err
}
return &policyInfo, nil
}
// BucketAccessPolicyToPolicy - converts minio-go/policy.BucketAccessPolicy to policy.Policy.
func BucketAccessPolicyToPolicy(policyInfo *miniogopolicy.BucketAccessPolicy) (*policy.Policy, error) {
data, err := json.Marshal(policyInfo)
if err != nil {
// This should not happen because policyInfo is valid to convert to JSON data.
return nil, err
}
var bucketPolicy policy.Policy
if err = json.Unmarshal(data, &bucketPolicy); err != nil {
// This should not happen because data is valid to JSON data.
return nil, err
}
return &bucketPolicy, nil
}

424
cmd/policy_test.go Normal file
View File

@@ -0,0 +1,424 @@
/*
* Minio Cloud Storage, (C) 2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"reflect"
"testing"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/set"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
)
func TestPolicySysSet(t *testing.T) {
case1PolicySys := NewPolicySys()
case1Policy := policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.PutObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
case1Result := NewPolicySys()
case1Result.bucketPolicyMap["mybucket"] = case1Policy
case2PolicySys := NewPolicySys()
case2PolicySys.bucketPolicyMap["mybucket"] = case1Policy
case2Policy := policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
case2Result := NewPolicySys()
case2Result.bucketPolicyMap["mybucket"] = case2Policy
case3PolicySys := NewPolicySys()
case3PolicySys.bucketPolicyMap["mybucket"] = case2Policy
case3Policy := policy.Policy{
ID: "MyPolicyForMyBucket",
Version: policy.DefaultVersion,
}
case3Result := NewPolicySys()
testCases := []struct {
policySys *PolicySys
bucketName string
bucketPolicy policy.Policy
expectedResult *PolicySys
}{
{case1PolicySys, "mybucket", case1Policy, case1Result},
{case2PolicySys, "mybucket", case2Policy, case2Result},
{case3PolicySys, "mybucket", case3Policy, case3Result},
}
for i, testCase := range testCases {
result := testCase.policySys
result.Set(testCase.bucketName, testCase.bucketPolicy)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPolicySysRemove(t *testing.T) {
case1Policy := policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.PutObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
case1PolicySys := NewPolicySys()
case1PolicySys.bucketPolicyMap["mybucket"] = case1Policy
case1Result := NewPolicySys()
case2Policy := policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
case2PolicySys := NewPolicySys()
case2PolicySys.bucketPolicyMap["mybucket"] = case2Policy
case2Result := NewPolicySys()
case2Result.bucketPolicyMap["mybucket"] = case2Policy
case3PolicySys := NewPolicySys()
case3Result := NewPolicySys()
testCases := []struct {
policySys *PolicySys
bucketName string
expectedResult *PolicySys
}{
{case1PolicySys, "mybucket", case1Result},
{case2PolicySys, "yourbucket", case2Result},
{case3PolicySys, "mybucket", case3Result},
}
for i, testCase := range testCases {
result := testCase.policySys
result.Remove(testCase.bucketName)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPolicySysIsAllowed(t *testing.T) {
policySys := NewPolicySys()
policySys.Set("mybucket", policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction),
policy.NewResourceSet(policy.NewResource("mybucket", "")),
condition.NewFunctions(),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.PutObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
})
anonGetBucketLocationArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
anonPutObjectActionArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
anonGetObjectActionArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
getBucketLocationArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
IsOwner: true,
}
putObjectActionArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
IsOwner: true,
ObjectName: "myobject",
}
getObjectActionArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
IsOwner: true,
ObjectName: "myobject",
}
yourbucketAnonGetObjectActionArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.GetObjectAction,
BucketName: "yourbucket",
ConditionValues: map[string][]string{},
ObjectName: "yourobject",
}
yourbucketGetObjectActionArgs := policy.Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: policy.GetObjectAction,
BucketName: "yourbucket",
ConditionValues: map[string][]string{},
IsOwner: true,
ObjectName: "yourobject",
}
testCases := []struct {
policySys *PolicySys
args policy.Args
expectedResult bool
}{
{policySys, anonGetBucketLocationArgs, true},
{policySys, anonPutObjectActionArgs, true},
{policySys, anonGetObjectActionArgs, false},
{policySys, getBucketLocationArgs, true},
{policySys, putObjectActionArgs, true},
{policySys, getObjectActionArgs, true},
{policySys, yourbucketAnonGetObjectActionArgs, false},
{policySys, yourbucketGetObjectActionArgs, true},
}
for i, testCase := range testCases {
result := testCase.policySys.IsAllowed(testCase.args)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func getReadOnlyStatement(bucketName, prefix string) []miniogopolicy.Statement {
return []miniogopolicy.Statement{
{
Effect: string(policy.Allow),
Principal: miniogopolicy.User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet(policy.NewResource(bucketName, "").String()),
Actions: set.CreateStringSet("s3:GetBucketLocation", "s3:ListBucket"),
},
{
Effect: string(policy.Allow),
Principal: miniogopolicy.User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet(policy.NewResource(bucketName, prefix).String()),
Actions: set.CreateStringSet("s3:GetObject"),
},
}
}
func TestPolicyToBucketAccessPolicy(t *testing.T) {
case1Policy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
policy.NewResourceSet(policy.NewResource("mybucket", "")),
condition.NewFunctions(),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
case1Result := &miniogopolicy.BucketAccessPolicy{
Version: policy.DefaultVersion,
Statements: getReadOnlyStatement("mybucket", "/myobject*"),
}
case2Policy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{},
}
case2Result := &miniogopolicy.BucketAccessPolicy{
Version: policy.DefaultVersion,
Statements: []miniogopolicy.Statement{},
}
case3Policy := &policy.Policy{
Version: "12-10-2012",
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.PutObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
testCases := []struct {
bucketPolicy *policy.Policy
expectedResult *miniogopolicy.BucketAccessPolicy
expectErr bool
}{
{case1Policy, case1Result, false},
{case2Policy, case2Result, false},
{case3Policy, nil, true},
}
for i, testCase := range testCases {
result, err := PolicyToBucketAccessPolicy(testCase.bucketPolicy)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %+v, got: %+v\n", i+1, testCase.expectedResult, result)
}
}
}
}
func TestBucketAccessPolicyToPolicy(t *testing.T) {
case1PolicyInfo := &miniogopolicy.BucketAccessPolicy{
Version: policy.DefaultVersion,
Statements: getReadOnlyStatement("mybucket", "/myobject*"),
}
case1Result := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
policy.NewResourceSet(policy.NewResource("mybucket", "")),
condition.NewFunctions(),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource("mybucket", "/myobject*")),
condition.NewFunctions(),
),
},
}
case2PolicyInfo := &miniogopolicy.BucketAccessPolicy{
Version: policy.DefaultVersion,
Statements: []miniogopolicy.Statement{},
}
case2Result := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{},
}
case3PolicyInfo := &miniogopolicy.BucketAccessPolicy{
Version: "12-10-2012",
Statements: getReadOnlyStatement("mybucket", "/myobject*"),
}
testCases := []struct {
policyInfo *miniogopolicy.BucketAccessPolicy
expectedResult *policy.Policy
expectErr bool
}{
{case1PolicyInfo, case1Result, false},
{case2PolicyInfo, case2Result, false},
{case3PolicyInfo, nil, true},
}
for i, testCase := range testCases {
result, err := BucketAccessPolicyToPolicy(testCase.policyInfo)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %+v, got: %+v\n", i+1, testCase.expectedResult, result)
}
}
}
}

View File

@@ -239,9 +239,12 @@ func serverMain(ctx *cli.Context) {
handler, err = configureServerHandler(globalEndpoints)
logger.FatalIf(err, "Unable to configure one of server's RPC services.")
// Initialize notification system.
// Create new notification system.
globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints)
logger.FatalIf(err, "Unable to initialize notification system.")
logger.FatalIf(err, "Unable to create new notification system.")
// Create new policy system.
globalPolicySys = NewPolicySys()
// Initialize Admin Peers inter-node communication only in distributed setup.
initGlobalAdminPeers(globalEndpoints)

View File

@@ -36,6 +36,7 @@ import (
"time"
humanize "github.com/dustin/go-humanize"
"github.com/minio/minio/pkg/policy"
)
// API suite container common to both FS and XL.
@@ -319,7 +320,7 @@ func (s *TestSuiteCommon) TestBucketSQSNotificationAMQP(c *check) {
// Deletes the policy and verifies the deletion by fetching it back.
func (s *TestSuiteCommon) TestBucketPolicy(c *check) {
// Sample bucket policy.
bucketPolicyBuf := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"],"Sid":""}]}`
bucketPolicyBuf := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"]},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"]}]}`
// generate a random bucket Name.
bucketName := getRandomBucketName()
@@ -361,7 +362,11 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *check) {
bucketPolicyReadBuf, err := ioutil.ReadAll(response.Body)
c.Assert(err, nil)
// Verify if downloaded policy matches with previousy uploaded.
c.Assert(bytes.Equal([]byte(bucketPolicyStr), bucketPolicyReadBuf), true)
expectedPolicy, err := policy.ParseConfig(strings.NewReader(bucketPolicyStr), bucketName)
c.Assert(err, nil)
gotPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyReadBuf), bucketName)
c.Assert(err, nil)
c.Assert(reflect.DeepEqual(expectedPolicy, gotPolicy), true)
// Delete policy.
request, err = newTestSignedRequest("DELETE", getDeletePolicyURL(s.endPoint, bucketName), 0, nil,

View File

@@ -53,12 +53,12 @@ import (
"github.com/fatih/color"
"github.com/gorilla/mux"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/s3signer"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bpool"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
)
// Tests should initNSLock only once.
@@ -354,9 +354,12 @@ func UnstartedTestServer(t TestErrHandler, instanceType string) TestServer {
globalMinioAddr = getEndpointsLocalAddr(testServer.Disks)
globalNotificationSys, err = NewNotificationSys(globalServerConfig, testServer.Disks)
if err != nil {
t.Fatalf("Unable to initialize queue configuration")
t.Fatalf("Unable to create new notification system. %v", err)
}
// Create new policy system.
globalPolicySys = NewPolicySys()
return testServer
}
@@ -1715,17 +1718,14 @@ func newTestObjectLayer(endpoints EndpointList) (newObject ObjectLayer, err erro
return xl.storageDisks
}
// Initialize and load bucket policies.
xl.bucketPolicies, err = initBucketPolicies(xl)
if err != nil {
return nil, err
}
// Initialize a new event notifier.
// Create new notification system.
if globalNotificationSys, err = NewNotificationSys(globalServerConfig, endpoints); err != nil {
return nil, err
}
// Create new policy system.
globalPolicySys = NewPolicySys()
return xl, nil
}
@@ -1821,7 +1821,7 @@ func prepareTestBackend(instanceType string) (ObjectLayer, []string, error) {
// STEP 2: Set the policy to allow the unsigned request, use the policyFunc to obtain the relevant statement and call
// the handler again to verify its success.
func ExecObjectLayerAPIAnonTest(t *testing.T, obj ObjectLayer, testName, bucketName, objectName, instanceType string, apiRouter http.Handler,
anonReq *http.Request, policyFunc func(string, string) policy.Statement) {
anonReq *http.Request, bucketPolicy *policy.Policy) {
anonTestStr := "Anonymous HTTP request test"
unknownSignTestStr := "Unknown HTTP signature test"
@@ -1863,7 +1863,8 @@ func ExecObjectLayerAPIAnonTest(t *testing.T, obj ObjectLayer, testName, bucketN
// HEAD HTTTP request doesn't contain response body.
if anonReq.Method != "HEAD" {
// read the response body.
actualContent, err := ioutil.ReadAll(rec.Body)
var actualContent []byte
actualContent, err = ioutil.ReadAll(rec.Body)
if err != nil {
t.Fatal(failTestStr(anonTestStr, fmt.Sprintf("Failed parsing response body: <ERROR> %v", err)))
}
@@ -1872,13 +1873,13 @@ func ExecObjectLayerAPIAnonTest(t *testing.T, obj ObjectLayer, testName, bucketN
t.Fatal(failTestStr(anonTestStr, "error response content differs from expected value"))
}
}
// Set write only policy on bucket to allow anonymous HTTP request for the operation under test.
// request to go through.
bp := policy.BucketAccessPolicy{
Version: "1.0",
Statements: []policy.Statement{policyFunc(bucketName, "")},
if err := obj.SetBucketPolicy(context.Background(), bucketName, bucketPolicy); err != nil {
t.Fatalf("unexpected error. %v", err)
}
obj.SetBucketPolicy(context.Background(), bucketName, bp)
globalPolicySys.Set(bucketName, *bucketPolicy)
defer globalPolicySys.Remove(bucketName)
// now call the handler again with the unsigned/anonymous request, it should be accepted.
rec = httptest.NewRecorder()

View File

@@ -33,12 +33,13 @@ import (
humanize "github.com/dustin/go-humanize"
"github.com/gorilla/mux"
"github.com/gorilla/rpc/v2/json2"
"github.com/minio/minio-go/pkg/policy"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/browser"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/event"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
)
// WebGenericArgs - empty struct for calls that don't accept arguments
@@ -155,15 +156,24 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
return toJSONError(errAuthentication)
}
ctx := context.Background()
deleteBucket := objectAPI.DeleteBucket
if web.CacheAPI() != nil {
deleteBucket = web.CacheAPI().DeleteBucket
}
err := deleteBucket(context.Background(), args.BucketName)
if err != nil {
if err := deleteBucket(ctx, args.BucketName); err != nil {
return toJSONError(err, args.BucketName)
}
globalNotificationSys.RemoveNotification(args.BucketName)
globalPolicySys.Remove(args.BucketName)
for addr, err := range globalNotificationSys.DeleteBucket(args.BucketName) {
logger.GetReqInfo(ctx).AppendTags("remotePeer", addr.Name)
logger.LogIf(ctx, err)
}
reply.UIVersion = browser.UIVersion
return nil
}
@@ -249,26 +259,37 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
if web.CacheAPI() != nil {
listObjects = web.CacheAPI().ListObjects
}
prefix := args.Prefix + "test" // To test if GetObject/PutObject with the specified prefix is allowed.
readable := isBucketActionAllowed("s3:GetObject", args.BucketName, prefix, objectAPI)
writable := isBucketActionAllowed("s3:PutObject", args.BucketName, prefix, objectAPI)
authErr := webRequestAuthenticate(r)
switch {
case authErr == errAuthentication:
return toJSONError(authErr)
case authErr == nil:
break
case readable && writable:
reply.Writable = true
break
case readable:
break
case writable:
reply.Writable = true
return nil
default:
return errAuthentication
// Check if anonymous (non-owner) has access to download objects.
readable := globalPolicySys.IsAllowed(policy.Args{
Action: policy.GetObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: args.Prefix + "/",
})
// Check if anonymous (non-owner) has access to upload objects.
writable := globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: args.Prefix + "/",
})
if authErr := webRequestAuthenticate(r); authErr != nil {
if authErr == errAuthentication {
return toJSONError(authErr)
}
// Error out anonymous (non-owner) has no access download or upload objects.
if !readable && !writable {
return errAuthentication
}
reply.Writable = writable
}
lo, err := listObjects(context.Background(), args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000)
if err != nil {
return &json2.Error{Message: err.Error()}
@@ -556,14 +577,23 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
bucket := vars["bucket"]
object := vars["object"]
authErr := webRequestAuthenticate(r)
if authErr == errAuthentication {
writeWebErrorResponse(w, errAuthentication)
return
}
if authErr != nil && !isBucketActionAllowed("s3:PutObject", bucket, object, objectAPI) {
writeWebErrorResponse(w, errAuthentication)
return
if authErr := webRequestAuthenticate(r); authErr != nil {
if authErr == errAuthentication {
writeWebErrorResponse(w, errAuthentication)
return
}
// Check if anonymous (non-owner) has access to upload objects.
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: object,
}) {
writeWebErrorResponse(w, errAuthentication)
return
}
}
// Require Content-Length to be set in the request
@@ -614,9 +644,18 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
object := vars["object"]
token := r.URL.Query().Get("token")
if !isAuthTokenValid(token) && !isBucketActionAllowed("s3:GetObject", bucket, object, objectAPI) {
writeWebErrorResponse(w, errAuthentication)
return
if !isAuthTokenValid(token) {
// Check if anonymous (non-owner) has access to download objects.
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.GetObjectAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: object,
}) {
writeWebErrorResponse(w, errAuthentication)
return
}
}
getObject := objectAPI.GetObject
@@ -669,7 +708,14 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if !isAuthTokenValid(token) {
for _, object := range args.Objects {
if !isBucketActionAllowed("s3:GetObject", args.BucketName, pathJoin(args.Prefix, object), objectAPI) {
// Check if anonymous (non-owner) has access to download objects.
if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.GetObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, ""),
IsOwner: false,
ObjectName: pathJoin(args.Prefix, object),
}) {
writeWebErrorResponse(w, errAuthentication)
return
}
@@ -742,8 +788,8 @@ type GetBucketPolicyArgs struct {
// GetBucketPolicyRep - get bucket policy reply.
type GetBucketPolicyRep struct {
UIVersion string `json:"uiVersion"`
Policy policy.BucketPolicy `json:"policy"`
UIVersion string `json:"uiVersion"`
Policy miniogopolicy.BucketPolicy `json:"policy"`
}
// GetBucketPolicy - get bucket policy for the requested prefix.
@@ -757,16 +803,21 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
return toJSONError(errAuthentication)
}
var policyInfo, err = objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
if err != nil {
_, ok := err.(BucketPolicyNotFound)
if !ok {
if _, ok := err.(BucketPolicyNotFound); !ok {
return toJSONError(err, args.BucketName)
}
}
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
return toJSONError(err, args.BucketName)
}
reply.UIVersion = browser.UIVersion
reply.Policy = policy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix)
reply.Policy = miniogopolicy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix)
return nil
}
@@ -778,9 +829,9 @@ type ListAllBucketPoliciesArgs struct {
// BucketAccessPolicy - Collection of canned bucket policy at a given prefix.
type BucketAccessPolicy struct {
Bucket string `json:"bucket"`
Prefix string `json:"prefix"`
Policy policy.BucketPolicy `json:"policy"`
Bucket string `json:"bucket"`
Prefix string `json:"prefix"`
Policy miniogopolicy.BucketPolicy `json:"policy"`
}
// ListAllBucketPoliciesRep - get all bucket policy reply.
@@ -789,7 +840,7 @@ type ListAllBucketPoliciesRep struct {
Policies []BucketAccessPolicy `json:"policies"`
}
// GetllBucketPolicy - get all bucket policy.
// ListAllBucketPolicies - get all bucket policy.
func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllBucketPoliciesArgs, reply *ListAllBucketPoliciesRep) error {
objectAPI := web.ObjectAPI()
if objectAPI == nil {
@@ -799,15 +850,22 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
if !isHTTPRequestValid(r) {
return toJSONError(errAuthentication)
}
var policyInfo, err = objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
if err != nil {
_, ok := err.(PolicyNotFound)
if !ok {
if _, ok := err.(BucketPolicyNotFound); !ok {
return toJSONError(err, args.BucketName)
}
}
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
return toJSONError(err, args.BucketName)
}
reply.UIVersion = browser.UIVersion
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName, "") {
for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, args.BucketName, "") {
bucketName, objectPrefix := urlPath2BucketObjectName(prefix)
objectPrefix = strings.TrimSuffix(objectPrefix, "*")
reply.Policies = append(reply.Policies, BucketAccessPolicy{
@@ -816,18 +874,19 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
Policy: policy,
})
}
return nil
}
// SetBucketPolicyArgs - set bucket policy args.
type SetBucketPolicyArgs struct {
// SetBucketPolicyWebArgs - set bucket policy args.
type SetBucketPolicyWebArgs struct {
BucketName string `json:"bucketName"`
Prefix string `json:"prefix"`
Policy string `json:"policy"`
}
// SetBucketPolicy - set bucket policy.
func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyArgs, reply *WebGenericRep) error {
func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyWebArgs, reply *WebGenericRep) error {
objectAPI := web.ObjectAPI()
reply.UIVersion = browser.UIVersion
@@ -839,52 +898,56 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
return toJSONError(errAuthentication)
}
bucketP := policy.BucketPolicy(args.Policy)
if !bucketP.IsValidBucketPolicy() {
policyType := miniogopolicy.BucketPolicy(args.Policy)
if !policyType.IsValidBucketPolicy() {
return &json2.Error{
Message: "Invalid policy type " + args.Policy,
}
}
var policyInfo, err = objectAPI.GetBucketPolicy(context.Background(), args.BucketName)
ctx := context.Background()
bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName)
if err != nil {
if _, ok := err.(PolicyNotFound); !ok {
if _, ok := err.(BucketPolicyNotFound); !ok {
return toJSONError(err, args.BucketName)
}
policyInfo = policy.BucketAccessPolicy{Version: "2012-10-17"}
}
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, bucketP, args.BucketName, args.Prefix)
policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
return toJSONError(err, args.BucketName)
}
policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
if len(policyInfo.Statements) == 0 {
if err = objectAPI.DeleteBucketPolicy(context.Background(), args.BucketName); err != nil {
if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil {
return toJSONError(err, args.BucketName)
}
globalPolicySys.Remove(args.BucketName)
return nil
}
_, err = json.Marshal(policyInfo)
bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo)
if err != nil {
return toJSONError(err)
}
// Parse check bucket policy.
if s3Error := checkBucketPolicyResources(args.BucketName, policyInfo); s3Error != ErrNone {
apiErr := getAPIError(s3Error)
var err error
if apiErr.Code == "XMinioPolicyNesting" {
err = PolicyNesting{}
} else {
err = fmt.Errorf(apiErr.Description)
}
// This should not happen.
return toJSONError(err, args.BucketName)
}
// Parse validate and save bucket policy.
if err := objectAPI.SetBucketPolicy(context.Background(), args.BucketName, policyInfo); err != nil {
if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil {
return toJSONError(err, args.BucketName)
}
globalPolicySys.Set(args.BucketName, *bucketPolicy)
for addr, err := range globalNotificationSys.SetBucketPolicy(args.BucketName, bucketPolicy) {
logger.GetReqInfo(ctx).AppendTags("remotePeer", addr.Name)
logger.LogIf(ctx, err)
}
return nil
}

View File

@@ -37,9 +37,10 @@ import (
jwtgo "github.com/dgrijalva/jwt-go"
humanize "github.com/dustin/go-humanize"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio-go/pkg/set"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
)
// Implement a dummy flush writer.
@@ -555,12 +556,22 @@ func testListObjectsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa
t.Fatalf("Expected error `%s`", err)
}
policy := policy.BucketAccessPolicy{
Version: "1.0",
Statements: []policy.Statement{getReadOnlyObjectStatement(bucketName, "")},
bucketPolicy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource(bucketName, "*")),
condition.NewFunctions(),
)},
}
obj.SetBucketPolicy(context.Background(), bucketName, policy)
if err = obj.SetBucketPolicy(context.Background(), bucketName, bucketPolicy); err != nil {
t.Fatalf("unexpected error. %v", err)
}
globalPolicySys.Set(bucketName, *bucketPolicy)
defer globalPolicySys.Remove(bucketName)
// Unauthenticated ListObjects with READ bucket policy should succeed.
err, reply = test("")
@@ -929,12 +940,22 @@ func testUploadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler
t.Fatalf("Expected the response status to be 403, but instead found `%d`", code)
}
bp := policy.BucketAccessPolicy{
Version: "1.0",
Statements: []policy.Statement{getWriteOnlyObjectStatement(bucketName, "")},
bucketPolicy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.PutObjectAction),
policy.NewResourceSet(policy.NewResource(bucketName, "*")),
condition.NewFunctions(),
)},
}
obj.SetBucketPolicy(context.Background(), bucketName, bp)
if err := obj.SetBucketPolicy(context.Background(), bucketName, bucketPolicy); err != nil {
t.Fatalf("unexpected error. %v", err)
}
globalPolicySys.Set(bucketName, *bucketPolicy)
defer globalPolicySys.Remove(bucketName)
// Unauthenticated upload with WRITE policy should succeed.
code = test("", true)
@@ -1036,12 +1057,22 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl
t.Fatalf("Expected the response status to be 403, but instead found `%d`", code)
}
bp := policy.BucketAccessPolicy{
Version: "1.0",
Statements: []policy.Statement{getReadOnlyObjectStatement(bucketName, "")},
bucketPolicy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource(bucketName, "*")),
condition.NewFunctions(),
)},
}
obj.SetBucketPolicy(context.Background(), bucketName, bp)
if err := obj.SetBucketPolicy(context.Background(), bucketName, bucketPolicy); err != nil {
t.Fatalf("unexpected error. %v", err)
}
globalPolicySys.Set(bucketName, *bucketPolicy)
defer globalPolicySys.Remove(bucketName)
// Unauthenticated download with READ policy should succeed.
code, bodyContent = test("")
@@ -1260,43 +1291,40 @@ func testWebGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
rec := httptest.NewRecorder()
bucketName := getRandomBucketName()
if err := obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil {
if err = obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil {
t.Fatal("Unexpected error: ", err)
}
policyVal := policy.BucketAccessPolicy{
Version: "2012-10-17",
bucketPolicy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
{
Actions: set.CreateStringSet("s3:GetBucketLocation", "s3:ListBucket"),
Effect: "Allow",
Principal: policy.User{
AWS: set.CreateStringSet("*"),
},
Resources: set.CreateStringSet(bucketARNPrefix + bucketName),
Sid: "",
},
{
Actions: set.CreateStringSet("s3:GetObject"),
Effect: "Allow",
Principal: policy.User{
AWS: set.CreateStringSet("*"),
},
Resources: set.CreateStringSet(bucketARNPrefix + bucketName + "/*"),
Sid: "",
},
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction, policy.ListBucketAction),
policy.NewResourceSet(policy.NewResource(bucketName, "")),
condition.NewFunctions(),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetObjectAction),
policy.NewResourceSet(policy.NewResource(bucketName, "*")),
condition.NewFunctions(),
),
},
}
if err := writeBucketPolicy(context.Background(), bucketName, obj, policyVal); err != nil {
if err = savePolicyConfig(obj, bucketName, bucketPolicy); err != nil {
t.Fatal("Unexpected error: ", err)
}
testCases := []struct {
bucketName string
prefix string
expectedResult policy.BucketPolicy
expectedResult miniogopolicy.BucketPolicy
}{
{bucketName, "", policy.BucketPolicyReadOnly},
{bucketName, "", miniogopolicy.BucketPolicyReadOnly},
}
for i, testCase := range testCases {
@@ -1338,57 +1366,63 @@ func testWebListAllBucketPoliciesHandler(obj ObjectLayer, instanceType string, t
rec := httptest.NewRecorder()
bucketName := getRandomBucketName()
if err := obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil {
if err = obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil {
t.Fatal("Unexpected error: ", err)
}
stringEqualsConditions := policy.ConditionMap{}
stringEqualsConditions["StringEquals"] = make(policy.ConditionKeyMap)
stringEqualsConditions["StringEquals"].Add("s3:prefix", set.CreateStringSet("hello"))
func1, err := condition.NewStringEqualsFunc(condition.S3Prefix, "hello")
if err != nil {
t.Fatalf("Unable to create string equals condition function. %v", err)
}
policyVal := policy.BucketAccessPolicy{
Version: "2012-10-17",
bucketPolicy := &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
{
Actions: set.CreateStringSet("s3:GetBucketLocation"),
Effect: "Allow",
Principal: policy.User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet(bucketARNPrefix + bucketName),
Sid: "",
},
{
Actions: set.CreateStringSet("s3:ListBucket"),
Conditions: stringEqualsConditions,
Effect: "Allow",
Principal: policy.User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet(bucketARNPrefix + bucketName),
Sid: "",
},
{
Actions: set.CreateStringSet("s3:ListBucketMultipartUploads"),
Effect: "Allow",
Principal: policy.User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet(bucketARNPrefix + bucketName),
Sid: "",
},
{
Actions: set.CreateStringSet("s3:AbortMultipartUpload", "s3:DeleteObject",
"s3:GetObject", "s3:ListMultipartUploadParts", "s3:PutObject"),
Effect: "Allow",
Principal: policy.User{AWS: set.CreateStringSet("*")},
Resources: set.CreateStringSet(bucketARNPrefix + bucketName + "/hello*"),
Sid: "",
},
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.GetBucketLocationAction),
policy.NewResourceSet(policy.NewResource(bucketName, "")),
condition.NewFunctions(),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.ListBucketAction),
policy.NewResourceSet(policy.NewResource(bucketName, "")),
condition.NewFunctions(func1),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(policy.ListBucketMultipartUploadsAction),
policy.NewResourceSet(policy.NewResource(bucketName, "")),
condition.NewFunctions(),
),
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(
policy.AbortMultipartUploadAction,
policy.DeleteObjectAction,
policy.GetObjectAction,
policy.ListMultipartUploadPartsAction,
policy.PutObjectAction,
),
policy.NewResourceSet(policy.NewResource(bucketName, "hello*")),
condition.NewFunctions(),
),
},
}
if err := writeBucketPolicy(context.Background(), bucketName, obj, policyVal); err != nil {
if err = savePolicyConfig(obj, bucketName, bucketPolicy); err != nil {
t.Fatal("Unexpected error: ", err)
}
testCaseResult1 := []BucketAccessPolicy{{
Bucket: bucketName,
Prefix: "hello",
Policy: policy.BucketPolicyReadWrite,
Policy: miniogopolicy.BucketPolicyReadWrite,
}}
testCases := []struct {
bucketName string
@@ -1460,7 +1494,7 @@ func testWebSetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
}
for i, testCase := range testCases {
args := &SetBucketPolicyArgs{BucketName: testCase.bucketName, Prefix: testCase.prefix, Policy: testCase.policy}
args := &SetBucketPolicyWebArgs{BucketName: testCase.bucketName, Prefix: testCase.prefix, Policy: testCase.policy}
reply := &WebGenericRep{}
// Call SetBucketPolicy RPC
req, err := newTestWebRPCRequest("Web.SetBucketPolicy", authorization, args)
@@ -1719,7 +1753,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) {
{"ListBuckets", AuthRPCArgs{Version: globalRPCAPIVersion}, ListBucketsRep{}},
{"ListObjects", ListObjectsArgs{BucketName: bucketName, Prefix: ""}, ListObjectsRep{}},
{"GetBucketPolicy", GetBucketPolicyArgs{BucketName: bucketName, Prefix: ""}, GetBucketPolicyRep{}},
{"SetBucketPolicy", SetBucketPolicyArgs{BucketName: bucketName, Prefix: "", Policy: "none"}, WebGenericRep{}},
{"SetBucketPolicy", SetBucketPolicyWebArgs{BucketName: bucketName, Prefix: "", Policy: "none"}, WebGenericRep{}},
}
for _, rpcCall := range webRPCs {

View File

@@ -21,17 +21,16 @@ import (
"fmt"
"hash/crc32"
"io"
"reflect"
"sort"
"strings"
"sync"
"time"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/bpool"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/madmin"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/sync/errgroup"
)
@@ -77,9 +76,6 @@ type xlSets struct {
// Distribution algorithm of choice.
distributionAlgo string
// Variable represents bucket policies in memory.
bucketPolicies *bucketPolicies
// Pack level listObjects pool management.
listPool *treeWalkPool
}
@@ -263,16 +259,14 @@ func newXLSets(endpoints EndpointList, format *formatXLV3, setCount int, drivesP
// Connect disks right away.
s.connectDisks()
// Initialize and load bucket policies.
var err error
s.bucketPolicies, err = initBucketPolicies(s)
if err != nil {
return nil, err
// Initialize notification system.
if err := globalNotificationSys.Init(s); err != nil {
return nil, fmt.Errorf("Unable to initialize notification system. %v", err)
}
// Initialize notification system.
if err = globalNotificationSys.Init(s); err != nil {
return nil, fmt.Errorf("Unable to initialize event notification. %s", err)
// Initialize policy system.
if err := globalPolicySys.Init(s); err != nil {
return nil, fmt.Errorf("Unable to initialize policy system. %v", err)
}
// Start the disk monitoring and connect routine.
@@ -473,35 +467,18 @@ func (s *xlSets) ListObjectsV2(ctx context.Context, bucket, prefix, continuation
}
// SetBucketPolicy persist the new policy on the bucket.
func (s *xlSets) SetBucketPolicy(ctx context.Context, bucket string, policy policy.BucketAccessPolicy) error {
return persistAndNotifyBucketPolicyChange(ctx, bucket, false, policy, s)
func (s *xlSets) SetBucketPolicy(ctx context.Context, bucket string, policy *policy.Policy) error {
return savePolicyConfig(s, bucket, policy)
}
// GetBucketPolicy will return a policy on a bucket
func (s *xlSets) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
// fetch bucket policy from cache.
bpolicy := s.bucketPolicies.GetBucketPolicy(bucket)
if reflect.DeepEqual(bpolicy, emptyBucketPolicy) {
return ReadBucketPolicy(bucket, s)
}
return bpolicy, nil
func (s *xlSets) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
return GetPolicyConfig(s, bucket)
}
// DeleteBucketPolicy deletes all policies on bucket
func (s *xlSets) DeleteBucketPolicy(ctx context.Context, bucket string) error {
return persistAndNotifyBucketPolicyChange(ctx, bucket, true, emptyBucketPolicy, s)
}
// RefreshBucketPolicy refreshes policy cache from disk
func (s *xlSets) RefreshBucketPolicy(ctx context.Context, bucket string) error {
policy, err := ReadBucketPolicy(bucket, s)
if err != nil {
if reflect.DeepEqual(policy, emptyBucketPolicy) {
return s.bucketPolicies.DeleteBucketPolicy(bucket)
}
return err
}
return s.bucketPolicies.SetBucketPolicy(bucket, policy)
return removePolicyConfig(ctx, s, bucket)
}
// IsNotificationSupported returns whether bucket notification is applicable for this layer.

View File

@@ -18,12 +18,11 @@ package cmd
import (
"context"
"reflect"
"sort"
"sync"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/policy"
)
// list all errors that can be ignore in a bucket operation.
@@ -280,36 +279,18 @@ func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string) error {
}
// SetBucketPolicy sets policy on bucket
func (xl xlObjects) SetBucketPolicy(ctx context.Context, bucket string, policy policy.BucketAccessPolicy) error {
return persistAndNotifyBucketPolicyChange(ctx, bucket, false, policy, xl)
func (xl xlObjects) SetBucketPolicy(ctx context.Context, bucket string, policy *policy.Policy) error {
return savePolicyConfig(xl, bucket, policy)
}
// GetBucketPolicy will get policy on bucket
func (xl xlObjects) GetBucketPolicy(ctx context.Context, bucket string) (policy.BucketAccessPolicy, error) {
// fetch bucket policy from cache.
bpolicy := xl.bucketPolicies.GetBucketPolicy(bucket)
if reflect.DeepEqual(bpolicy, emptyBucketPolicy) {
return ReadBucketPolicy(bucket, xl)
}
return bpolicy, nil
func (xl xlObjects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
return GetPolicyConfig(xl, bucket)
}
// DeleteBucketPolicy deletes all policies on bucket
func (xl xlObjects) DeleteBucketPolicy(ctx context.Context, bucket string) error {
return persistAndNotifyBucketPolicyChange(ctx, bucket, true, emptyBucketPolicy, xl)
}
// RefreshBucketPolicy refreshes policy cache from disk
func (xl xlObjects) RefreshBucketPolicy(ctx context.Context, bucket string) error {
policy, err := ReadBucketPolicy(bucket, xl)
if err != nil {
if reflect.DeepEqual(policy, emptyBucketPolicy) {
return xl.bucketPolicies.DeleteBucketPolicy(bucket)
}
return err
}
return xl.bucketPolicies.SetBucketPolicy(bucket, policy)
return removePolicyConfig(ctx, xl, bucket)
}
// IsNotificationSupported returns whether bucket notification is applicable for this layer.

View File

@@ -43,9 +43,6 @@ type xlObjects struct {
// Byte pools used for temporary i/o buffers.
bp *bpool.BytePoolCap
// Variable represents bucket policies in memory.
bucketPolicies *bucketPolicies
// TODO: Deprecated only kept here for tests, should be removed in future.
storageDisks []StorageAPI