mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
				
	
	
		
			712 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			712 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | ||
|  * 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"
 | ||
| 	"testing"
 | ||
| 
 | ||
| 	"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) policyStatement {
 | ||
| 	objectResourceStatement := policyStatement{}
 | ||
| 	objectResourceStatement.Effect = "Allow"
 | ||
| 	objectResourceStatement.Principal = map[string]interface{}{
 | ||
| 		"AWS": "*",
 | ||
| 	}
 | ||
| 	objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
 | ||
| 	objectResourceStatement.Actions = set.CreateStringSet(readWriteObjectActions...)
 | ||
| 	return objectResourceStatement
 | ||
| }
 | ||
| 
 | ||
| // Obtain object statement for read-write bucketPolicy.
 | ||
| func getReadWriteBucketStatement(bucketName, objectPrefix string) policyStatement {
 | ||
| 	bucketResourceStatement := policyStatement{}
 | ||
| 	bucketResourceStatement.Effect = "Allow"
 | ||
| 	bucketResourceStatement.Principal = map[string]interface{}{
 | ||
| 		"AWS": "*",
 | ||
| 	}
 | ||
| 	bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
 | ||
| 	bucketResourceStatement.Actions = set.CreateStringSet(readWriteBucketActions...)
 | ||
| 	return bucketResourceStatement
 | ||
| }
 | ||
| 
 | ||
| // Obtain statements for read-write bucketPolicy.
 | ||
| func getReadWriteStatement(bucketName, objectPrefix string) []policyStatement {
 | ||
| 	statements := []policyStatement{}
 | ||
| 	// 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) policyStatement {
 | ||
| 	bucketResourceStatement := policyStatement{}
 | ||
| 	bucketResourceStatement.Effect = "Allow"
 | ||
| 	bucketResourceStatement.Principal = map[string]interface{}{
 | ||
| 		"AWS": "*",
 | ||
| 	}
 | ||
| 	bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
 | ||
| 	bucketResourceStatement.Actions = set.CreateStringSet(readOnlyBucketActions...)
 | ||
| 	return bucketResourceStatement
 | ||
| }
 | ||
| 
 | ||
| // Obtain object statement for read only bucketPolicy.
 | ||
| func getReadOnlyObjectStatement(bucketName, objectPrefix string) policyStatement {
 | ||
| 	objectResourceStatement := policyStatement{}
 | ||
| 	objectResourceStatement.Effect = "Allow"
 | ||
| 	objectResourceStatement.Principal = map[string]interface{}{
 | ||
| 		"AWS": "*",
 | ||
| 	}
 | ||
| 	objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
 | ||
| 	objectResourceStatement.Actions = set.CreateStringSet(readOnlyObjectActions...)
 | ||
| 	return objectResourceStatement
 | ||
| }
 | ||
| 
 | ||
| // Obtain statements for read only bucketPolicy.
 | ||
| func getReadOnlyStatement(bucketName, objectPrefix string) []policyStatement {
 | ||
| 	statements := []policyStatement{}
 | ||
| 	// 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) policyStatement {
 | ||
| 
 | ||
| 	bucketResourceStatement := policyStatement{}
 | ||
| 	bucketResourceStatement.Effect = "Allow"
 | ||
| 	bucketResourceStatement.Principal = map[string]interface{}{
 | ||
| 		"AWS": "*",
 | ||
| 	}
 | ||
| 	bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
 | ||
| 	bucketResourceStatement.Actions = set.CreateStringSet(writeOnlyBucketActions...)
 | ||
| 	return bucketResourceStatement
 | ||
| }
 | ||
| 
 | ||
| // Obtain object statements for write only bucketPolicy.
 | ||
| func getWriteOnlyObjectStatement(bucketName, objectPrefix string) policyStatement {
 | ||
| 	objectResourceStatement := policyStatement{}
 | ||
| 	objectResourceStatement.Effect = "Allow"
 | ||
| 	objectResourceStatement.Principal = map[string]interface{}{
 | ||
| 		"AWS": "*",
 | ||
| 	}
 | ||
| 	objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
 | ||
| 	objectResourceStatement.Actions = set.CreateStringSet(writeOnlyObjectActions...)
 | ||
| 	return objectResourceStatement
 | ||
| }
 | ||
| 
 | ||
| // Obtain statements for write only bucketPolicy.
 | ||
| func getWriteOnlyStatement(bucketName, objectPrefix string) []policyStatement {
 | ||
| 	statements := []policyStatement{}
 | ||
| 	// 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 "arn:aws:s3:::".
 | ||
| 		{[]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 "arn:aws:s3:::".
 | ||
| 		{[]string{"arn:aws:s3:::"}, 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 "arn:aws:s3:::".
 | ||
| 		{[]string{"arn:aws:s3:::/"}, errors.New("Invalid resource style found: ‘arn:aws:s3:::/’, please validate your policy document."), false},
 | ||
| 
 | ||
| 		// Test cases with valid Resources.
 | ||
| 		{[]string{"arn:aws:s3:::my-bucket"}, nil, true},
 | ||
| 		{[]string{"arn:aws:s3:::my-bucket/Asia/*"}, nil, true},
 | ||
| 		{[]string{"arn:aws:s3:::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 {
 | ||
| 		err := isValidPrincipals(map[string]interface{}{
 | ||
| 			"AWS": testCase.principals,
 | ||
| 		})
 | ||
| 		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 policyStatement condition validator.
 | ||
| func TestIsValidConditions(t *testing.T) {
 | ||
| 	// returns empty conditions map.
 | ||
| 	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]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]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]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]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]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]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]set.StringSet{
 | ||
| 		generateConditions("StringValues", "s3:max-keys", "100"),
 | ||
| 		generateConditions("StringEquals", "s3:Object", "100"),
 | ||
| 		generateAmbigiousConditions(),
 | ||
| 		generateValidInvalidConditions(),
 | ||
| 		generateValidInvalidConditionKeys(),
 | ||
| 		setEmptyConditions(),
 | ||
| 		setEmptyStringEquals(),
 | ||
| 		setEmptyStringNotEquals(),
 | ||
| 		generateConditions("StringEquals", "s3:prefix", "Asia/"),
 | ||
| 		generateConditions("StringEquals", "s3:max-keys", "100"),
 | ||
| 		generateConditions("StringNotEquals", "s3:prefix", "Asia/"),
 | ||
| 		generateConditions("StringNotEquals", "s3:max-keys", "100"),
 | ||
| 	}
 | ||
