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