github.com/crossplane/upjet@v1.3.0/pkg/resource/sensitive_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package resource 6 7 import ( 8 "context" 9 "testing" 10 11 xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" 12 "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" 13 "github.com/crossplane/crossplane-runtime/pkg/test" 14 "github.com/golang/mock/gomock" 15 "github.com/google/go-cmp/cmp" 16 "github.com/pkg/errors" 17 v1 "k8s.io/api/core/v1" 18 kerrors "k8s.io/apimachinery/pkg/api/errors" 19 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 20 "k8s.io/apimachinery/pkg/runtime" 21 22 "github.com/crossplane/upjet/pkg/config" 23 "github.com/crossplane/upjet/pkg/resource/fake" 24 "github.com/crossplane/upjet/pkg/resource/fake/mocks" 25 "github.com/crossplane/upjet/pkg/resource/json" 26 ) 27 28 var ( 29 testData = []byte(` 30 { 31 "top_level_optional": null, 32 "top_level_secret": "sensitive-data-top-level-secret", 33 "top_config_secretmap": { 34 "inner_config_secretmap.first": "sensitive-data-inner-first", 35 "inner_config_secretmap_second": "sensitive-data-inner-second", 36 "inner_config_secretmap_third": "sensitive-data-inner-third" 37 }, 38 "top_object_with_number": { "key1": 1, "key2": 2, "key3": 3}, 39 "top_config_array": [ 40 { 41 "inner_some_field": "non-sensitive-data-1", 42 "inner_config_array": [ 43 { 44 "bottom_some_field": "non-sensitive-data-1", 45 "bottom_level_secret": "sensitive-data-bottom-level-1" 46 } 47 ] 48 }, 49 { 50 "inner_some_field": "non-sensitive-data-2" 51 }, 52 { 53 "inner_some_field": "non-sensitive-data-3", 54 "inner_config_array": [ 55 { 56 "bottom_some_field": "non-sensitive-data-3a", 57 "bottom_level_secret": "sensitive-data-bottom-level-3a" 58 }, 59 { 60 "bottom_some_field": "non-sensitive-data-3a", 61 "bottom_level_secret": "sensitive-data-bottom-level-3b" 62 } 63 ] 64 }, 65 { 66 "inner_optional": null 67 } 68 ] 69 } 70 `) 71 errBoom = errors.New("boom") 72 ) 73 74 type secretKeySelectorModifier func(s *xpv1.SecretKeySelector) 75 76 func secretKeySelectorWithKey(v string) secretKeySelectorModifier { 77 return func(s *xpv1.SecretKeySelector) { 78 s.Key = v 79 } 80 } 81 82 func secretKeySelectorWithSecretReference(v xpv1.SecretReference) secretKeySelectorModifier { 83 return func(s *xpv1.SecretKeySelector) { 84 s.SecretReference = v 85 } 86 } 87 88 func secretKeySelector(sm ...secretKeySelectorModifier) *xpv1.SecretKeySelector { 89 s := &xpv1.SecretKeySelector{} 90 for _, m := range sm { 91 m(s) 92 } 93 return s 94 } 95 96 func TestGetConnectionDetails(t *testing.T) { 97 type args struct { 98 tr Terraformed 99 cfg *config.Resource 100 data map[string]any 101 } 102 type want struct { 103 out managed.ConnectionDetails 104 err error 105 } 106 cases := map[string]struct { 107 args 108 want 109 }{ 110 "NoConnectionDetails": { 111 args: args{ 112 tr: &fake.Terraformed{}, 113 cfg: config.DefaultResource("upjet_resource", nil, nil, nil), 114 }, 115 }, 116 "OnlyDefaultConnectionDetails": { 117 args: args{ 118 tr: &fake.Terraformed{ 119 MetadataProvider: fake.MetadataProvider{ 120 ConnectionDetailsMapping: map[string]string{ 121 "top_level_secret": "some.field", 122 }, 123 }, 124 }, 125 cfg: config.DefaultResource("upjet_resource", nil, nil, nil), 126 data: map[string]any{ 127 "top_level_secret": "sensitive-data-top-level-secret", 128 }, 129 }, 130 want: want{ 131 out: map[string][]byte{ 132 "attribute.top_level_secret": []byte("sensitive-data-top-level-secret"), 133 }, 134 }, 135 }, 136 "SecretList": { 137 args: args{ 138 tr: &fake.Terraformed{ 139 MetadataProvider: fake.MetadataProvider{ 140 ConnectionDetailsMapping: map[string]string{ 141 "top_level_secrets": "status.atProvider.topLevelSecrets", 142 }, 143 }, 144 }, 145 cfg: config.DefaultResource("upjet_resource", nil, nil, nil), 146 data: map[string]any{ 147 "top_level_secrets": []any{ 148 "val1", 149 "val2", 150 "val3", 151 }, 152 }, 153 }, 154 want: want{ 155 out: map[string][]byte{ 156 "attribute.top_level_secret.0": []byte("val1"), 157 "attribute.top_level_secret.1": []byte("val2"), 158 "attribute.top_level_secret.2": []byte("val3"), 159 }, 160 }, 161 }, 162 "Map": { 163 args: args{ 164 tr: &fake.Terraformed{ 165 MetadataProvider: fake.MetadataProvider{ 166 ConnectionDetailsMapping: map[string]string{ 167 "top_level_secrets": "status.atProvider.topLevelSecrets", 168 }, 169 }, 170 }, 171 cfg: config.DefaultResource("upjet_resource", nil, nil, nil), 172 data: map[string]any{ 173 "top_level_secrets": map[string]any{ 174 "key1": "val1", 175 "key2": "val2", 176 "key3": "val3", 177 }, 178 }, 179 }, 180 want: want{ 181 out: map[string][]byte{ 182 "attribute.top_level_secret.key1": []byte("val1"), 183 "attribute.top_level_secret.key2": []byte("val2"), 184 "attribute.top_level_secret.key3": []byte("val3"), 185 }, 186 }, 187 }, 188 "OnlyAdditionalConnectionDetails": { 189 args: args{ 190 tr: &fake.Terraformed{}, 191 cfg: &config.Resource{ 192 Sensitive: config.Sensitive{ 193 AdditionalConnectionDetailsFn: func(attr map[string]any) (map[string][]byte, error) { 194 return map[string][]byte{ 195 "top_level_secret_custom": []byte(attr["top_level_secret"].(string)), 196 }, nil 197 }, 198 }, 199 }, 200 data: map[string]any{ 201 "top_level_secret": "sensitive-data-top-level-secret", 202 }, 203 }, 204 want: want{ 205 out: map[string][]byte{ 206 "top_level_secret_custom": []byte("sensitive-data-top-level-secret"), 207 }, 208 }, 209 }, 210 "AdditionalConnectionDetailsFailed": { 211 args: args{ 212 tr: &fake.Terraformed{}, 213 cfg: &config.Resource{ 214 Sensitive: config.Sensitive{ 215 AdditionalConnectionDetailsFn: func(attr map[string]any) (map[string][]byte, error) { 216 return nil, errBoom 217 }, 218 }, 219 }, 220 }, 221 want: want{ 222 err: errors.Wrap(errBoom, errGetAdditionalConnectionDetails), 223 }, 224 }, 225 "CannotOverrideExistingKey": { 226 args: args{ 227 tr: &fake.Terraformed{ 228 MetadataProvider: fake.MetadataProvider{ 229 ConnectionDetailsMapping: map[string]string{ 230 "top_level_secret": "some.field", 231 }, 232 }, 233 }, 234 cfg: &config.Resource{ 235 Sensitive: config.Sensitive{ 236 AdditionalConnectionDetailsFn: func(attr map[string]any) (map[string][]byte, error) { 237 return map[string][]byte{ 238 "attribute.top_level_secret": []byte(""), 239 }, nil 240 }, 241 }, 242 }, 243 data: map[string]any{ 244 "id": "secret-id", 245 "top_level_secret": "sensitive-data-top-level-secret", 246 }, 247 }, 248 want: want{ 249 err: errors.Errorf(errFmtCannotOverrideExistingKey, "attribute.top_level_secret"), 250 }, 251 }, 252 } 253 for name, tc := range cases { 254 t.Run(name, func(t *testing.T) { 255 got, gotErr := GetConnectionDetails(tc.data, tc.tr, tc.cfg) 256 if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { 257 t.Fatalf("GetConnectionDetails(...): -want error, +got error: %s", diff) 258 } 259 if diff := cmp.Diff(tc.want.out, got); diff != "" { 260 t.Errorf("\nGetConnectionDetails(...): -want error, +got error:\n%s", diff) 261 } 262 }) 263 } 264 } 265 266 func TestGetSensitiveAttributes(t *testing.T) { 267 testInput := map[string]any{} 268 if err := json.JSParser.Unmarshal(testData, &testInput); err != nil { 269 t.Fatalf("cannot unmarshall test data: %v", err) 270 } 271 type args struct { 272 paths map[string]string 273 data map[string]any 274 } 275 type want struct { 276 out map[string][]byte 277 err error 278 } 279 cases := map[string]struct { 280 args 281 want 282 }{ 283 "Single": { 284 args: args{ 285 paths: map[string]string{"top_level_secret": ""}, 286 data: testInput, 287 }, 288 want: want{ 289 out: map[string][]byte{ 290 prefixAttribute + "top_level_secret": []byte("sensitive-data-top-level-secret"), 291 }, 292 }, 293 }, 294 "Optional": { 295 args: args{ 296 paths: map[string]string{"top_level_optional": ""}, 297 data: testInput, 298 }, 299 want: want{ 300 out: nil, 301 }, 302 }, 303 "SingleNonExisting": { 304 args: args{ 305 paths: map[string]string{"missing_field": ""}, 306 data: testInput, 307 }, 308 }, 309 "SingleGettingNumber": { 310 args: args{ 311 paths: map[string]string{"top_object_with_number[key1]": ""}, 312 data: testInput, 313 }, 314 want: want{ 315 err: errors.Errorf(errFmtCannotGetStringForFieldPath, "top_object_with_number.key1"), 316 }, 317 }, 318 "WildcardMultipleFromMap": { 319 args: args{ 320 paths: map[string]string{"top_config_secretmap[*]": ""}, 321 data: testInput, 322 }, 323 want: want{ 324 out: map[string][]byte{ 325 prefixAttribute + "top_config_secretmap...inner_config_secretmap.first...": []byte("sensitive-data-inner-first"), 326 prefixAttribute + "top_config_secretmap.inner_config_secretmap_second": []byte("sensitive-data-inner-second"), 327 prefixAttribute + "top_config_secretmap.inner_config_secretmap_third": []byte("sensitive-data-inner-third"), 328 }, 329 }, 330 }, 331 "WildcardMultipleFromArray": { 332 args: args{ 333 paths: map[string]string{"top_config_array[*].inner_some_field": ""}, 334 data: testInput, 335 }, 336 want: want{ 337 out: map[string][]byte{ 338 prefixAttribute + "top_config_array.0.inner_some_field": []byte("non-sensitive-data-1"), 339 prefixAttribute + "top_config_array.1.inner_some_field": []byte("non-sensitive-data-2"), 340 prefixAttribute + "top_config_array.2.inner_some_field": []byte("non-sensitive-data-3"), 341 }, 342 }, 343 }, 344 "WildcardMultipleFromArrayMultipleLevel": { 345 args: args{ 346 paths: map[string]string{"top_config_array[*].inner_config_array[*].bottom_level_secret": ""}, 347 data: testInput, 348 }, 349 want: want{ 350 out: map[string][]byte{ 351 prefixAttribute + "top_config_array.0.inner_config_array.0.bottom_level_secret": []byte("sensitive-data-bottom-level-1"), 352 prefixAttribute + "top_config_array.2.inner_config_array.0.bottom_level_secret": []byte("sensitive-data-bottom-level-3a"), 353 prefixAttribute + "top_config_array.2.inner_config_array.1.bottom_level_secret": []byte("sensitive-data-bottom-level-3b"), 354 }, 355 }, 356 }, 357 "WildcardMixedWithNumbers": { 358 args: args{ 359 paths: map[string]string{"top_config_array[2].inner_config_array[*].bottom_level_secret": ""}, 360 data: testInput, 361 }, 362 want: want{ 363 out: map[string][]byte{ 364 prefixAttribute + "top_config_array.2.inner_config_array.0.bottom_level_secret": []byte("sensitive-data-bottom-level-3a"), 365 prefixAttribute + "top_config_array.2.inner_config_array.1.bottom_level_secret": []byte("sensitive-data-bottom-level-3b"), 366 }, 367 }, 368 }, 369 "MultipleFieldPaths": { 370 args: args{ 371 paths: map[string]string{"top_level_secret": "", "top_config_secretmap.*": "", "top_config_array[2].inner_config_array[*].bottom_level_secret": ""}, 372 data: testInput, 373 }, 374 want: want{ 375 out: map[string][]byte{ 376 prefixAttribute + "top_level_secret": []byte("sensitive-data-top-level-secret"), 377 prefixAttribute + "top_config_secretmap...inner_config_secretmap.first...": []byte("sensitive-data-inner-first"), 378 prefixAttribute + "top_config_secretmap.inner_config_secretmap_second": []byte("sensitive-data-inner-second"), 379 prefixAttribute + "top_config_secretmap.inner_config_secretmap_third": []byte("sensitive-data-inner-third"), 380 prefixAttribute + "top_config_array.2.inner_config_array.0.bottom_level_secret": []byte("sensitive-data-bottom-level-3a"), 381 prefixAttribute + "top_config_array.2.inner_config_array.1.bottom_level_secret": []byte("sensitive-data-bottom-level-3b"), 382 }, 383 }, 384 }, 385 "NotAValue": { 386 args: args{ 387 paths: map[string]string{"inner_optional": ""}, 388 data: testInput, 389 }, 390 want: want{ 391 out: nil, 392 }, 393 }, 394 "UnexpectedWildcard": { 395 args: args{ 396 paths: map[string]string{"top_level_secret.*": ""}, 397 data: testInput, 398 }, 399 want: want{ 400 err: errors.Wrapf(errors.Wrapf( 401 errors.Errorf("%q: unexpected wildcard usage", "top_level_secret"), 402 "cannot expand wildcards for segments: %q", "top_level_secret[*]"), 403 errCannotExpandWildcards), 404 }, 405 }, 406 "NoData": { 407 args: args{ 408 paths: map[string]string{"top_level_secret": ""}, 409 data: nil, 410 }, 411 }, 412 } 413 for name, tc := range cases { 414 t.Run(name, func(t *testing.T) { 415 got, gotErr := GetSensitiveAttributes(tc.data, tc.paths) 416 if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { 417 t.Fatalf("GetFields(...): -want error, +got error: %s", diff) 418 } 419 if diff := cmp.Diff(tc.want.out, got); diff != "" { 420 t.Errorf("\nGetSensitiveAttributes(...): -want error, +got error:\n%s", diff) 421 } 422 }) 423 } 424 } 425 426 func TestGetSensitiveParameters(t *testing.T) { 427 type args struct { 428 clientFn func(client *mocks.MockSecretClient) 429 from runtime.Object 430 into map[string]any 431 mapping map[string]string 432 } 433 type want struct { 434 out map[string]any 435 err error 436 } 437 cases := map[string]struct { 438 args 439 want 440 }{ 441 "NoSensitiveData": { 442 args: args{ 443 clientFn: func(client *mocks.MockSecretClient) {}, 444 from: &unstructured.Unstructured{ 445 Object: map[string]any{ 446 "spec": map[string]any{ 447 "forProvider": map[string]any{ 448 "adminPasswordSecretRef": nil, 449 }, 450 }, 451 }, 452 }, 453 into: map[string]any{ 454 "some_other_key": "some_other_value", 455 }, 456 mapping: map[string]string{ 457 "admin_password": "spec.forProvider.adminPasswordSecretRef", 458 }, 459 }, 460 want: want{ 461 out: map[string]any{ 462 "some_other_key": "some_other_value", 463 }, 464 }, 465 }, 466 "SingleNoWildcard": { 467 args: args{ 468 clientFn: func(client *mocks.MockSecretClient) { 469 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 470 SecretReference: xpv1.SecretReference{ 471 Name: "admin-password", 472 Namespace: "crossplane-system", 473 }, 474 Key: "pass", 475 })).Return([]byte("foo"), nil) 476 }, 477 from: &unstructured.Unstructured{ 478 Object: map[string]any{ 479 "spec": map[string]any{ 480 "forProvider": map[string]any{ 481 "adminPasswordSecretRef": map[string]any{ 482 "key": "pass", 483 "name": "admin-password", 484 "namespace": "crossplane-system", 485 }, 486 }, 487 }, 488 }, 489 }, 490 into: map[string]any{ 491 "some_other_key": "some_other_value", 492 }, 493 mapping: map[string]string{ 494 "admin_password": "spec.forProvider.adminPasswordSecretRef", 495 }, 496 }, 497 want: want{ 498 out: map[string]any{ 499 "some_other_key": "some_other_value", 500 "admin_password": "foo", 501 }, 502 }, 503 }, 504 "SingleNoWildcardWithNoSecret": { 505 args: args{ 506 clientFn: func(client *mocks.MockSecretClient) { 507 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 508 SecretReference: xpv1.SecretReference{ 509 Name: "admin-password", 510 Namespace: "crossplane-system", 511 }, 512 Key: "pass", 513 })).Return([]byte(""), kerrors.NewNotFound(v1.Resource("secret"), "admin-password")) 514 }, 515 from: &unstructured.Unstructured{ 516 Object: map[string]any{ 517 "spec": map[string]any{ 518 "forProvider": map[string]any{ 519 "adminPasswordSecretRef": map[string]any{ 520 "key": "pass", 521 "name": "admin-password", 522 "namespace": "crossplane-system", 523 }, 524 }, 525 }, 526 }, 527 }, 528 into: map[string]any{ 529 "some_other_key": "some_other_value", 530 }, 531 mapping: map[string]string{ 532 "admin_password": "spec.forProvider.adminPasswordSecretRef", 533 }, 534 }, 535 want: want{ 536 out: map[string]any{ 537 "some_other_key": "some_other_value", 538 "admin_password": "", 539 }, 540 }, 541 }, 542 "SingleNoWildcardWithSlice": { 543 args: args{ 544 clientFn: func(client *mocks.MockSecretClient) { 545 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 546 SecretReference: xpv1.SecretReference{ 547 Name: "db-passwords", 548 Namespace: "crossplane-system", 549 }, 550 Key: "admin", 551 })).Return([]byte("admin_pwd"), nil) 552 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 553 SecretReference: xpv1.SecretReference{ 554 Name: "db-passwords", 555 Namespace: "crossplane-system", 556 }, 557 Key: "system", 558 })).Return([]byte("system_pwd"), nil) 559 }, 560 from: &unstructured.Unstructured{ 561 Object: map[string]any{ 562 "spec": map[string]any{ 563 "forProvider": map[string]any{ 564 "passwordsSecretRef": []any{ 565 secretKeySelector( 566 secretKeySelectorWithKey("admin"), 567 secretKeySelectorWithSecretReference(xpv1.SecretReference{ 568 Name: "db-passwords", 569 Namespace: "crossplane-system", 570 }), 571 ), 572 secretKeySelector( 573 secretKeySelectorWithKey("system"), 574 secretKeySelectorWithSecretReference(xpv1.SecretReference{ 575 Name: "db-passwords", 576 Namespace: "crossplane-system", 577 }), 578 ), 579 }, 580 }, 581 }, 582 }, 583 }, 584 into: map[string]any{ 585 "some_other_key": "some_other_value", 586 }, 587 mapping: map[string]string{ 588 "db_passwords": "spec.forProvider.passwordsSecretRef", 589 }, 590 }, 591 want: want{ 592 out: map[string]any{ 593 "some_other_key": "some_other_value", 594 "db_passwords": []any{ 595 "admin_pwd", 596 "system_pwd", 597 }, 598 }, 599 }, 600 }, 601 "SingleNoWildcardWithSecretReference": { 602 args: args{ 603 clientFn: func(client *mocks.MockSecretClient) { 604 client.EXPECT().GetSecretData(gomock.Any(), gomock.Eq(&xpv1.SecretReference{ 605 Name: "db-passwords", 606 Namespace: "crossplane-system", 607 })).Return(map[string][]byte{"admin": []byte("admin_pwd"), "system": []byte("system_pwd")}, nil) 608 }, 609 from: &unstructured.Unstructured{ 610 Object: map[string]any{ 611 "spec": map[string]any{ 612 "forProvider": map[string]any{ 613 "dbPasswordsSecretRef": map[string]any{ 614 "name": "db-passwords", 615 "namespace": "crossplane-system", 616 }, 617 }, 618 }, 619 }, 620 }, 621 into: map[string]any{ 622 "some_other_key": "some_other_value", 623 }, 624 mapping: map[string]string{ 625 "db_passwords": "spec.forProvider.dbPasswordsSecretRef", 626 }, 627 }, 628 want: want{ 629 out: map[string]any{ 630 "some_other_key": "some_other_value", 631 "db_passwords": map[string]any{ 632 "admin": "admin_pwd", 633 "system": "system_pwd", 634 }, 635 }, 636 }, 637 }, 638 "MultipleNoWildcard": { 639 args: args{ 640 clientFn: func(client *mocks.MockSecretClient) { 641 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 642 SecretReference: xpv1.SecretReference{ 643 Name: "admin-password", 644 Namespace: "crossplane-system", 645 }, 646 Key: "pass", 647 })).Return([]byte("foo"), nil) 648 }, 649 from: &unstructured.Unstructured{ 650 Object: map[string]any{ 651 "spec": map[string]any{ 652 "forProvider": map[string]any{ 653 "adminPasswordSecretRef": map[string]any{ 654 "key": "pass", 655 "name": "admin-password", 656 "namespace": "crossplane-system", 657 }, 658 }, 659 }, 660 }, 661 }, 662 into: map[string]any{ 663 "some_other_key": "some_other_value", 664 }, 665 mapping: map[string]string{ 666 "admin_password": "spec.forProvider.adminPasswordSecretRef", 667 "admin_key": "spec.forProvider.adminKeySecretRef", 668 }, 669 }, 670 want: want{ 671 out: map[string]any{ 672 "some_other_key": "some_other_value", 673 "admin_password": "foo", 674 }, 675 }, 676 }, 677 "MultipleWithWildcard": { 678 args: args{ 679 clientFn: func(client *mocks.MockSecretClient) { 680 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 681 SecretReference: xpv1.SecretReference{ 682 Name: "admin-password", 683 Namespace: "crossplane-system", 684 }, 685 Key: "pass", 686 })).Return([]byte("foo"), nil) 687 client.EXPECT().GetSecretValue(gomock.Any(), gomock.Eq(xpv1.SecretKeySelector{ 688 SecretReference: xpv1.SecretReference{ 689 Name: "maintenance-password", 690 Namespace: "crossplane-system", 691 }, 692 Key: "pass", 693 })).Return([]byte("baz"), nil) 694 }, 695 from: &unstructured.Unstructured{ 696 Object: map[string]any{ 697 "spec": map[string]any{ 698 "forProvider": map[string]any{ 699 "databaseUsers": []any{ 700 map[string]any{ 701 "name": "admin", 702 "passwordSecretRef": map[string]any{ 703 "key": "pass", 704 "name": "admin-password", 705 "namespace": "crossplane-system", 706 }, 707 "displayName": "Administrator", 708 }, 709 map[string]any{ 710 "name": "system", 711 // Intentionally skip providing this optional parameter 712 // to test the behaviour when an optional parameter 713 // not provided. 714 /*"passwordSecretRef": map[string]any{ 715 "name": "system-password", 716 "namespace": "crossplane-system", 717 "key": "pass", 718 },*/ 719 "displayName": "System", 720 }, 721 map[string]any{ 722 "name": "maintenance", 723 "passwordSecretRef": map[string]any{ 724 "key": "pass", 725 "name": "maintenance-password", 726 "namespace": "crossplane-system", 727 }, 728 "displayName": "Maintenance", 729 }, 730 }, 731 }, 732 }, 733 }, 734 }, 735 into: map[string]any{ 736 "some_other_key": "some_other_value", 737 "database_users": []any{ 738 map[string]any{ 739 "name": "admin", 740 "display_name": "Administrator", 741 }, 742 map[string]any{ 743 "name": "system", 744 "display_name": "System", 745 }, 746 map[string]any{ 747 "name": "maintenance", 748 "display_name": "Maintenance", 749 }, 750 }, 751 }, 752 mapping: map[string]string{ 753 "database_users[*].password": "spec.forProvider.databaseUsers[*].passwordSecretRef", 754 }, 755 }, 756 want: want{ 757 out: map[string]any{ 758 "some_other_key": "some_other_value", 759 "database_users": []any{ 760 map[string]any{ 761 "name": "admin", 762 "password": "foo", 763 "display_name": "Administrator", 764 }, 765 map[string]any{ 766 "name": "system", 767 "display_name": "System", 768 }, 769 map[string]any{ 770 "name": "maintenance", 771 "password": "baz", 772 "display_name": "Maintenance", 773 }, 774 }, 775 }, 776 }, 777 }, 778 } 779 for name, tc := range cases { 780 ctrl := gomock.NewController(t) 781 m := mocks.NewMockSecretClient(ctrl) 782 783 tc.args.clientFn(m) 784 t.Run(name, func(t *testing.T) { 785 gotErr := GetSensitiveParameters(context.Background(), m, tc.args.from, tc.args.into, tc.args.mapping) 786 if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { 787 t.Fatalf("GetSensitiveParameters(...): -want error, +got error: %s", diff) 788 } 789 if diff := cmp.Diff(tc.want.out, tc.args.into); diff != "" { 790 t.Errorf("GetSensitiveParameters(...) out = %v, want %v", tc.args.into, tc.want.out) 791 } 792 }) 793 } 794 } 795 796 func TestGetSensitiveObservation(t *testing.T) { 797 connSecretRef := &xpv1.SecretReference{ 798 Name: "connection-details", 799 Namespace: "crossplane-system", 800 } 801 type args struct { 802 clientFn func(client *mocks.MockSecretClient) 803 into map[string]any 804 } 805 type want struct { 806 out map[string]any 807 err error 808 } 809 cases := map[string]struct { 810 args 811 want 812 }{ 813 "SingleNoWildcard": { 814 args: args{ 815 clientFn: func(client *mocks.MockSecretClient) { 816 client.EXPECT().GetSecretData(gomock.Any(), connSecretRef). 817 Return(map[string][]byte{ 818 prefixAttribute + "admin_password": []byte("foo"), 819 "a_custom_key": []byte("t0p-s3cr3t"), 820 }, nil) 821 }, 822 into: map[string]any{ 823 "some_other_key": "some_other_value", 824 }, 825 }, 826 want: want{ 827 out: map[string]any{ 828 "some_other_key": "some_other_value", 829 "admin_password": "foo", 830 }, 831 }, 832 }, 833 "MultipleNoWildcard": { 834 args: args{ 835 clientFn: func(client *mocks.MockSecretClient) { 836 client.EXPECT(). 837 GetSecretData(gomock.Any(), connSecretRef). 838 Return(map[string][]byte{ 839 prefixAttribute + "admin_password": []byte("foo"), 840 prefixAttribute + "admin_private_key": []byte("bar"), 841 }, nil) 842 }, 843 into: map[string]any{ 844 "some_other_key": "some_other_value", 845 }, 846 }, 847 want: want{ 848 out: map[string]any{ 849 "some_other_key": "some_other_value", 850 "admin_password": "foo", 851 "admin_private_key": "bar", 852 }, 853 }, 854 }, 855 "MultipleWithWildcard": { 856 args: args{ 857 clientFn: func(client *mocks.MockSecretClient) { 858 client.EXPECT().GetSecretData(gomock.Any(), connSecretRef). 859 Return(map[string][]byte{ 860 prefixAttribute + "database_users.0.password": []byte("foo"), 861 prefixAttribute + "database_users.1.password": []byte("bar"), 862 prefixAttribute + "database_users.2.password": []byte("baz"), 863 "a_custom_key": []byte("t0p-s3cr3t"), 864 }, nil) 865 }, 866 into: map[string]any{ 867 "some_other_key": "some_other_value", 868 "database_users": []any{ 869 map[string]any{ 870 "name": "admin", 871 "display_name": "Administrator", 872 }, 873 map[string]any{ 874 "name": "system", 875 "display_name": "System", 876 }, 877 map[string]any{ 878 "name": "maintenance", 879 "display_name": "Maintenance", 880 }, 881 }, 882 }, 883 }, 884 want: want{ 885 out: map[string]any{ 886 "some_other_key": "some_other_value", 887 "database_users": []any{ 888 map[string]any{ 889 "name": "admin", 890 "password": "foo", 891 "display_name": "Administrator", 892 }, 893 map[string]any{ 894 "name": "system", 895 "password": "bar", 896 "display_name": "System", 897 }, 898 map[string]any{ 899 "name": "maintenance", 900 "password": "baz", 901 "display_name": "Maintenance", 902 }, 903 }, 904 }, 905 }, 906 }, 907 } 908 for name, tc := range cases { 909 t.Run(name, func(t *testing.T) { 910 ctrl := gomock.NewController(t) 911 m := mocks.NewMockSecretClient(ctrl) 912 913 tc.args.clientFn(m) 914 gotErr := GetSensitiveObservation(context.Background(), m, connSecretRef, tc.args.into) 915 if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { 916 t.Fatalf("GetSensitiveObservation(...): -want error, +got error: %s", diff) 917 } 918 if diff := cmp.Diff(tc.want.out, tc.args.into); diff != "" { 919 t.Errorf("GetSensitiveObservation(...) out = %v, want %v", tc.args.into, tc.want.out) 920 } 921 }) 922 } 923 } 924 925 func Test_secretKeyToFieldPath(t *testing.T) { 926 type args struct { 927 s string 928 } 929 type want struct { 930 out string 931 err error 932 } 933 cases := map[string]struct { 934 args 935 want 936 }{ 937 "EndIndex": { 938 args{ 939 s: "kube_config.0", 940 }, 941 want{ 942 out: "kube_config[0]", 943 err: nil, 944 }, 945 }, 946 "MiddleIndex": { 947 args{ 948 s: "kube_config.0.password", 949 }, 950 want{ 951 out: "kube_config[0].password", 952 err: nil, 953 }, 954 }, 955 "MultipleIndexes": { 956 args{ 957 s: "kube_config.0.users.1.keys.0", 958 }, 959 want{ 960 out: "kube_config[0].users[1].keys[0]", 961 err: nil, 962 }, 963 }, 964 "EndsKeyWithDots": { 965 args{ 966 s: "metadata.annotations...crossplane.io/external-name...", 967 }, 968 want{ 969 out: "metadata.annotations[crossplane.io/external-name]", 970 err: nil, 971 }, 972 }, 973 "MiddleKeyWithDots": { 974 args{ 975 s: "users...crossplane.io/test-user....test", 976 }, 977 want{ 978 out: "users[crossplane.io/test-user].test", 979 err: nil, 980 }, 981 }, 982 "MultipleKeysWithDots": { 983 args{ 984 s: "users...crossplane.io/test-user....test...abc.xyz...", 985 }, 986 want{ 987 out: "users[crossplane.io/test-user].test[abc.xyz]", 988 err: nil, 989 }, 990 }, 991 "MixedDotsAndIndexes": { 992 args{ 993 s: "users...crossplane.io/test-user....test.0.users.3", 994 }, 995 want{ 996 out: "users[crossplane.io/test-user].test[0].users[3]", 997 err: nil, 998 }, 999 }, 1000 } 1001 for name, tc := range cases { 1002 t.Run(name, func(t *testing.T) { 1003 got, gotErr := secretKeyToFieldPath(tc.args.s) 1004 if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { 1005 t.Fatalf("secretKeyToFieldPath(...): -want error, +got error: %s", diff) 1006 } 1007 if diff := cmp.Diff(tc.want.out, got); diff != "" { 1008 t.Errorf("secretKeyToFieldPath(...) out = %v, want %v", got, tc.want.out) 1009 } 1010 }) 1011 } 1012 } 1013 1014 func Test_fieldPathToSecretKey(t *testing.T) { 1015 type args struct { 1016 s string 1017 } 1018 type want struct { 1019 out string 1020 err error 1021 } 1022 cases := map[string]struct { 1023 args 1024 want 1025 }{ 1026 "EndIndex": { 1027 args{ 1028 s: "kube_config[0]", 1029 }, 1030 want{ 1031 out: "kube_config.0", 1032 err: nil, 1033 }, 1034 }, 1035 "MiddleIndex": { 1036 args{ 1037 s: "kube_config[0].password", 1038 }, 1039 want{ 1040 out: "kube_config.0.password", 1041 err: nil, 1042 }, 1043 }, 1044 "MultipleIndexes": { 1045 args{ 1046 s: "kube_config[0].users[1].keys[0]", 1047 }, 1048 want{ 1049 out: "kube_config.0.users.1.keys.0", 1050 err: nil, 1051 }, 1052 }, 1053 "EndsKeyWithDots": { 1054 args{ 1055 s: "metadata.annotations[crossplane.io/external-name]", 1056 }, 1057 want{ 1058 out: "metadata.annotations...crossplane.io/external-name...", 1059 err: nil, 1060 }, 1061 }, 1062 "MiddleKeyWithDots": { 1063 args{ 1064 s: "users[crossplane.io/test-user].test", 1065 }, 1066 want{ 1067 out: "users...crossplane.io/test-user....test", 1068 err: nil, 1069 }, 1070 }, 1071 "MixedDotsAndIndexes": { 1072 args{ 1073 s: "users[crossplane.io/test-user].test[0].users[3]", 1074 }, 1075 want{ 1076 out: "users...crossplane.io/test-user....test.0.users.3", 1077 err: nil, 1078 }, 1079 }, 1080 } 1081 for name, tc := range cases { 1082 t.Run(name, func(t *testing.T) { 1083 got, gotErr := fieldPathToSecretKey(tc.args.s) 1084 if diff := cmp.Diff(tc.want.err, gotErr, test.EquateErrors()); diff != "" { 1085 t.Fatalf("secretKeyToFieldPath(...): -want error, +got error: %s", diff) 1086 } 1087 if diff := cmp.Diff(tc.want.out, got); diff != "" { 1088 t.Errorf("secretKeyToFieldPath(...) out = %v, want %v", got, tc.want.out) 1089 } 1090 }) 1091 } 1092 }