| 
 | ||
| 	testCases := []struct {
 | ||
| 		inputCondition map[string]map[string]set.StringSet
 | ||
| 		// expected result.
 | ||
| 		expectedErr error
 | ||
| 		// flag indicating whether test should pass.
 | ||
| 		shouldPass bool
 | ||
| 	}{
 | ||
| 		// Malformed conditions.
 | ||
| 		// Test case - 1.
 | ||
| 		// "StringValues" is an invalid type.
 | ||
| 		{testConditions[0], fmt.Errorf("Unsupported condition type 'StringValues', " +
 | ||
| 			"please validate your policy document."), false},
 | ||
| 		// Test case - 2.
 | ||
| 		// "s3:Object" is an invalid key.
 | ||
| 		{testConditions[1], fmt.Errorf("Unsupported condition key " +
 | ||
| 			"'StringEquals', please validate your policy document."), false},
 | ||
| 		// Test case - 3.
 | ||
| 		// Test case with Ambigious conditions set.
 | ||
| 		{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.
 | ||
| 		{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.
 | ||
| 		{testConditions[4], fmt.Errorf("Unsupported condition key 'StringEquals', " +
 | ||
| 			"please validate your policy document."), false},
 | ||
| 		// Test cases with valid conditions.
 | ||
| 		// Test case - 6.
 | ||
| 		{testConditions[5], nil, true},
 | ||
| 		// Test case - 7.
 | ||
| 		{testConditions[6], nil, true},
 | ||
| 		// Test case - 8.
 | ||
| 		{testConditions[7], nil, true},
 | ||
| 		// Test case - 9.
 | ||
| 		{testConditions[8], nil, true},
 | ||
| 		// Test case - 10.
 | ||
| 		{testConditions[9], nil, true},
 | ||
| 		// Test case - 11.
 | ||
| 		{testConditions[10], nil, true},
 | ||
| 		// Test case 10.
 | ||
| 		{testConditions[11], nil, true},
 | ||
| 	}
 | ||
| 	for i, testCase := range testCases {
 | ||
| 		actualErr := isValidConditions(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 []policyStatement) []policyStatement {
 | ||
| 		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 = 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 = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}...)
 | ||
| 		return statements
 | ||
| 	}
 | ||
| 
 | ||
| 	// List of bucketPolicy used for tests.
 | ||
| 	bucketAccessPolicies := []bucketPolicy{
 | ||
| 		// 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 bucketPolicy
 | ||
| 		// 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 []policyStatement) []policyStatement {
 | ||
| 		// "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 []policyStatement) []policyStatement {
 | ||
| 		// Effect "Don't allow" is Unsupported.
 | ||
| 		statements[0].Effect = "DontAllow"
 | ||
| 		return statements
 | ||
| 	}
 | ||
| 	// set unsupported principals.
 | ||
| 	setUnsupportedPrincipals := func(statements []policyStatement) []policyStatement {
 | ||
| 		// "User1111"" is an Unsupported Principal.
 | ||
| 		statements[0].Principal = map[string]interface{}{
 | ||
| 			"AWS": []string{"*", "User1111"},
 | ||
| 		}
 | ||
| 		return statements
 | ||
| 	}
 | ||
| 	// set unsupported Resources.
 | ||
| 	setUnsupportedResources := func(statements []policyStatement) []policyStatement {
 | ||
| 		// "s3:DeleteEverything"" is an Unsupported Action.
 | ||
| 		statements[0].Resources = set.CreateStringSet([]string{"my-resource"}...)
 | ||
| 		return statements
 | ||
| 	}
 | ||
| 	// List of bucketPolicy used for test cases.
 | ||
| 	bucketAccesPolicies := []bucketPolicy{
 | ||
| 		// bucketPolicy - 0.
 | ||
| 		// bucketPolicy statement empty.
 | ||
| 		{Version: "1.0"},
 | ||
| 		// bucketPolicy - 1.
 | ||
| 		// bucketPolicy version empty.
 | ||
| 		{Version: "", Statements: []policyStatement{}},
 | ||
| 		// 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 bucketPolicy
 | ||
| 		// expected results.
 | ||
| 		expectedPolicy bucketPolicy
 | ||
| 		err            error
 | ||
| 		// Flag indicating whether the test should pass.
 | ||
| 		shouldPass bool
 | ||
| 	}{
 | ||
| 		// Test case - 1.
 | ||
| 		// bucketPolicy statement empty.
 | ||
| 		{bucketAccesPolicies[0], bucketPolicy{}, errors.New("Policy statement cannot be empty."), false},
 | ||
| 		// Test case - 2.
 | ||
| 		// bucketPolicy version empty.
 | ||
| 		{bucketAccesPolicies[1], bucketPolicy{}, 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 = &bucketPolicy{}
 | ||
| 		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 testCase.expectedPolicy.String() != actualAccessPolicy.String() {
 | ||
| 				t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 |