storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/iam/policy/statement_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  	"testing"
    24  
    25  	"storj.io/minio/pkg/bucket/policy"
    26  	"storj.io/minio/pkg/bucket/policy/condition"
    27  )
    28  
    29  func TestStatementIsAllowed(t *testing.T) {
    30  	case1Statement := NewStatement(
    31  		policy.Allow,
    32  		NewActionSet(GetBucketLocationAction, PutObjectAction),
    33  		NewResourceSet(NewResource("*", "")),
    34  		condition.NewFunctions(),
    35  	)
    36  
    37  	case2Statement := NewStatement(
    38  		policy.Allow,
    39  		NewActionSet(GetObjectAction, PutObjectAction),
    40  		NewResourceSet(NewResource("mybucket", "/myobject*")),
    41  		condition.NewFunctions(),
    42  	)
    43  
    44  	_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
    45  	if err != nil {
    46  		t.Fatalf("unexpected error. %v\n", err)
    47  	}
    48  	func1, err := condition.NewIPAddressFunc(
    49  		condition.AWSSourceIP,
    50  		IPNet1,
    51  	)
    52  	if err != nil {
    53  		t.Fatalf("unexpected error. %v\n", err)
    54  	}
    55  
    56  	case3Statement := NewStatement(
    57  		policy.Allow,
    58  		NewActionSet(GetObjectAction, PutObjectAction),
    59  		NewResourceSet(NewResource("mybucket", "/myobject*")),
    60  		condition.NewFunctions(func1),
    61  	)
    62  
    63  	case4Statement := NewStatement(
    64  		policy.Deny,
    65  		NewActionSet(GetObjectAction, PutObjectAction),
    66  		NewResourceSet(NewResource("mybucket", "/myobject*")),
    67  		condition.NewFunctions(func1),
    68  	)
    69  
    70  	anonGetBucketLocationArgs := Args{
    71  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
    72  		Action:          GetBucketLocationAction,
    73  		BucketName:      "mybucket",
    74  		ConditionValues: map[string][]string{},
    75  	}
    76  
    77  	anonPutObjectActionArgs := Args{
    78  		AccountName: "Q3AM3UQ867SPQQA43P2F",
    79  		Action:      PutObjectAction,
    80  		BucketName:  "mybucket",
    81  		ConditionValues: map[string][]string{
    82  			"x-amz-copy-source": {"mybucket/myobject"},
    83  			"SourceIp":          {"192.168.1.10"},
    84  		},
    85  		ObjectName: "myobject",
    86  	}
    87  
    88  	anonGetObjectActionArgs := Args{
    89  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
    90  		Action:          GetObjectAction,
    91  		BucketName:      "mybucket",
    92  		ConditionValues: map[string][]string{},
    93  		ObjectName:      "myobject",
    94  	}
    95  
    96  	getBucketLocationArgs := Args{
    97  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
    98  		Action:          GetBucketLocationAction,
    99  		BucketName:      "mybucket",
   100  		ConditionValues: map[string][]string{},
   101  	}
   102  
   103  	putObjectActionArgs := Args{
   104  		AccountName: "Q3AM3UQ867SPQQA43P2F",
   105  		Action:      PutObjectAction,
   106  		BucketName:  "mybucket",
   107  		ConditionValues: map[string][]string{
   108  			"x-amz-copy-source": {"mybucket/myobject"},
   109  			"SourceIp":          {"192.168.1.10"},
   110  		},
   111  		ObjectName: "myobject",
   112  	}
   113  
   114  	getObjectActionArgs := Args{
   115  		AccountName:     "Q3AM3UQ867SPQQA43P2F",
   116  		Action:          GetObjectAction,
   117  		BucketName:      "mybucket",
   118  		ConditionValues: map[string][]string{},
   119  		ObjectName:      "myobject",
   120  	}
   121  
   122  	testCases := []struct {
   123  		statement      Statement
   124  		args           Args
   125  		expectedResult bool
   126  	}{
   127  		{case1Statement, anonGetBucketLocationArgs, true},
   128  		{case1Statement, anonPutObjectActionArgs, true},
   129  		{case1Statement, anonGetObjectActionArgs, false},
   130  		{case1Statement, getBucketLocationArgs, true},
   131  		{case1Statement, putObjectActionArgs, true},
   132  		{case1Statement, getObjectActionArgs, false},
   133  
   134  		{case2Statement, anonGetBucketLocationArgs, false},
   135  		{case2Statement, anonPutObjectActionArgs, true},
   136  		{case2Statement, anonGetObjectActionArgs, true},
   137  		{case2Statement, getBucketLocationArgs, false},
   138  		{case2Statement, putObjectActionArgs, true},
   139  		{case2Statement, getObjectActionArgs, true},
   140  
   141  		{case3Statement, anonGetBucketLocationArgs, false},
   142  		{case3Statement, anonPutObjectActionArgs, true},
   143  		{case3Statement, anonGetObjectActionArgs, false},
   144  		{case3Statement, getBucketLocationArgs, false},
   145  		{case3Statement, putObjectActionArgs, true},
   146  		{case3Statement, getObjectActionArgs, false},
   147  
   148  		{case4Statement, anonGetBucketLocationArgs, true},
   149  		{case4Statement, anonPutObjectActionArgs, false},
   150  		{case4Statement, anonGetObjectActionArgs, true},
   151  		{case4Statement, getBucketLocationArgs, true},
   152  		{case4Statement, putObjectActionArgs, false},
   153  		{case4Statement, getObjectActionArgs, true},
   154  	}
   155  
   156  	for i, testCase := range testCases {
   157  		result := testCase.statement.IsAllowed(testCase.args)
   158  
   159  		if result != testCase.expectedResult {
   160  			t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
   161  		}
   162  	}
   163  }
   164  
   165  func TestStatementIsValid(t *testing.T) {
   166  	_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
   167  	if err != nil {
   168  		t.Fatalf("unexpected error. %v\n", err)
   169  	}
   170  	func1, err := condition.NewIPAddressFunc(
   171  		condition.AWSSourceIP,
   172  		IPNet1,
   173  	)
   174  	if err != nil {
   175  		t.Fatalf("unexpected error. %v\n", err)
   176  	}
   177  
   178  	func2, err := condition.NewStringEqualsFunc(
   179  		condition.S3XAmzCopySource,
   180  		"mybucket/myobject",
   181  	)
   182  	if err != nil {
   183  		t.Fatalf("unexpected error. %v\n", err)
   184  	}
   185  
   186  	func3, err := condition.NewStringEqualsFunc(
   187  		condition.AWSUserAgent,
   188  		"NSPlayer",
   189  	)
   190  	if err != nil {
   191  		t.Fatalf("unexpected error. %v\n", err)
   192  	}
   193  
   194  	testCases := []struct {
   195  		statement Statement
   196  		expectErr bool
   197  	}{
   198  		// Invalid effect error.
   199  		{NewStatement(
   200  			policy.Effect("foo"),
   201  			NewActionSet(GetBucketLocationAction, PutObjectAction),
   202  			NewResourceSet(NewResource("*", "")),
   203  			condition.NewFunctions(),
   204  		), true},
   205  		// Empty actions error.
   206  		{NewStatement(
   207  			policy.Allow,
   208  			NewActionSet(),
   209  			NewResourceSet(NewResource("*", "")),
   210  			condition.NewFunctions(),
   211  		), true},
   212  		// Empty resources error.
   213  		{NewStatement(
   214  			policy.Allow,
   215  			NewActionSet(GetBucketLocationAction, PutObjectAction),
   216  			NewResourceSet(),
   217  			condition.NewFunctions(),
   218  		), true},
   219  		// Unsupported conditions for GetObject
   220  		{NewStatement(
   221  			policy.Allow,
   222  			NewActionSet(GetObjectAction, PutObjectAction),
   223  			NewResourceSet(NewResource("mybucket", "myobject*")),
   224  			condition.NewFunctions(func1, func2),
   225  		), true},
   226  		{NewStatement(
   227  			policy.Allow,
   228  			NewActionSet(GetBucketLocationAction, PutObjectAction),
   229  			NewResourceSet(NewResource("mybucket", "myobject*")),
   230  			condition.NewFunctions(),
   231  		), false},
   232  		{NewStatement(
   233  			policy.Allow,
   234  			NewActionSet(GetBucketLocationAction, PutObjectAction),
   235  			NewResourceSet(NewResource("mybucket", "")),
   236  			condition.NewFunctions(),
   237  		), false},
   238  		{NewStatement(
   239  			policy.Deny,
   240  			NewActionSet(GetObjectAction, PutObjectAction),
   241  			NewResourceSet(NewResource("mybucket", "myobject*")),
   242  			condition.NewFunctions(func1),
   243  		), false},
   244  		{NewStatement(
   245  			policy.Allow,
   246  			NewActionSet(CreateUserAdminAction, DeleteUserAdminAction),
   247  			nil,
   248  			condition.NewFunctions(func2, func3),
   249  		), true},
   250  		{NewStatement(
   251  			policy.Allow,
   252  			NewActionSet(CreateUserAdminAction, DeleteUserAdminAction),
   253  			nil,
   254  			condition.NewFunctions(),
   255  		), false},
   256  	}
   257  
   258  	for i, testCase := range testCases {
   259  		err := testCase.statement.isValid()
   260  		expectErr := (err != nil)
   261  
   262  		if expectErr != testCase.expectErr {
   263  			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
   264  		}
   265  	}
   266  }
   267  
   268  func TestStatementUnmarshalJSONAndValidate(t *testing.T) {
   269  	case1Data := []byte(`{
   270      "Sid": "SomeId1",
   271      "Effect": "Allow",
   272      "Action": "s3:PutObject",
   273      "Resource": "arn:aws:s3:::mybucket/myobject*"
   274  }`)
   275  	case1Statement := NewStatement(
   276  		policy.Allow,
   277  		NewActionSet(PutObjectAction),
   278  		NewResourceSet(NewResource("mybucket", "/myobject*")),
   279  		condition.NewFunctions(),
   280  	)
   281  	case1Statement.SID = "SomeId1"
   282  
   283  	case2Data := []byte(`{
   284      "Effect": "Allow",
   285      "Action": "s3:PutObject",
   286      "Resource": "arn:aws:s3:::mybucket/myobject*",
   287      "Condition": {
   288          "Null": {
   289              "s3:x-amz-copy-source": true
   290          }
   291      }
   292  }`)
   293  	func1, err := condition.NewNullFunc(
   294  		condition.S3XAmzCopySource,
   295  		true,
   296  	)
   297  	if err != nil {
   298  		t.Fatalf("unexpected error. %v\n", err)
   299  	}
   300  	case2Statement := NewStatement(
   301  		policy.Allow,
   302  		NewActionSet(PutObjectAction),
   303  		NewResourceSet(NewResource("mybucket", "/myobject*")),
   304  		condition.NewFunctions(func1),
   305  	)
   306  
   307  	case3Data := []byte(`{
   308      "Effect": "Deny",
   309      "Action": [
   310          "s3:PutObject",
   311          "s3:GetObject"
   312      ],
   313      "Resource": "arn:aws:s3:::mybucket/myobject*",
   314      "Condition": {
   315          "Null": {
   316              "s3:x-amz-server-side-encryption": "false"
   317          }
   318      }
   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  	case3Statement := NewStatement(
   328  		policy.Deny,
   329  		NewActionSet(PutObjectAction, GetObjectAction),
   330  		NewResourceSet(NewResource("mybucket", "/myobject*")),
   331  		condition.NewFunctions(func2),
   332  	)
   333  
   334  	case4Data := []byte(`{
   335      "Effect": "Allow",
   336      "Action": "s3:PutObjec,
   337      "Resource": "arn:aws:s3:::mybucket/myobject*"
   338  }`)
   339  
   340  	case5Data := []byte(`{
   341      "Action": "s3:PutObject",
   342      "Resource": "arn:aws:s3:::mybucket/myobject*"
   343  }`)
   344  
   345  	case7Data := []byte(`{
   346      "Effect": "Allow",
   347      "Resource": "arn:aws:s3:::mybucket/myobject*"
   348  }`)
   349  
   350  	case8Data := []byte(`{
   351      "Effect": "Allow",
   352      "Action": "s3:PutObject"
   353  }`)
   354  
   355  	case9Data := []byte(`{
   356      "Effect": "Allow",
   357      "Action": "s3:PutObject",
   358      "Resource": "arn:aws:s3:::mybucket/myobject*",
   359      "Condition": {
   360      }
   361  }`)
   362  
   363  	case10Data := []byte(`{
   364      "Effect": "Deny",
   365      "Action": [
   366          "s3:PutObject",
   367          "s3:GetObject"
   368      ],
   369      "Resource": "arn:aws:s3:::mybucket/myobject*",
   370      "Condition": {
   371          "StringEquals": {
   372              "s3:x-amz-copy-source": "yourbucket/myobject*"
   373          }
   374      }
   375  }`)
   376  
   377  	testCases := []struct {
   378  		data                []byte
   379  		expectedResult      Statement
   380  		expectUnmarshalErr  bool
   381  		expectValidationErr bool
   382  	}{
   383  		{case1Data, case1Statement, false, false},
   384  		{case2Data, case2Statement, false, false},
   385  		{case3Data, case3Statement, false, false},
   386  		// JSON unmarshaling error.
   387  		{case4Data, Statement{}, true, true},
   388  		// Invalid effect error.
   389  		{case5Data, Statement{}, false, true},
   390  		// Empty action error.
   391  		{case7Data, Statement{}, false, true},
   392  		// Empty resource error.
   393  		{case8Data, Statement{}, false, true},
   394  		// Empty condition error.
   395  		{case9Data, Statement{}, true, false},
   396  		// Unsupported condition key error.
   397  		{case10Data, Statement{}, false, true},
   398  	}
   399  
   400  	for i, testCase := range testCases {
   401  		var result Statement
   402  		expectErr := (json.Unmarshal(testCase.data, &result) != nil)
   403  
   404  		if expectErr != testCase.expectUnmarshalErr {
   405  			t.Fatalf("case %v: error during unmarshal: expected: %v, got: %v", i+1, testCase.expectUnmarshalErr, expectErr)
   406  		}
   407  
   408  		expectErr = (result.Validate() != nil)
   409  		if expectErr != testCase.expectValidationErr {
   410  			t.Fatalf("case %v: error during validation: expected: %v, got: %v", i+1, testCase.expectValidationErr, expectErr)
   411  		}
   412  
   413  		if !testCase.expectUnmarshalErr && !testCase.expectValidationErr {
   414  			if !reflect.DeepEqual(result, testCase.expectedResult) {
   415  				t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
   416  			}
   417  		}
   418  	}
   419  }
   420  
   421  func TestStatementValidate(t *testing.T) {
   422  	case1Statement := NewStatement(
   423  		policy.Allow,
   424  		NewActionSet(PutObjectAction),
   425  		NewResourceSet(NewResource("mybucket", "/myobject*")),
   426  		condition.NewFunctions(),
   427  	)
   428  
   429  	func1, err := condition.NewNullFunc(
   430  		condition.S3XAmzCopySource,
   431  		true,
   432  	)
   433  	if err != nil {
   434  		t.Fatalf("unexpected error. %v\n", err)
   435  	}
   436  	func2, err := condition.NewNullFunc(
   437  		condition.S3XAmzServerSideEncryption,
   438  		false,
   439  	)
   440  	if err != nil {
   441  		t.Fatalf("unexpected error. %v\n", err)
   442  	}
   443  	case2Statement := NewStatement(
   444  		policy.Allow,
   445  		NewActionSet(GetObjectAction, PutObjectAction),
   446  		NewResourceSet(NewResource("mybucket", "myobject*")),
   447  		condition.NewFunctions(func1, func2),
   448  	)
   449  
   450  	testCases := []struct {
   451  		statement Statement
   452  		expectErr bool
   453  	}{
   454  		{case1Statement, false},
   455  		{case2Statement, true},
   456  	}
   457  
   458  	for i, testCase := range testCases {
   459  		err := testCase.statement.Validate()
   460  		expectErr := (err != nil)
   461  
   462  		if expectErr != testCase.expectErr {
   463  			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
   464  		}
   465  	}
   466  }