Fail PutBucketPolicy if conditions are incompatible with actions. (#3659)

This commit is contained in:
Krishnan Parthasarathi 2017-01-30 22:50:16 +05:30 committed by Harshavardhana
parent 9b6bcb30d9
commit 2665aba555
3 changed files with 52 additions and 29 deletions

View file

@ -97,6 +97,9 @@ func bucketPolicyConditionMatch(conditions map[string]set.StringSet, statement p
// - s3:max-keys // - s3:max-keys
// - s3:aws-Referer // - s3:aws-Referer
// 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 { for condition, conditionKeyVal := range statement.Conditions {
prefixConditon := conditionKeyVal["s3:prefix"] prefixConditon := conditionKeyVal["s3:prefix"]
maxKeyCondition := conditionKeyVal["s3:max-keys"] maxKeyCondition := conditionKeyVal["s3:max-keys"]
@ -126,13 +129,17 @@ func bucketPolicyConditionMatch(conditions map[string]set.StringSet, statement p
} }
// wildcard match of referer in statement was not empty. // wildcard match of referer in statement was not empty.
// StringLike has a match, i.e, condition evaluates to true. // StringLike has a match, i.e, condition evaluates to true.
refererFound := false
for referer := range conditions["referer"] { for referer := range conditions["referer"] {
if !awsReferers.FuncMatch(refererMatch, referer).IsEmpty() { if !awsReferers.FuncMatch(refererMatch, referer).IsEmpty() {
return true refererFound = true
break
} }
} }
// No matching referer found, so the condition is false. // No matching referer found, so the condition is false.
return false if !refererFound {
return false
}
} else if condition == "StringNotLike" { } else if condition == "StringNotLike" {
awsReferers := conditionKeyVal["aws:Referer"] awsReferers := conditionKeyVal["aws:Referer"]
// Skip empty condition, it is trivially satisfied. // Skip empty condition, it is trivially satisfied.
@ -146,11 +153,9 @@ func bucketPolicyConditionMatch(conditions map[string]set.StringSet, statement p
return false return false
} }
} }
// No matching referer found, so the condition is true.
return true
} }
} }
// No conditions were present in the statement, so trivially true (always).
return true return true
} }

View file

