1// Copyright (c) 2015-2021 MinIO, Inc. 2// 3// This file is part of MinIO Object Storage stack 4// 5// This program is free software: you can redistribute it and/or modify 6// it under the terms of the GNU Affero General Public License as published by 7// the Free Software Foundation, either version 3 of the License, or 8// (at your option) any later version. 9// 10// This program is distributed in the hope that it will be useful 11// but WITHOUT ANY WARRANTY; without even the implied warranty of 12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13// GNU Affero General Public License for more details. 14// 15// You should have received a copy of the GNU Affero General Public License 16// along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18package iampolicy 19 20import ( 21 "encoding/json" 22 "net" 23 "reflect" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/minio/minio-go/v7/pkg/set" 29 "github.com/minio/pkg/bucket/policy" 30 "github.com/minio/pkg/bucket/policy/condition" 31) 32 33func 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 77func 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.ToKey(), 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 229func 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 264func 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.ToKey(), 315 true, 316 ) 317 if err != nil { 318 t.Fatalf("unexpected error. %v\n", err) 319 } 320 func2, err := condition.NewNullFunc( 321 condition.S3XAmzServerSideEncryption.ToKey(), 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 439func 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 691func 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.ToKey(), 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.ToKey(), 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 1103func 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.ToKey(), 1118 true, 1119 ) 1120 if err != nil { 1121 t.Fatalf("unexpected error. %v\n", err) 1122 } 1123 func2, err := condition.NewNullFunc( 1124 condition.S3XAmzServerSideEncryption.ToKey(), 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} 1174