mirror of
https://github.com/minio/minio.git
synced 2025-11-07 04:42:56 -05:00
api/bucketPolicy: Use minio-go/pkg/set and fix bucket policy regression. (#2506)
Current master has a regression 'mc policy <policy-type> alias/bucket/prefix' does not work anymore, due to the way new minio-go changes do json marshalling. This led to a regression on server side when a ``prefix`` is provided policy is rejected as malformed from th server which is not the case with AWS S3. This patch uses the new ``minio-go/pkg/set`` package to address the unmarshalling problems. Fixes #2503
This commit is contained in:
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
|
||||
mux "github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
)
|
||||
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||
@@ -43,13 +44,13 @@ func enforceBucketPolicy(bucket string, action string, reqURL *url.URL) (s3Error
|
||||
resource := AWSResourcePrefix + strings.TrimPrefix(reqURL.Path, "/")
|
||||
|
||||
// Get conditions for policy verification.
|
||||
conditions := make(map[string]string)
|
||||
conditionKeyMap := make(map[string]set.StringSet)
|
||||
for queryParam := range reqURL.Query() {
|
||||
conditions[queryParam] = reqURL.Query().Get(queryParam)
|
||||
conditionKeyMap[queryParam] = set.CreateStringSet(reqURL.Query().Get(queryParam))
|
||||
}
|
||||
|
||||
// Validate action, resource and conditions with current policy statements.
|
||||
if !bucketPolicyEvalStatements(action, resource, conditions, policy.Statements) {
|
||||
if !bucketPolicyEvalStatements(action, resource, conditionKeyMap, policy.Statements) {
|
||||
return ErrAccessDenied
|
||||
}
|
||||
return ErrNone
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
mux "github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
)
|
||||
|
||||
@@ -32,7 +33,7 @@ const maxAccessPolicySize = 20 * 1024 // 20KiB.
|
||||
|
||||
// Verify if a given action is valid for the url path based on the
|
||||
// existing bucket access policy.
|
||||
func bucketPolicyEvalStatements(action string, resource string, conditions map[string]string, statements []policyStatement) bool {
|
||||
func bucketPolicyEvalStatements(action string, resource string, conditions map[string]set.StringSet, statements []policyStatement) bool {
|
||||
for _, statement := range statements {
|
||||
if bucketPolicyMatchStatement(action, resource, conditions, statement) {
|
||||
if statement.Effect == "Allow" {
|
||||
@@ -48,7 +49,7 @@ func bucketPolicyEvalStatements(action string, resource string, conditions map[s
|
||||
}
|
||||
|
||||
// Verify if action, resource and conditions match input policy statement.
|
||||
func bucketPolicyMatchStatement(action string, resource string, conditions map[string]string, statement policyStatement) bool {
|
||||
func bucketPolicyMatchStatement(action string, resource string, conditions map[string]set.StringSet, statement policyStatement) bool {
|
||||
// Verify if action matches.
|
||||
if bucketPolicyActionMatch(action, statement) {
|
||||
// Verify if resource matches.
|
||||
@@ -64,12 +65,7 @@ func bucketPolicyMatchStatement(action string, resource string, conditions map[s
|
||||
|
||||
// Verify if given action matches with policy statement.
|
||||
func bucketPolicyActionMatch(action string, statement policyStatement) bool {
|
||||
for _, policyAction := range statement.Actions {
|
||||
if matched := actionMatch(policyAction, action); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return !statement.Actions.FuncMatch(actionMatch, action).IsEmpty()
|
||||
}
|
||||
|
||||
// Match function matches wild cards in 'pattern' for resource.
|
||||
@@ -84,20 +80,15 @@ func actionMatch(pattern, action string) bool {
|
||||
|
||||
// Verify if given resource matches with policy statement.
|
||||
func bucketPolicyResourceMatch(resource string, statement policyStatement) bool {
|
||||
for _, resourcep := range statement.Resources {
|
||||
// the resource rule for object could contain "*" wild card.
|
||||
// the requested object can be given access based on the already set bucket policy if
|
||||
// the match is successful.
|
||||
// More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html .
|
||||
if matched := resourceMatch(resourcep, resource); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
// 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 map[string]string, statement policyStatement) bool {
|
||||
func bucketPolicyConditionMatch(conditions map[string]set.StringSet, statement policyStatement) bool {
|
||||
// Supports following conditions.
|
||||
// - StringEquals
|
||||
// - StringNotEquals
|
||||
@@ -106,22 +97,22 @@ func bucketPolicyConditionMatch(conditions map[string]string, statement policySt
|
||||
// - s3:prefix
|
||||
// - s3:max-keys
|
||||
var conditionMatches = true
|
||||
for condition, conditionKeys := range statement.Conditions {
|
||||
for condition, conditionKeyVal := range statement.Conditions {
|
||||
if condition == "StringEquals" {
|
||||
if conditionKeys["s3:prefix"] != conditions["prefix"] {
|
||||
if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
if conditionKeys["s3:max-keys"] != conditions["max-keys"] {
|
||||
if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
} else if condition == "StringNotEquals" {
|
||||
if conditionKeys["s3:prefix"] == conditions["prefix"] {
|
||||
if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
if conditionKeys["s3:max-keys"] == conditions["max-keys"] {
|
||||
if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) {
|
||||
conditionMatches = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
)
|
||||
|
||||
// Tests validate Bucket policy resource matcher.
|
||||
@@ -31,7 +33,7 @@ func TestBucketPolicyResourceMatch(t *testing.T) {
|
||||
// generates statement with given resource..
|
||||
generateStatement := func(resource string) policyStatement {
|
||||
statement := policyStatement{}
|
||||
statement.Resources = []string{resource}
|
||||
statement.Resources = set.CreateStringSet([]string{resource}...)
|
||||
return statement
|
||||
}
|
||||
|
||||
@@ -336,7 +338,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
credentials := serverConfig.GetCredential()
|
||||
|
||||
// template for constructing HTTP request body for PUT bucket policy.
|
||||
bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::%s"]},{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/this*"]}]}`
|
||||
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":""}]}`
|
||||
|
||||
// Writing bucket policy before running test on GetBucketPolicy.
|
||||
putTestPolicies := []struct {
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,50 +36,39 @@ const (
|
||||
)
|
||||
|
||||
// supportedActionMap - lists all the actions supported by minio.
|
||||
var supportedActionMap = map[string]struct{}{
|
||||
"*": {},
|
||||
"s3:*": {},
|
||||
"s3:GetObject": {},
|
||||
"s3:ListBucket": {},
|
||||
"s3:PutObject": {},
|
||||
"s3:GetBucketLocation": {},
|
||||
"s3:DeleteObject": {},
|
||||
"s3:AbortMultipartUpload": {},
|
||||
"s3:ListBucketMultipartUploads": {},
|
||||
"s3:ListMultipartUploadParts": {},
|
||||
}
|
||||
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 = map[string]struct{}{
|
||||
"StringEquals": {},
|
||||
"StringNotEquals": {},
|
||||
}
|
||||
var supportedConditionsType = set.CreateStringSet("StringEquals", "StringNotEquals")
|
||||
|
||||
// Validate s3:prefix, s3:max-keys are present if not
|
||||
// supported keys for the conditions.
|
||||
var supportedConditionsKey = map[string]struct{}{
|
||||
"s3:prefix": {},
|
||||
"s3:max-keys": {},
|
||||
}
|
||||
var supportedConditionsKey = set.CreateStringSet("s3:prefix", "s3:max-keys")
|
||||
|
||||
// User - canonical users list.
|
||||
// supportedEffectMap - supported effects.
|
||||
var supportedEffectMap = set.CreateStringSet("Allow", "Deny")
|
||||
|
||||
// policyUser - canonical users list.
|
||||
type policyUser struct {
|
||||
AWS []string
|
||||
AWS set.StringSet `json:"AWS,omitempty"`
|
||||
CanonicalUser set.StringSet `json:"CanonicalUser,omitempty"`
|
||||
}
|
||||
|
||||
// Statement - minio policy statement
|
||||
type policyStatement struct {
|
||||
Sid string
|
||||
Actions set.StringSet `json:"Action"`
|
||||
Conditions map[string]map[string]set.StringSet `json:"Condition,omitempty"`
|
||||
Effect string
|
||||
Principal policyUser `json:"Principal"`
|
||||
Actions []string `json:"Action"`
|
||||
Resources []string `json:"Resource"`
|
||||
Conditions map[string]map[string]string `json:"Condition,omitempty"`
|
||||
Principal policyUser `json:"Principal"`
|
||||
Resources set.StringSet `json:"Resource"`
|
||||
Sid string
|
||||
}
|
||||
|
||||
// bucketPolicy - collection of various bucket policy statements.
|
||||
type bucketPolicy struct {
|
||||
Version string // date in 0000-00-00 format
|
||||
Version string // date in YYYY-MM-DD format
|
||||
Statements []policyStatement `json:"Statement"`
|
||||
}
|
||||
|
||||
@@ -91,51 +82,42 @@ func (b bucketPolicy) String() string {
|
||||
return string(bbytes)
|
||||
}
|
||||
|
||||
// supportedEffectMap - supported effects.
|
||||
var supportedEffectMap = map[string]struct{}{
|
||||
"Allow": {},
|
||||
"Deny": {},
|
||||
}
|
||||
|
||||
// isValidActions - are actions valid.
|
||||
func isValidActions(actions []string) (err error) {
|
||||
func isValidActions(actions set.StringSet) (err error) {
|
||||
// Statement actions cannot be empty.
|
||||
if len(actions) == 0 {
|
||||
err = errors.New("Action list cannot be empty.")
|
||||
return err
|
||||
}
|
||||
for _, action := range actions {
|
||||
if _, ok := supportedActionMap[action]; !ok {
|
||||
err = errors.New("Unsupported action found: ‘" + action + "’, please validate your policy document.")
|
||||
return err
|
||||
}
|
||||
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) error {
|
||||
func isValidEffect(effect string) (err error) {
|
||||
// Statement effect cannot be empty.
|
||||
if len(effect) == 0 {
|
||||
err := errors.New("Policy effect cannot be empty.")
|
||||
if effect == "" {
|
||||
err = errors.New("Policy effect cannot be empty.")
|
||||
return err
|
||||
}
|
||||
_, ok := supportedEffectMap[effect]
|
||||
if !ok {
|
||||
err := errors.New("Unsupported Effect found: ‘" + effect + "’, please validate your policy document.")
|
||||
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 []string) (err error) {
|
||||
func isValidResources(resources set.StringSet) (err error) {
|
||||
// Statement resources cannot be empty.
|
||||
if len(resources) == 0 {
|
||||
err = errors.New("Resource list cannot be empty.")
|
||||
return err
|
||||
}
|
||||
for _, resource := range resources {
|
||||
for resource := range resources {
|
||||
if !strings.HasPrefix(resource, AWSResourcePrefix) {
|
||||
err = errors.New("Unsupported resource style found: ‘" + resource + "’, please validate your policy document.")
|
||||
return err
|
||||
@@ -150,63 +132,50 @@ func isValidResources(resources []string) (err error) {
|
||||
}
|
||||
|
||||
// isValidPrincipals - are valid principals.
|
||||
func isValidPrincipals(principals []string) (err error) {
|
||||
func isValidPrincipals(principals set.StringSet) (err error) {
|
||||
// Statement principal should have a value.
|
||||
if len(principals) == 0 {
|
||||
err = errors.New("Principal cannot be empty.")
|
||||
return err
|
||||
}
|
||||
for _, principal := range principals {
|
||||
if unsuppPrincipals := principals.Difference(set.CreateStringSet([]string{"*"}...)); !unsuppPrincipals.IsEmpty() {
|
||||
// Minio does not support or implement IAM, "*" is the only valid value.
|
||||
// Amazon s3 doc on principals: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Principal
|
||||
if principal != "*" {
|
||||
err = fmt.Errorf("Unsupported principal style found: ‘%s’, please validate your policy document.", principal)
|
||||
return err
|
||||
}
|
||||
err = fmt.Errorf("Unsupported principals found: ‘%#v’, please validate your policy document.", unsuppPrincipals)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidConditions - are valid conditions.
|
||||
func isValidConditions(conditions map[string]map[string]string) (err error) {
|
||||
// Returns true if string 'a' is found in the list.
|
||||
findString := func(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
conditionKeyVal := make(map[string][]string)
|
||||
func isValidConditions(conditions map[string]map[string]set.StringSet) (err error) {
|
||||
// Verify conditions should be valid.
|
||||
// Validate if stringEquals, stringNotEquals are present
|
||||
// if not throw an error.
|
||||
conditionKeyVal := make(map[string]set.StringSet)
|
||||
for conditionType := range conditions {
|
||||
_, validType := supportedConditionsType[conditionType]
|
||||
if !validType {
|
||||
if !supportedConditionsType.Contains(conditionType) {
|
||||
err = fmt.Errorf("Unsupported condition type '%s', please validate your policy document.", conditionType)
|
||||
return err
|
||||
}
|
||||
for key := range conditions[conditionType] {
|
||||
_, validKey := supportedConditionsKey[key]
|
||||
if !validKey {
|
||||
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
|
||||
}
|
||||
conditionArray, ok := conditionKeyVal[key]
|
||||
if ok && findString(conditions[conditionType][key], conditionArray) {
|
||||
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] = append(conditionKeyVal[key], conditions[conditionType][key])
|
||||
conditionKeyVal[key] = value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List of actions for which prefixes are not allowed.
|
||||
var invalidPrefixActions = map[string]struct{}{
|
||||
var invalidPrefixActions = set.StringSet{
|
||||
"s3:GetBucketLocation": {},
|
||||
"s3:ListBucket": {},
|
||||
"s3:ListBucketMultipartUploads": {},
|
||||
@@ -227,10 +196,10 @@ func resourcePrefix(resource string) string {
|
||||
func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIErrorCode {
|
||||
// Validate statements for special actions and collect resources
|
||||
// for others to validate nesting.
|
||||
var resourceMap = make(map[string]struct{})
|
||||
var resourceMap = set.NewStringSet()
|
||||
for _, statement := range bucketPolicy.Statements {
|
||||
for _, action := range statement.Actions {
|
||||
for _, resource := range statement.Resources {
|
||||
for action := range statement.Actions {
|
||||
for resource := range statement.Resources {
|
||||
resourcePrefix := strings.SplitAfter(resource, AWSResourcePrefix)[1]
|
||||
if _, ok := invalidPrefixActions[action]; ok {
|
||||
// Resource prefix is not equal to bucket for
|
||||
@@ -245,7 +214,7 @@ func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIEr
|
||||
return ErrMalformedPolicy
|
||||
}
|
||||
// All valid resources collect them separately to verify nesting.
|
||||
resourceMap[resourcePrefix] = struct{}{}
|
||||
resourceMap.Add(resourcePrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
)
|
||||
|
||||
// Common bucket actions for both read and write policies.
|
||||
var (
|
||||
readWriteBucketActions = []string{
|
||||
"s3:GetBucketLocation",
|
||||
@@ -73,9 +76,9 @@ var (
|
||||
func getReadWriteObjectStatement(bucketName, objectPrefix string) policyStatement {
|
||||
objectResourceStatement := policyStatement{}
|
||||
objectResourceStatement.Effect = "Allow"
|
||||
objectResourceStatement.Principal.AWS = []string{"*"}
|
||||
objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}
|
||||
objectResourceStatement.Actions = readWriteObjectActions
|
||||
objectResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...)
|
||||
objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
|
||||
objectResourceStatement.Actions = set.CreateStringSet(readWriteObjectActions...)
|
||||
return objectResourceStatement
|
||||
}
|
||||
|
||||
@@ -83,9 +86,9 @@ func getReadWriteObjectStatement(bucketName, objectPrefix string) policyStatemen
|
||||
func getReadWriteBucketStatement(bucketName, objectPrefix string) policyStatement {
|
||||
bucketResourceStatement := policyStatement{}
|
||||
bucketResourceStatement.Effect = "Allow"
|
||||
bucketResourceStatement.Principal.AWS = []string{"*"}
|
||||
bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}
|
||||
bucketResourceStatement.Actions = readWriteBucketActions
|
||||
bucketResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...)
|
||||
bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
|
||||
bucketResourceStatement.Actions = set.CreateStringSet(readWriteBucketActions...)
|
||||
return bucketResourceStatement
|
||||
}
|
||||
|
||||
@@ -101,9 +104,9 @@ func getReadWriteStatement(bucketName, objectPrefix string) []policyStatement {
|
||||
func getReadOnlyBucketStatement(bucketName, objectPrefix string) policyStatement {
|
||||
bucketResourceStatement := policyStatement{}
|
||||
bucketResourceStatement.Effect = "Allow"
|
||||
bucketResourceStatement.Principal.AWS = []string{"*"}
|
||||
bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}
|
||||
bucketResourceStatement.Actions = readOnlyBucketActions
|
||||
bucketResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...)
|
||||
bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
|
||||
bucketResourceStatement.Actions = set.CreateStringSet(readOnlyBucketActions...)
|
||||
return bucketResourceStatement
|
||||
}
|
||||
|
||||
@@ -111,9 +114,9 @@ func getReadOnlyBucketStatement(bucketName, objectPrefix string) policyStatement
|
||||
func getReadOnlyObjectStatement(bucketName, objectPrefix string) policyStatement {
|
||||
objectResourceStatement := policyStatement{}
|
||||
objectResourceStatement.Effect = "Allow"
|
||||
objectResourceStatement.Principal.AWS = []string{"*"}
|
||||
objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}
|
||||
objectResourceStatement.Actions = readOnlyObjectActions
|
||||
objectResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...)
|
||||
objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
|
||||
objectResourceStatement.Actions = set.CreateStringSet(readOnlyObjectActions...)
|
||||
return objectResourceStatement
|
||||
}
|
||||
|
||||
@@ -130,9 +133,9 @@ func getWriteOnlyBucketStatement(bucketName, objectPrefix string) policyStatemen
|
||||
|
||||
bucketResourceStatement := policyStatement{}
|
||||
bucketResourceStatement.Effect = "Allow"
|
||||
bucketResourceStatement.Principal.AWS = []string{"*"}
|
||||
bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}
|
||||
bucketResourceStatement.Actions = writeOnlyBucketActions
|
||||
bucketResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...)
|
||||
bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
|
||||
bucketResourceStatement.Actions = set.CreateStringSet(writeOnlyBucketActions...)
|
||||
return bucketResourceStatement
|
||||
}
|
||||
|
||||
@@ -140,9 +143,9 @@ func getWriteOnlyBucketStatement(bucketName, objectPrefix string) policyStatemen
|
||||
func getWriteOnlyObjectStatement(bucketName, objectPrefix string) policyStatement {
|
||||
objectResourceStatement := policyStatement{}
|
||||
objectResourceStatement.Effect = "Allow"
|
||||
objectResourceStatement.Principal.AWS = []string{"*"}
|
||||
objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}
|
||||
objectResourceStatement.Actions = writeOnlyObjectActions
|
||||
objectResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...)
|
||||
objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
|
||||
objectResourceStatement.Actions = set.CreateStringSet(writeOnlyObjectActions...)
|
||||
return objectResourceStatement
|
||||
}
|
||||
|
||||
@@ -159,7 +162,7 @@ func getWriteOnlyStatement(bucketName, objectPrefix string) []policyStatement {
|
||||
func TestIsValidActions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
// input.
|
||||
actions []string
|
||||
actions set.StringSet
|
||||
// expected output.
|
||||
err error
|
||||
// flag indicating whether the test should pass.
|
||||
@@ -168,19 +171,22 @@ func TestIsValidActions(t *testing.T) {
|
||||
// Inputs with unsupported Action.
|
||||
// Test case - 1.
|
||||
// "s3:ListObject" is an invalid Action.
|
||||
{[]string{"s3:GetObject", "s3:ListObject", "s3:RemoveObject"}, errors.New("Unsupported action found: ‘s3:ListObject’, please validate your policy document."), false},
|
||||
{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.
|
||||
{[]string{}, errors.New("Action list cannot be empty."), false},
|
||||
{set.CreateStringSet([]string{}...), errors.New("Action list cannot be empty."), false},
|
||||
// Test case - 3.
|
||||
// "s3:DeleteEverything"" is an invalid Action.
|
||||
{[]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"},
|
||||
errors.New("Unsupported action found: ‘s3:DeleteEverything’, please validate your policy document."), false},
|
||||
|
||||
{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.
|
||||
{[]string{"s3:*", "*", "s3:GetObject", "s3:ListBucket",
|
||||
"s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject", "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts"}, nil, true},
|
||||
{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)
|
||||
@@ -190,13 +196,6 @@ func TestIsValidActions(t *testing.T) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,16 +211,18 @@ func TestIsValidEffect(t *testing.T) {
|
||||
}{
|
||||
// Inputs with unsupported Effect.
|
||||
// Test case - 1.
|
||||
{"DontAllow", errors.New("Unsupported Effect found: ‘DontAllow’, please validate your policy document."), false},
|
||||
{"", errors.New("Policy effect cannot be empty."), false},
|
||||
// Test case - 2.
|
||||
{"NeverAllow", errors.New("Unsupported Effect found: ‘NeverAllow’, please validate your policy document."), false},
|
||||
{"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 - 4.
|
||||
{"Allow", nil, true},
|
||||
// Test Case - 5.
|
||||
{"Allow", nil, true},
|
||||
// Test Case - 6.
|
||||
{"Deny", nil, true},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
@@ -271,7 +272,7 @@ func TestIsValidResources(t *testing.T) {
|
||||
{[]string{"arn:aws:s3:::my-bucket/Asia/India/*"}, nil, true},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
err := isValidResources(testCase.resources)
|
||||
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())
|
||||
}
|
||||
@@ -303,16 +304,15 @@ func TestIsValidPrincipals(t *testing.T) {
|
||||
{[]string{}, errors.New("Principal cannot be empty."), false},
|
||||
// Test case - 2.
|
||||
// "*" is the only valid principal.
|
||||
{[]string{"my-principal"}, errors.New("Unsupported principal style found: ‘my-principal’, please validate your policy document."), false},
|
||||
{[]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 principal style found: ‘111122233’, please validate your policy document."), false},
|
||||
|
||||
{[]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 {
|
||||
err := isValidPrincipals(testCase.principals)
|
||||
err := isValidPrincipals(set.CreateStringSet(testCase.principals...))
|
||||
if err != nil && testCase.shouldPass {
|
||||
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
|
||||
}
|
||||
@@ -331,70 +331,70 @@ func TestIsValidPrincipals(t *testing.T) {
|
||||
// Tests validate policyStatement condition validator.
|
||||
func TestIsValidConditions(t *testing.T) {
|
||||
// returns empty conditions map.
|
||||
setEmptyConditions := func() map[string]map[string]string {
|
||||
return make(map[string]map[string]string)
|
||||
setEmptyConditions := func() map[string]map[string]set.StringSet {
|
||||
return make(map[string]map[string]set.StringSet)
|
||||
}
|
||||
|
||||
// returns map with the "StringEquals" set to empty map.
|
||||
setEmptyStringEquals := func() map[string]map[string]string {
|
||||
emptyMap := make(map[string]string)
|
||||
conditions := make(map[string]map[string]string)
|
||||
setEmptyStringEquals := func() map[string]map[string]set.StringSet {
|
||||
emptyMap := make(map[string]set.StringSet)
|
||||
conditions := make(map[string]map[string]set.StringSet)
|
||||
conditions["StringEquals"] = emptyMap
|
||||
return conditions
|
||||
|
||||
}
|
||||
|
||||
// returns map with the "StringNotEquals" set to empty map.
|
||||
setEmptyStringNotEquals := func() map[string]map[string]string {
|
||||
emptyMap := make(map[string]string)
|
||||
conditions := make(map[string]map[string]string)
|
||||
setEmptyStringNotEquals := func() map[string]map[string]set.StringSet {
|
||||
emptyMap := make(map[string]set.StringSet)
|
||||
conditions := make(map[string]map[string]set.StringSet)
|
||||
conditions["StringNotEquals"] = emptyMap
|
||||
return conditions
|
||||
|
||||
}
|
||||
// Generate conditions.
|
||||
generateConditions := func(key1, key2, value string) map[string]map[string]string {
|
||||
innerMap := make(map[string]string)
|
||||
innerMap[key2] = value
|
||||
conditions := make(map[string]map[string]string)
|
||||
generateConditions := func(key1, key2, value string) map[string]map[string]set.StringSet {
|
||||
innerMap := make(map[string]set.StringSet)
|
||||
innerMap[key2] = set.CreateStringSet(value)
|
||||
conditions := make(map[string]map[string]set.StringSet)
|
||||
conditions[key1] = innerMap
|
||||
return conditions
|
||||
}
|
||||
|
||||
// generate ambigious conditions.
|
||||
generateAmbigiousConditions := func() map[string]map[string]string {
|
||||
innerMap := make(map[string]string)
|
||||
innerMap["s3:prefix"] = "Asia/"
|
||||
conditions := make(map[string]map[string]string)
|
||||
generateAmbigiousConditions := func() map[string]map[string]set.StringSet {
|
||||
innerMap := make(map[string]set.StringSet)
|
||||
innerMap["s3:prefix"] = set.CreateStringSet("Asia/")
|
||||
conditions := make(map[string]map[string]set.StringSet)
|
||||
conditions["StringEquals"] = innerMap
|
||||
conditions["StringNotEquals"] = innerMap
|
||||
return conditions
|
||||
}
|
||||
|
||||
// generate valid and non valid type in the condition map.
|
||||
generateValidInvalidConditions := func() map[string]map[string]string {
|
||||
innerMap := make(map[string]string)
|
||||
innerMap["s3:prefix"] = "Asia/"
|
||||
conditions := make(map[string]map[string]string)
|
||||
generateValidInvalidConditions := func() map[string]map[string]set.StringSet {
|
||||
innerMap := make(map[string]set.StringSet)
|
||||
innerMap["s3:prefix"] = set.CreateStringSet("Asia/")
|
||||
conditions := make(map[string]map[string]set.StringSet)
|
||||
conditions["StringEquals"] = innerMap
|
||||
conditions["InvalidType"] = innerMap
|
||||
return conditions
|
||||
}
|
||||
|
||||
// generate valid and invalid keys for valid types in the same condition map.
|
||||
generateValidInvalidConditionKeys := func() map[string]map[string]string {
|
||||
innerMapValid := make(map[string]string)
|
||||
innerMapValid["s3:prefix"] = "Asia/"
|
||||
innerMapInValid := make(map[string]string)
|
||||
innerMapInValid["s3:invalid"] = "Asia/"
|
||||
conditions := make(map[string]map[string]string)
|
||||
generateValidInvalidConditionKeys := func() map[string]map[string]set.StringSet {
|
||||
innerMapValid := make(map[string]set.StringSet)
|
||||
innerMapValid["s3:prefix"] = set.CreateStringSet("Asia/")
|
||||
innerMapInValid := make(map[string]set.StringSet)
|
||||
innerMapInValid["s3:invalid"] = set.CreateStringSet("Asia/")
|
||||
conditions := make(map[string]map[string]set.StringSet)
|
||||
conditions["StringEquals"] = innerMapValid
|
||||
conditions["StringEquals"] = innerMapInValid
|
||||
return conditions
|
||||
}
|
||||
|
||||
// List of Conditions used for test cases.
|
||||
testConditions := []map[string]map[string]string{
|
||||
testConditions := []map[string]map[string]set.StringSet{
|
||||
generateConditions("StringValues", "s3:max-keys", "100"),
|
||||
generateConditions("StringEquals", "s3:Object", "100"),
|
||||
generateAmbigiousConditions(),
|
||||
@@ -410,7 +410,7 @@ func TestIsValidConditions(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
inputCondition map[string]map[string]string
|
||||
inputCondition map[string]map[string]set.StringSet
|
||||
// expected result.
|
||||
expectedErr error
|
||||
// flag indicating whether test should pass.
|
||||
@@ -474,20 +474,20 @@ func TestIsValidConditions(t *testing.T) {
|
||||
func TestCheckbucketPolicyResources(t *testing.T) {
|
||||
// constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
|
||||
setValidPrefixActions := func(statements []policyStatement) []policyStatement {
|
||||
statements[0].Actions = []string{"s3:DeleteObject", "s3:PutObject"}
|
||||
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 []policyStatement) []policyStatement {
|
||||
statements[0].Resources = []string{"arn:aws:s3:::minio-bucket/Asia/*", "arn:aws:s3:::minio-bucket/Asia/India/*"}
|
||||
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 []policyStatement) []policyStatement {
|
||||
statements[0].Resources = []string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}
|
||||
statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}...)
|
||||
return statements
|
||||
}
|
||||
|
||||
@@ -575,7 +575,7 @@ func TestParseBucketPolicy(t *testing.T) {
|
||||
// set Unsupported Actions.
|
||||
setUnsupportedActions := func(statements []policyStatement) []policyStatement {
|
||||
// "s3:DeleteEverything"" is an Unsupported Action.
|
||||
statements[0].Actions = []string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}
|
||||
statements[0].Actions = set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...)
|
||||
return statements
|
||||
}
|
||||
// set unsupported Effect.
|
||||
@@ -587,13 +587,13 @@ func TestParseBucketPolicy(t *testing.T) {
|
||||
// set unsupported principals.
|
||||
setUnsupportedPrincipals := func(statements []policyStatement) []policyStatement {
|
||||
// "User1111"" is an Unsupported Principal.
|
||||
statements[0].Principal.AWS = []string{"*", "User1111"}
|
||||
statements[0].Principal.AWS = set.CreateStringSet([]string{"*", "User1111"}...)
|
||||
return statements
|
||||
}
|
||||
// set unsupported Resources.
|
||||
setUnsupportedResources := func(statements []policyStatement) []policyStatement {
|
||||
// "s3:DeleteEverything"" is an Unsupported Action.
|
||||
statements[0].Resources = []string{"my-resource"}
|
||||
statements[0].Resources = set.CreateStringSet([]string{"my-resource"}...)
|
||||
return statements
|
||||
}
|
||||
// List of bucketPolicy used for test cases.
|
||||
@@ -652,13 +652,13 @@ func TestParseBucketPolicy(t *testing.T) {
|
||||
{bucketAccesPolicies[4], bucketAccesPolicies[4], nil, true},
|
||||
// Test case - 6.
|
||||
// bucketPolicy statement contains unsupported action.
|
||||
{bucketAccesPolicies[5], bucketAccesPolicies[5], fmt.Errorf("Unsupported action found: ‘s3:DeleteEverything’, please validate your policy document."), false},
|
||||
{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 principal style found: ‘User1111’, please validate your policy document."), false},
|
||||
{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},
|
||||
|
||||
@@ -162,7 +162,7 @@ func (s *TestSuiteCommon) TestBucketNotification(c *C) {
|
||||
// Deletes the policy and verifies the deletion by fetching it back.
|
||||
func (s *TestSuiteCommon) TestBucketPolicy(c *C) {
|
||||
// Sample bucket policy.
|
||||
bucketPolicyBuf := `{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::%s"]},{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/this*"]}]}`
|
||||
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":""}]}`
|
||||
|
||||
// generate a random bucket Name.
|
||||
bucketName := getRandomBucketName()
|
||||
|
||||
Reference in New Issue
Block a user