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 }