storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/iam/policy/policy_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package iampolicy
    18  
    19  import (
    20  	"encoding/json"
    21  	"net"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/minio/minio-go/v7/pkg/set"
    28  
    29  	"storj.io/minio/pkg/bucket/policy"
    30  	"storj.io/minio/pkg/bucket/policy/condition"
    31  )
    32  
    33  func TestGetPoliciesFromClaims(t *testing.T) {
    34  	attributesArray := `{
    35    "exp": 1594690452,
    36    "iat": 1594689552,
    37    "auth_time": 1594689552,
    38    "jti": "18ed05c9-2c69-45d5-a33f-8c94aca99ad5",
    39    "iss": "http://localhost:8080/auth/realms/minio",
    40    "aud": "account",
    41    "sub": "7e5e2f30-1c97-4616-8623-2eae14dee9b1",
    42    "typ": "ID",
    43    "azp": "account",
    44    "nonce": "66ZoLzwJbjdkiedI",
    45    "session_state": "3df7b526-5310-4038-9f35-50ecd295a31d",
    46    "acr": "1",
    47    "upn": "harsha",
    48    "address": {},
    49    "email_verified": false,
    50    "groups": [
    51      "offline_access"
    52    ],
    53    "preferred_username": "harsha",
    54    "policy": [
    55      "readwrite",
    56      "readwrite,readonly",
    57      "  readonly",
    58      ""
    59    ]}`
    60  	var m = make(map[string]interface{})
    61  	if err := json.Unmarshal([]byte(attributesArray), &m); err != nil {
    62  		t.Fatal(err)
    63  	}
    64  	var expectedSet = set.CreateStringSet("readwrite", "readonly")
    65  	gotSet, ok := GetPoliciesFromClaims(m, "policy")
    66  	if !ok {
    67  		t.Fatal("no policy claim was found")
    68  	}
    69  	if gotSet.IsEmpty() {
    70  		t.Fatal("no policies were found in policy claim")
    71  	}
    72  	if !gotSet.Equals(expectedSet) {
    73  		t.Fatalf("Expected %v got %v", expectedSet, gotSet)
    74  	}
    75  }
    76  
    77  func TestPolicyIsAllowed(t *testing.T) {
    78  	case1Policy := Policy{
    79  		Version: DefaultVersion,
    80  		Statements: []Statement{
    81  			NewStatement(
    82  				policy.Allow,
    83  				NewActionSet(GetBucketLocationAction, PutObjectAction),
    84  				NewResourceSet(NewResource("*", "")),
    85  				condition.NewFunctions(),
    86  			)},
    87  	}
    88  
    89  	case2Policy := Policy{
    90  		Version: DefaultVersion,
    91  		Statements: []Statement{
    92  			NewStatement(
    93  				policy.Allow,
    94  				NewActionSet(GetObjectAction, PutObjectAction),
    95  				NewResourceSet(NewResource("mybucket", "/myobject*")),
    96  				condition.NewFunctions(),
    97  			)},
    98  	}
    99  
   100  	_, IPNet, err := net.ParseCIDR("192.168.1.0/24")
   101  	if err != nil {
   102  		t.Fatalf("unexpected error. %v\n", err)
   103  	}
   104  	func1, err := condition.NewIPAddressFunc(
   105  		condition.AWSSourceIP,
   106  		IPNet,
   107  	)
   108  	if err != nil {
   109  		t.Fatalf("unexpected error. %v\n", err)
   110  	}
   111  
   112  	case3Policy := Policy{
   113  		Version: DefaultVersion,
   114  		Statements: []Statement{
   115  			NewStatement(
   116  				policy.Allow,
   117  				NewActionSet(GetObjectAction, PutObjectAction),
   118  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   119  				condition.NewFunctions(func1),
   120  			)},
   121  	}
   122  
   123  	case4Policy := Policy{
   124  		Version: DefaultVersion,
   125  		Statements: []Statement{
   126  			NewStatement(
   127  				policy.Deny,
   128  				NewActionSet(GetObjectAction, PutObjectAction),
   129  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   130  				condition.NewFunctions(func1),
   131  			)},
   132  	}
   133  
   134  	anonGetBucketLocationArgs := Args{
   135  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
   136  		Action:          GetBucketLocationAction,
   137  		BucketName:      "mybucket",
   138  		ConditionValues: map[string][]string{},
   139  	}
   140  
   141  	anonPutObjectActionArgs := Args{
   142  		AccountName: "Q3AM3UQ867SPQQA43P2F",
   143  		Action:      PutObjectAction,
   144  		BucketName:  "mybucket",
   145  		ConditionValues: map[string][]string{
   146  			"x-amz-copy-source": {"mybucket/myobject"},
   147  			"SourceIp":          {"192.168.1.10"},
   148  		},
   149  		ObjectName: "myobject",
   150  	}
   151  
   152  	anonGetObjectActionArgs := Args{
   153  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
   154  		Action:          GetObjectAction,
   155  		BucketName:      "mybucket",
   156  		ConditionValues: map[string][]string{},
   157  		ObjectName:      "myobject",
   158  	}
   159  
   160  	getBucketLocationArgs := Args{
   161  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
   162  		Action:          GetBucketLocationAction,
   163  		BucketName:      "mybucket",
   164  		ConditionValues: map[string][]string{},
   165  	}
   166  
   167  	putObjectActionArgs := Args{
   168  		AccountName: "Q3AM3UQ867SPQQA43P2F",
   169  		Action:      PutObjectAction,
   170  		BucketName:  "mybucket",
   171  		ConditionValues: map[string][]string{
   172  			"x-amz-copy-source": {"mybucket/myobject"},
   173  			"SourceIp":          {"192.168.1.10"},
   174  		},
   175  		ObjectName: "myobject",
   176  	}
   177  
   178  	getObjectActionArgs := Args{
   179  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
   180  		Action:          GetObjectAction,
   181  		BucketName:      "mybucket",
   182  		ConditionValues: map[string][]string{},
   183  		ObjectName:      "myobject",
   184  	}
   185  
   186  	testCases := []struct {
   187  		policy         Policy
   188  		args           Args
   189  		expectedResult bool
   190  	}{
   191  		{case1Policy, anonGetBucketLocationArgs, true},
   192  		{case1Policy, anonPutObjectActionArgs, true},
   193  		{case1Policy, anonGetObjectActionArgs, false},
   194  		{case1Policy, getBucketLocationArgs, true},
   195  		{case1Policy, putObjectActionArgs, true},
   196  		{case1Policy, getObjectActionArgs, false},
   197  
   198  		{case2Policy, anonGetBucketLocationArgs, false},
   199  		{case2Policy, anonPutObjectActionArgs, true},
   200  		{case2Policy, anonGetObjectActionArgs, true},
   201  		{case2Policy, getBucketLocationArgs, false},
   202  		{case2Policy, putObjectActionArgs, true},
   203  		{case2Policy, getObjectActionArgs, true},
   204  
   205  		{case3Policy, anonGetBucketLocationArgs, false},
   206  		{case3Policy, anonPutObjectActionArgs, true},
   207  		{case3Policy, anonGetObjectActionArgs, false},
   208  		{case3Policy, getBucketLocationArgs, false},
   209  		{case3Policy, putObjectActionArgs, true},
   210  		{case3Policy, getObjectActionArgs, false},
   211  
   212  		{case4Policy, anonGetBucketLocationArgs, false},
   213  		{case4Policy, anonPutObjectActionArgs, false},
   214  		{case4Policy, anonGetObjectActionArgs, false},
   215  		{case4Policy, getBucketLocationArgs, false},
   216  		{case4Policy, putObjectActionArgs, false},
   217  		{case4Policy, getObjectActionArgs, false},
   218  	}
   219  
   220  	for i, testCase := range testCases {
   221  		result := testCase.policy.IsAllowed(testCase.args)
   222  
   223  		if result != testCase.expectedResult {
   224  			t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
   225  		}
   226  	}
   227  }
   228  
   229  func TestPolicyIsEmpty(t *testing.T) {
   230  	case1Policy := Policy{
   231  		Version: DefaultVersion,
   232  		Statements: []Statement{
   233  			NewStatement(
   234  				policy.Allow,
   235  				NewActionSet(PutObjectAction),
   236  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   237  				condition.NewFunctions(),
   238  			),
   239  		},
   240  	}
   241  
   242  	case2Policy := Policy{
   243  		ID:      "MyPolicyForMyBucket",
   244  		Version: DefaultVersion,
   245  	}
   246  
   247  	testCases := []struct {
   248  		policy         Policy
   249  		expectedResult bool
   250  	}{
   251  		{case1Policy, false},
   252  		{case2Policy, true},
   253  	}
   254  
   255  	for i, testCase := range testCases {
   256  		result := testCase.policy.IsEmpty()
   257  
   258  		if result != testCase.expectedResult {
   259  			t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
   260  		}
   261  	}
   262  }
   263  
   264  func TestPolicyIsValid(t *testing.T) {
   265  	case1Policy := Policy{
   266  		Version: DefaultVersion,
   267  		Statements: []Statement{
   268  			NewStatement(
   269  				policy.Allow,
   270  				NewActionSet(PutObjectAction),
   271  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   272  				condition.NewFunctions(),
   273  			),
   274  		},
   275  	}
   276  
   277  	case2Policy := Policy{
   278  		Version: DefaultVersion,
   279  		Statements: []Statement{
   280  			NewStatement(
   281  				policy.Allow,
   282  				NewActionSet(PutObjectAction),
   283  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   284  				condition.NewFunctions(),
   285  			),
   286  			NewStatement(
   287  				policy.Deny,
   288  				NewActionSet(GetObjectAction),
   289  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   290  				condition.NewFunctions(),
   291  			),
   292  		},
   293  	}
   294  
   295  	case3Policy := Policy{
   296  		Version: DefaultVersion,
   297  		Statements: []Statement{
   298  			NewStatement(
   299  				policy.Allow,
   300  				NewActionSet(PutObjectAction),
   301  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   302  				condition.NewFunctions(),
   303  			),
   304  			NewStatement(
   305  				policy.Deny,
   306  				NewActionSet(PutObjectAction),
   307  				NewResourceSet(NewResource("mybucket", "/yourobject*")),
   308  				condition.NewFunctions(),
   309  			),
   310  		},
   311  	}
   312  
   313  	func1, err := condition.NewNullFunc(
   314  		condition.S3XAmzCopySource,
   315  		true,
   316  	)
   317  	if err != nil {
   318  		t.Fatalf("unexpected error. %v\n", err)
   319  	}
   320  	func2, err := condition.NewNullFunc(
   321  		condition.S3XAmzServerSideEncryption,
   322  		false,
   323  	)
   324  	if err != nil {
   325  		t.Fatalf("unexpected error. %v\n", err)
   326  	}
   327  
   328  	case4Policy := Policy{
   329  		Version: DefaultVersion,
   330  		Statements: []Statement{
   331  			NewStatement(
   332  				policy.Allow,
   333  				NewActionSet(PutObjectAction),
   334  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   335  				condition.NewFunctions(func1),
   336  			),
   337  			NewStatement(
   338  				policy.Deny,
   339  				NewActionSet(PutObjectAction),
   340  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   341  				condition.NewFunctions(func2),
   342  			),
   343  		},
   344  	}
   345  
   346  	case5Policy := Policy{
   347  		Version: "17-10-2012",
   348  		Statements: []Statement{
   349  			NewStatement(
   350  				policy.Allow,
   351  				NewActionSet(PutObjectAction),
   352  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   353  				condition.NewFunctions(),
   354  			),
   355  		},
   356  	}
   357  
   358  	case6Policy := Policy{
   359  		ID:      "MyPolicyForMyBucket1",
   360  		Version: DefaultVersion,
   361  		Statements: []Statement{
   362  			NewStatement(
   363  				policy.Allow,
   364  				NewActionSet(GetObjectAction, PutObjectAction),
   365  				NewResourceSet(NewResource("mybucket", "myobject*")),
   366  				condition.NewFunctions(func1, func2),
   367  			),
   368  		},
   369  	}
   370  
   371  	case7Policy := Policy{
   372  		Version: DefaultVersion,
   373  		Statements: []Statement{
   374  			NewStatement(
   375  				policy.Allow,
   376  				NewActionSet(PutObjectAction),
   377  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   378  				condition.NewFunctions(),
   379  			),
   380  			NewStatement(
   381  				policy.Deny,
   382  				NewActionSet(PutObjectAction),
   383  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   384  				condition.NewFunctions(),
   385  			),
   386  		},
   387  	}
   388  
   389  	case8Policy := Policy{
   390  		Version: DefaultVersion,
   391  		Statements: []Statement{
   392  			NewStatement(
   393  				policy.Allow,
   394  				NewActionSet(PutObjectAction),
   395  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   396  				condition.NewFunctions(),
   397  			),
   398  			NewStatement(
   399  				policy.Allow,
   400  				NewActionSet(PutObjectAction),
   401  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   402  				condition.NewFunctions(),
   403  			),
   404  		},
   405  	}
   406  
   407  	testCases := []struct {
   408  		policy    Policy
   409  		expectErr bool
   410  	}{
   411  		{case1Policy, false},
   412  		// allowed duplicate principal.
   413  		{case2Policy, false},
   414  		// allowed duplicate principal and action.
   415  		{case3Policy, false},
   416  		// allowed duplicate principal, action and resource.
   417  		{case4Policy, false},
   418  		// Invalid version error.
   419  		{case5Policy, true},
   420  		// Invalid statement error.
   421  		{case6Policy, true},
   422  		// Duplicate statement different Effects.
   423  		{case7Policy, false},
   424  		// Duplicate statement same Effects, duplicate effect will be removed.
   425  		{case8Policy, false},
   426  	}
   427  
   428  	for i, testCase := range testCases {
   429  		err := testCase.policy.isValid()
   430  		expectErr := (err != nil)
   431  
   432  		if expectErr != testCase.expectErr {
   433  			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
   434  		}
   435  	}
   436  }
   437  
   438  // Parse config with location constraints
   439  func TestPolicyParseConfig(t *testing.T) {
   440  	policy1LocationConstraint := `{
   441     "Version":"2012-10-17",
   442     "Statement":[
   443        {
   444           "Sid":"statement1",
   445           "Effect":"Allow",
   446           "Action": "s3:CreateBucket",
   447           "Resource": "arn:aws:s3:::*",
   448           "Condition": {
   449               "StringLike": {
   450                   "s3:LocationConstraint": "us-east-1"
   451               }
   452           }
   453         },
   454        {
   455           "Sid":"statement2",
   456           "Effect":"Deny",
   457           "Action": "s3:CreateBucket",
   458           "Resource": "arn:aws:s3:::*",
   459           "Condition": {
   460               "StringNotLike": {
   461                   "s3:LocationConstraint": "us-east-1"
   462               }
   463           }
   464         }
   465      ]
   466  }`
   467  	policy2Condition := `{
   468      "Version": "2012-10-17",
   469      "Statement": [
   470          {
   471              "Sid": "statement1",
   472              "Effect": "Allow",
   473              "Action": "s3:GetObjectVersion",
   474              "Resource": "arn:aws:s3:::test/HappyFace.jpg"
   475          },
   476          {
   477              "Sid": "statement2",
   478              "Effect": "Deny",
   479              "Action": "s3:GetObjectVersion",
   480              "Resource": "arn:aws:s3:::test/HappyFace.jpg",
   481              "Condition": {
   482                  "StringNotEquals": {
   483                      "s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
   484                  }
   485              }
   486          }
   487      ]
   488  }`
   489  
   490  	policy3ConditionActionRegex := `{
   491      "Version": "2012-10-17",
   492      "Statement": [
   493          {
   494              "Sid": "statement2",
   495              "Effect": "Allow",
   496              "Action": "s3:Get*",
   497              "Resource": "arn:aws:s3:::test/HappyFace.jpg",
   498              "Condition": {
   499                  "StringEquals": {
   500                      "s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
   501                  }
   502              }
   503          }
   504      ]
   505  }`
   506  
   507  	policy4ConditionAction := `{
   508      "Version": "2012-10-17",
   509      "Statement": [
   510          {
   511              "Sid": "statement2",
   512              "Effect": "Allow",
   513              "Action": "s3:GetObject",
   514              "Resource": "arn:aws:s3:::test/HappyFace.jpg",
   515              "Condition": {
   516                  "StringEquals": {
   517                      "s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
   518                  }
   519              }
   520          }
   521      ]
   522  }`
   523  
   524  	policy5ConditionCurrenTime := `{
   525   "Version": "2012-10-17",
   526   "Statement": [
   527    {
   528     "Effect": "Allow",
   529     "Action": [
   530      "s3:Get*",
   531      "s3:Put*"
   532     ],
   533     "Resource": [
   534      "arn:aws:s3:::test/*"
   535     ],
   536     "Condition": {
   537      "DateGreaterThan": {
   538       "aws:CurrentTime": [
   539        "2017-02-28T00:00:00Z"
   540       ]
   541      }
   542     }
   543    }
   544   ]
   545  }`
   546  
   547  	policy5ConditionCurrenTimeLesser := `{
   548   "Version": "2012-10-17",
   549   "Statement": [
   550    {
   551     "Effect": "Allow",
   552     "Action": [
   553      "s3:Get*",
   554      "s3:Put*"
   555     ],
   556     "Resource": [
   557      "arn:aws:s3:::test/*"
   558     ],
   559     "Condition": {
   560      "DateLessThan": {
   561       "aws:CurrentTime": [
   562        "2017-02-28T00:00:00Z"
   563       ]
   564      }
   565     }
   566    }
   567   ]
   568  }`
   569  
   570  	tests := []struct {
   571  		p       string
   572  		args    Args
   573  		allowed bool
   574  	}{
   575  		{
   576  			p:       policy1LocationConstraint,
   577  			allowed: true,
   578  			args: Args{
   579  				AccountName:     "allowed",
   580  				Action:          CreateBucketAction,
   581  				BucketName:      "test",
   582  				ConditionValues: map[string][]string{"LocationConstraint": {"us-east-1"}},
   583  			},
   584  		},
   585  		{
   586  			p:       policy1LocationConstraint,
   587  			allowed: false,
   588  			args: Args{
   589  				AccountName:     "disallowed",
   590  				Action:          CreateBucketAction,
   591  				BucketName:      "test",
   592  				ConditionValues: map[string][]string{"LocationConstraint": {"us-east-2"}},
   593  			},
   594  		},
   595  		{
   596  			p:       policy2Condition,
   597  			allowed: true,
   598  			args: Args{
   599  				AccountName:     "allowed",
   600  				Action:          GetObjectAction,
   601  				BucketName:      "test",
   602  				ObjectName:      "HappyFace.jpg",
   603  				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
   604  			},
   605  		},
   606  		{
   607  			p:       policy2Condition,
   608  			allowed: false,
   609  			args: Args{
   610  				AccountName:     "disallowed",
   611  				Action:          GetObjectAction,
   612  				BucketName:      "test",
   613  				ObjectName:      "HappyFace.jpg",
   614  				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5f"}},
   615  			},
   616  		},
   617  		{
   618  			p:       policy3ConditionActionRegex,
   619  			allowed: true,
   620  			args: Args{
   621  				AccountName:     "allowed",
   622  				Action:          GetObjectAction,
   623  				BucketName:      "test",
   624  				ObjectName:      "HappyFace.jpg",
   625  				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
   626  			},
   627  		},
   628  		{
   629  			p:       policy3ConditionActionRegex,
   630  			allowed: false,
   631  			args: Args{
   632  				AccountName:     "disallowed",
   633  				Action:          GetObjectAction,
   634  				BucketName:      "test",
   635  				ObjectName:      "HappyFace.jpg",
   636  				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5f"}},
   637  			},
   638  		},
   639  		{
   640  			p:       policy4ConditionAction,
   641  			allowed: true,
   642  			args: Args{
   643  				AccountName:     "allowed",
   644  				Action:          GetObjectAction,
   645  				BucketName:      "test",
   646  				ObjectName:      "HappyFace.jpg",
   647  				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
   648  			},
   649  		},
   650  		{
   651  			p:       policy5ConditionCurrenTime,
   652  			allowed: true,
   653  			args: Args{
   654  				AccountName: "allowed",
   655  				Action:      GetObjectAction,
   656  				BucketName:  "test",
   657  				ObjectName:  "HappyFace.jpg",
   658  				ConditionValues: map[string][]string{
   659  					"CurrentTime": {time.Now().Format(time.RFC3339)},
   660  				},
   661  			},
   662  		},
   663  		{
   664  			p:       policy5ConditionCurrenTimeLesser,
   665  			allowed: false,
   666  			args: Args{
   667  				AccountName: "disallowed",
   668  				Action:      GetObjectAction,
   669  				BucketName:  "test",
   670  				ObjectName:  "HappyFace.jpg",
   671  				ConditionValues: map[string][]string{
   672  					"CurrentTime": {time.Now().Format(time.RFC3339)},
   673  				},
   674  			},
   675  		},
   676  	}
   677  	for _, test := range tests {
   678  		test := test
   679  		t.Run(test.args.AccountName, func(t *testing.T) {
   680  			ip, err := ParseConfig(strings.NewReader(test.p))
   681  			if err != nil {
   682  				t.Error(err)
   683  			}
   684  			if got := ip.IsAllowed(test.args); got != test.allowed {
   685  				t.Errorf("Expected %t, got %t", test.allowed, got)
   686  			}
   687  		})
   688  	}
   689  }
   690  
   691  func TestPolicyUnmarshalJSONAndValidate(t *testing.T) {
   692  	case1Data := []byte(`{
   693      "ID": "MyPolicyForMyBucket1",
   694      "Version": "2012-10-17",
   695      "Statement": [
   696          {
   697              "Sid": "SomeId1",
   698              "Effect": "Allow",
   699              "Action": "s3:PutObject",
   700              "Resource": "arn:aws:s3:::mybucket/myobject*"
   701          }
   702      ]
   703  }`)
   704  	case1Policy := Policy{
   705  		ID:      "MyPolicyForMyBucket1",
   706  		Version: DefaultVersion,
   707  		Statements: []Statement{
   708  			NewStatement(
   709  				policy.Allow,
   710  				NewActionSet(PutObjectAction),
   711  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   712  				condition.NewFunctions(),
   713  			),
   714  		},
   715  	}
   716  	case1Policy.Statements[0].SID = "SomeId1"
   717  
   718  	case2Data := []byte(`{
   719      "Version": "2012-10-17",
   720      "Statement": [
   721          {
   722              "Effect": "Allow",
   723              "Action": "s3:PutObject",
   724              "Resource": "arn:aws:s3:::mybucket/myobject*"
   725          },
   726          {
   727              "Effect": "Deny",
   728              "Action": "s3:GetObject",
   729              "Resource": "arn:aws:s3:::mybucket/yourobject*",
   730              "Condition": {
   731                  "IpAddress": {
   732                      "aws:SourceIp": "192.168.1.0/24"
   733                  }
   734              }
   735          }
   736      ]
   737  }`)
   738  	_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
   739  	if err != nil {
   740  		t.Fatalf("unexpected error. %v\n", err)
   741  	}
   742  	func1, err := condition.NewIPAddressFunc(
   743  		condition.AWSSourceIP,
   744  		IPNet1,
   745  	)
   746  	if err != nil {
   747  		t.Fatalf("unexpected error. %v\n", err)
   748  	}
   749  
   750  	case2Policy := Policy{
   751  		Version: DefaultVersion,
   752  		Statements: []Statement{
   753  			NewStatement(
   754  				policy.Allow,
   755  				NewActionSet(PutObjectAction),
   756  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   757  				condition.NewFunctions(),
   758  			),
   759  			NewStatement(
   760  				policy.Deny,
   761  				NewActionSet(GetObjectAction),
   762  				NewResourceSet(NewResource("mybucket", "/yourobject*")),
   763  				condition.NewFunctions(func1),
   764  			),
   765  		},
   766  	}
   767  
   768  	case3Data := []byte(`{
   769      "ID": "MyPolicyForMyBucket1",
   770      "Version": "2012-10-17",
   771      "Statement": [
   772          {
   773              "Effect": "Allow",
   774              "Action": "s3:GetObject",
   775              "Resource": "arn:aws:s3:::mybucket/myobject*"
   776          },
   777          {
   778              "Effect": "Allow",
   779              "Action": "s3:PutObject",
   780              "Resource": "arn:aws:s3:::mybucket/myobject*"
   781          }
   782      ]
   783  }`)
   784  	case3Policy := Policy{
   785  		ID:      "MyPolicyForMyBucket1",
   786  		Version: DefaultVersion,
   787  		Statements: []Statement{
   788  			NewStatement(
   789  				policy.Allow,
   790  				NewActionSet(GetObjectAction),
   791  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   792  				condition.NewFunctions(),
   793  			),
   794  			NewStatement(
   795  				policy.Allow,
   796  				NewActionSet(PutObjectAction),
   797  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   798  				condition.NewFunctions(),
   799  			),
   800  		},
   801  	}
   802  
   803  	case4Data := []byte(`{
   804      "ID": "MyPolicyForMyBucket1",
   805      "Version": "2012-10-17",
   806      "Statement": [
   807          {
   808              "Effect": "Allow",
   809              "Action": "s3:PutObject",
   810              "Resource": "arn:aws:s3:::mybucket/myobject*"
   811          },
   812          {
   813              "Effect": "Allow",
   814              "Action": "s3:GetObject",
   815              "Resource": "arn:aws:s3:::mybucket/myobject*"
   816          }
   817      ]
   818  }`)
   819  	case4Policy := Policy{
   820  		ID:      "MyPolicyForMyBucket1",
   821  		Version: DefaultVersion,
   822  		Statements: []Statement{
   823  			NewStatement(
   824  				policy.Allow,
   825  				NewActionSet(PutObjectAction),
   826  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   827  				condition.NewFunctions(),
   828  			),
   829  			NewStatement(
   830  				policy.Allow,
   831  				NewActionSet(GetObjectAction),
   832  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   833  				condition.NewFunctions(),
   834  			),
   835  		},
   836  	}
   837  
   838  	case5Data := []byte(`{
   839      "ID": "MyPolicyForMyBucket1",
   840      "Version": "2012-10-17",
   841      "Statement": [
   842          {
   843              "Effect": "Allow",
   844              "Action": "s3:PutObject",
   845              "Resource": "arn:aws:s3:::mybucket/myobject*"
   846          },
   847          {
   848              "Effect": "Allow",
   849              "Action": "s3:PutObject",
   850              "Resource": "arn:aws:s3:::mybucket/yourobject*"
   851          }
   852      ]
   853  }`)
   854  	case5Policy := Policy{
   855  		ID:      "MyPolicyForMyBucket1",
   856  		Version: DefaultVersion,
   857  		Statements: []Statement{
   858  			NewStatement(
   859  				policy.Allow,
   860  				NewActionSet(PutObjectAction),
   861  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   862  				condition.NewFunctions(),
   863  			),
   864  			NewStatement(
   865  				policy.Allow,
   866  				NewActionSet(PutObjectAction),
   867  				NewResourceSet(NewResource("mybucket", "/yourobject*")),
   868  				condition.NewFunctions(),
   869  			),
   870  		},
   871  	}
   872  
   873  	case6Data := []byte(`{
   874      "ID": "MyPolicyForMyBucket1",
   875      "Version": "2012-10-17",
   876      "Statement": [
   877          {
   878              "Effect": "Allow",
   879              "Action": "s3:PutObject",
   880              "Resource": "arn:aws:s3:::mybucket/myobject*",
   881              "Condition": {
   882                  "IpAddress": {
   883                      "aws:SourceIp": "192.168.1.0/24"
   884                  }
   885              }
   886          },
   887          {
   888              "Effect": "Allow",
   889              "Action": "s3:PutObject",
   890              "Resource": "arn:aws:s3:::mybucket/myobject*",
   891              "Condition": {
   892                  "IpAddress": {
   893                      "aws:SourceIp": "192.168.2.0/24"
   894                  }
   895              }
   896          }
   897      ]
   898  }`)
   899  	_, IPNet2, err := net.ParseCIDR("192.168.2.0/24")
   900  	if err != nil {
   901  		t.Fatalf("unexpected error. %v\n", err)
   902  	}
   903  	func2, err := condition.NewIPAddressFunc(
   904  		condition.AWSSourceIP,
   905  		IPNet2,
   906  	)
   907  	if err != nil {
   908  		t.Fatalf("unexpected error. %v\n", err)
   909  	}
   910  
   911  	case6Policy := Policy{
   912  		ID:      "MyPolicyForMyBucket1",
   913  		Version: DefaultVersion,
   914  		Statements: []Statement{
   915  			NewStatement(
   916  				policy.Allow,
   917  				NewActionSet(PutObjectAction),
   918  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   919  				condition.NewFunctions(func1),
   920  			),
   921  			NewStatement(
   922  				policy.Allow,
   923  				NewActionSet(PutObjectAction),
   924  				NewResourceSet(NewResource("mybucket", "/myobject*")),
   925  				condition.NewFunctions(func2),
   926  			),
   927  		},
   928  	}
   929  
   930  	case7Data := []byte(`{
   931      "ID": "MyPolicyForMyBucket1",
   932      "Version": "2012-10-17",
   933      "Statement": [
   934          {
   935              "Effect": "Allow",
   936              "Action": "s3:GetBucketLocation",
   937              "Resource": "arn:aws:s3:::mybucket"
   938          }
   939      ]
   940  }`)
   941  
   942  	case7Policy := Policy{
   943  		ID:      "MyPolicyForMyBucket1",
   944  		Version: DefaultVersion,
   945  		Statements: []Statement{
   946  			NewStatement(
   947  				policy.Allow,
   948  				NewActionSet(GetBucketLocationAction),
   949  				NewResourceSet(NewResource("mybucket", "")),
   950  				condition.NewFunctions(),
   951  			),
   952  		},
   953  	}
   954  
   955  	case8Data := []byte(`{
   956      "ID": "MyPolicyForMyBucket1",
   957      "Version": "2012-10-17",
   958      "Statement": [
   959          {
   960              "Effect": "Allow",
   961              "Action": "s3:GetBucketLocation",
   962              "Resource": "arn:aws:s3:::*"
   963          }
   964      ]
   965  }`)
   966  
   967  	case8Policy := Policy{
   968  		ID:      "MyPolicyForMyBucket1",
   969  		Version: DefaultVersion,
   970  		Statements: []Statement{
   971  			NewStatement(
   972  				policy.Allow,
   973  				NewActionSet(GetBucketLocationAction),
   974  				NewResourceSet(NewResource("*", "")),
   975  				condition.NewFunctions(),
   976  			),
   977  		},
   978  	}
   979  
   980  	case9Data := []byte(`{
   981      "ID": "MyPolicyForMyBucket1",
   982      "Version": "17-10-2012",
   983      "Statement": [
   984          {
   985              "Effect": "Allow",
   986              "Action": "s3:PutObject",
   987              "Resource": "arn:aws:s3:::mybucket/myobject*"
   988          }
   989      ]
   990  }`)
   991  
   992  	case10Data := []byte(`{
   993      "ID": "MyPolicyForMyBucket1",
   994      "Version": "2012-10-17",
   995      "Statement": [
   996          {
   997              "Effect": "Allow",
   998              "Action": "s3:PutObject",
   999              "Resource": "arn:aws:s3:::mybucket/myobject*"
  1000          },
  1001          {
  1002              "Effect": "Allow",
  1003              "Action": "s3:PutObject",
  1004              "Resource": "arn:aws:s3:::mybucket/myobject*"
  1005          }
  1006      ]
  1007  }`)
  1008  	case10Policy := Policy{
  1009  		ID:      "MyPolicyForMyBucket1",
  1010  		Version: DefaultVersion,
  1011  		Statements: []Statement{
  1012  			NewStatement(
  1013  				policy.Allow,
  1014  				NewActionSet(PutObjectAction),
  1015  				NewResourceSet(NewResource("mybucket", "myobject*")),
  1016  				condition.NewFunctions(),
  1017  			),
  1018  		},
  1019  	}
  1020  
  1021  	case11Data := []byte(`{
  1022      "ID": "MyPolicyForMyBucket1",
  1023      "Version": "2012-10-17",
  1024      "Statement": [
  1025          {
  1026              "Effect": "Allow",
  1027              "Action": "s3:PutObject",
  1028              "Resource": "arn:aws:s3:::mybucket/myobject*"
  1029          },
  1030          {
  1031              "Effect": "Deny",
  1032              "Action": "s3:PutObject",
  1033              "Resource": "arn:aws:s3:::mybucket/myobject*"
  1034          }
  1035      ]
  1036  }`)
  1037  
  1038  	case11Policy := Policy{
  1039  		ID:      "MyPolicyForMyBucket1",
  1040  		Version: DefaultVersion,
  1041  		Statements: []Statement{
  1042  			NewStatement(
  1043  				policy.Allow,
  1044  				NewActionSet(PutObjectAction),
  1045  				NewResourceSet(NewResource("mybucket", "myobject*")),
  1046  				condition.NewFunctions(),
  1047  			),
  1048  			NewStatement(
  1049  				policy.Deny,
  1050  				NewActionSet(PutObjectAction),
  1051  				NewResourceSet(NewResource("mybucket", "myobject*")),
  1052  				condition.NewFunctions(),
  1053  			),
  1054  		},
  1055  	}
  1056  
  1057  	testCases := []struct {
  1058  		data                []byte
  1059  		expectedResult      Policy
  1060  		expectUnmarshalErr  bool
  1061  		expectValidationErr bool
  1062  	}{
  1063  		{case1Data, case1Policy, false, false},
  1064  		{case2Data, case2Policy, false, false},
  1065  		{case3Data, case3Policy, false, false},
  1066  		{case4Data, case4Policy, false, false},
  1067  		{case5Data, case5Policy, false, false},
  1068  		{case6Data, case6Policy, false, false},
  1069  		{case7Data, case7Policy, false, false},
  1070  		{case8Data, case8Policy, false, false},
  1071  		// Invalid version error.
  1072  		{case9Data, Policy{}, false, true},
  1073  		// Duplicate statement success, duplicate statement is removed.
  1074  		{case10Data, case10Policy, false, false},
  1075  		// Duplicate statement success (Effect differs).
  1076  		{case11Data, case11Policy, false, false},
  1077  	}
  1078  
  1079  	for i, testCase := range testCases {
  1080  		var result Policy
  1081  		err := json.Unmarshal(testCase.data, &result)
  1082  		expectErr := (err != nil)
  1083  
  1084  		if expectErr != testCase.expectUnmarshalErr {
  1085  			t.Errorf("case %v: error during unmarshal: expected: %v, got: %v", i+1, testCase.expectUnmarshalErr, expectErr)
  1086  		}
  1087  
  1088  		err = result.Validate()
  1089  		expectErr = (err != nil)
  1090  
  1091  		if expectErr != testCase.expectValidationErr {
  1092  			t.Errorf("case %v: error during validation: expected: %v, got: %v", i+1, testCase.expectValidationErr, expectErr)
  1093  		}
  1094  
  1095  		if !testCase.expectUnmarshalErr && !testCase.expectValidationErr {
  1096  			if !reflect.DeepEqual(result, testCase.expectedResult) {
  1097  				t.Errorf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
  1098  			}
  1099  		}
  1100  	}
  1101  }
  1102  
  1103  func TestPolicyValidate(t *testing.T) {
  1104  	case1Policy := Policy{
  1105  		Version: DefaultVersion,
  1106  		Statements: []Statement{
  1107  			NewStatement(
  1108  				policy.Allow,
  1109  				NewActionSet(PutObjectAction),
  1110  				NewResourceSet(NewResource("", "")),
  1111  				condition.NewFunctions(),
  1112  			),
  1113  		},
  1114  	}
  1115  
  1116  	func1, err := condition.NewNullFunc(
  1117  		condition.S3XAmzCopySource,
  1118  		true,
  1119  	)
  1120  	if err != nil {
  1121  		t.Fatalf("unexpected error. %v\n", err)
  1122  	}
  1123  	func2, err := condition.NewNullFunc(
  1124  		condition.S3XAmzServerSideEncryption,
  1125  		false,
  1126  	)
  1127  	if err != nil {
  1128  		t.Fatalf("unexpected error. %v\n", err)
  1129  	}
  1130  	case2Policy := Policy{
  1131  		ID:      "MyPolicyForMyBucket1",
  1132  		Version: DefaultVersion,
  1133  		Statements: []Statement{
  1134  			NewStatement(
  1135  				policy.Allow,
  1136  				NewActionSet(GetObjectAction, PutObjectAction),
  1137  				NewResourceSet(NewResource("mybucket", "myobject*")),
  1138  				condition.NewFunctions(func1, func2),
  1139  			),
  1140  		},
  1141  	}
  1142  
  1143  	case3Policy := Policy{
  1144  		ID:      "MyPolicyForMyBucket1",
  1145  		Version: DefaultVersion,
  1146  		Statements: []Statement{
  1147  			NewStatement(
  1148  				policy.Allow,
  1149  				NewActionSet(GetObjectAction, PutObjectAction),
  1150  				NewResourceSet(NewResource("mybucket", "myobject*")),
  1151  				condition.NewFunctions(),
  1152  			),
  1153  		},
  1154  	}
  1155  
  1156  	testCases := []struct {
  1157  		policy    Policy
  1158  		expectErr bool
  1159  	}{
  1160  		{case1Policy, true},
  1161  		{case2Policy, true},
  1162  		{case3Policy, false},
  1163  	}
  1164  
  1165  	for i, testCase := range testCases {
  1166  		err := testCase.policy.Validate()
  1167  		expectErr := (err != nil)
  1168  
  1169  		if expectErr != testCase.expectErr {
  1170  			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
  1171  		}
  1172  	}
  1173  }