@ -29,6 +29,11 @@ import (
"github.com/minio/minio-go/pkg/set" "github.com/minio/minio-go/pkg/set"
) )
var conditionKeyActionMap = map[string]set.StringSet{
"s3:prefix": set.CreateStringSet("s3:ListBucket"),
"s3:max-keys": set.CreateStringSet("s3:ListBucket"),
}
// supportedActionMap - lists all the actions supported by minio. // supportedActionMap - lists all the actions supported by minio.
var supportedActionMap = set.CreateStringSet("*", "s3:*", "s3:GetObject", var supportedActionMap = set.CreateStringSet("*", "s3:*", "s3:GetObject",
"s3:ListBucket", "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject", "s3:ListBucket", "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject",
@ -178,11 +183,12 @@ func isValidPrincipals(principal interface{}) (err error) {
return nil return nil
} }
// isValidConditions - are valid conditions. // isValidConditions - returns nil if the given conditions valid and
func isValidConditions(conditions map[string]map[string]set.StringSet) (err error) { // corresponding error otherwise.
// Verify conditions should be valid. func isValidConditions(actions set.StringSet, conditions map[string]map[string]set.StringSet) (err error) {
// Validate if stringEquals, stringNotEquals are present // Verify conditions should be valid. Validate if only
// if not throw an error. // supported condition keys are present and return error
// otherwise.
conditionKeyVal := make(map[string]set.StringSet) conditionKeyVal := make(map[string]set.StringSet)
for conditionType := range conditions { for conditionType := range conditions {
if !supportedConditionsType.Contains(conditionType) { if !supportedConditionsType.Contains(conditionType) {
@ -194,6 +200,15 @@ func isValidConditions(conditions map[string]map[string]set.StringSet) (err erro
err = fmt.Errorf("Unsupported condition key '%s', please validate your policy document", conditionType) err = fmt.Errorf("Unsupported condition key '%s', please validate your policy document", conditionType)
return err 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] conditionVal, ok := conditionKeyVal[key]
if ok && !value.Intersection(conditionVal).IsEmpty() { if ok && !value.Intersection(conditionVal).IsEmpty() {
err = fmt.Errorf("Ambigious condition values for key '%s', please validate your policy document", key) err = fmt.Errorf("Ambigious condition values for key '%s', please validate your policy document", key)
@ -222,8 +237,8 @@ func resourcePrefix(resource string) string {
} }
// checkBucketPolicyResources validates Resources in unmarshalled bucket policy structure. // checkBucketPolicyResources validates Resources in unmarshalled bucket policy structure.
// First valation of Resources done for given set of Actions. // - Resources are validated against the given set of Actions.
// Later its validated for recursive Resources. // -
func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIErrorCode { func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIErrorCode {
// Validate statements for special actions and collect resources // Validate statements for special actions and collect resources
// for others to validate nesting. // for others to validate nesting.
@ -317,7 +332,7 @@ func parseBucketPolicy(bucketPolicyReader io.Reader, policy *bucketPolicy) (err
return err return err
} }
// Statement conditions should be valid. // Statement conditions should be valid.
if err := isValidConditions(statement.Conditions); err != nil { if err := isValidConditions(statement.Actions, statement.Conditions); err != nil {
return err return err
} }
} }

View file

@ -433,7 +433,12 @@ func TestIsValidConditions(t *testing.T) {
generateConditions("StringNotEquals", "s3:max-keys", "100"), 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 { testCases := []struct {
inputActions set.StringSet
inputCondition map[string]map[string]set.StringSet inputCondition map[string]map[string]set.StringSet
// expected result. // expected result.
expectedErr error expectedErr error
@ -443,46 +448,44 @@ func TestIsValidConditions(t *testing.T) {
// Malformed conditions. // Malformed conditions.
// Test case - 1. // Test case - 1.
// "StringValues" is an invalid type. // "StringValues" is an invalid type.
{testConditions[0], fmt.Errorf("Unsupported condition type 'StringValues', " + {roBucketActionSet, testConditions[0], fmt.Errorf("Unsupported condition type 'StringValues', " +
"please validate your policy document"), false}, "please validate your policy document"), false},
// Test case - 2. // Test case - 2.
// "s3:Object" is an invalid key. // "s3:Object" is an invalid key.
{testConditions[1], fmt.Errorf("Unsupported condition key " + {roBucketActionSet, testConditions[1], fmt.Errorf("Unsupported condition key " +
"'StringEquals', please validate your policy document"), false}, "'StringEquals', please validate your policy document"), false},
// Test case - 3. // Test case - 3.
// Test case with Ambigious conditions set. // Test case with Ambigious conditions set.
{testConditions[2], fmt.Errorf("Ambigious condition values for key 's3:prefix', " + {roBucketActionSet, testConditions[2], fmt.Errorf("Ambigious condition values for key 's3:prefix', " +
"please validate your policy document"), false}, "please validate your policy document"), false},
// Test case - 4. // Test case - 4.
// Test case with valid and invalid condition types. // Test case with valid and invalid condition types.
{testConditions[3], fmt.Errorf("Unsupported condition type 'InvalidType', " + {roBucketActionSet, testConditions[3], fmt.Errorf("Unsupported condition type 'InvalidType', " +
"please validate your policy document"), false}, "please validate your policy document"), false},
// Test case - 5. // Test case - 5.
// Test case with valid and invalid condition keys. // Test case with valid and invalid condition keys.
{testConditions[4], fmt.Errorf("Unsupported condition key 'StringEquals', " + {roBucketActionSet, testConditions[4], fmt.Errorf("Unsupported condition key 'StringEquals', " +
"please validate your policy document"), false}, "please validate your policy document"), false},
// Test cases with valid conditions. // Test cases with valid conditions.
// Test case - 6. // Test case - 6.
{testConditions[5], nil, true}, {roBucketActionSet, testConditions[5], nil, true},
// Test case - 7. // Test case - 7.
{testConditions[6], nil, true}, {roBucketActionSet, testConditions[6], nil, true},
// Test case - 8. // Test case - 8.
{testConditions[7], nil, true}, {roBucketActionSet, testConditions[7], nil, true},
// Test case - 9. // Test case - 9.
{testConditions[8], nil, true}, {roBucketActionSet, testConditions[8], nil, true},
// Test case - 10. // Test case - 10.
{testConditions[9], nil, true}, {roBucketActionSet, testConditions[9], nil, true},
// Test case - 11. // Test case - 11.
{testConditions[10], nil, true}, {roBucketActionSet, testConditions[10], nil, true},
// Test case - 12. // Test case - 12.
{testConditions[11], nil, true}, {roBucketActionSet, testConditions[11], nil, true},
// Test case - 13. // Test case - 13.
{testConditions[11], nil, true}, {getObjectActionSet, testConditions[11], maxKeysConditionErr, false},
// Test case - 14.
{testConditions[11], nil, true},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
actualErr := isValidConditions(testCase.inputCondition) actualErr := isValidConditions(testCase.inputActions, testCase.inputCondition)
if actualErr != nil && testCase.shouldPass { if actualErr != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, actualErr.Error()) t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, actualErr.Error())
} }