client_manager_test.go 13 KB

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