client_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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 ngrok
  14. import (
  15. "encoding/json"
  16. "errors"
  17. "github.com/ngrok/ngrok-api-go/v7"
  18. corev1 "k8s.io/api/core/v1"
  19. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  23. "github.com/external-secrets/external-secrets/providers/v1/ngrok/fake"
  24. . "github.com/onsi/ginkgo/v2"
  25. . "github.com/onsi/gomega"
  26. )
  27. type pushSecretRemoteRef struct {
  28. remoteKey string
  29. property string
  30. }
  31. func (p pushSecretRemoteRef) GetRemoteKey() string {
  32. return p.remoteKey
  33. }
  34. func (p pushSecretRemoteRef) GetProperty() string {
  35. return p.property
  36. }
  37. type testClientOpts struct {
  38. vaults []*ngrok.Vault
  39. secrets []*ngrok.Secret
  40. secretsListErr error
  41. vaultName string
  42. }
  43. type testClientOpt func(opts *testClientOpts)
  44. func WithVaults(vaults ...*ngrok.Vault) testClientOpt {
  45. return func(opts *testClientOpts) {
  46. opts.vaults = vaults
  47. }
  48. }
  49. func WithSecrets(secrets ...*ngrok.Secret) testClientOpt {
  50. return func(opts *testClientOpts) {
  51. opts.secrets = secrets
  52. }
  53. }
  54. func WithSecretsListError(err error) testClientOpt {
  55. return func(opts *testClientOpts) {
  56. opts.secretsListErr = err
  57. }
  58. }
  59. func WithVaultName(vaultName string) testClientOpt {
  60. return func(opts *testClientOpts) {
  61. opts.vaultName = vaultName
  62. }
  63. }
  64. var _ = Describe("client", func() {
  65. var (
  66. s *fake.Store
  67. c *client
  68. vaultName string
  69. listVaultsErr error
  70. listSecretsErr error
  71. )
  72. BeforeEach(func() {
  73. vaultName = "test-vault"
  74. listSecretsErr = nil
  75. listVaultsErr = nil
  76. s = fake.NewStore()
  77. })
  78. JustBeforeEach(func() {
  79. c = &client{
  80. vaultClient: s.VaultClient().WithListError(listVaultsErr),
  81. secretsClient: s.SecretsClient().WithListError(listSecretsErr),
  82. vaultName: vaultName,
  83. }
  84. })
  85. Describe("PushSecret", func() {
  86. var (
  87. k8Secret *corev1.Secret
  88. pushData v1alpha1.PushSecretData
  89. vault *ngrok.Vault
  90. secret *ngrok.Secret
  91. ngrokSecretName string
  92. pushErr error
  93. )
  94. BeforeEach(func() {
  95. ngrokSecretName = "secret-" + fake.GenerateRandomString(10)
  96. k8Secret = &corev1.Secret{
  97. ObjectMeta: metav1.ObjectMeta{
  98. Name: "my-secret",
  99. },
  100. Data: map[string][]byte{
  101. "key": []byte("new value"),
  102. "foo": []byte("bar"),
  103. },
  104. }
  105. pushData = v1alpha1.PushSecretData{
  106. Match: v1alpha1.PushSecretMatch{
  107. SecretKey: "key",
  108. RemoteRef: v1alpha1.PushSecretRemoteRef{
  109. RemoteKey: ngrokSecretName,
  110. },
  111. },
  112. }
  113. vault = nil
  114. })
  115. JustBeforeEach(func(ctx SpecContext) {
  116. if vault != nil {
  117. // Set the client's vault ID. This is normally initialized by the provider's NewClient method.
  118. c.setVaultID(vault.ID)
  119. }
  120. pushErr = c.PushSecret(ctx, k8Secret, pushData)
  121. })
  122. When("the vault exists", func() {
  123. var (
  124. getSecretErr error
  125. )
  126. BeforeEach(func(ctx SpecContext) {
  127. var vaultCreateErr error
  128. vault, vaultCreateErr = s.VaultClient().Create(ctx, &ngrok.VaultCreate{
  129. Name: vaultName,
  130. })
  131. Expect(vaultCreateErr).ToNot(HaveOccurred())
  132. })
  133. // Re-fetch the secret after the push to verify it was updated
  134. JustBeforeEach(func(ctx SpecContext) {
  135. secret = nil
  136. iter := s.SecretsClient().List(nil)
  137. for iter.Next(ctx) {
  138. if iter.Item().Name == ngrokSecretName && iter.Item().Vault.ID == vault.ID {
  139. secret = iter.Item()
  140. break
  141. }
  142. }
  143. getSecretErr = iter.Err()
  144. })
  145. When("the secret does not exist", func() {
  146. It("should not return an error", func(ctx SpecContext) {
  147. Expect(pushErr).ToNot(HaveOccurred())
  148. })
  149. It("should create the ngrok secret", func(ctx SpecContext) {
  150. Expect(getSecretErr).ToNot(HaveOccurred())
  151. Expect(secret).ToNot(BeNil())
  152. Expect(secret.Name).To(Equal(ngrokSecretName))
  153. Expect(secret.ID).ToNot(BeEmpty())
  154. Expect(secret.Description).To(Equal(defaultDescription))
  155. })
  156. })
  157. When("the secret exists", func() {
  158. BeforeEach(func(ctx SpecContext) {
  159. var createErr error
  160. secret, createErr = s.SecretsClient().Create(ctx, &ngrok.SecretCreate{
  161. VaultID: vault.ID,
  162. Name: ngrokSecretName,
  163. Value: "old-value",
  164. })
  165. Expect(createErr).ToNot(HaveOccurred())
  166. })
  167. It("should not return an error", func(ctx SpecContext) {
  168. Expect(pushErr).ToNot(HaveOccurred())
  169. })
  170. It("should update the ngrok secret description", func(ctx SpecContext) {
  171. Expect(secret.Description).To(Equal(defaultDescription))
  172. })
  173. It("should update the ngrok secret metadata", func(ctx SpecContext) {
  174. // The metadata should include the sha256 of the new value.
  175. // sha256sum "new value" = 9c51d0b0f64dfb3662ed85ce945dd1e8f6130665c289754e4e9257a58013e61d
  176. Expect(secret.Metadata).To(Equal(`{"_sha256":"9c51d0b0f64dfb3662ed85ce945dd1e8f6130665c289754e4e9257a58013e61d"}`))
  177. })
  178. When("The secret key is not specified on the push data", func() {
  179. BeforeEach(func() {
  180. pushData.Match.SecretKey = ""
  181. })
  182. It("should marshal the entire secret data as JSON", func(ctx SpecContext) {
  183. data := map[string]string{}
  184. err := json.Unmarshal([]byte(secret.Metadata), &data)
  185. Expect(err).ToNot(HaveOccurred())
  186. Expect(data).To(HaveKeyWithValue("_sha256", "146ed8bb7a977ee78ee11cf262924e3ae93423c413ab6d612a8d159a0ae4e1ad"))
  187. })
  188. })
  189. When("the secret key does not exist in the k8s secret", func() {
  190. BeforeEach(func() {
  191. pushData.Match.SecretKey = "nonexistent-key"
  192. })
  193. It("should return an error", func(ctx SpecContext) {
  194. Expect(pushErr).To(HaveOccurred())
  195. Expect(pushErr.Error()).To(ContainSubstring("key nonexistent-key not found in secret"))
  196. })
  197. })
  198. When("push metadata is provided", func() {
  199. When("the metadata is valid", func() {
  200. BeforeEach(func() {
  201. pushData.Metadata = &apiextensionsv1.JSON{
  202. Raw: []byte(`
  203. apiVersion: kubernetes.external-secrets.io/v1alpha1
  204. kind: PushSecretMetadata
  205. spec:
  206. metadata:
  207. environment: production
  208. team: frontend
  209. description: "my custom description"`),
  210. }
  211. })
  212. It("should update the ngrok secret description", func(ctx SpecContext) {
  213. Expect(secret.Description).To(Equal("my custom description"))
  214. })
  215. It("should update the ngrok secret metadata", func(ctx SpecContext) {
  216. data := map[string]string{}
  217. err := json.Unmarshal([]byte(secret.Metadata), &data)
  218. Expect(err).ToNot(HaveOccurred())
  219. Expect(data).To(HaveKeyWithValue("environment", "production"))
  220. Expect(data).To(HaveKeyWithValue("team", "frontend"))
  221. Expect(data).To(HaveKeyWithValue("_sha256", "9c51d0b0f64dfb3662ed85ce945dd1e8f6130665c289754e4e9257a58013e61d"))
  222. })
  223. })
  224. When("the metadata is invalid", func() {
  225. BeforeEach(func() {
  226. pushData.Metadata = &apiextensionsv1.JSON{
  227. Raw: []byte(`{ this is not valid json`),
  228. }
  229. })
  230. It("should return an error", func(ctx SpecContext) {
  231. Expect(pushErr).To(HaveOccurred())
  232. Expect(pushErr.Error()).To(ContainSubstring("failed to parse push secret metadata"))
  233. })
  234. })
  235. })
  236. })
  237. })
  238. })
  239. Describe("SecretExists", func() {
  240. var (
  241. secretName string
  242. exists bool
  243. err error
  244. )
  245. BeforeEach(func() {
  246. secretName = "my-secret"
  247. })
  248. JustBeforeEach(func(ctx SpecContext) {
  249. exists, err = c.SecretExists(ctx, pushSecretRemoteRef{
  250. remoteKey: secretName,
  251. })
  252. })
  253. When("the vault does not exist", func() {
  254. It("should return exists as false without an error", func(ctx SpecContext) {
  255. Expect(err).ToNot(HaveOccurred())
  256. Expect(exists).To(BeFalse())
  257. })
  258. })
  259. When("the vault exists", func() {
  260. var (
  261. vault *ngrok.Vault
  262. )
  263. BeforeEach(func(ctx SpecContext) {
  264. vault, err = s.VaultClient().Create(ctx, &ngrok.VaultCreate{
  265. Name: c.vaultName,
  266. })
  267. Expect(err).ToNot(HaveOccurred())
  268. })
  269. When("the secret does not exist", func() {
  270. It("should return exists as false without an error", func(ctx SpecContext) {
  271. Expect(err).ToNot(HaveOccurred())
  272. Expect(exists).To(BeFalse())
  273. })
  274. })
  275. When("the secret exists", func() {
  276. BeforeEach(func(ctx SpecContext) {
  277. _, err = s.SecretsClient().Create(ctx, &ngrok.SecretCreate{
  278. VaultID: vault.ID,
  279. Name: secretName,
  280. Value: "supersecret",
  281. })
  282. Expect(err).ToNot(HaveOccurred())
  283. })
  284. It("should return exists as true without an error", func(ctx SpecContext) {
  285. Expect(err).ToNot(HaveOccurred())
  286. Expect(exists).To(BeTrue())
  287. })
  288. })
  289. })
  290. When("an error occurs listing vaults", func() {
  291. BeforeEach(func() {
  292. listVaultsErr = errors.New("failed to list vaults")
  293. })
  294. It("should return exists as false", func() {
  295. Expect(exists).To(BeFalse())
  296. })
  297. It("should return the listing error", func() {
  298. Expect(err).To(HaveOccurred())
  299. Expect(err.Error()).To(ContainSubstring("failed to list vaults"))
  300. })
  301. })
  302. })
  303. Describe("DeleteSecret", func() {
  304. var (
  305. secretName string
  306. err error
  307. )
  308. BeforeEach(func() {
  309. secretName = "my-secret"
  310. })
  311. JustBeforeEach(func(ctx SpecContext) {
  312. err = c.DeleteSecret(ctx, pushSecretRemoteRef{
  313. remoteKey: secretName,
  314. })
  315. })
  316. When("the vault does not exist", func() {
  317. It("should not return an error", func(ctx SpecContext) {
  318. Expect(err).ToNot(HaveOccurred())
  319. })
  320. })
  321. When("the vault exists but the secret does not", func() {
  322. BeforeEach(func(ctx SpecContext) {
  323. _, err := c.vaultClient.Create(ctx, &ngrok.VaultCreate{
  324. Name: c.vaultName,
  325. })
  326. Expect(err).ToNot(HaveOccurred())
  327. })
  328. It("should not return an error", func(ctx SpecContext) {
  329. Expect(err).ToNot(HaveOccurred())
  330. })
  331. })
  332. When("the vault and secret both exist", func() {
  333. BeforeEach(func(ctx SpecContext) {
  334. vault, err := s.VaultClient().Create(ctx, &ngrok.VaultCreate{
  335. Name: c.vaultName,
  336. })
  337. Expect(err).ToNot(HaveOccurred())
  338. _, err = s.SecretsClient().Create(ctx, &ngrok.SecretCreate{
  339. VaultID: vault.ID,
  340. Name: secretName,
  341. Value: "supersecret",
  342. })
  343. Expect(err).ToNot(HaveOccurred())
  344. })
  345. It("should not return an error", func(ctx SpecContext) {
  346. Expect(err).ToNot(HaveOccurred())
  347. })
  348. })
  349. When("an error occurs listing vaults", func() {
  350. BeforeEach(func() {
  351. listVaultsErr = errors.New("failed to list vaults")
  352. })
  353. It("should return the listing error", func() {
  354. Expect(err).To(HaveOccurred())
  355. Expect(err.Error()).To(ContainSubstring("failed to list vaults"))
  356. })
  357. })
  358. })
  359. Describe("Validate", func() {
  360. var (
  361. result esv1.ValidationResult
  362. err error
  363. )
  364. JustBeforeEach(func(ctx SpecContext) {
  365. result, err = c.Validate()
  366. })
  367. When("the client can list secrets", func() {
  368. When("there are no secrets", func() {
  369. It("should return ValidationResultReady without an error", func() {
  370. Expect(err).To(BeNil())
  371. Expect(result).To(Equal(esv1.ValidationResultReady))
  372. })
  373. })
  374. When("there are some secrets", func() {
  375. BeforeEach(func(ctx SpecContext) {
  376. vault, err := s.VaultClient().Create(ctx, &ngrok.VaultCreate{
  377. Name: c.vaultName,
  378. })
  379. Expect(err).ToNot(HaveOccurred())
  380. _, err = s.SecretsClient().Create(ctx, &ngrok.SecretCreate{
  381. VaultID: vault.ID,
  382. Name: "my-secret",
  383. Value: "supersecret",
  384. })
  385. Expect(err).ToNot(HaveOccurred())
  386. })
  387. It("should return ValidationResultReady without an error", func() {
  388. Expect(err).To(BeNil())
  389. Expect(result).To(Equal(esv1.ValidationResultReady))
  390. })
  391. })
  392. })
  393. When("the client cannot list secrets", func() {
  394. BeforeEach(func() {
  395. listSecretsErr = errors.New("failed to list secrets")
  396. })
  397. It("should return ValidationResultError with the listing error", func() {
  398. Expect(err).ToNot(BeNil())
  399. Expect(err.Error()).To(ContainSubstring("failed to list secrets"))
  400. Expect(result).To(Equal(esv1.ValidationResultError))
  401. })
  402. })
  403. })
  404. Describe("GetSecret", func() {
  405. var (
  406. ref esv1.ExternalSecretDataRemoteRef
  407. secret []byte
  408. err error
  409. )
  410. JustBeforeEach(func(ctx SpecContext) {
  411. secret, err = c.GetSecret(ctx, ref)
  412. })
  413. It("should always return an error indicating write-only operations", func(ctx SpecContext) {
  414. Expect(secret).To(BeNil())
  415. Expect(err).To(HaveOccurred())
  416. Expect(err).To(Equal(errWriteOnlyOperations))
  417. })
  418. })
  419. Describe("GetSecretMap", func() {
  420. var (
  421. ref esv1.ExternalSecretDataRemoteRef
  422. secretMap map[string][]byte
  423. err error
  424. )
  425. JustBeforeEach(func(ctx SpecContext) {
  426. secretMap, err = c.GetSecretMap(ctx, ref)
  427. })
  428. It("should always return an error indicating write-only operations", func(ctx SpecContext) {
  429. Expect(secretMap).To(BeNil())
  430. Expect(err).To(HaveOccurred())
  431. Expect(err).To(Equal(errWriteOnlyOperations))
  432. })
  433. })
  434. Describe("GetAllSecrets", func() {
  435. var (
  436. find esv1.ExternalSecretFind
  437. secrets map[string][]byte
  438. err error
  439. )
  440. JustBeforeEach(func(ctx SpecContext) {
  441. secrets, err = c.GetAllSecrets(ctx, find)
  442. })
  443. It("should always return an error indicating write-only operations", func(ctx SpecContext) {
  444. Expect(secrets).To(BeNil())
  445. Expect(err).To(HaveOccurred())
  446. Expect(err).To(Equal(errWriteOnlyOperations))
  447. })
  448. })
  449. Describe("Close", func() {
  450. It("should not return an error", func(ctx SpecContext) {
  451. Expect(c.Close(ctx)).To(BeNil())
  452. })
  453. })
  454. })