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  }