client_manager_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package secretstore
  14. import (
  15. "context"
  16. "testing"
  17. "github.com/go-logr/logr"
  18. "github.com/stretchr/testify/assert"
  19. "github.com/stretchr/testify/require"
  20. corev1 "k8s.io/api/core/v1"
  21. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  25. clientgoscheme "k8s.io/client-go/kubernetes/scheme"
  26. "sigs.k8s.io/controller-runtime/pkg/client"
  27. fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
  28. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  29. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  30. )
  31. func TestManagerGet(t *testing.T) {
  32. scheme := runtime.NewScheme()
  33. // add kubernetes schemes
  34. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  35. utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
  36. // add external-secrets schemes
  37. utilruntime.Must(esv1.AddToScheme(scheme))
  38. // We have a test provider to control
  39. // the behavior of the NewClient func.
  40. fakeProvider := &WrapProvider{}
  41. esv1.ForceRegister(fakeProvider, &esv1.SecretStoreProvider{
  42. AWS: &esv1.AWSProvider{},
  43. }, esv1.MaintenanceStatusMaintained)
  44. // fake clients are re-used to compare the
  45. // in-memory reference
  46. clientA := &MockFakeClient{id: "1"}
  47. clientB := &MockFakeClient{id: "2"}
  48. const testNamespace = "foo"
  49. readyStatus := esv1.SecretStoreStatus{
  50. Conditions: []esv1.SecretStoreStatusCondition{
  51. {
  52. Type: esv1.SecretStoreReady,
  53. Status: corev1.ConditionTrue,
  54. },
  55. },
  56. }
  57. fakeSpec := esv1.SecretStoreSpec{
  58. Provider: &esv1.SecretStoreProvider{
  59. AWS: &esv1.AWSProvider{},
  60. },
  61. }
  62. defaultStore := &esv1.SecretStore{
  63. TypeMeta: metav1.TypeMeta{Kind: esv1.SecretStoreKind},
  64. ObjectMeta: metav1.ObjectMeta{
  65. Name: "foo",
  66. Namespace: testNamespace,
  67. },
  68. Spec: fakeSpec,
  69. Status: readyStatus,
  70. }
  71. otherStore := &esv1.SecretStore{
  72. TypeMeta: metav1.TypeMeta{Kind: esv1.SecretStoreKind},
  73. ObjectMeta: metav1.ObjectMeta{
  74. Name: "other",
  75. Namespace: testNamespace,
  76. },
  77. Spec: fakeSpec,
  78. Status: readyStatus,
  79. }
  80. var mgr *Manager
  81. provKey := clientKey{
  82. providerType: "*secretstore.WrapProvider",
  83. }
  84. type fields struct {
  85. client client.Client
  86. clientMap map[clientKey]*clientVal
  87. }
  88. type args struct {
  89. storeRef esv1.SecretStoreRef
  90. namespace string
  91. sourceRef *esv1.StoreGeneratorSourceRef
  92. }
  93. tests := []struct {
  94. name string
  95. fields fields
  96. args args
  97. clientConstructor func(
  98. ctx context.Context,
  99. store esv1.GenericStore,
  100. kube client.Client,
  101. namespace string) (esv1.SecretsClient, error)
  102. verify func(esv1.SecretsClient)
  103. afterClose func()
  104. want esv1.SecretsClient
  105. wantErr bool
  106. }{
  107. {
  108. name: "creates a new client from storeRef and stores it",
  109. wantErr: false,
  110. fields: fields{
  111. client: fakeclient.NewClientBuilder().
  112. WithScheme(scheme).
  113. WithObjects(defaultStore).
  114. Build(),
  115. clientMap: make(map[clientKey]*clientVal),
  116. },
  117. args: args{
  118. storeRef: esv1.SecretStoreRef{
  119. Name: defaultStore.Name,
  120. Kind: esv1.SecretStoreKind,
  121. },
  122. namespace: defaultStore.Namespace,
  123. sourceRef: nil,
  124. },
  125. clientConstructor: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
  126. return clientA, nil
  127. },
  128. verify: func(sc esv1.SecretsClient) {
  129. // we now must have this provider in the clientMap
  130. // and it mustbe the client defined in clientConstructor
  131. assert.NotNil(t, sc)
  132. c, ok := mgr.clientMap[provKey]
  133. require.True(t, ok)
  134. assert.Same(t, c.client, clientA)
  135. },
  136. afterClose: func() {
  137. v, ok := mgr.clientMap[provKey]
  138. assert.False(t, ok)
  139. assert.Nil(t, v)
  140. },
  141. },
  142. {
  143. name: "creates a new client using both storeRef and sourceRef",
  144. wantErr: false,
  145. fields: fields{
  146. client: fakeclient.NewClientBuilder().
  147. WithScheme(scheme).
  148. WithObjects(otherStore).
  149. Build(),
  150. clientMap: make(map[clientKey]*clientVal),
  151. },
  152. args: args{
  153. storeRef: esv1.SecretStoreRef{
  154. Name: defaultStore.Name,
  155. Kind: esv1.SecretStoreKind,
  156. },
  157. // this should take precedence
  158. sourceRef: &esv1.StoreGeneratorSourceRef{
  159. SecretStoreRef: &esv1.SecretStoreRef{
  160. Name: otherStore.Name,
  161. Kind: esv1.SecretStoreKind,
  162. },
  163. },
  164. namespace: defaultStore.Namespace,
  165. },
  166. clientConstructor: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
  167. return clientB, nil
  168. },
  169. verify: func(sc esv1.SecretsClient) {
  170. // we now must have this provider in the clientMap
  171. // and it mustbe the client defined in clientConstructor
  172. assert.NotNil(t, sc)
  173. c, ok := mgr.clientMap[provKey]
  174. assert.True(t, ok)
  175. assert.Same(t, c.client, clientB)
  176. },
  177. afterClose: func() {
  178. v, ok := mgr.clientMap[provKey]
  179. assert.False(t, ok)
  180. assert.True(t, clientB.closeCalled)
  181. assert.Nil(t, v)
  182. },
  183. },
  184. {
  185. name: "sourceRef overrides storeRef if both configured",
  186. wantErr: false,
  187. fields: fields{
  188. client: fakeclient.NewClientBuilder().
  189. WithScheme(scheme).
  190. WithObjects(defaultStore, otherStore).
  191. Build(),
  192. clientMap: make(map[clientKey]*clientVal),
  193. },
  194. args: args{
  195. storeRef: esv1.SecretStoreRef{
  196. Name: defaultStore.Name,
  197. Kind: esv1.SecretStoreKind,
  198. },
  199. sourceRef: &esv1.StoreGeneratorSourceRef{
  200. SecretStoreRef: &esv1.SecretStoreRef{
  201. Name: otherStore.Name,
  202. Kind: esv1.SecretStoreKind,
  203. },
  204. },
  205. namespace: testNamespace,
  206. },
  207. clientConstructor: func(_ context.Context, store esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
  208. // Assert that NewClient receives the *referenced* store, not the primary
  209. assert.Equal(t, otherStore.Name, store.GetName(),
  210. "NewClient should receive the sourceRef store, not the primary storeRef")
  211. assert.Equal(t, esv1.SecretStoreKind, store.GetKind())
  212. return clientB, nil
  213. },
  214. verify: func(sc esv1.SecretsClient) {
  215. assert.NotNil(t, sc)
  216. assert.Same(t, sc, clientB)
  217. },
  218. afterClose: func() {
  219. _, ok := mgr.clientMap[provKey]
  220. assert.False(t, ok)
  221. },
  222. },
  223. {
  224. name: "retrieve cached client when store matches",
  225. wantErr: false,
  226. fields: fields{
  227. client: fakeclient.NewClientBuilder().
  228. WithScheme(scheme).
  229. WithObjects(defaultStore).
  230. Build(),
  231. clientMap: map[clientKey]*clientVal{
  232. provKey: {
  233. client: clientA,
  234. store: defaultStore,
  235. },
  236. },
  237. },
  238. args: args{
  239. storeRef: esv1.SecretStoreRef{
  240. Name: defaultStore.Name,
  241. Kind: esv1.SecretStoreKind,
  242. },
  243. namespace: defaultStore.Namespace,
  244. sourceRef: nil,
  245. },
  246. clientConstructor: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
  247. // constructor should not be called,
  248. // the client from the cache should be returned instead
  249. t.Fail()
  250. return nil, nil
  251. },
  252. verify: func(sc esv1.SecretsClient) {
  253. // verify that the secretsClient is the one from cache
  254. assert.NotNil(t, sc)
  255. c, ok := mgr.clientMap[provKey]
  256. assert.True(t, ok)
  257. assert.Same(t, c.client, clientA)
  258. assert.Same(t, sc, clientA)
  259. },
  260. afterClose: func() {
  261. v, ok := mgr.clientMap[provKey]
  262. assert.False(t, ok)
  263. assert.True(t, clientA.closeCalled)
  264. assert.Nil(t, v)
  265. },
  266. },
  267. {
  268. name: "create new client when store doesn't match",
  269. wantErr: false,
  270. fields: fields{
  271. client: fakeclient.NewClientBuilder().
  272. WithScheme(scheme).
  273. WithObjects(otherStore).
  274. Build(),
  275. clientMap: map[clientKey]*clientVal{
  276. provKey: {
  277. // we have clientA in cache pointing at defaultStore
  278. client: clientA,
  279. store: defaultStore,
  280. },
  281. },
  282. },
  283. args: args{
  284. storeRef: esv1.SecretStoreRef{
  285. Name: otherStore.Name,
  286. Kind: esv1.SecretStoreKind,
  287. },
  288. namespace: otherStore.Namespace,
  289. sourceRef: nil,
  290. },
  291. clientConstructor: func(_ context.Context, _ esv1.GenericStore, _ client.Client, _ string) (esv1.SecretsClient, error) {
  292. // because there is a store mismatch
  293. // we create a new client
  294. return clientB, nil
  295. },
  296. verify: func(sc esv1.SecretsClient) {
  297. // verify that SecretsClient is NOT the one from cache
  298. assert.NotNil(t, sc)
  299. c, ok := mgr.clientMap[provKey]
  300. assert.True(t, ok)
  301. assert.Same(t, c.client, clientB)
  302. assert.Same(t, sc, clientB)
  303. assert.True(t, clientA.closeCalled)
  304. },
  305. afterClose: func() {
  306. v, ok := mgr.clientMap[provKey]
  307. assert.False(t, ok)
  308. assert.True(t, clientB.closeCalled)
  309. assert.Nil(t, v)
  310. },
  311. },
  312. }
  313. for _, tt := range tests {
  314. t.Run(tt.name, func(t *testing.T) {
  315. mgr = &Manager{
  316. log: logr.Discard(),
  317. client: tt.fields.client,
  318. enableFloodgate: true,
  319. clientMap: tt.fields.clientMap,
  320. }
  321. fakeProvider.newClientFunc = tt.clientConstructor
  322. clientA.closeCalled = false
  323. clientB.closeCalled = false
  324. got, err := mgr.Get(context.Background(), tt.args.storeRef, tt.args.namespace, tt.args.sourceRef)
  325. if (err != nil) != tt.wantErr {
  326. t.Errorf("Manager.Get() error = %v, wantErr %v", err, tt.wantErr)
  327. return
  328. }
  329. tt.verify(got)
  330. mgr.Close(context.Background())
  331. tt.afterClose()
  332. })
  333. }
  334. }
  335. func TestShouldProcessSecret(t *testing.T) {
  336. scheme := runtime.NewScheme()
  337. // add kubernetes schemes
  338. utilruntime.Must(clientgoscheme.AddToScheme(scheme))
  339. utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
  340. // add external-secrets schemes
  341. utilruntime.Must(esv1.AddToScheme(scheme))
  342. testNamespace := "test-a"
  343. testCases := []struct {
  344. name string
  345. conditions []esv1.ClusterSecretStoreCondition
  346. namespace *corev1.Namespace
  347. wantErr string
  348. want bool
  349. }{
  350. {
  351. name: "processes a regex condition",
  352. conditions: []esv1.ClusterSecretStoreCondition{
  353. {
  354. NamespaceRegexes: []string{`test-*`},
  355. },
  356. },
  357. namespace: &corev1.Namespace{
  358. ObjectMeta: metav1.ObjectMeta{
  359. Name: testNamespace,
  360. },
  361. },
  362. want: true,
  363. },
  364. {
  365. name: "process multiple regexes",
  366. conditions: []esv1.ClusterSecretStoreCondition{
  367. {
  368. NamespaceRegexes: []string{`nope`, `test-*`},
  369. },
  370. },
  371. namespace: &corev1.Namespace{
  372. ObjectMeta: metav1.ObjectMeta{
  373. Name: testNamespace,
  374. },
  375. },
  376. want: true,
  377. },
  378. {
  379. name: "shouldn't process if nothing matches",
  380. conditions: []esv1.ClusterSecretStoreCondition{
  381. {
  382. NamespaceRegexes: []string{`nope`},
  383. },
  384. },
  385. namespace: &corev1.Namespace{
  386. ObjectMeta: metav1.ObjectMeta{
  387. Name: testNamespace,
  388. },
  389. },
  390. want: false,
  391. },
  392. }
  393. for _, tt := range testCases {
  394. t.Run(tt.name, func(t *testing.T) {
  395. fakeSpec := esv1.SecretStoreSpec{
  396. Conditions: tt.conditions,
  397. }
  398. defaultStore := &esv1.ClusterSecretStore{
  399. TypeMeta: metav1.TypeMeta{Kind: esv1.ClusterSecretStoreKind},
  400. ObjectMeta: metav1.ObjectMeta{
  401. Name: "foo",
  402. Namespace: tt.namespace.Name,
  403. },
  404. Spec: fakeSpec,
  405. }
  406. client := fakeclient.NewClientBuilder().WithScheme(scheme).WithObjects(defaultStore, tt.namespace).Build()
  407. clientMap := make(map[clientKey]*clientVal)
  408. mgr := &Manager{
  409. log: logr.Discard(),
  410. client: client,
  411. enableFloodgate: true,
  412. clientMap: clientMap,
  413. }
  414. got, err := mgr.shouldProcessSecret(defaultStore, tt.namespace.Name)
  415. require.NoError(t, err)
  416. assert.Equal(t, tt.want, got)
  417. })
  418. }
  419. }
  420. type WrapProvider struct {
  421. newClientFunc func(
  422. context.Context,
  423. esv1.GenericStore,
  424. client.Client,
  425. string) (esv1.SecretsClient, error)
  426. }
  427. // NewClient constructs a SecretsManager Provider.
  428. func (f *WrapProvider) NewClient(
  429. ctx context.Context,
  430. store esv1.GenericStore,
  431. kube client.Client,
  432. namespace string) (esv1.SecretsClient, error) {
  433. return f.newClientFunc(ctx, store, kube, namespace)
  434. }
  435. func (f *WrapProvider) Capabilities() esv1.SecretStoreCapabilities {
  436. return esv1.SecretStoreReadOnly
  437. }
  438. // ValidateStore checks if the provided store is valid.
  439. func (f *WrapProvider) ValidateStore(_ esv1.GenericStore) (admission.Warnings, error) {
  440. return nil, nil
  441. }
  442. type MockFakeClient struct {
  443. id string
  444. closeCalled bool
  445. }
  446. func (c *MockFakeClient) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1.PushSecretData) error {
  447. return nil
  448. }
  449. func (c *MockFakeClient) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
  450. return nil
  451. }
  452. func (c *MockFakeClient) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  453. return false, nil
  454. }
  455. func (c *MockFakeClient) GetSecret(_ context.Context, _ esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  456. return nil, nil
  457. }
  458. func (c *MockFakeClient) Validate() (esv1.ValidationResult, error) {
  459. return esv1.ValidationResultReady, nil
  460. }
  461. // GetSecretMap returns multiple k/v pairs from the provider.
  462. func (c *MockFakeClient) GetSecretMap(_ context.Context, _ esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  463. return nil, nil
  464. }
  465. // GetAllSecrets returns multiple k/v pairs from the provider.
  466. func (c *MockFakeClient) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {
  467. return nil, nil
  468. }
  469. func (c *MockFakeClient) Close(_ context.Context) error {
  470. c.closeCalled = true
  471. return nil
  472. }