externalsecret_controller_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  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 externalsecret
  13. import (
  14. "context"
  15. "fmt"
  16. "time"
  17. . "github.com/onsi/ginkgo"
  18. . "github.com/onsi/gomega"
  19. v1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/types"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. "sigs.k8s.io/controller-runtime/pkg/client"
  24. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  25. "github.com/external-secrets/external-secrets/pkg/provider"
  26. "github.com/external-secrets/external-secrets/pkg/provider/fake"
  27. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  28. )
  29. var fakeProvider *fake.Client
  30. var _ = Describe("ExternalSecret controller", func() {
  31. const (
  32. ExternalSecretName = "test-es"
  33. ExternalSecretStore = "test-store"
  34. ExternalSecretTargetSecretName = "test-secret"
  35. timeout = time.Second * 5
  36. interval = time.Millisecond * 250
  37. )
  38. var ExternalSecretNamespace string
  39. BeforeEach(func() {
  40. var err error
  41. ExternalSecretNamespace, err = CreateNamespace("test-ns", k8sClient)
  42. Expect(err).ToNot(HaveOccurred())
  43. Expect(k8sClient.Create(context.Background(), &esv1alpha1.SecretStore{
  44. ObjectMeta: metav1.ObjectMeta{
  45. Name: ExternalSecretStore,
  46. Namespace: ExternalSecretNamespace,
  47. },
  48. Spec: esv1alpha1.SecretStoreSpec{
  49. Provider: &esv1alpha1.SecretStoreProvider{
  50. AWS: &esv1alpha1.AWSProvider{
  51. Service: esv1alpha1.AWSServiceSecretsManager,
  52. },
  53. },
  54. },
  55. })).To(Succeed())
  56. })
  57. AfterEach(func() {
  58. Expect(k8sClient.Delete(context.Background(), &v1.Namespace{
  59. ObjectMeta: metav1.ObjectMeta{
  60. Name: ExternalSecretNamespace,
  61. },
  62. }, client.PropagationPolicy(metav1.DeletePropagationBackground)), client.GracePeriodSeconds(0)).To(Succeed())
  63. Expect(k8sClient.Delete(context.Background(), &esv1alpha1.SecretStore{
  64. ObjectMeta: metav1.ObjectMeta{
  65. Name: ExternalSecretStore,
  66. Namespace: ExternalSecretNamespace,
  67. },
  68. }, client.PropagationPolicy(metav1.DeletePropagationBackground)), client.GracePeriodSeconds(0)).To(Succeed())
  69. })
  70. Context("When updating ExternalSecret Status", func() {
  71. It("should set the condition eventually", func() {
  72. By("creating an ExternalSecret")
  73. ctx := context.Background()
  74. es := &esv1alpha1.ExternalSecret{
  75. ObjectMeta: metav1.ObjectMeta{
  76. Name: ExternalSecretName,
  77. Namespace: ExternalSecretNamespace,
  78. },
  79. Spec: esv1alpha1.ExternalSecretSpec{
  80. SecretStoreRef: esv1alpha1.SecretStoreRef{
  81. Name: ExternalSecretStore,
  82. },
  83. Target: esv1alpha1.ExternalSecretTarget{
  84. Name: ExternalSecretTargetSecretName,
  85. },
  86. },
  87. }
  88. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  89. esLookupKey := types.NamespacedName{Name: ExternalSecretName, Namespace: ExternalSecretNamespace}
  90. createdES := &esv1alpha1.ExternalSecret{}
  91. Eventually(func() bool {
  92. err := k8sClient.Get(ctx, esLookupKey, createdES)
  93. if err != nil {
  94. return false
  95. }
  96. cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
  97. if cond == nil || cond.Status != v1.ConditionTrue {
  98. return false
  99. }
  100. return true
  101. }, timeout, interval).Should(BeTrue())
  102. })
  103. })
  104. Context("When syncing ExternalSecret value", func() {
  105. It("should set the secret value and sync labels/annotations", func() {
  106. By("creating an ExternalSecret")
  107. ctx := context.Background()
  108. const targetProp = "targetProperty"
  109. const secretVal = "someValue"
  110. es := &esv1alpha1.ExternalSecret{
  111. ObjectMeta: metav1.ObjectMeta{
  112. Name: ExternalSecretName,
  113. Namespace: ExternalSecretNamespace,
  114. Labels: map[string]string{
  115. "fooobar": "bazz",
  116. "bazzing": "booze",
  117. },
  118. Annotations: map[string]string{
  119. "hihihih": "hehehe",
  120. "harharhra": "yallayalla",
  121. },
  122. },
  123. Spec: esv1alpha1.ExternalSecretSpec{
  124. SecretStoreRef: esv1alpha1.SecretStoreRef{
  125. Name: ExternalSecretStore,
  126. },
  127. Target: esv1alpha1.ExternalSecretTarget{
  128. Name: ExternalSecretTargetSecretName,
  129. },
  130. Data: []esv1alpha1.ExternalSecretData{
  131. {
  132. SecretKey: targetProp,
  133. RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
  134. Key: "barz",
  135. Property: "bang",
  136. },
  137. },
  138. },
  139. },
  140. }
  141. fakeProvider.WithGetSecret([]byte(secretVal), nil)
  142. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  143. secretLookupKey := types.NamespacedName{
  144. Name: ExternalSecretTargetSecretName,
  145. Namespace: ExternalSecretNamespace}
  146. syncedSecret := &v1.Secret{}
  147. Eventually(func() bool {
  148. err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
  149. if err != nil {
  150. return false
  151. }
  152. v := syncedSecret.Data[targetProp]
  153. return string(v) == secretVal
  154. }, timeout, interval).Should(BeTrue())
  155. Expect(syncedSecret.ObjectMeta.Labels).To(BeEquivalentTo(es.ObjectMeta.Labels))
  156. Expect(syncedSecret.ObjectMeta.Annotations).To(BeEquivalentTo(es.ObjectMeta.Annotations))
  157. })
  158. It("should refresh secret value", func() {
  159. By("creating an ExternalSecret")
  160. ctx := context.Background()
  161. const targetProp = "targetProperty"
  162. const secretVal = "someValue"
  163. es := &esv1alpha1.ExternalSecret{
  164. ObjectMeta: metav1.ObjectMeta{
  165. Name: ExternalSecretName,
  166. Namespace: ExternalSecretNamespace,
  167. },
  168. Spec: esv1alpha1.ExternalSecretSpec{
  169. RefreshInterval: &metav1.Duration{Duration: time.Second},
  170. SecretStoreRef: esv1alpha1.SecretStoreRef{
  171. Name: ExternalSecretStore,
  172. },
  173. Target: esv1alpha1.ExternalSecretTarget{
  174. Name: ExternalSecretTargetSecretName,
  175. },
  176. Data: []esv1alpha1.ExternalSecretData{
  177. {
  178. SecretKey: targetProp,
  179. RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
  180. Key: "barz",
  181. },
  182. },
  183. },
  184. },
  185. }
  186. fakeProvider.WithGetSecret([]byte(secretVal), nil)
  187. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  188. secretLookupKey := types.NamespacedName{
  189. Name: ExternalSecretTargetSecretName,
  190. Namespace: ExternalSecretNamespace}
  191. syncedSecret := &v1.Secret{}
  192. Eventually(func() bool {
  193. err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
  194. if err != nil {
  195. return false
  196. }
  197. v := syncedSecret.Data[targetProp]
  198. return string(v) == secretVal
  199. }, timeout, interval).Should(BeTrue())
  200. newValue := "NEW VALUE"
  201. fakeProvider.WithGetSecret([]byte(newValue), nil)
  202. Eventually(func() bool {
  203. err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
  204. if err != nil {
  205. return false
  206. }
  207. v := syncedSecret.Data[targetProp]
  208. return string(v) == newValue
  209. }, timeout, interval).Should(BeTrue())
  210. })
  211. It("should fetch secrets using dataFrom", func() {
  212. By("creating an ExternalSecret")
  213. ctx := context.Background()
  214. const secretVal = "someValue"
  215. es := &esv1alpha1.ExternalSecret{
  216. ObjectMeta: metav1.ObjectMeta{
  217. Name: ExternalSecretName,
  218. Namespace: ExternalSecretNamespace,
  219. },
  220. Spec: esv1alpha1.ExternalSecretSpec{
  221. SecretStoreRef: esv1alpha1.SecretStoreRef{
  222. Name: ExternalSecretStore,
  223. },
  224. Target: esv1alpha1.ExternalSecretTarget{
  225. Name: ExternalSecretTargetSecretName,
  226. },
  227. DataFrom: []esv1alpha1.ExternalSecretDataRemoteRef{
  228. {
  229. Key: "barz",
  230. },
  231. },
  232. },
  233. }
  234. fakeProvider.WithGetSecretMap(map[string][]byte{
  235. "foo": []byte("bar"),
  236. "baz": []byte("bang"),
  237. }, nil)
  238. fakeProvider.WithGetSecret([]byte(secretVal), nil)
  239. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  240. secretLookupKey := types.NamespacedName{
  241. Name: ExternalSecretTargetSecretName,
  242. Namespace: ExternalSecretNamespace}
  243. syncedSecret := &v1.Secret{}
  244. Eventually(func() bool {
  245. err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
  246. if err != nil {
  247. return false
  248. }
  249. x := syncedSecret.Data["foo"]
  250. y := syncedSecret.Data["baz"]
  251. return string(x) == "bar" && string(y) == "bang"
  252. }, timeout, interval).Should(BeTrue())
  253. })
  254. It("should set an error condition when provider errors", func() {
  255. By("creating an ExternalSecret")
  256. ctx := context.Background()
  257. const targetProp = "targetProperty"
  258. es := &esv1alpha1.ExternalSecret{
  259. ObjectMeta: metav1.ObjectMeta{
  260. Name: ExternalSecretName,
  261. Namespace: ExternalSecretNamespace,
  262. },
  263. Spec: esv1alpha1.ExternalSecretSpec{
  264. SecretStoreRef: esv1alpha1.SecretStoreRef{
  265. Name: ExternalSecretStore,
  266. },
  267. Target: esv1alpha1.ExternalSecretTarget{
  268. Name: ExternalSecretTargetSecretName,
  269. },
  270. Data: []esv1alpha1.ExternalSecretData{
  271. {
  272. SecretKey: targetProp,
  273. RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
  274. Key: "barz",
  275. Property: "bang",
  276. },
  277. },
  278. },
  279. },
  280. }
  281. fakeProvider.WithGetSecret(nil, fmt.Errorf("artificial testing error"))
  282. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  283. esLookupKey := types.NamespacedName{
  284. Name: ExternalSecretName,
  285. Namespace: ExternalSecretNamespace}
  286. createdES := &esv1alpha1.ExternalSecret{}
  287. Eventually(func() bool {
  288. err := k8sClient.Get(ctx, esLookupKey, createdES)
  289. if err != nil {
  290. return false
  291. }
  292. // condition must be false
  293. cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
  294. if cond == nil || cond.Status != v1.ConditionFalse || cond.Reason != esv1alpha1.ConditionReasonSecretSyncedError {
  295. return false
  296. }
  297. return true
  298. }, timeout, interval).Should(BeTrue())
  299. })
  300. It("should set an error condition when store does not exist", func() {
  301. By("creating an ExternalSecret")
  302. ctx := context.Background()
  303. const targetProp = "targetProperty"
  304. es := &esv1alpha1.ExternalSecret{
  305. ObjectMeta: metav1.ObjectMeta{
  306. Name: ExternalSecretName,
  307. Namespace: ExternalSecretNamespace,
  308. },
  309. Spec: esv1alpha1.ExternalSecretSpec{
  310. SecretStoreRef: esv1alpha1.SecretStoreRef{
  311. Name: "storeshouldnotexist",
  312. },
  313. Target: esv1alpha1.ExternalSecretTarget{
  314. Name: ExternalSecretTargetSecretName,
  315. },
  316. Data: []esv1alpha1.ExternalSecretData{
  317. {
  318. SecretKey: targetProp,
  319. RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
  320. Key: "barz",
  321. Property: "bang",
  322. },
  323. },
  324. },
  325. },
  326. }
  327. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  328. esLookupKey := types.NamespacedName{
  329. Name: ExternalSecretName,
  330. Namespace: ExternalSecretNamespace}
  331. createdES := &esv1alpha1.ExternalSecret{}
  332. Eventually(func() bool {
  333. err := k8sClient.Get(ctx, esLookupKey, createdES)
  334. if err != nil {
  335. return false
  336. }
  337. // condition must be false
  338. cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
  339. if cond == nil || cond.Status != v1.ConditionFalse || cond.Reason != esv1alpha1.ConditionReasonSecretSyncedError {
  340. return false
  341. }
  342. return true
  343. }, timeout, interval).Should(BeTrue())
  344. })
  345. It("should set an error condition when store provider constructor fails", func() {
  346. By("creating an ExternalSecret")
  347. ctx := context.Background()
  348. const targetProp = "targetProperty"
  349. es := &esv1alpha1.ExternalSecret{
  350. ObjectMeta: metav1.ObjectMeta{
  351. Name: ExternalSecretName,
  352. Namespace: ExternalSecretNamespace,
  353. },
  354. Spec: esv1alpha1.ExternalSecretSpec{
  355. SecretStoreRef: esv1alpha1.SecretStoreRef{
  356. Name: ExternalSecretStore,
  357. },
  358. Target: esv1alpha1.ExternalSecretTarget{
  359. Name: ExternalSecretTargetSecretName,
  360. },
  361. Data: []esv1alpha1.ExternalSecretData{
  362. {
  363. SecretKey: targetProp,
  364. RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
  365. Key: "barz",
  366. Property: "bang",
  367. },
  368. },
  369. },
  370. },
  371. }
  372. fakeProvider.WithNew(func(context.Context, esv1alpha1.GenericStore, client.Client,
  373. string) (provider.SecretsClient, error) {
  374. return nil, fmt.Errorf("artificial constructor error")
  375. })
  376. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  377. esLookupKey := types.NamespacedName{
  378. Name: ExternalSecretName,
  379. Namespace: ExternalSecretNamespace}
  380. createdES := &esv1alpha1.ExternalSecret{}
  381. Eventually(func() bool {
  382. err := k8sClient.Get(ctx, esLookupKey, createdES)
  383. if err != nil {
  384. return false
  385. }
  386. // condition must be false
  387. cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
  388. if cond == nil || cond.Status != v1.ConditionFalse || cond.Reason != esv1alpha1.ConditionReasonSecretSyncedError {
  389. return false
  390. }
  391. return true
  392. }, timeout, interval).Should(BeTrue())
  393. })
  394. It("should not process stores with mismatching controller field", func() {
  395. By("creating an ExternalSecret")
  396. ctx := context.Background()
  397. storeName := "example-ts-foo"
  398. Expect(k8sClient.Create(context.Background(), &esv1alpha1.SecretStore{
  399. ObjectMeta: metav1.ObjectMeta{
  400. Name: storeName,
  401. Namespace: ExternalSecretNamespace,
  402. },
  403. Spec: esv1alpha1.SecretStoreSpec{
  404. Controller: "some-other-controller",
  405. Provider: &esv1alpha1.SecretStoreProvider{
  406. AWS: &esv1alpha1.AWSProvider{
  407. Service: esv1alpha1.AWSServiceSecretsManager,
  408. },
  409. },
  410. },
  411. })).To(Succeed())
  412. defer func() {
  413. Expect(k8sClient.Delete(context.Background(), &esv1alpha1.SecretStore{
  414. ObjectMeta: metav1.ObjectMeta{
  415. Name: storeName,
  416. Namespace: ExternalSecretNamespace,
  417. },
  418. })).To(Succeed())
  419. }()
  420. es := &esv1alpha1.ExternalSecret{
  421. ObjectMeta: metav1.ObjectMeta{
  422. Name: ExternalSecretName,
  423. Namespace: ExternalSecretNamespace,
  424. },
  425. Spec: esv1alpha1.ExternalSecretSpec{
  426. SecretStoreRef: esv1alpha1.SecretStoreRef{
  427. Name: storeName,
  428. },
  429. Target: esv1alpha1.ExternalSecretTarget{
  430. Name: ExternalSecretTargetSecretName,
  431. },
  432. Data: []esv1alpha1.ExternalSecretData{
  433. {
  434. SecretKey: "doesnothing",
  435. RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
  436. Key: "barz",
  437. Property: "bang",
  438. },
  439. },
  440. },
  441. },
  442. }
  443. Expect(k8sClient.Create(ctx, es)).Should(Succeed())
  444. secretLookupKey := types.NamespacedName{
  445. Name: ExternalSecretName,
  446. Namespace: ExternalSecretNamespace,
  447. }
  448. // COND
  449. createdES := &esv1alpha1.ExternalSecret{}
  450. Consistently(func() bool {
  451. err := k8sClient.Get(ctx, secretLookupKey, createdES)
  452. if err != nil {
  453. return false
  454. }
  455. cond := GetExternalSecretCondition(createdES.Status, esv1alpha1.ExternalSecretReady)
  456. return cond == nil
  457. }, timeout, interval).Should(BeTrue())
  458. })
  459. })
  460. })
  461. // CreateNamespace creates a new namespace in the cluster.
  462. func CreateNamespace(baseName string, c client.Client) (string, error) {
  463. genName := fmt.Sprintf("ctrl-test-%v", baseName)
  464. ns := &v1.Namespace{
  465. ObjectMeta: metav1.ObjectMeta{
  466. GenerateName: genName,
  467. },
  468. }
  469. var err error
  470. err = wait.Poll(time.Second, 10*time.Second, func() (bool, error) {
  471. err = c.Create(context.Background(), ns)
  472. if err != nil {
  473. return false, nil
  474. }
  475. return true, nil
  476. })
  477. if err != nil {
  478. return "", err
  479. }
  480. return ns.Name, nil
  481. }
  482. func init() {
  483. fakeProvider = fake.New()
  484. schema.ForceRegister(fakeProvider, &esv1alpha1.SecretStoreProvider{
  485. AWS: &esv1alpha1.AWSProvider{
  486. Service: esv1alpha1.AWSServiceSecretsManager,
  487. },
  488. })
  489. }