pushsecret_controller_test.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  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 pushsecret
  13. import (
  14. "bytes"
  15. "context"
  16. "fmt"
  17. "os"
  18. "strconv"
  19. "time"
  20. v1 "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. "sigs.k8s.io/controller-runtime/pkg/client"
  24. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  25. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  26. ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
  27. "github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/psmetrics"
  28. "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
  29. . "github.com/onsi/ginkgo/v2"
  30. . "github.com/onsi/gomega"
  31. )
  32. var (
  33. fakeProvider *fake.Client
  34. timeout = time.Second * 10
  35. interval = time.Millisecond * 250
  36. )
  37. type testCase struct {
  38. store v1beta1.GenericStore
  39. pushsecret *v1alpha1.PushSecret
  40. secret *v1.Secret
  41. assert func(pushsecret *v1alpha1.PushSecret, secret *v1.Secret) bool
  42. }
  43. func init() {
  44. fakeProvider = fake.New()
  45. v1beta1.ForceRegister(fakeProvider, &v1beta1.SecretStoreProvider{
  46. Fake: &v1beta1.FakeProvider{},
  47. })
  48. psmetrics.SetUpMetrics()
  49. }
  50. func checkCondition(status v1alpha1.PushSecretStatus, cond v1alpha1.PushSecretStatusCondition) bool {
  51. for _, condition := range status.Conditions {
  52. if condition.Message == cond.Message &&
  53. condition.Reason == cond.Reason &&
  54. condition.Status == cond.Status &&
  55. condition.Type == cond.Type {
  56. return true
  57. }
  58. }
  59. return false
  60. }
  61. type testTweaks func(*testCase)
  62. var _ = Describe("ExternalSecret controller", func() {
  63. const (
  64. PushSecretName = "test-es"
  65. PushSecretStore = "test-store"
  66. SecretName = "test-secret"
  67. )
  68. var PushSecretNamespace string
  69. // if we are in debug and need to increase the timeout for testing, we can do so by using an env var
  70. if customTimeout := os.Getenv("TEST_CUSTOM_TIMEOUT_SEC"); customTimeout != "" {
  71. if t, err := strconv.Atoi(customTimeout); err == nil {
  72. timeout = time.Second * time.Duration(t)
  73. }
  74. }
  75. BeforeEach(func() {
  76. var err error
  77. PushSecretNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
  78. Expect(err).ToNot(HaveOccurred())
  79. fakeProvider.Reset()
  80. })
  81. AfterEach(func() {
  82. k8sClient.Delete(context.Background(), &v1alpha1.PushSecret{
  83. ObjectMeta: metav1.ObjectMeta{
  84. Name: PushSecretName,
  85. Namespace: PushSecretNamespace,
  86. },
  87. })
  88. // give a time for reconciler to remove finalizers before removing SecretStores
  89. // TODO: Secret Stores should have finalizers bound to PushSecrets if DeletionPolicy == Delete
  90. time.Sleep(2 * time.Second)
  91. k8sClient.Delete(context.Background(), &v1beta1.SecretStore{
  92. ObjectMeta: metav1.ObjectMeta{
  93. Name: PushSecretStore,
  94. Namespace: PushSecretNamespace,
  95. },
  96. })
  97. k8sClient.Delete(context.Background(), &v1beta1.ClusterSecretStore{
  98. ObjectMeta: metav1.ObjectMeta{
  99. Name: PushSecretStore,
  100. },
  101. })
  102. k8sClient.Delete(context.Background(), &v1.Secret{
  103. ObjectMeta: metav1.ObjectMeta{
  104. Name: SecretName,
  105. Namespace: PushSecretNamespace,
  106. },
  107. })
  108. Expect(k8sClient.Delete(context.Background(), &v1.Namespace{
  109. ObjectMeta: metav1.ObjectMeta{
  110. Name: PushSecretNamespace,
  111. },
  112. })).To(Succeed())
  113. })
  114. const (
  115. defaultKey = "key"
  116. defaultVal = "value"
  117. defaultPath = "path/to/key"
  118. otherKey = "other-key"
  119. otherVal = "other-value"
  120. otherPath = "path/to/other-key"
  121. newKey = "new-key"
  122. newVal = "new-value"
  123. storePrefixTemplate = "SecretStore/%v"
  124. )
  125. makeDefaultTestcase := func() *testCase {
  126. return &testCase{
  127. pushsecret: &v1alpha1.PushSecret{
  128. ObjectMeta: metav1.ObjectMeta{
  129. Name: PushSecretName,
  130. Namespace: PushSecretNamespace,
  131. },
  132. Spec: v1alpha1.PushSecretSpec{
  133. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  134. {
  135. Name: PushSecretStore,
  136. Kind: "SecretStore",
  137. },
  138. },
  139. Selector: v1alpha1.PushSecretSelector{
  140. Secret: v1alpha1.PushSecretSecret{
  141. Name: SecretName,
  142. },
  143. },
  144. Data: []v1alpha1.PushSecretData{
  145. {
  146. Match: v1alpha1.PushSecretMatch{
  147. SecretKey: defaultKey,
  148. RemoteRef: v1alpha1.PushSecretRemoteRef{
  149. RemoteKey: defaultPath,
  150. },
  151. },
  152. },
  153. },
  154. },
  155. },
  156. secret: &v1.Secret{
  157. ObjectMeta: metav1.ObjectMeta{
  158. Name: SecretName,
  159. Namespace: PushSecretNamespace,
  160. },
  161. Data: map[string][]byte{
  162. defaultKey: []byte(defaultVal),
  163. },
  164. },
  165. store: &v1beta1.SecretStore{
  166. ObjectMeta: metav1.ObjectMeta{
  167. Name: PushSecretStore,
  168. Namespace: PushSecretNamespace,
  169. },
  170. TypeMeta: metav1.TypeMeta{
  171. Kind: "SecretStore",
  172. },
  173. Spec: v1beta1.SecretStoreSpec{
  174. Provider: &v1beta1.SecretStoreProvider{
  175. Fake: &v1beta1.FakeProvider{
  176. Data: []v1beta1.FakeProviderData{},
  177. },
  178. },
  179. },
  180. },
  181. }
  182. }
  183. // if target Secret name is not specified it should use the ExternalSecret name.
  184. syncSuccessfully := func(tc *testCase) {
  185. fakeProvider.SetSecretFn = func() error {
  186. return nil
  187. }
  188. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  189. Eventually(func() bool {
  190. By("checking if Provider value got updated")
  191. secretValue := secret.Data[defaultKey]
  192. providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
  193. if !ok {
  194. return false
  195. }
  196. got := providerValue.Value
  197. return bytes.Equal(got, secretValue)
  198. }, time.Second*10, time.Second).Should(BeTrue())
  199. return true
  200. }
  201. }
  202. updateIfNotExists := func(tc *testCase) {
  203. fakeProvider.SetSecretFn = func() error {
  204. return nil
  205. }
  206. fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
  207. _, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
  208. return ok, nil
  209. }
  210. tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
  211. initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
  212. tc.secret.Data[defaultKey] = []byte(newVal)
  213. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  214. Eventually(func() bool {
  215. By("checking if Provider value did not get updated")
  216. Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
  217. providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
  218. if !ok {
  219. return false
  220. }
  221. got := providerValue.Value
  222. return bytes.Equal(got, initialValue)
  223. }, time.Second*10, time.Second).Should(BeTrue())
  224. return true
  225. }
  226. }
  227. updateIfNotExistsPartialSecrets := func(tc *testCase) {
  228. fakeProvider.SetSecretFn = func() error {
  229. return nil
  230. }
  231. fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
  232. _, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
  233. return ok, nil
  234. }
  235. tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
  236. tc.pushsecret.Spec.Data = append(tc.pushsecret.Spec.Data, v1alpha1.PushSecretData{
  237. Match: v1alpha1.PushSecretMatch{
  238. SecretKey: otherKey,
  239. RemoteRef: v1alpha1.PushSecretRemoteRef{
  240. RemoteKey: otherPath,
  241. },
  242. },
  243. })
  244. initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
  245. tc.secret.Data[defaultKey] = []byte(newVal) // change initial value in secret
  246. tc.secret.Data[otherKey] = []byte(otherVal)
  247. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  248. Eventually(func() bool {
  249. By("checking if only not existing Provider value got updated")
  250. Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
  251. providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
  252. if !ok {
  253. return false
  254. }
  255. got := providerValue.Value
  256. otherProviderValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[1].Match.RemoteRef.RemoteKey]
  257. if !ok {
  258. return false
  259. }
  260. gotOther := otherProviderValue.Value
  261. return bytes.Equal(gotOther, tc.secret.Data[otherKey]) && bytes.Equal(got, initialValue)
  262. }, time.Second*10, time.Second).Should(BeTrue())
  263. return true
  264. }
  265. }
  266. updateIfNotExistsSyncStatus := func(tc *testCase) {
  267. fakeProvider.SetSecretFn = func() error {
  268. return nil
  269. }
  270. fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
  271. _, ok := fakeProvider.SetSecretArgs[ref.GetRemoteKey()]
  272. return ok, nil
  273. }
  274. tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
  275. tc.pushsecret.Spec.Data = append(tc.pushsecret.Spec.Data, v1alpha1.PushSecretData{
  276. Match: v1alpha1.PushSecretMatch{
  277. SecretKey: otherKey,
  278. RemoteRef: v1alpha1.PushSecretRemoteRef{
  279. RemoteKey: otherPath,
  280. },
  281. },
  282. })
  283. tc.secret.Data[defaultKey] = []byte(newVal)
  284. tc.secret.Data[otherKey] = []byte(otherVal)
  285. updatedPS := &v1alpha1.PushSecret{}
  286. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  287. Eventually(func() bool {
  288. By("checking if PushSecret status gets updated correctly with UpdatePolicy=IfNotExists")
  289. Expect(k8sClient.Update(context.Background(), secret, &client.UpdateOptions{})).Should(Succeed())
  290. psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
  291. err := k8sClient.Get(context.Background(), psKey, updatedPS)
  292. if err != nil {
  293. return false
  294. }
  295. _, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][defaultPath]
  296. if !ok {
  297. return false
  298. }
  299. _, ok = updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][otherPath]
  300. if !ok {
  301. return false
  302. }
  303. expected := v1alpha1.PushSecretStatusCondition{
  304. Type: v1alpha1.PushSecretReady,
  305. Status: v1.ConditionTrue,
  306. Reason: v1alpha1.ReasonSynced,
  307. Message: "PushSecret synced successfully. Existing secrets in providers unchanged.",
  308. }
  309. return checkCondition(ps.Status, expected)
  310. }, time.Second*10, time.Second).Should(BeTrue())
  311. return true
  312. }
  313. }
  314. updateIfNotExistsSyncFailed := func(tc *testCase) {
  315. fakeProvider.SetSecretFn = func() error {
  316. return nil
  317. }
  318. fakeProvider.SecretExistsFn = func(ctx context.Context, ref v1beta1.PushSecretRemoteRef) (bool, error) {
  319. return false, fmt.Errorf("don't know")
  320. }
  321. tc.pushsecret.Spec.UpdatePolicy = v1alpha1.PushSecretUpdatePolicyIfNotExists
  322. initialValue := fakeProvider.SetSecretArgs[tc.pushsecret.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
  323. tc.secret.Data[defaultKey] = []byte(newVal)
  324. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  325. Eventually(func() bool {
  326. By("checking if sync failed if secret existence cannot be verified in Provider")
  327. providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
  328. if !ok {
  329. return false
  330. }
  331. got := providerValue.Value
  332. expected := v1alpha1.PushSecretStatusCondition{
  333. Type: v1alpha1.PushSecretReady,
  334. Status: v1.ConditionFalse,
  335. Reason: v1alpha1.ReasonErrored,
  336. Message: "set secret failed: could not verify if secret exists in store: don't know",
  337. }
  338. return checkCondition(ps.Status, expected) && bytes.Equal(got, initialValue)
  339. }, time.Second*10, time.Second).Should(BeTrue())
  340. return true
  341. }
  342. }
  343. // if target Secret name is not specified it should use the ExternalSecret name.
  344. syncSuccessfullyWithTemplate := func(tc *testCase) {
  345. fakeProvider.SetSecretFn = func() error {
  346. return nil
  347. }
  348. tc.pushsecret = &v1alpha1.PushSecret{
  349. ObjectMeta: metav1.ObjectMeta{
  350. Name: PushSecretName,
  351. Namespace: PushSecretNamespace,
  352. },
  353. Spec: v1alpha1.PushSecretSpec{
  354. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  355. {
  356. Name: PushSecretStore,
  357. Kind: "SecretStore",
  358. },
  359. },
  360. Selector: v1alpha1.PushSecretSelector{
  361. Secret: v1alpha1.PushSecretSecret{
  362. Name: SecretName,
  363. },
  364. },
  365. Data: []v1alpha1.PushSecretData{
  366. {
  367. Match: v1alpha1.PushSecretMatch{
  368. SecretKey: defaultKey,
  369. RemoteRef: v1alpha1.PushSecretRemoteRef{
  370. RemoteKey: defaultPath,
  371. },
  372. },
  373. },
  374. },
  375. Template: &v1beta1.ExternalSecretTemplate{
  376. Metadata: v1beta1.ExternalSecretTemplateMetadata{
  377. Labels: map[string]string{
  378. "foos": "ball",
  379. },
  380. Annotations: map[string]string{
  381. "hihi": "ga",
  382. },
  383. },
  384. Type: v1.SecretTypeOpaque,
  385. EngineVersion: v1beta1.TemplateEngineV2,
  386. Data: map[string]string{
  387. defaultKey: "{{ .key | toString | upper }} was templated",
  388. },
  389. },
  390. },
  391. }
  392. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  393. Eventually(func() bool {
  394. By("checking if Provider value got updated")
  395. providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
  396. if !ok {
  397. return false
  398. }
  399. got := providerValue.Value
  400. return bytes.Equal(got, []byte("VALUE was templated"))
  401. }, time.Second*10, time.Second).Should(BeTrue())
  402. return true
  403. }
  404. }
  405. // if target Secret name is not specified it should use the ExternalSecret name.
  406. syncAndDeleteSuccessfully := func(tc *testCase) {
  407. fakeProvider.SetSecretFn = func() error {
  408. return nil
  409. }
  410. tc.pushsecret = &v1alpha1.PushSecret{
  411. ObjectMeta: metav1.ObjectMeta{
  412. Name: PushSecretName,
  413. Namespace: PushSecretNamespace,
  414. },
  415. Spec: v1alpha1.PushSecretSpec{
  416. DeletionPolicy: v1alpha1.PushSecretDeletionPolicyDelete,
  417. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  418. {
  419. Name: PushSecretStore,
  420. Kind: "SecretStore",
  421. },
  422. },
  423. Selector: v1alpha1.PushSecretSelector{
  424. Secret: v1alpha1.PushSecretSecret{
  425. Name: SecretName,
  426. },
  427. },
  428. Data: []v1alpha1.PushSecretData{
  429. {
  430. Match: v1alpha1.PushSecretMatch{
  431. SecretKey: defaultKey,
  432. RemoteRef: v1alpha1.PushSecretRemoteRef{
  433. RemoteKey: defaultPath,
  434. },
  435. },
  436. },
  437. },
  438. },
  439. }
  440. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  441. ps.Spec.Data[0].Match.RemoteRef.RemoteKey = newKey
  442. updatedPS := &v1alpha1.PushSecret{}
  443. Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
  444. Eventually(func() bool {
  445. psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
  446. By("checking if Provider value got updated")
  447. err := k8sClient.Get(context.Background(), psKey, updatedPS)
  448. if err != nil {
  449. return false
  450. }
  451. key, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][newKey]
  452. if !ok {
  453. return false
  454. }
  455. return key.Match.SecretKey == defaultKey
  456. }, time.Second*10, time.Second).Should(BeTrue())
  457. return true
  458. }
  459. }
  460. failDelete := func(tc *testCase) {
  461. fakeProvider.SetSecretFn = func() error {
  462. return nil
  463. }
  464. fakeProvider.DeleteSecretFn = func() error {
  465. return fmt.Errorf("Nope")
  466. }
  467. tc.pushsecret = &v1alpha1.PushSecret{
  468. ObjectMeta: metav1.ObjectMeta{
  469. Name: PushSecretName,
  470. Namespace: PushSecretNamespace,
  471. },
  472. Spec: v1alpha1.PushSecretSpec{
  473. DeletionPolicy: v1alpha1.PushSecretDeletionPolicyDelete,
  474. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  475. {
  476. Name: PushSecretStore,
  477. Kind: "SecretStore",
  478. },
  479. },
  480. Selector: v1alpha1.PushSecretSelector{
  481. Secret: v1alpha1.PushSecretSecret{
  482. Name: SecretName,
  483. },
  484. },
  485. Data: []v1alpha1.PushSecretData{
  486. {
  487. Match: v1alpha1.PushSecretMatch{
  488. SecretKey: defaultKey,
  489. RemoteRef: v1alpha1.PushSecretRemoteRef{
  490. RemoteKey: defaultPath,
  491. },
  492. },
  493. },
  494. },
  495. },
  496. }
  497. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  498. ps.Spec.Data[0].Match.RemoteRef.RemoteKey = newKey
  499. updatedPS := &v1alpha1.PushSecret{}
  500. Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
  501. Eventually(func() bool {
  502. psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
  503. By("checking if synced secrets correspond to both keys")
  504. err := k8sClient.Get(context.Background(), psKey, updatedPS)
  505. if err != nil {
  506. return false
  507. }
  508. _, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][newKey]
  509. if !ok {
  510. return false
  511. }
  512. _, ok = updatedPS.Status.SyncedPushSecrets[fmt.Sprintf(storePrefixTemplate, PushSecretStore)][defaultPath]
  513. return ok
  514. }, time.Second*10, time.Second).Should(BeTrue())
  515. return true
  516. }
  517. }
  518. failDeleteStore := func(tc *testCase) {
  519. fakeProvider.SetSecretFn = func() error {
  520. return nil
  521. }
  522. fakeProvider.DeleteSecretFn = func() error {
  523. return fmt.Errorf("boom")
  524. }
  525. tc.pushsecret.Spec.DeletionPolicy = v1alpha1.PushSecretDeletionPolicyDelete
  526. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  527. secondStore := &v1beta1.SecretStore{
  528. ObjectMeta: metav1.ObjectMeta{
  529. Name: "new-store",
  530. Namespace: PushSecretNamespace,
  531. },
  532. TypeMeta: metav1.TypeMeta{
  533. Kind: "SecretStore",
  534. },
  535. Spec: v1beta1.SecretStoreSpec{
  536. Provider: &v1beta1.SecretStoreProvider{
  537. Fake: &v1beta1.FakeProvider{
  538. Data: []v1beta1.FakeProviderData{},
  539. },
  540. },
  541. },
  542. }
  543. Expect(k8sClient.Create(context.Background(), secondStore, &client.CreateOptions{})).Should(Succeed())
  544. ps.Spec.SecretStoreRefs[0].Name = "new-store"
  545. updatedPS := &v1alpha1.PushSecret{}
  546. Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
  547. Eventually(func() bool {
  548. psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
  549. By("checking if Provider value got updated")
  550. err := k8sClient.Get(context.Background(), psKey, updatedPS)
  551. if err != nil {
  552. return false
  553. }
  554. syncedLen := len(updatedPS.Status.SyncedPushSecrets)
  555. return syncedLen == 2
  556. }, time.Second*10, time.Second).Should(BeTrue())
  557. return true
  558. }
  559. }
  560. deleteWholeStore := func(tc *testCase) {
  561. fakeProvider.SetSecretFn = func() error {
  562. return nil
  563. }
  564. fakeProvider.DeleteSecretFn = func() error {
  565. return nil
  566. }
  567. tc.pushsecret.Spec.DeletionPolicy = v1alpha1.PushSecretDeletionPolicyDelete
  568. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  569. secondStore := &v1beta1.SecretStore{
  570. ObjectMeta: metav1.ObjectMeta{
  571. Name: "new-store",
  572. Namespace: PushSecretNamespace,
  573. },
  574. TypeMeta: metav1.TypeMeta{
  575. Kind: "SecretStore",
  576. },
  577. Spec: v1beta1.SecretStoreSpec{
  578. Provider: &v1beta1.SecretStoreProvider{
  579. Fake: &v1beta1.FakeProvider{
  580. Data: []v1beta1.FakeProviderData{},
  581. },
  582. },
  583. },
  584. }
  585. Expect(k8sClient.Create(context.Background(), secondStore, &client.CreateOptions{})).Should(Succeed())
  586. ps.Spec.SecretStoreRefs[0].Name = "new-store"
  587. updatedPS := &v1alpha1.PushSecret{}
  588. Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
  589. Eventually(func() bool {
  590. psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
  591. By("checking if Provider value got updated")
  592. err := k8sClient.Get(context.Background(), psKey, updatedPS)
  593. if err != nil {
  594. return false
  595. }
  596. key, ok := updatedPS.Status.SyncedPushSecrets["SecretStore/new-store"][defaultPath]
  597. if !ok {
  598. return false
  599. }
  600. syncedLen := len(updatedPS.Status.SyncedPushSecrets)
  601. if syncedLen != 1 {
  602. return false
  603. }
  604. return key.Match.SecretKey == defaultKey
  605. }, time.Second*10, time.Second).Should(BeTrue())
  606. return true
  607. }
  608. }
  609. // if conversion strategy is defined, revert the keys based on the strategy.
  610. syncSuccessfullyWithConversionStrategy := func(tc *testCase) {
  611. fakeProvider.SetSecretFn = func() error {
  612. return nil
  613. }
  614. tc.pushsecret = &v1alpha1.PushSecret{
  615. ObjectMeta: metav1.ObjectMeta{
  616. Name: PushSecretName,
  617. Namespace: PushSecretNamespace,
  618. },
  619. Spec: v1alpha1.PushSecretSpec{
  620. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  621. {
  622. Name: PushSecretStore,
  623. Kind: "SecretStore",
  624. },
  625. },
  626. Selector: v1alpha1.PushSecretSelector{
  627. Secret: v1alpha1.PushSecretSecret{
  628. Name: SecretName,
  629. },
  630. },
  631. Data: []v1alpha1.PushSecretData{
  632. {
  633. ConversionStrategy: v1alpha1.PushSecretConversionReverseUnicode,
  634. Match: v1alpha1.PushSecretMatch{
  635. SecretKey: "some-array[0].entity",
  636. RemoteRef: v1alpha1.PushSecretRemoteRef{
  637. RemoteKey: "path/to/key",
  638. },
  639. },
  640. },
  641. },
  642. },
  643. }
  644. tc.secret = &v1.Secret{
  645. ObjectMeta: metav1.ObjectMeta{
  646. Name: SecretName,
  647. Namespace: PushSecretNamespace,
  648. },
  649. Data: map[string][]byte{
  650. "some-array_U005b_0_U005d_.entity": []byte("value"),
  651. },
  652. }
  653. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  654. Eventually(func() bool {
  655. By("checking if Provider value got updated")
  656. secretValue := secret.Data["some-array_U005b_0_U005d_.entity"]
  657. providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
  658. if !ok {
  659. return false
  660. }
  661. got := providerValue.Value
  662. return bytes.Equal(got, secretValue)
  663. }, time.Second*10, time.Second).Should(BeTrue())
  664. return true
  665. }
  666. }
  667. // if target Secret name is not specified it should use the ExternalSecret name.
  668. syncMatchingLabels := func(tc *testCase) {
  669. fakeProvider.SetSecretFn = func() error {
  670. return nil
  671. }
  672. fakeProvider.DeleteSecretFn = func() error {
  673. return nil
  674. }
  675. tc.pushsecret = &v1alpha1.PushSecret{
  676. ObjectMeta: metav1.ObjectMeta{
  677. Name: PushSecretName,
  678. Namespace: PushSecretNamespace,
  679. },
  680. Spec: v1alpha1.PushSecretSpec{
  681. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  682. {
  683. LabelSelector: &metav1.LabelSelector{
  684. MatchLabels: map[string]string{
  685. "foo": "bar",
  686. },
  687. },
  688. Kind: "SecretStore",
  689. },
  690. },
  691. Selector: v1alpha1.PushSecretSelector{
  692. Secret: v1alpha1.PushSecretSecret{
  693. Name: SecretName,
  694. },
  695. },
  696. Data: []v1alpha1.PushSecretData{
  697. {
  698. Match: v1alpha1.PushSecretMatch{
  699. SecretKey: defaultKey,
  700. RemoteRef: v1alpha1.PushSecretRemoteRef{
  701. RemoteKey: defaultPath,
  702. },
  703. },
  704. },
  705. },
  706. },
  707. }
  708. tc.store = &v1beta1.SecretStore{
  709. TypeMeta: metav1.TypeMeta{
  710. Kind: "SecretStore",
  711. },
  712. ObjectMeta: metav1.ObjectMeta{
  713. Name: PushSecretStore,
  714. Namespace: PushSecretNamespace,
  715. Labels: map[string]string{
  716. "foo": "bar",
  717. },
  718. },
  719. Spec: v1beta1.SecretStoreSpec{
  720. Provider: &v1beta1.SecretStoreProvider{
  721. Fake: &v1beta1.FakeProvider{
  722. Data: []v1beta1.FakeProviderData{},
  723. },
  724. },
  725. },
  726. }
  727. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  728. secretValue := secret.Data[defaultKey]
  729. providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
  730. expected := v1alpha1.PushSecretStatusCondition{
  731. Type: v1alpha1.PushSecretReady,
  732. Status: v1.ConditionTrue,
  733. Reason: v1alpha1.ReasonSynced,
  734. Message: "PushSecret synced successfully",
  735. }
  736. return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
  737. }
  738. }
  739. syncWithClusterStore := func(tc *testCase) {
  740. fakeProvider.SetSecretFn = func() error {
  741. return nil
  742. }
  743. tc.store = &v1beta1.ClusterSecretStore{
  744. TypeMeta: metav1.TypeMeta{
  745. Kind: "ClusterSecretStore",
  746. },
  747. ObjectMeta: metav1.ObjectMeta{
  748. Name: PushSecretStore,
  749. },
  750. Spec: v1beta1.SecretStoreSpec{
  751. Provider: &v1beta1.SecretStoreProvider{
  752. Fake: &v1beta1.FakeProvider{
  753. Data: []v1beta1.FakeProviderData{},
  754. },
  755. },
  756. },
  757. }
  758. tc.pushsecret.Spec.SecretStoreRefs[0].Kind = "ClusterSecretStore"
  759. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  760. secretValue := secret.Data[defaultKey]
  761. providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
  762. expected := v1alpha1.PushSecretStatusCondition{
  763. Type: v1alpha1.PushSecretReady,
  764. Status: v1.ConditionTrue,
  765. Reason: v1alpha1.ReasonSynced,
  766. Message: "PushSecret synced successfully",
  767. }
  768. return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
  769. }
  770. }
  771. // if target Secret name is not specified it should use the ExternalSecret name.
  772. syncWithClusterStoreMatchingLabels := func(tc *testCase) {
  773. fakeProvider.SetSecretFn = func() error {
  774. return nil
  775. }
  776. tc.pushsecret = &v1alpha1.PushSecret{
  777. ObjectMeta: metav1.ObjectMeta{
  778. Name: PushSecretName,
  779. Namespace: PushSecretNamespace,
  780. },
  781. Spec: v1alpha1.PushSecretSpec{
  782. SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
  783. {
  784. LabelSelector: &metav1.LabelSelector{
  785. MatchLabels: map[string]string{
  786. "foo": "bar",
  787. },
  788. },
  789. Kind: "ClusterSecretStore",
  790. },
  791. },
  792. Selector: v1alpha1.PushSecretSelector{
  793. Secret: v1alpha1.PushSecretSecret{
  794. Name: SecretName,
  795. },
  796. },
  797. Data: []v1alpha1.PushSecretData{
  798. {
  799. Match: v1alpha1.PushSecretMatch{
  800. SecretKey: defaultKey,
  801. RemoteRef: v1alpha1.PushSecretRemoteRef{
  802. RemoteKey: defaultPath,
  803. },
  804. },
  805. },
  806. },
  807. },
  808. }
  809. tc.store = &v1beta1.ClusterSecretStore{
  810. ObjectMeta: metav1.ObjectMeta{
  811. Name: PushSecretStore,
  812. Labels: map[string]string{
  813. "foo": "bar",
  814. },
  815. },
  816. Spec: v1beta1.SecretStoreSpec{
  817. Provider: &v1beta1.SecretStoreProvider{
  818. Fake: &v1beta1.FakeProvider{
  819. Data: []v1beta1.FakeProviderData{},
  820. },
  821. },
  822. },
  823. }
  824. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  825. secretValue := secret.Data[defaultKey]
  826. providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
  827. expected := v1alpha1.PushSecretStatusCondition{
  828. Type: v1alpha1.PushSecretReady,
  829. Status: v1.ConditionTrue,
  830. Reason: v1alpha1.ReasonSynced,
  831. Message: "PushSecret synced successfully",
  832. }
  833. return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
  834. }
  835. }
  836. // if target Secret name is not specified it should use the ExternalSecret name.
  837. failNoSecret := func(tc *testCase) {
  838. fakeProvider.SetSecretFn = func() error {
  839. return nil
  840. }
  841. tc.secret = nil
  842. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  843. expected := v1alpha1.PushSecretStatusCondition{
  844. Type: v1alpha1.PushSecretReady,
  845. Status: v1.ConditionFalse,
  846. Reason: v1alpha1.ReasonErrored,
  847. Message: "could not get source secret",
  848. }
  849. return checkCondition(ps.Status, expected)
  850. }
  851. }
  852. // if target Secret name is not specified it should use the ExternalSecret name.
  853. failNoSecretKey := func(tc *testCase) {
  854. fakeProvider.SetSecretFn = func() error {
  855. return nil
  856. }
  857. tc.pushsecret.Spec.Data[0].Match.SecretKey = "unexisting"
  858. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  859. expected := v1alpha1.PushSecretStatusCondition{
  860. Type: v1alpha1.PushSecretReady,
  861. Status: v1.ConditionFalse,
  862. Reason: v1alpha1.ReasonErrored,
  863. Message: "set secret failed: secret key unexisting does not exist",
  864. }
  865. return checkCondition(ps.Status, expected)
  866. }
  867. }
  868. // if target Secret name is not specified it should use the ExternalSecret name.
  869. failNoSecretStore := func(tc *testCase) {
  870. fakeProvider.SetSecretFn = func() error {
  871. return nil
  872. }
  873. tc.store = nil
  874. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  875. expected := v1alpha1.PushSecretStatusCondition{
  876. Type: v1alpha1.PushSecretReady,
  877. Status: v1.ConditionFalse,
  878. Reason: v1alpha1.ReasonErrored,
  879. Message: "could not get SecretStore \"test-store\", secretstores.external-secrets.io \"test-store\" not found",
  880. }
  881. return checkCondition(ps.Status, expected)
  882. }
  883. }
  884. // if target Secret name is not specified it should use the ExternalSecret name.
  885. failNoClusterStore := func(tc *testCase) {
  886. fakeProvider.SetSecretFn = func() error {
  887. return nil
  888. }
  889. tc.store = nil
  890. tc.pushsecret.Spec.SecretStoreRefs[0].Kind = "ClusterSecretStore"
  891. tc.pushsecret.Spec.SecretStoreRefs[0].Name = "unexisting"
  892. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  893. expected := v1alpha1.PushSecretStatusCondition{
  894. Type: v1alpha1.PushSecretReady,
  895. Status: v1.ConditionFalse,
  896. Reason: v1alpha1.ReasonErrored,
  897. Message: "could not get ClusterSecretStore \"unexisting\", clustersecretstores.external-secrets.io \"unexisting\" not found",
  898. }
  899. return checkCondition(ps.Status, expected)
  900. }
  901. } // if target Secret name is not specified it should use the ExternalSecret name.
  902. setSecretFail := func(tc *testCase) {
  903. fakeProvider.SetSecretFn = func() error {
  904. return fmt.Errorf("boom")
  905. }
  906. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  907. expected := v1alpha1.PushSecretStatusCondition{
  908. Type: v1alpha1.PushSecretReady,
  909. Status: v1.ConditionFalse,
  910. Reason: v1alpha1.ReasonErrored,
  911. Message: "set secret failed: could not write remote ref key to target secretstore test-store: boom",
  912. }
  913. return checkCondition(ps.Status, expected)
  914. }
  915. }
  916. // if target Secret name is not specified it should use the ExternalSecret name.
  917. newClientFail := func(tc *testCase) {
  918. fakeProvider.NewFn = func(context.Context, v1beta1.GenericStore, client.Client, string) (v1beta1.SecretsClient, error) {
  919. return nil, fmt.Errorf("boom")
  920. }
  921. tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
  922. expected := v1alpha1.PushSecretStatusCondition{
  923. Type: v1alpha1.PushSecretReady,
  924. Status: v1.ConditionFalse,
  925. Reason: v1alpha1.ReasonErrored,
  926. Message: "set secret failed: could not get secrets client for store test-store: boom",
  927. }
  928. return checkCondition(ps.Status, expected)
  929. }
  930. }
  931. DescribeTable("When reconciling a PushSecret",
  932. func(tweaks ...testTweaks) {
  933. tc := makeDefaultTestcase()
  934. for _, tweak := range tweaks {
  935. tweak(tc)
  936. }
  937. ctx := context.Background()
  938. By("creating a secret store, secret and pushsecret")
  939. if tc.store != nil {
  940. Expect(k8sClient.Create(ctx, tc.store)).To(Succeed())
  941. }
  942. if tc.secret != nil {
  943. Expect(k8sClient.Create(ctx, tc.secret)).To(Succeed())
  944. }
  945. if tc.pushsecret != nil {
  946. Expect(k8sClient.Create(ctx, tc.pushsecret)).Should(Succeed())
  947. }
  948. time.Sleep(2 * time.Second)
  949. psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
  950. createdPS := &v1alpha1.PushSecret{}
  951. By("checking the pushSecret condition")
  952. Eventually(func() bool {
  953. err := k8sClient.Get(ctx, psKey, createdPS)
  954. if err != nil {
  955. return false
  956. }
  957. return tc.assert(createdPS, tc.secret)
  958. }, timeout, interval).Should(BeTrue())
  959. // this must be optional so we can test faulty es configuration
  960. },
  961. Entry("should sync", syncSuccessfully),
  962. Entry("should not update existing secret if UpdatePolicy=IfNotExists", updateIfNotExists),
  963. Entry("should only update parts of secret that don't already exist if UpdatePolicy=IfNotExists", updateIfNotExistsPartialSecrets),
  964. Entry("should update the PushSecret status correctly if UpdatePolicy=IfNotExists", updateIfNotExistsSyncStatus),
  965. Entry("should fail if secret existence cannot be verified if UpdatePolicy=IfNotExists", updateIfNotExistsSyncFailed),
  966. Entry("should sync with template", syncSuccessfullyWithTemplate),
  967. Entry("should sync with conversion strategy", syncSuccessfullyWithConversionStrategy),
  968. Entry("should delete if DeletionPolicy=Delete", syncAndDeleteSuccessfully),
  969. Entry("should track deletion tasks if Delete fails", failDelete),
  970. Entry("should track deleted stores if Delete fails", failDeleteStore),
  971. Entry("should delete all secrets if SecretStore changes", deleteWholeStore),
  972. Entry("should sync to stores matching labels", syncMatchingLabels),
  973. Entry("should sync with ClusterStore", syncWithClusterStore),
  974. Entry("should sync with ClusterStore matching labels", syncWithClusterStoreMatchingLabels),
  975. Entry("should fail if Secret is not created", failNoSecret),
  976. Entry("should fail if Secret Key does not exist", failNoSecretKey),
  977. Entry("should fail if SetSecret fails", setSecretFail),
  978. Entry("should fail if no valid SecretStore", failNoSecretStore),
  979. Entry("should fail if no valid ClusterSecretStore", failNoClusterStore),
  980. Entry("should fail if NewClient fails", newClientFail),
  981. )
  982. })