github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/structs/acl_test.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/nomad/ci" 9 "github.com/hashicorp/nomad/helper/pointer" 10 "github.com/hashicorp/nomad/helper/uuid" 11 "github.com/shoenig/test/must" 12 "github.com/stretchr/testify/require" 13 ) 14 15 func TestACLToken_Canonicalize(t *testing.T) { 16 testCases := []struct { 17 name string 18 testFn func() 19 }{ 20 { 21 name: "token with accessor", 22 testFn: func() { 23 mockToken := &ACLToken{ 24 AccessorID: uuid.Generate(), 25 SecretID: uuid.Generate(), 26 Name: "my cool token " + uuid.Generate(), 27 Type: "client", 28 Policies: []string{"foo", "bar"}, 29 Roles: []*ACLTokenRoleLink{}, 30 Global: false, 31 CreateTime: time.Now().UTC(), 32 CreateIndex: 10, 33 ModifyIndex: 20, 34 } 35 mockToken.SetHash() 36 copiedMockToken := mockToken.Copy() 37 38 mockToken.Canonicalize() 39 require.Equal(t, copiedMockToken, mockToken) 40 }, 41 }, 42 { 43 name: "token without accessor", 44 testFn: func() { 45 mockToken := &ACLToken{ 46 Name: "my cool token " + uuid.Generate(), 47 Type: "client", 48 Policies: []string{"foo", "bar"}, 49 Global: false, 50 } 51 52 mockToken.Canonicalize() 53 require.NotEmpty(t, mockToken.AccessorID) 54 require.NotEmpty(t, mockToken.SecretID) 55 require.NotEmpty(t, mockToken.CreateTime) 56 }, 57 }, 58 { 59 name: "token with ttl without accessor", 60 testFn: func() { 61 mockToken := &ACLToken{ 62 Name: "my cool token " + uuid.Generate(), 63 Type: "client", 64 Policies: []string{"foo", "bar"}, 65 Global: false, 66 ExpirationTTL: 10 * time.Hour, 67 } 68 69 mockToken.Canonicalize() 70 require.NotEmpty(t, mockToken.AccessorID) 71 require.NotEmpty(t, mockToken.SecretID) 72 require.NotEmpty(t, mockToken.CreateTime) 73 require.NotEmpty(t, mockToken.ExpirationTime) 74 }, 75 }, 76 } 77 78 for _, tc := range testCases { 79 t.Run(tc.name, func(t *testing.T) { 80 tc.testFn() 81 }) 82 } 83 } 84 85 func TestACLTokenValidate(t *testing.T) { 86 ci.Parallel(t) 87 88 testCases := []struct { 89 name string 90 inputACLToken *ACLToken 91 inputExistingACLToken *ACLToken 92 expectedErrorContains string 93 }{ 94 { 95 name: "missing type", 96 inputACLToken: &ACLToken{}, 97 inputExistingACLToken: nil, 98 expectedErrorContains: "client or management", 99 }, 100 { 101 name: "missing policies or roles", 102 inputACLToken: &ACLToken{ 103 Type: ACLClientToken, 104 }, 105 inputExistingACLToken: nil, 106 expectedErrorContains: "missing policies or roles", 107 }, 108 { 109 name: "invalid policies", 110 inputACLToken: &ACLToken{ 111 Type: ACLManagementToken, 112 Policies: []string{"foo"}, 113 }, 114 inputExistingACLToken: nil, 115 expectedErrorContains: "associated with policies or roles", 116 }, 117 { 118 name: "invalid roles", 119 inputACLToken: &ACLToken{ 120 Type: ACLManagementToken, 121 Roles: []*ACLTokenRoleLink{{Name: "foo"}}, 122 }, 123 inputExistingACLToken: nil, 124 expectedErrorContains: "associated with policies or roles", 125 }, 126 { 127 name: "name too long", 128 inputACLToken: &ACLToken{ 129 Type: ACLManagementToken, 130 Name: uuid.Generate() + uuid.Generate() + uuid.Generate() + uuid.Generate() + 131 uuid.Generate() + uuid.Generate() + uuid.Generate() + uuid.Generate(), 132 }, 133 inputExistingACLToken: nil, 134 expectedErrorContains: "name too long", 135 }, 136 { 137 name: "negative TTL", 138 inputACLToken: &ACLToken{ 139 Type: ACLManagementToken, 140 Name: "foo", 141 ExpirationTTL: -1 * time.Hour, 142 }, 143 inputExistingACLToken: nil, 144 expectedErrorContains: "should not be negative", 145 }, 146 { 147 name: "TTL too small", 148 inputACLToken: &ACLToken{ 149 Type: ACLManagementToken, 150 Name: "foo", 151 CreateTime: time.Date(2022, time.July, 11, 16, 23, 0, 0, time.UTC), 152 ExpirationTime: pointer.Of(time.Date(2022, time.July, 11, 16, 23, 10, 0, time.UTC)), 153 }, 154 inputExistingACLToken: nil, 155 expectedErrorContains: "expiration time cannot be less than", 156 }, 157 { 158 name: "TTL too large", 159 inputACLToken: &ACLToken{ 160 Type: ACLManagementToken, 161 Name: "foo", 162 CreateTime: time.Date(2022, time.July, 11, 16, 23, 0, 0, time.UTC), 163 ExpirationTime: pointer.Of(time.Date(2042, time.July, 11, 16, 23, 0, 0, time.UTC)), 164 }, 165 inputExistingACLToken: nil, 166 expectedErrorContains: "expiration time cannot be more than", 167 }, 168 { 169 name: "valid management", 170 inputACLToken: &ACLToken{ 171 Type: ACLManagementToken, 172 Name: "foo", 173 }, 174 inputExistingACLToken: nil, 175 expectedErrorContains: "", 176 }, 177 { 178 name: "valid client", 179 inputACLToken: &ACLToken{ 180 Type: ACLClientToken, 181 Name: "foo", 182 Policies: []string{"foo"}, 183 }, 184 inputExistingACLToken: nil, 185 expectedErrorContains: "", 186 }, 187 } 188 189 for _, tc := range testCases { 190 t.Run(tc.name, func(t *testing.T) { 191 actualOutputError := tc.inputACLToken.Validate(1*time.Minute, 24*time.Hour, tc.inputExistingACLToken) 192 if tc.expectedErrorContains != "" { 193 require.ErrorContains(t, actualOutputError, tc.expectedErrorContains) 194 } else { 195 require.NoError(t, actualOutputError) 196 } 197 }) 198 } 199 } 200 201 func TestACLToken_HasExpirationTime(t *testing.T) { 202 testCases := []struct { 203 name string 204 inputACLToken *ACLToken 205 expectedOutput bool `` 206 }{ 207 { 208 name: "nil acl token", 209 inputACLToken: nil, 210 expectedOutput: false, 211 }, 212 { 213 name: "default empty value", 214 inputACLToken: &ACLToken{}, 215 expectedOutput: false, 216 }, 217 { 218 name: "expiration set to now", 219 inputACLToken: &ACLToken{ 220 ExpirationTime: pointer.Of(time.Now().UTC()), 221 }, 222 expectedOutput: true, 223 }, 224 { 225 name: "expiration set to past", 226 inputACLToken: &ACLToken{ 227 ExpirationTime: pointer.Of(time.Date(2022, time.February, 21, 19, 35, 0, 0, time.UTC)), 228 }, 229 expectedOutput: true, 230 }, 231 { 232 name: "expiration set to future", 233 inputACLToken: &ACLToken{ 234 ExpirationTime: pointer.Of(time.Date(2087, time.April, 25, 12, 0, 0, 0, time.UTC)), 235 }, 236 expectedOutput: true, 237 }, 238 } 239 240 for _, tc := range testCases { 241 t.Run(tc.name, func(t *testing.T) { 242 actualOutput := tc.inputACLToken.HasExpirationTime() 243 require.Equal(t, tc.expectedOutput, actualOutput) 244 }) 245 } 246 } 247 248 func TestACLToken_IsExpired(t *testing.T) { 249 testCases := []struct { 250 name string 251 inputACLToken *ACLToken 252 inputTime time.Time 253 expectedOutput bool 254 }{ 255 { 256 name: "token without expiry", 257 inputACLToken: &ACLToken{}, 258 inputTime: time.Now().UTC(), 259 expectedOutput: false, 260 }, 261 { 262 name: "empty input time", 263 inputACLToken: &ACLToken{}, 264 inputTime: time.Time{}, 265 expectedOutput: false, 266 }, 267 { 268 name: "token not expired", 269 inputACLToken: &ACLToken{ 270 ExpirationTime: pointer.Of(time.Date(2022, time.May, 9, 10, 27, 0, 0, time.UTC)), 271 }, 272 inputTime: time.Date(2022, time.May, 9, 10, 26, 0, 0, time.UTC), 273 expectedOutput: false, 274 }, 275 { 276 name: "token expired", 277 inputACLToken: &ACLToken{ 278 ExpirationTime: pointer.Of(time.Date(2022, time.May, 9, 10, 27, 0, 0, time.UTC)), 279 }, 280 inputTime: time.Date(2022, time.May, 9, 10, 28, 0, 0, time.UTC), 281 expectedOutput: true, 282 }, 283 { 284 name: "empty input time", 285 inputACLToken: &ACLToken{ 286 ExpirationTime: pointer.Of(time.Date(2022, time.May, 9, 10, 27, 0, 0, time.UTC)), 287 }, 288 inputTime: time.Time{}, 289 expectedOutput: true, 290 }, 291 } 292 293 for _, tc := range testCases { 294 t.Run(tc.name, func(t *testing.T) { 295 actualOutput := tc.inputACLToken.IsExpired(tc.inputTime) 296 require.Equal(t, tc.expectedOutput, actualOutput) 297 }) 298 } 299 } 300 301 func TestACLToken_HasRoles(t *testing.T) { 302 testCases := []struct { 303 name string 304 inputToken *ACLToken 305 inputRoleIDs []string 306 expectedOutput bool 307 }{ 308 { 309 name: "client token request all subset", 310 inputToken: &ACLToken{ 311 Type: ACLClientToken, 312 Roles: []*ACLTokenRoleLink{ 313 {ID: "foo"}, 314 {ID: "bar"}, 315 {ID: "baz"}, 316 }, 317 }, 318 inputRoleIDs: []string{"foo", "bar", "baz"}, 319 expectedOutput: true, 320 }, 321 { 322 name: "client token request partial subset", 323 inputToken: &ACLToken{ 324 Type: ACLClientToken, 325 Roles: []*ACLTokenRoleLink{ 326 {ID: "foo"}, 327 {ID: "bar"}, 328 {ID: "baz"}, 329 }, 330 }, 331 inputRoleIDs: []string{"foo", "baz"}, 332 expectedOutput: true, 333 }, 334 { 335 name: "client token request one subset", 336 inputToken: &ACLToken{ 337 Type: ACLClientToken, 338 Roles: []*ACLTokenRoleLink{ 339 {ID: "foo"}, 340 {ID: "bar"}, 341 {ID: "baz"}, 342 }, 343 }, 344 inputRoleIDs: []string{"baz"}, 345 expectedOutput: true, 346 }, 347 { 348 name: "client token request no subset", 349 inputToken: &ACLToken{ 350 Type: ACLClientToken, 351 Roles: []*ACLTokenRoleLink{ 352 {ID: "foo"}, 353 {ID: "bar"}, 354 {ID: "baz"}, 355 }, 356 }, 357 inputRoleIDs: []string{"new"}, 358 expectedOutput: false, 359 }, 360 } 361 362 for _, tc := range testCases { 363 t.Run(tc.name, func(t *testing.T) { 364 actualOutput := tc.inputToken.HasRoles(tc.inputRoleIDs) 365 require.Equal(t, tc.expectedOutput, actualOutput) 366 }) 367 } 368 } 369 370 func TestACLRole_SetHash(t *testing.T) { 371 testCases := []struct { 372 name string 373 inputACLRole *ACLRole 374 expectedOutput []byte 375 }{ 376 { 377 name: "no hash set", 378 inputACLRole: &ACLRole{ 379 Name: "acl-role", 380 Description: "mocked-test-acl-role", 381 Policies: []*ACLRolePolicyLink{ 382 {Name: "mocked-test-policy-1"}, 383 {Name: "mocked-test-policy-2"}, 384 }, 385 CreateIndex: 10, 386 ModifyIndex: 10, 387 Hash: []byte{}, 388 }, 389 expectedOutput: []byte{ 390 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 391 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 392 }, 393 }, 394 { 395 name: "hash set with change", 396 inputACLRole: &ACLRole{ 397 Name: "acl-role", 398 Description: "mocked-test-acl-role", 399 Policies: []*ACLRolePolicyLink{ 400 {Name: "mocked-test-policy-1"}, 401 {Name: "mocked-test-policy-2"}, 402 }, 403 CreateIndex: 10, 404 ModifyIndex: 10, 405 Hash: []byte{ 406 137, 147, 2, 29, 53, 94, 78, 13, 45, 51, 127, 193, 21, 248, 230, 126, 34, 407 106, 216, 73, 248, 219, 209, 146, 204, 107, 185, 2, 89, 255, 198, 5, 408 }, 409 }, 410 expectedOutput: []byte{ 411 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 412 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 413 }, 414 }, 415 } 416 417 for _, tc := range testCases { 418 t.Run(tc.name, func(t *testing.T) { 419 actualOutput := tc.inputACLRole.SetHash() 420 require.Equal(t, tc.expectedOutput, actualOutput) 421 require.Equal(t, tc.inputACLRole.Hash, actualOutput) 422 }) 423 } 424 } 425 426 func TestACLRole_Validate(t *testing.T) { 427 testCases := []struct { 428 name string 429 inputACLRole *ACLRole 430 expectedError bool 431 expectedErrorContains string 432 }{ 433 { 434 name: "role name too long", 435 inputACLRole: &ACLRole{ 436 Name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 437 }, 438 expectedError: true, 439 expectedErrorContains: "invalid name", 440 }, 441 { 442 name: "role name too short", 443 inputACLRole: &ACLRole{ 444 Name: "", 445 }, 446 expectedError: true, 447 expectedErrorContains: "invalid name", 448 }, 449 { 450 name: "role name with invalid characters", 451 inputACLRole: &ACLRole{ 452 Name: "--#$%$^%_%%_?>", 453 }, 454 expectedError: true, 455 expectedErrorContains: "invalid name", 456 }, 457 { 458 name: "description too long", 459 inputACLRole: &ACLRole{ 460 Name: "acl-role", 461 Description: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 462 }, 463 expectedError: true, 464 expectedErrorContains: "description longer than", 465 }, 466 { 467 name: "no policies", 468 inputACLRole: &ACLRole{ 469 Name: "acl-role", 470 Description: "", 471 }, 472 expectedError: true, 473 expectedErrorContains: "at least one policy should be specified", 474 }, 475 { 476 name: "valid", 477 inputACLRole: &ACLRole{ 478 Name: "acl-role", 479 Description: "", 480 Policies: []*ACLRolePolicyLink{ 481 {Name: "policy-1"}, 482 }, 483 }, 484 expectedError: false, 485 expectedErrorContains: "", 486 }, 487 } 488 489 for _, tc := range testCases { 490 t.Run(tc.name, func(t *testing.T) { 491 actualOutput := tc.inputACLRole.Validate() 492 if tc.expectedError { 493 require.ErrorContains(t, actualOutput, tc.expectedErrorContains) 494 } else { 495 require.NoError(t, actualOutput) 496 } 497 }) 498 } 499 } 500 501 func TestACLRole_Canonicalize(t *testing.T) { 502 testCases := []struct { 503 name string 504 inputACLRole *ACLRole 505 }{ 506 { 507 name: "no ID set", 508 inputACLRole: &ACLRole{}, 509 }, 510 { 511 name: "id set", 512 inputACLRole: &ACLRole{ID: "some-random-uuid"}, 513 }, 514 } 515 516 for _, tc := range testCases { 517 t.Run(tc.name, func(t *testing.T) { 518 existing := tc.inputACLRole.Copy() 519 tc.inputACLRole.Canonicalize() 520 if existing.ID == "" { 521 require.NotEmpty(t, tc.inputACLRole.ID) 522 } else { 523 require.Equal(t, existing.ID, tc.inputACLRole.ID) 524 } 525 }) 526 } 527 } 528 529 func TestACLRole_Equals(t *testing.T) { 530 testCases := []struct { 531 name string 532 composedACLRole *ACLRole 533 inputACLRole *ACLRole 534 expectedOutput bool 535 }{ 536 { 537 name: "equal with hash set", 538 composedACLRole: &ACLRole{ 539 Name: "acl-role-", 540 Description: "mocked-test-acl-role", 541 Policies: []*ACLRolePolicyLink{ 542 {Name: "mocked-test-policy-1"}, 543 {Name: "mocked-test-policy-2"}, 544 }, 545 CreateIndex: 10, 546 ModifyIndex: 10, 547 Hash: []byte{ 548 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 549 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 550 }, 551 }, 552 inputACLRole: &ACLRole{ 553 Name: "acl-role", 554 Description: "mocked-test-acl-role", 555 Policies: []*ACLRolePolicyLink{ 556 {Name: "mocked-test-policy-1"}, 557 {Name: "mocked-test-policy-2"}, 558 }, 559 CreateIndex: 10, 560 ModifyIndex: 10, 561 Hash: []byte{ 562 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 563 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 564 }, 565 }, 566 expectedOutput: true, 567 }, 568 { 569 name: "equal without hash set", 570 composedACLRole: &ACLRole{ 571 Name: "acl-role", 572 Description: "mocked-test-acl-role", 573 Policies: []*ACLRolePolicyLink{ 574 {Name: "mocked-test-policy-1"}, 575 {Name: "mocked-test-policy-2"}, 576 }, 577 CreateIndex: 10, 578 ModifyIndex: 10, 579 Hash: []byte{}, 580 }, 581 inputACLRole: &ACLRole{ 582 Name: "acl-role", 583 Description: "mocked-test-acl-role", 584 Policies: []*ACLRolePolicyLink{ 585 {Name: "mocked-test-policy-1"}, 586 {Name: "mocked-test-policy-2"}, 587 }, 588 CreateIndex: 10, 589 ModifyIndex: 10, 590 Hash: []byte{}, 591 }, 592 expectedOutput: true, 593 }, 594 { 595 name: "both nil", 596 composedACLRole: nil, 597 inputACLRole: nil, 598 expectedOutput: true, 599 }, 600 { 601 name: "not equal composed nil", 602 composedACLRole: nil, 603 inputACLRole: &ACLRole{ 604 Name: "acl-role", 605 Description: "mocked-test-acl-role", 606 Policies: []*ACLRolePolicyLink{ 607 {Name: "mocked-test-policy-1"}, 608 {Name: "mocked-test-policy-2"}, 609 }, 610 CreateIndex: 10, 611 ModifyIndex: 10, 612 Hash: []byte{ 613 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 614 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 615 }, 616 }, 617 expectedOutput: false, 618 }, 619 { 620 name: "not equal input nil", 621 composedACLRole: &ACLRole{ 622 Name: "acl-role", 623 Description: "mocked-test-acl-role", 624 Policies: []*ACLRolePolicyLink{ 625 {Name: "mocked-test-policy-1"}, 626 {Name: "mocked-test-policy-2"}, 627 }, 628 CreateIndex: 10, 629 ModifyIndex: 10, 630 Hash: []byte{ 631 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 632 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 633 }, 634 }, 635 inputACLRole: nil, 636 expectedOutput: false, 637 }, 638 { 639 name: "not equal with hash set", 640 composedACLRole: &ACLRole{ 641 Name: "acl-role", 642 Description: "mocked-test-acl-role", 643 Policies: []*ACLRolePolicyLink{ 644 {Name: "mocked-test-policy-1"}, 645 {Name: "mocked-test-policy-2"}, 646 }, 647 CreateIndex: 10, 648 ModifyIndex: 10, 649 Hash: []byte{ 650 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 651 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 652 }, 653 }, 654 inputACLRole: &ACLRole{ 655 Name: "acl-role", 656 Description: "mocked-test-acl-role", 657 Policies: []*ACLRolePolicyLink{ 658 {Name: "mocked-test-policy-1"}, 659 }, 660 CreateIndex: 10, 661 ModifyIndex: 10, 662 Hash: []byte{ 663 137, 147, 2, 29, 53, 94, 78, 13, 45, 51, 127, 193, 21, 248, 230, 126, 34, 664 106, 216, 73, 248, 219, 209, 146, 204, 107, 185, 2, 89, 255, 198, 5, 665 }, 666 }, 667 expectedOutput: false, 668 }, 669 { 670 name: "not equal without hash set", 671 composedACLRole: &ACLRole{ 672 Name: "acl-role", 673 Description: "mocked-test-acl-role", 674 Policies: []*ACLRolePolicyLink{ 675 {Name: "mocked-test-policy-1"}, 676 {Name: "mocked-test-policy-2"}, 677 }, 678 CreateIndex: 10, 679 ModifyIndex: 10, 680 Hash: []byte{}, 681 }, 682 inputACLRole: &ACLRole{ 683 Name: "acl-role", 684 Description: "mocked-test-acl-role", 685 Policies: []*ACLRolePolicyLink{ 686 {Name: "mocked-test-policy-1"}, 687 }, 688 CreateIndex: 10, 689 ModifyIndex: 10, 690 Hash: []byte{}, 691 }, 692 expectedOutput: false, 693 }, 694 } 695 696 for _, tc := range testCases { 697 t.Run(tc.name, func(t *testing.T) { 698 actualOutput := tc.composedACLRole.Equal(tc.inputACLRole) 699 require.Equal(t, tc.expectedOutput, actualOutput) 700 }) 701 } 702 } 703 704 func TestACLRole_Copy(t *testing.T) { 705 testCases := []struct { 706 name string 707 inputACLRole *ACLRole 708 }{ 709 { 710 name: "nil input", 711 inputACLRole: nil, 712 }, 713 { 714 name: "general 1", 715 inputACLRole: &ACLRole{ 716 Name: fmt.Sprintf("acl-role"), 717 Description: "mocked-test-acl-role", 718 Policies: []*ACLRolePolicyLink{ 719 {Name: "mocked-test-policy-1"}, 720 {Name: "mocked-test-policy-2"}, 721 }, 722 CreateIndex: 10, 723 ModifyIndex: 10, 724 Hash: []byte{ 725 122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160, 726 171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102, 727 }, 728 }, 729 }, 730 } 731 732 for _, tc := range testCases { 733 t.Run(tc.name, func(t *testing.T) { 734 actualOutput := tc.inputACLRole.Copy() 735 require.Equal(t, tc.inputACLRole, actualOutput) 736 }) 737 } 738 } 739 740 func TestACLRole_Stub(t *testing.T) { 741 testCases := []struct { 742 name string 743 inputACLRole *ACLRole 744 expectedOutput *ACLRoleListStub 745 }{ 746 { 747 name: "partially hydrated", 748 inputACLRole: &ACLRole{ 749 ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51", 750 Name: "my-lovely-role", 751 Description: "", 752 Policies: []*ACLRolePolicyLink{ 753 {Name: "my-lovely-policy"}, 754 }, 755 Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, 756 CreateIndex: 24, 757 ModifyIndex: 24, 758 }, 759 expectedOutput: &ACLRoleListStub{ 760 ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51", 761 Name: "my-lovely-role", 762 Description: "", 763 Policies: []*ACLRolePolicyLink{ 764 {Name: "my-lovely-policy"}, 765 }, 766 Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, 767 CreateIndex: 24, 768 ModifyIndex: 24, 769 }, 770 }, 771 { 772 name: "hully hydrated", 773 inputACLRole: &ACLRole{ 774 ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51", 775 Name: "my-lovely-role", 776 Description: "this-is-my-lovely-role", 777 Policies: []*ACLRolePolicyLink{ 778 {Name: "my-lovely-policy"}, 779 }, 780 Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, 781 CreateIndex: 24, 782 ModifyIndex: 24, 783 }, 784 expectedOutput: &ACLRoleListStub{ 785 ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51", 786 Name: "my-lovely-role", 787 Description: "this-is-my-lovely-role", 788 Policies: []*ACLRolePolicyLink{ 789 {Name: "my-lovely-policy"}, 790 }, 791 Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, 792 CreateIndex: 24, 793 ModifyIndex: 24, 794 }, 795 }, 796 } 797 798 for _, tc := range testCases { 799 t.Run(tc.name, func(t *testing.T) { 800 actualOutput := tc.inputACLRole.Stub() 801 require.Equal(t, tc.expectedOutput, actualOutput) 802 }) 803 } 804 } 805 806 func Test_ACLRolesUpsertRequest(t *testing.T) { 807 req := ACLRolesUpsertRequest{} 808 require.False(t, req.IsRead()) 809 } 810 811 func Test_ACLRolesDeleteByIDRequest(t *testing.T) { 812 req := ACLRolesDeleteByIDRequest{} 813 require.False(t, req.IsRead()) 814 } 815 816 func Test_ACLRolesListRequest(t *testing.T) { 817 req := ACLRolesListRequest{} 818 require.True(t, req.IsRead()) 819 } 820 821 func Test_ACLRolesByIDRequest(t *testing.T) { 822 req := ACLRolesByIDRequest{} 823 require.True(t, req.IsRead()) 824 } 825 826 func Test_ACLRoleByIDRequest(t *testing.T) { 827 req := ACLRoleByIDRequest{} 828 require.True(t, req.IsRead()) 829 } 830 831 func Test_ACLRoleByNameRequest(t *testing.T) { 832 req := ACLRoleByNameRequest{} 833 require.True(t, req.IsRead()) 834 } 835 836 func Test_ACLAuthMethodListRequest(t *testing.T) { 837 req := ACLAuthMethodListRequest{} 838 must.True(t, req.IsRead()) 839 } 840 841 func Test_ACLAuthMethodGetRequest(t *testing.T) { 842 req := ACLAuthMethodGetRequest{} 843 must.True(t, req.IsRead()) 844 } 845 846 func TestACLAuthMethodSetHash(t *testing.T) { 847 ci.Parallel(t) 848 849 am := &ACLAuthMethod{ 850 Name: "foo", 851 Type: "bad type", 852 } 853 out1 := am.SetHash() 854 must.NotNil(t, out1) 855 must.NotNil(t, am.Hash) 856 must.Eq(t, out1, am.Hash) 857 858 am.Type = "good type" 859 out2 := am.SetHash() 860 must.NotNil(t, out2) 861 must.NotNil(t, am.Hash) 862 must.Eq(t, out2, am.Hash) 863 must.NotEq(t, out1, out2) 864 } 865 866 func TestACLAuthMethod_Stub(t *testing.T) { 867 ci.Parallel(t) 868 869 maxTokenTTL, _ := time.ParseDuration("3600s") 870 am := ACLAuthMethod{ 871 Name: fmt.Sprintf("acl-auth-method-%s", uuid.Short()), 872 Type: "acl-auth-mock-type", 873 TokenLocality: "locality", 874 MaxTokenTTL: maxTokenTTL, 875 Default: true, 876 Config: &ACLAuthMethodConfig{ 877 OIDCDiscoveryURL: "http://example.com", 878 OIDCClientID: "mock", 879 OIDCClientSecret: "very secret secret", 880 BoundAudiences: []string{"audience1", "audience2"}, 881 AllowedRedirectURIs: []string{"foo", "bar"}, 882 DiscoveryCaPem: []string{"foo"}, 883 SigningAlgs: []string{"bar"}, 884 ClaimMappings: map[string]string{"foo": "bar"}, 885 ListClaimMappings: map[string]string{"foo": "bar"}, 886 }, 887 CreateTime: time.Now().UTC(), 888 CreateIndex: 10, 889 ModifyIndex: 10, 890 } 891 am.SetHash() 892 893 must.Eq(t, am.Stub(), &ACLAuthMethodStub{ 894 Name: am.Name, 895 Type: am.Type, 896 Default: am.Default, 897 Hash: am.Hash, 898 CreateIndex: am.CreateIndex, 899 ModifyIndex: am.ModifyIndex, 900 }) 901 902 nilAuthMethod := &ACLAuthMethod{} 903 must.Eq(t, nilAuthMethod.Stub(), &ACLAuthMethodStub{}) 904 } 905 906 func TestACLAuthMethod_Equal(t *testing.T) { 907 ci.Parallel(t) 908 909 maxTokenTTL, _ := time.ParseDuration("3600s") 910 am1 := &ACLAuthMethod{ 911 Name: fmt.Sprintf("acl-auth-method-%s", uuid.Short()), 912 Type: "acl-auth-mock-type", 913 TokenLocality: "locality", 914 MaxTokenTTL: maxTokenTTL, 915 Default: true, 916 Config: &ACLAuthMethodConfig{ 917 OIDCDiscoveryURL: "http://example.com", 918 OIDCClientID: "mock", 919 OIDCClientSecret: "very secret secret", 920 BoundAudiences: []string{"audience1", "audience2"}, 921 AllowedRedirectURIs: []string{"foo", "bar"}, 922 DiscoveryCaPem: []string{"foo"}, 923 SigningAlgs: []string{"bar"}, 924 ClaimMappings: map[string]string{"foo": "bar"}, 925 ListClaimMappings: map[string]string{"foo": "bar"}, 926 }, 927 CreateTime: time.Now().UTC(), 928 CreateIndex: 10, 929 ModifyIndex: 10, 930 } 931 am1.SetHash() 932 933 // am2 differs from am1 by 1 nested conf field 934 am2 := am1.Copy() 935 am2.Config.OIDCClientID = "mock2" 936 am2.SetHash() 937 938 tests := []struct { 939 name string 940 method1 *ACLAuthMethod 941 method2 *ACLAuthMethod 942 want bool 943 }{ 944 {"one nil", am1, &ACLAuthMethod{}, false}, 945 {"both nil", &ACLAuthMethod{}, &ACLAuthMethod{}, true}, 946 {"one is different than the other", am1, am2, false}, 947 {"equal", am1, am1.Copy(), true}, 948 } 949 for _, tt := range tests { 950 t.Run(tt.name, func(t *testing.T) { 951 got := tt.method1.Equal(tt.method2) 952 must.Eq(t, got, tt.want, must.Sprintf( 953 "ACLAuthMethod.Equal() got %v, want %v, test case: %s", got, tt.want, tt.name)) 954 }) 955 } 956 } 957 958 func TestACLAuthMethod_Copy(t *testing.T) { 959 ci.Parallel(t) 960 961 maxTokenTTL, _ := time.ParseDuration("3600s") 962 am1 := &ACLAuthMethod{ 963 Name: fmt.Sprintf("acl-auth-method-%s", uuid.Short()), 964 Type: "acl-auth-mock-type", 965 TokenLocality: "locality", 966 MaxTokenTTL: maxTokenTTL, 967 Default: true, 968 Config: &ACLAuthMethodConfig{ 969 OIDCDiscoveryURL: "http://example.com", 970 OIDCClientID: "mock", 971 OIDCClientSecret: "very secret secret", 972 BoundAudiences: []string{"audience1", "audience2"}, 973 AllowedRedirectURIs: []string{"foo", "bar"}, 974 DiscoveryCaPem: []string{"foo"}, 975 SigningAlgs: []string{"bar"}, 976 ClaimMappings: map[string]string{"foo": "bar"}, 977 ListClaimMappings: map[string]string{"foo": "bar"}, 978 }, 979 CreateTime: time.Now().UTC(), 980 CreateIndex: 10, 981 ModifyIndex: 10, 982 } 983 am1.SetHash() 984 985 am2 := am1.Copy() 986 am2.SetHash() 987 must.Eq(t, am1, am2) 988 989 am3 := am1.Copy() 990 am3.Config.AllowedRedirectURIs = []string{"new", "urls"} 991 am3.SetHash() 992 must.NotEq(t, am1, am3) 993 } 994 995 func TestACLAuthMethod_Validate(t *testing.T) { 996 ci.Parallel(t) 997 998 goodTTL, _ := time.ParseDuration("3600s") 999 badTTL, _ := time.ParseDuration("3600h") 1000 1001 tests := []struct { 1002 name string 1003 method *ACLAuthMethod 1004 wantErr bool 1005 errContains string 1006 }{ 1007 { 1008 "valid method", 1009 &ACLAuthMethod{ 1010 Name: "mock-auth-method", 1011 Type: "OIDC", 1012 TokenLocality: "local", 1013 MaxTokenTTL: goodTTL, 1014 }, 1015 false, 1016 "", 1017 }, 1018 {"invalid name", &ACLAuthMethod{Name: "is this name invalid?"}, true, "invalid name"}, 1019 {"invalid token locality", &ACLAuthMethod{TokenLocality: "regional"}, true, "invalid token locality"}, 1020 {"invalid type", &ACLAuthMethod{Type: "groovy"}, true, "invalid token type"}, 1021 {"invalid max ttl", &ACLAuthMethod{MaxTokenTTL: badTTL}, true, "invalid token type"}, 1022 } 1023 for _, tt := range tests { 1024 t.Run(tt.name, func(t *testing.T) { 1025 minTTL, _ := time.ParseDuration("10s") 1026 maxTTL, _ := time.ParseDuration("10h") 1027 got := tt.method.Validate(minTTL, maxTTL) 1028 if tt.wantErr { 1029 must.Error(t, got, must.Sprintf( 1030 "ACLAuthMethod.Validate() got error, didn't expect it; test case: %s", tt.name)) 1031 must.StrContains(t, got.Error(), tt.errContains, must.Sprintf( 1032 "ACLAuthMethod.Validate() got %v error message, expected %v; test case: %s", 1033 got, tt.errContains, tt.name)) 1034 } else { 1035 must.NoError(t, got, must.Sprintf( 1036 "ACLAuthMethod.Validate() expected an error but didn't get one; test case: %s", tt.name)) 1037 } 1038 }) 1039 } 1040 } 1041 1042 func TestACLAuthMethodConfig_Copy(t *testing.T) { 1043 ci.Parallel(t) 1044 1045 amc1 := &ACLAuthMethodConfig{ 1046 OIDCDiscoveryURL: "http://example.com", 1047 OIDCClientID: "mock", 1048 OIDCClientSecret: "very secret secret", 1049 BoundAudiences: []string{"audience1", "audience2"}, 1050 AllowedRedirectURIs: []string{"foo", "bar"}, 1051 DiscoveryCaPem: []string{"foo"}, 1052 SigningAlgs: []string{"bar"}, 1053 ClaimMappings: map[string]string{"foo": "bar"}, 1054 ListClaimMappings: map[string]string{"foo": "bar"}, 1055 } 1056 1057 amc2 := amc1.Copy() 1058 must.Eq(t, amc1, amc2) 1059 1060 amc3 := amc1.Copy() 1061 amc3.AllowedRedirectURIs = []string{"new", "urls"} 1062 must.NotEq(t, amc1, amc3) 1063 } 1064 1065 func TestACLAuthMethod_Canonicalize(t *testing.T) { 1066 now := time.Now().UTC() 1067 tests := []struct { 1068 name string 1069 inputMethod *ACLAuthMethod 1070 }{ 1071 { 1072 "no create time or modify time set", 1073 &ACLAuthMethod{}, 1074 }, 1075 { 1076 "create time set to now, modify time not set", 1077 &ACLAuthMethod{CreateTime: now}, 1078 }, 1079 { 1080 "both create time and modify time set", 1081 &ACLAuthMethod{CreateTime: now, ModifyTime: now.Add(time.Hour)}, 1082 }, 1083 } 1084 for _, tt := range tests { 1085 t.Run(tt.name, func(t *testing.T) { 1086 existing := tt.inputMethod.Copy() 1087 tt.inputMethod.Canonicalize() 1088 if existing.CreateTime.IsZero() { 1089 must.NotEq(t, time.Time{}, tt.inputMethod.CreateTime) 1090 } else { 1091 must.Eq(t, existing.CreateTime, tt.inputMethod.CreateTime) 1092 } 1093 if existing.ModifyTime.IsZero() { 1094 must.NotEq(t, time.Time{}, tt.inputMethod.ModifyTime) 1095 } 1096 }) 1097 } 1098 } 1099 1100 func TestACLBindingRule_Canonicalize(t *testing.T) { 1101 ci.Parallel(t) 1102 1103 testCases := []struct { 1104 name string 1105 inputACLBindingRule *ACLBindingRule 1106 }{ 1107 { 1108 name: "new binding rule", 1109 inputACLBindingRule: &ACLBindingRule{}, 1110 }, 1111 { 1112 name: "existing binding rule", 1113 inputACLBindingRule: &ACLBindingRule{ 1114 ID: "some-random-uuid", 1115 CreateTime: time.Now().UTC(), 1116 ModifyTime: time.Now().UTC(), 1117 }, 1118 }, 1119 } 1120 1121 for _, tc := range testCases { 1122 t.Run(tc.name, func(t *testing.T) { 1123 1124 // Make a copy, so we can compare the modified object to it. 1125 copiedBindingRule := tc.inputACLBindingRule.Copy() 1126 1127 tc.inputACLBindingRule.Canonicalize() 1128 1129 if copiedBindingRule.ID == "" { 1130 must.NotEq(t, "", tc.inputACLBindingRule.ID) 1131 must.NotEq(t, copiedBindingRule.CreateTime, tc.inputACLBindingRule.CreateTime) 1132 must.NotEq(t, copiedBindingRule.ModifyTime, tc.inputACLBindingRule.ModifyTime) 1133 } else { 1134 must.Eq(t, copiedBindingRule.ID, tc.inputACLBindingRule.ID) 1135 must.Eq(t, copiedBindingRule.CreateTime, tc.inputACLBindingRule.CreateTime) 1136 must.NotEq(t, copiedBindingRule.ModifyTime, tc.inputACLBindingRule.ModifyTime) 1137 } 1138 }) 1139 } 1140 } 1141 1142 func TestACLBindingRule_Validate(t *testing.T) { 1143 ci.Parallel(t) 1144 1145 // Quite possibly the most invalid binding rule to have ever existed. 1146 totallyInvalidACLBindingRule := ACLBindingRule{ 1147 Description: uuid.Generate() + uuid.Generate() + uuid.Generate() + uuid.Generate() + 1148 uuid.Generate() + uuid.Generate() + uuid.Generate() + uuid.Generate(), 1149 AuthMethod: "", 1150 BindType: "", 1151 BindName: "", 1152 } 1153 err := totallyInvalidACLBindingRule.Validate() 1154 must.StrContains(t, err.Error(), "auth method is missing") 1155 must.StrContains(t, err.Error(), "bind name is missing") 1156 must.StrContains(t, err.Error(), "description longer than 256") 1157 must.StrContains(t, err.Error(), "bind type is missing") 1158 1159 // Update the bind type, so we get the alternative error when this is not 1160 // empty, but incorrectly set. 1161 totallyInvalidACLBindingRule.BindType = "service" 1162 err = totallyInvalidACLBindingRule.Validate() 1163 must.StrContains(t, err.Error(), `unsupported bind type: "service"`) 1164 } 1165 1166 func TestACLBindingRule_SetHash(t *testing.T) { 1167 ci.Parallel(t) 1168 1169 bindingRule := &ACLBindingRule{ 1170 ID: uuid.Generate(), 1171 AuthMethod: "okta", 1172 } 1173 out1 := bindingRule.SetHash() 1174 must.NotNil(t, out1) 1175 must.NotNil(t, bindingRule.Hash) 1176 must.Eq(t, out1, bindingRule.Hash) 1177 1178 bindingRule.Description = "my lovely rule" 1179 out2 := bindingRule.SetHash() 1180 must.NotNil(t, out2) 1181 must.NotNil(t, bindingRule.Hash) 1182 must.Eq(t, out2, bindingRule.Hash) 1183 must.NotEq(t, out1, out2) 1184 } 1185 1186 func TestACLBindingRule_Equal(t *testing.T) { 1187 ci.Parallel(t) 1188 1189 aclBindingRule1 := &ACLBindingRule{ 1190 ID: uuid.Short(), 1191 Description: "mocked-acl-binding-rule", 1192 AuthMethod: "auth0", 1193 Selector: "engineering in list.roles", 1194 BindType: "role-id", 1195 BindName: "eng-ro", 1196 CreateTime: time.Now().UTC(), 1197 ModifyTime: time.Now().UTC(), 1198 CreateIndex: 10, 1199 ModifyIndex: 10, 1200 } 1201 aclBindingRule1.SetHash() 1202 1203 // Create a second binding rule, and modify this from the first. 1204 aclBindingRule2 := aclBindingRule1.Copy() 1205 aclBindingRule2.Description = "" 1206 aclBindingRule2.SetHash() 1207 1208 testCases := []struct { 1209 name string 1210 method1 *ACLBindingRule 1211 method2 *ACLBindingRule 1212 want bool 1213 }{ 1214 {"one nil", aclBindingRule1, &ACLBindingRule{}, false}, 1215 {"both nil", &ACLBindingRule{}, &ACLBindingRule{}, true}, 1216 {"not equal", aclBindingRule1, aclBindingRule2, false}, 1217 {"equal", aclBindingRule2, aclBindingRule2.Copy(), true}, 1218 } 1219 for _, tc := range testCases { 1220 t.Run(tc.name, func(t *testing.T) { 1221 got := tc.method1.Equal(tc.method2) 1222 must.Eq(t, got, tc.want) 1223 }) 1224 } 1225 } 1226 1227 func TestACLBindingRule_Copy(t *testing.T) { 1228 ci.Parallel(t) 1229 1230 aclBindingRule1 := &ACLBindingRule{ 1231 ID: uuid.Short(), 1232 Description: "mocked-acl-binding-rule", 1233 AuthMethod: "auth0", 1234 Selector: "engineering in list.roles", 1235 BindType: "role-id", 1236 BindName: "eng-ro", 1237 CreateTime: time.Now().UTC(), 1238 ModifyTime: time.Now().UTC(), 1239 CreateIndex: 10, 1240 ModifyIndex: 10, 1241 } 1242 aclBindingRule1.SetHash() 1243 1244 aclBindingRule2 := aclBindingRule1.Copy() 1245 must.Eq(t, aclBindingRule1, aclBindingRule2) 1246 1247 aclBindingRule3 := aclBindingRule1.Copy() 1248 aclBindingRule3.Description = "" 1249 aclBindingRule3.SetHash() 1250 must.NotEq(t, aclBindingRule1, aclBindingRule3) 1251 } 1252 1253 func TestACLBindingRule_Stub(t *testing.T) { 1254 ci.Parallel(t) 1255 1256 aclBindingRule := ACLBindingRule{ 1257 ID: "some-uuid", 1258 Description: "my-binding-rule", 1259 AuthMethod: "auth0", 1260 Selector: "some selector.pattern", 1261 BindType: "role", 1262 BindName: "some-role-id-or-name", 1263 CreateTime: time.Now().UTC(), 1264 ModifyTime: time.Now().UTC(), 1265 CreateIndex: 1309, 1266 ModifyIndex: 9031, 1267 } 1268 aclBindingRule.SetHash() 1269 1270 must.Eq(t, &ACLBindingRuleListStub{ 1271 ID: "some-uuid", 1272 Description: "my-binding-rule", 1273 AuthMethod: "auth0", 1274 Hash: aclBindingRule.Hash, 1275 CreateIndex: 1309, 1276 ModifyIndex: 9031, 1277 }, aclBindingRule.Stub()) 1278 } 1279 1280 func Test_ACLBindingRulesUpsertRequest(t *testing.T) { 1281 ci.Parallel(t) 1282 1283 req := ACLBindingRulesUpsertRequest{} 1284 require.False(t, req.IsRead()) 1285 } 1286 1287 func Test_ACLBindingRulesDeleteRequest(t *testing.T) { 1288 ci.Parallel(t) 1289 1290 req := ACLBindingRulesDeleteRequest{} 1291 require.False(t, req.IsRead()) 1292 } 1293 1294 func Test_ACLBindingRulesListRequest(t *testing.T) { 1295 ci.Parallel(t) 1296 1297 req := ACLBindingRulesListRequest{} 1298 require.True(t, req.IsRead()) 1299 } 1300 1301 func Test_ACLBindingRulesRequest(t *testing.T) { 1302 ci.Parallel(t) 1303 1304 req := ACLBindingRulesRequest{} 1305 require.True(t, req.IsRead()) 1306 } 1307 1308 func Test_ACLBindingRuleRequest(t *testing.T) { 1309 ci.Parallel(t) 1310 1311 req := ACLBindingRuleRequest{} 1312 require.True(t, req.IsRead()) 1313 }