client_push_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  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 vault
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "strings"
  19. "testing"
  20. corev1 "k8s.io/api/core/v1"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. "github.com/external-secrets/external-secrets/providers/v1/vault/fake"
  23. vaultutil "github.com/external-secrets/external-secrets/providers/v1/vault/util"
  24. testingfake "github.com/external-secrets/external-secrets/runtime/testing/fake"
  25. )
  26. const (
  27. fakeKey = "fake-key"
  28. fakeValue = "fake-value"
  29. managedBy = "managed-by"
  30. managedByESO = "external-secrets"
  31. )
  32. func TestDeleteSecret(t *testing.T) {
  33. type args struct {
  34. store *esv1.VaultProvider
  35. vLogical vaultutil.Logical
  36. }
  37. type want struct {
  38. err error
  39. }
  40. tests := map[string]struct {
  41. reason string
  42. args args
  43. ref *testingfake.PushSecretData
  44. want want
  45. value []byte
  46. }{
  47. "DeleteSecretNoOpKV1": {
  48. reason: "delete secret is a no-op if v1 secret does not exist",
  49. args: args{
  50. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  51. vLogical: &fake.Logical{
  52. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  53. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  54. DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
  55. },
  56. },
  57. want: want{
  58. err: nil,
  59. },
  60. },
  61. "DeleteSecretNoOpKV2": {
  62. reason: "delete secret is a no-op if v2 secret does not exist",
  63. args: args{
  64. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  65. vLogical: &fake.Logical{
  66. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  67. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  68. DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
  69. },
  70. },
  71. want: want{
  72. err: nil,
  73. },
  74. },
  75. "DeleteSecretFailIfErrorKV1": {
  76. reason: "delete v1 secret fails if error occurs",
  77. args: args{
  78. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  79. vLogical: &fake.Logical{
  80. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errors.New("failed to read")),
  81. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  82. DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
  83. },
  84. },
  85. want: want{
  86. err: errors.New("failed to read"),
  87. },
  88. },
  89. "DeleteSecretFailIfErrorKV2": {
  90. reason: "delete v2 secret fails if error occurs",
  91. args: args{
  92. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  93. vLogical: &fake.Logical{
  94. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, errors.New("failed to read")),
  95. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  96. DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
  97. },
  98. },
  99. want: want{
  100. err: errors.New("failed to read"),
  101. },
  102. },
  103. "DeleteSecretNotManagedKV1": {
  104. reason: "delete v1 secret when not managed by ESO",
  105. args: args{
  106. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  107. vLogical: &fake.Logical{
  108. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  109. fakeKey: fakeValue,
  110. "custom_metadata": map[string]any{
  111. managedBy: "another-secret-tool",
  112. },
  113. }, nil),
  114. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  115. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
  116. },
  117. },
  118. want: want{
  119. err: nil,
  120. },
  121. },
  122. "DeleteSecretNotManagedKV2": {
  123. reason: "delete v2 secret when not managed by eso",
  124. args: args{
  125. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  126. vLogical: &fake.Logical{
  127. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  128. "data": map[string]any{
  129. fakeKey: fakeValue,
  130. },
  131. "custom_metadata": map[string]any{
  132. managedBy: "another-secret-tool",
  133. },
  134. }, nil),
  135. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  136. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
  137. },
  138. },
  139. want: want{
  140. err: nil,
  141. },
  142. },
  143. "DeleteSecretSuccessKV1": {
  144. reason: "delete secret succeeds if secret is managed by ESO and exists in vault v1",
  145. args: args{
  146. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  147. vLogical: &fake.Logical{
  148. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  149. fakeKey: fakeValue,
  150. "custom_metadata": map[string]any{
  151. managedBy: managedByESO,
  152. },
  153. }, nil),
  154. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  155. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
  156. },
  157. },
  158. want: want{
  159. err: nil,
  160. },
  161. },
  162. "DeleteSecretSuccessKV2": {
  163. reason: "delete secret succeeds if secret is managed by ESO and exists in vault v2",
  164. args: args{
  165. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  166. vLogical: &fake.Logical{
  167. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  168. "data": map[string]any{
  169. fakeKey: fakeValue,
  170. },
  171. "custom_metadata": map[string]any{
  172. managedBy: managedByESO,
  173. },
  174. }, nil),
  175. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  176. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
  177. },
  178. },
  179. want: want{
  180. err: nil,
  181. },
  182. },
  183. "DeleteSecretErrorKV1": {
  184. reason: "delete secret fails if error occurs v1",
  185. args: args{
  186. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  187. vLogical: &fake.Logical{
  188. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  189. fakeKey: fakeValue,
  190. "custom_metadata": map[string]any{
  191. managedBy: managedByESO,
  192. },
  193. }, nil),
  194. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  195. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, errors.New("failed to delete")),
  196. },
  197. },
  198. want: want{
  199. err: errors.New("failed to delete"),
  200. },
  201. },
  202. "DeleteSecretErrorKV2": {
  203. reason: "delete secret fails if error occurs v2",
  204. args: args{
  205. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  206. vLogical: &fake.Logical{
  207. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  208. "data": map[string]any{
  209. fakeKey: fakeValue,
  210. },
  211. "custom_metadata": map[string]any{
  212. managedBy: managedByESO,
  213. },
  214. }, nil),
  215. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  216. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, errors.New("failed to delete")),
  217. },
  218. },
  219. want: want{
  220. err: errors.New("failed to delete"),
  221. },
  222. },
  223. "DeleteSecretUpdatePropertyKV1": {
  224. reason: "Secret should only be updated if Property is set v1",
  225. ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: fakeKey},
  226. args: args{
  227. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  228. vLogical: &fake.Logical{
  229. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  230. fakeKey: fakeValue,
  231. "foo": "bar",
  232. "custom_metadata": map[string]any{
  233. managedBy: managedByESO,
  234. },
  235. }, nil),
  236. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  237. "foo": "bar",
  238. "custom_metadata": map[string]any{
  239. managedBy: managedByESO,
  240. }}),
  241. DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
  242. },
  243. },
  244. want: want{
  245. err: nil,
  246. },
  247. },
  248. "DeleteSecretUpdatePropertyKV2": {
  249. reason: "Secret should only be updated if Property is set v2",
  250. ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: fakeKey},
  251. args: args{
  252. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  253. vLogical: &fake.Logical{
  254. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  255. "data": map[string]any{
  256. fakeKey: fakeValue,
  257. "foo": "bar",
  258. },
  259. "custom_metadata": map[string]any{
  260. managedBy: managedByESO,
  261. },
  262. }, nil),
  263. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{"foo": "bar"}}),
  264. DeleteWithContextFn: fake.ExpectDeleteWithContextNoCall(),
  265. },
  266. },
  267. want: want{
  268. err: nil,
  269. },
  270. },
  271. "DeleteSecretIfNoOtherPropertiesKV1": {
  272. reason: "Secret should only be deleted if no other properties are set v1",
  273. ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "foo"},
  274. args: args{
  275. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  276. vLogical: &fake.Logical{
  277. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  278. "foo": "bar",
  279. "custom_metadata": map[string]any{
  280. managedBy: managedByESO,
  281. },
  282. }, nil),
  283. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  284. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
  285. },
  286. },
  287. want: want{
  288. err: nil,
  289. },
  290. },
  291. "DeleteSecretIfNoOtherPropertiesKV2": {
  292. reason: "Secret should only be deleted if no other properties are set v2",
  293. ref: &testingfake.PushSecretData{RemoteKey: "secret", Property: "foo"},
  294. args: args{
  295. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  296. vLogical: &fake.Logical{
  297. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  298. "data": map[string]any{
  299. "foo": "bar",
  300. },
  301. "custom_metadata": map[string]any{
  302. managedBy: managedByESO,
  303. },
  304. }, nil),
  305. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  306. DeleteWithContextFn: fake.NewDeleteWithContextFn(nil, nil),
  307. },
  308. },
  309. want: want{
  310. err: nil,
  311. },
  312. },
  313. }
  314. for name, tc := range tests {
  315. t.Run(name, func(t *testing.T) {
  316. ref := testingfake.PushSecretData{RemoteKey: "secret", Property: ""}
  317. if tc.ref != nil {
  318. ref = *tc.ref
  319. }
  320. client := &client{
  321. logical: tc.args.vLogical,
  322. store: tc.args.store,
  323. }
  324. err := client.DeleteSecret(context.Background(), ref)
  325. // Error nil XOR tc.want.err nil
  326. if ((err == nil) || (tc.want.err == nil)) && !((err == nil) && (tc.want.err == nil)) {
  327. t.Errorf("\nTesting DeleteSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, err)
  328. }
  329. // if errors are the same type but their contents do not match
  330. if err != nil && tc.want.err != nil {
  331. if !strings.Contains(err.Error(), tc.want.err.Error()) {
  332. t.Errorf("\nTesting DeleteSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error got nil", name, tc.reason, tc.want.err)
  333. }
  334. }
  335. })
  336. }
  337. }
  338. func TestPushSecret(t *testing.T) {
  339. secretKey := "secret-key"
  340. noPermission := errors.New("no permission")
  341. type args struct {
  342. store *esv1.VaultProvider
  343. vLogical vaultutil.Logical
  344. }
  345. type want struct {
  346. err error
  347. }
  348. tests := map[string]struct {
  349. reason string
  350. args args
  351. want want
  352. data *testingfake.PushSecretData
  353. value []byte
  354. secret *corev1.Secret
  355. }{
  356. "SetSecretKV1": {
  357. reason: "secret is successfully set, with no existing vault secret",
  358. args: args{
  359. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  360. vLogical: &fake.Logical{
  361. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  362. WriteWithContextFn: fake.NewWriteWithContextFn(nil, nil),
  363. },
  364. },
  365. want: want{
  366. err: nil,
  367. },
  368. },
  369. "SetSecretKV2": {
  370. reason: "secret is successfully set, with no existing vault secret",
  371. args: args{
  372. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  373. vLogical: &fake.Logical{
  374. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  375. WriteWithContextFn: fake.NewWriteWithContextFn(nil, nil),
  376. },
  377. },
  378. want: want{
  379. err: nil,
  380. },
  381. },
  382. "SetSecretWithWriteErrorKV1": {
  383. reason: "secret cannot be pushed if write fails",
  384. args: args{
  385. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  386. vLogical: &fake.Logical{
  387. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  388. WriteWithContextFn: fake.NewWriteWithContextFn(nil, noPermission),
  389. },
  390. },
  391. want: want{
  392. err: noPermission,
  393. },
  394. },
  395. "SetSecretWithWriteErrorKV2": {
  396. reason: "secret cannot be pushed if write fails",
  397. args: args{
  398. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  399. vLogical: &fake.Logical{
  400. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  401. WriteWithContextFn: fake.NewWriteWithContextFn(nil, noPermission),
  402. },
  403. },
  404. want: want{
  405. err: noPermission,
  406. },
  407. },
  408. "SetSecretEqualsPushSecretV1": {
  409. reason: "vault secret kv equals secret to push kv",
  410. args: args{
  411. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  412. vLogical: &fake.Logical{
  413. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  414. fakeKey: fakeValue,
  415. "custom_metadata": map[string]any{
  416. managedBy: managedByESO,
  417. },
  418. }, nil),
  419. },
  420. },
  421. want: want{
  422. err: nil,
  423. },
  424. },
  425. "SetSecretEqualsPushSecretV2": {
  426. reason: "vault secret kv equals secret to push kv",
  427. args: args{
  428. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  429. vLogical: &fake.Logical{
  430. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  431. "data": map[string]any{
  432. fakeKey: fakeValue,
  433. },
  434. "custom_metadata": map[string]any{
  435. managedBy: managedByESO,
  436. },
  437. }, nil),
  438. },
  439. },
  440. want: want{
  441. err: nil,
  442. },
  443. },
  444. "PushSecretPropertyKV1": {
  445. reason: "push secret with property adds the property",
  446. value: []byte(fakeValue),
  447. data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
  448. args: args{
  449. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  450. vLogical: &fake.Logical{
  451. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  452. fakeKey: fakeValue,
  453. "custom_metadata": map[string]any{
  454. managedBy: managedByESO,
  455. },
  456. }, nil),
  457. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  458. fakeKey: fakeValue,
  459. "custom_metadata": map[string]string{
  460. managedBy: managedByESO,
  461. },
  462. "foo": fakeValue,
  463. }),
  464. },
  465. },
  466. want: want{
  467. err: nil,
  468. },
  469. },
  470. "PushSecretPropertyKV2": {
  471. reason: "push secret with property adds the property",
  472. value: []byte(fakeValue),
  473. data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
  474. args: args{
  475. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  476. vLogical: &fake.Logical{
  477. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  478. "data": map[string]any{
  479. fakeKey: fakeValue,
  480. },
  481. "custom_metadata": map[string]any{
  482. managedBy: managedByESO,
  483. },
  484. }, nil),
  485. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{fakeKey: fakeValue, "foo": fakeValue}}),
  486. },
  487. },
  488. want: want{
  489. err: nil,
  490. },
  491. },
  492. "PushSecretUpdatePropertyKV1": {
  493. reason: "push secret with property only updates the property",
  494. value: []byte("new-value"),
  495. data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
  496. args: args{
  497. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  498. vLogical: &fake.Logical{
  499. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  500. "foo": fakeValue,
  501. "custom_metadata": map[string]any{
  502. managedBy: managedByESO,
  503. },
  504. }, nil),
  505. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  506. "foo": "new-value",
  507. "custom_metadata": map[string]string{
  508. managedBy: managedByESO,
  509. },
  510. }),
  511. },
  512. },
  513. want: want{
  514. err: nil,
  515. },
  516. },
  517. "PushSecretUpdatePropertyKV2": {
  518. reason: "push secret with property only updates the property",
  519. value: []byte("new-value"),
  520. data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
  521. args: args{
  522. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  523. vLogical: &fake.Logical{
  524. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  525. "data": map[string]any{
  526. "foo": fakeValue,
  527. },
  528. "custom_metadata": map[string]any{
  529. managedBy: managedByESO,
  530. },
  531. }, nil),
  532. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{"foo": "new-value"}}),
  533. },
  534. },
  535. want: want{
  536. err: nil,
  537. },
  538. },
  539. "PushSecretPropertyNoUpdateKV1": {
  540. reason: "push secret with property only updates the property",
  541. value: []byte(fakeValue),
  542. data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
  543. args: args{
  544. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  545. vLogical: &fake.Logical{
  546. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  547. "foo": fakeValue,
  548. "custom_metadata": map[string]any{
  549. managedBy: managedByESO,
  550. },
  551. }, nil),
  552. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  553. },
  554. },
  555. want: want{
  556. err: nil,
  557. },
  558. },
  559. "PushSecretPropertyNoUpdateKV2": {
  560. reason: "push secret with property only updates the property",
  561. value: []byte(fakeValue),
  562. data: &testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: "foo"},
  563. args: args{
  564. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  565. vLogical: &fake.Logical{
  566. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  567. "data": map[string]any{
  568. "foo": fakeValue,
  569. },
  570. "custom_metadata": map[string]any{
  571. managedBy: managedByESO,
  572. },
  573. }, nil),
  574. WriteWithContextFn: fake.ExpectWriteWithContextNoCall(),
  575. },
  576. },
  577. want: want{
  578. err: nil,
  579. },
  580. },
  581. "SetSecretErrorReadingSecretKV1": {
  582. reason: "error occurs if secret cannot be read",
  583. args: args{
  584. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  585. vLogical: &fake.Logical{
  586. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, noPermission),
  587. },
  588. },
  589. want: want{
  590. err: fmt.Errorf(errReadSecret, noPermission),
  591. },
  592. },
  593. "SetSecretErrorReadingSecretKV2": {
  594. reason: "error occurs if secret cannot be read",
  595. args: args{
  596. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  597. vLogical: &fake.Logical{
  598. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, noPermission),
  599. },
  600. },
  601. want: want{
  602. err: fmt.Errorf(errReadSecret, noPermission),
  603. },
  604. },
  605. "SetSecretNotManagedByESOV1": {
  606. reason: "a secret not managed by ESO cannot be updated",
  607. args: args{
  608. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  609. vLogical: &fake.Logical{
  610. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  611. fakeKey: "fake-value2",
  612. "custom_metadata": map[string]any{
  613. managedBy: "not-external-secrets",
  614. },
  615. }, nil),
  616. },
  617. },
  618. want: want{
  619. err: errors.New("secret not managed by external-secrets"),
  620. },
  621. },
  622. "SetSecretNotManagedByESOV2": {
  623. reason: "a secret not managed by ESO cannot be updated",
  624. args: args{
  625. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  626. vLogical: &fake.Logical{
  627. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  628. "data": map[string]any{
  629. fakeKey: "fake-value2",
  630. "custom_metadata": map[string]any{
  631. managedBy: "not-external-secrets",
  632. },
  633. },
  634. }, nil),
  635. },
  636. },
  637. want: want{
  638. err: errors.New("secret not managed by external-secrets"),
  639. },
  640. },
  641. "WholeSecretKV2": {
  642. reason: "secret is successfully set, with no existing vault secret",
  643. args: args{
  644. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  645. vLogical: &fake.Logical{
  646. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  647. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{"data": map[string]any{"key1": "value1", "key2": "value2"}}),
  648. },
  649. },
  650. data: &testingfake.PushSecretData{SecretKey: "", RemoteKey: "secret", Property: ""},
  651. secret: &corev1.Secret{Data: map[string][]byte{"key1": []byte(`value1`), "key2": []byte(`value2`)}},
  652. want: want{
  653. err: nil,
  654. },
  655. },
  656. "CASRequiredNewSecretKV2": {
  657. reason: "CAS required: new secret should be created with cas=0",
  658. args: args{
  659. store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  660. vLogical: &fake.Logical{
  661. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  662. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  663. "options": map[string]any{
  664. "cas": 0,
  665. },
  666. "data": map[string]any{fakeKey: fakeValue},
  667. }),
  668. },
  669. },
  670. want: want{
  671. err: nil,
  672. },
  673. },
  674. "CASRequiredExistingSecretKV2": {
  675. reason: "CAS required: existing secret should be updated with current version",
  676. args: args{
  677. store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  678. vLogical: &fake.Logical{
  679. ReadWithDataWithContextFn: fake.NewReadWithDataAndMetadataFn(
  680. map[string]any{
  681. "data": map[string]any{
  682. "existing": "value",
  683. },
  684. },
  685. map[string]any{
  686. "custom_metadata": map[string]any{
  687. managedBy: managedByESO,
  688. },
  689. "current_version": 3,
  690. },
  691. nil, nil,
  692. ),
  693. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  694. "options": map[string]any{
  695. "cas": 3,
  696. },
  697. "data": map[string]any{fakeKey: fakeValue},
  698. }),
  699. },
  700. },
  701. want: want{
  702. err: nil,
  703. },
  704. },
  705. "CASRequiredPropertyUpdateKV2": {
  706. reason: "CAS required: property update should use current version",
  707. value: []byte("property-value"),
  708. data: &testingfake.PushSecretData{SecretKey: "secret-key", RemoteKey: "secret", Property: "new-prop"},
  709. args: args{
  710. store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  711. vLogical: &fake.Logical{
  712. ReadWithDataWithContextFn: fake.NewReadWithDataAndMetadataFn(
  713. map[string]any{
  714. "data": map[string]any{
  715. "existing": "value",
  716. },
  717. },
  718. map[string]any{
  719. "custom_metadata": map[string]any{
  720. managedBy: managedByESO,
  721. },
  722. "current_version": 2,
  723. },
  724. nil, nil,
  725. ),
  726. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  727. "options": map[string]any{
  728. "cas": 2,
  729. },
  730. "data": map[string]any{
  731. "existing": "value",
  732. "new-prop": "property-value",
  733. },
  734. }),
  735. },
  736. },
  737. want: want{
  738. err: nil,
  739. },
  740. },
  741. "CASNotRequiredKV2": {
  742. reason: "CAS not required: should work without CAS options",
  743. args: args{
  744. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  745. vLogical: &fake.Logical{
  746. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  747. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  748. "data": map[string]any{fakeKey: fakeValue},
  749. }),
  750. },
  751. },
  752. want: want{
  753. err: nil,
  754. },
  755. },
  756. "CASIgnoredKV1": {
  757. reason: "CAS ignored for KV v1: should work without CAS options even when required",
  758. args: args{
  759. store: makeValidSecretStoreWithCASRequired(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  760. vLogical: &fake.Logical{
  761. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  762. WriteWithContextFn: fake.ExpectWriteWithContextValue(map[string]any{
  763. fakeKey: fakeValue,
  764. "custom_metadata": map[string]string{
  765. managedBy: managedByESO,
  766. },
  767. }),
  768. },
  769. },
  770. want: want{
  771. err: nil,
  772. },
  773. },
  774. // Security regression tests: ensure json.Unmarshal errors don't leak secret data
  775. "InvalidJSONDoesNotLeakSecretDataKV1": {
  776. reason: "json.Unmarshal error should not leak secret data in error message",
  777. value: []byte(`not-valid-json-contains-secret-8019210420527506405`),
  778. args: args{
  779. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  780. vLogical: &fake.Logical{
  781. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  782. },
  783. },
  784. want: want{
  785. err: errors.New("error unmarshalling vault secret: invalid JSON format"),
  786. },
  787. },
  788. "InvalidJSONDoesNotLeakSecretDataKV2": {
  789. reason: "json.Unmarshal error should not leak secret data in error message",
  790. value: []byte(`not-valid-json-contains-secret-8019210420527506405`),
  791. args: args{
  792. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  793. vLogical: &fake.Logical{
  794. ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, nil),
  795. },
  796. },
  797. want: want{
  798. err: errors.New("error unmarshalling vault secret: invalid JSON format"),
  799. },
  800. },
  801. "InvalidJSONCompareDoesNotLeakSecretDataKV1": {
  802. reason: "json.Unmarshal error during comparison should not leak secret data",
  803. value: []byte(`invalid-json-with-api-key-12345`),
  804. args: args{
  805. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV1).Spec.Provider.Vault,
  806. vLogical: &fake.Logical{
  807. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  808. fakeKey: fakeValue,
  809. "custom_metadata": map[string]any{
  810. managedBy: managedByESO,
  811. },
  812. }, nil),
  813. },
  814. },
  815. want: want{
  816. err: errors.New("error unmarshalling incoming secret value: invalid JSON format"),
  817. },
  818. },
  819. "InvalidJSONCompareDoesNotLeakSecretDataKV2": {
  820. reason: "json.Unmarshal error during comparison should not leak secret data",
  821. value: []byte(`invalid-json-with-api-key-12345`),
  822. args: args{
  823. store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
  824. vLogical: &fake.Logical{
  825. ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]any{
  826. "data": map[string]any{
  827. fakeKey: fakeValue,
  828. },
  829. "custom_metadata": map[string]any{
  830. managedBy: managedByESO,
  831. },
  832. }, nil),
  833. },
  834. },
  835. want: want{
  836. err: errors.New("error unmarshalling incoming secret value: invalid JSON format"),
  837. },
  838. },
  839. }
  840. for name, tc := range tests {
  841. t.Run(name, func(t *testing.T) {
  842. data := testingfake.PushSecretData{SecretKey: secretKey, RemoteKey: "secret", Property: ""}
  843. if tc.data != nil {
  844. data = *tc.data
  845. }
  846. client := &client{
  847. logical: tc.args.vLogical,
  848. store: tc.args.store,
  849. }
  850. s := tc.secret
  851. if s == nil {
  852. val := tc.value
  853. if val == nil {
  854. val = []byte(`{"fake-key":"fake-value"}`)
  855. }
  856. s = &corev1.Secret{Data: map[string][]byte{secretKey: val}}
  857. }
  858. err := client.PushSecret(context.Background(), s, data)
  859. // Error nil XOR tc.want.err nil
  860. if ((err == nil) || (tc.want.err == nil)) && !((err == nil) && (tc.want.err == nil)) {
  861. t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, err)
  862. }
  863. // if errors are the same type but their contents do not match
  864. if err != nil && tc.want.err != nil {
  865. if !strings.Contains(err.Error(), tc.want.err.Error()) {
  866. t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error got nil", name, tc.reason, tc.want.err)
  867. }
  868. }
  869. // Security regression: ensure error messages don't leak secret data
  870. if err != nil && tc.value != nil {
  871. secretData := string(tc.value)
  872. if strings.Contains(err.Error(), secretData) {
  873. t.Errorf("\nSECURITY REGRESSION: Error message contains secret data!\nName: %v\nSecret data: %v\nError: %v", name, secretData, err)
  874. }
  875. }
  876. })
  877. }
  878. }
  879. func makeValidSecretStoreWithCASRequired(version esv1.VaultKVStoreVersion) *esv1.SecretStore {
  880. store := makeValidSecretStoreWithVersion(version)
  881. store.Spec.Provider.Vault.CheckAndSet = &esv1.VaultCheckAndSet{
  882. Required: true,
  883. }
  884. return store
  885. }