webhook_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  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 webhook
  13. import (
  14. "bytes"
  15. "context"
  16. "errors"
  17. "io"
  18. "net/http"
  19. "net/http/httptest"
  20. "strings"
  21. "testing"
  22. "time"
  23. "gopkg.in/yaml.v3"
  24. corev1 "k8s.io/api/core/v1"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  28. )
  29. type testCase struct {
  30. Case string `json:"case,omitempty"`
  31. Args args `json:"args"`
  32. Want want `json:"want"`
  33. }
  34. type secret struct {
  35. Name string `json:"name"`
  36. Data map[string]string `json:"data"`
  37. }
  38. type args struct {
  39. URL string `json:"url,omitempty"`
  40. Body string `json:"body,omitempty"`
  41. Timeout string `json:"timeout,omitempty"`
  42. Key string `json:"key,omitempty"`
  43. SecretKey string `json:"secretkey,omitempty"`
  44. Property string `json:"property,omitempty"`
  45. Version string `json:"version,omitempty"`
  46. JSONPath string `json:"jsonpath,omitempty"`
  47. Response string `json:"response,omitempty"`
  48. StatusCode int `json:"statuscode,omitempty"`
  49. PushSecret bool `json:"pushsecret,omitempty"`
  50. Secret secret `json:"secret,omitempty"`
  51. }
  52. type want struct {
  53. Path string `json:"path,omitempty"`
  54. Body *string `json:"body,omitempty"`
  55. Err string `json:"err,omitempty"`
  56. Result string `json:"result,omitempty"`
  57. ResultMap map[string]string `json:"resultmap,omitempty"`
  58. }
  59. var testCases = `
  60. case: error url
  61. args:
  62. url: /api/getsecret?id={{ .unclosed.template
  63. want:
  64. err: failed to parse url
  65. ---
  66. case: error body
  67. args:
  68. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  69. body: Body error {{ .unclosed.template
  70. want:
  71. err: failed to parse body
  72. ---
  73. case: error connection
  74. args:
  75. url: 1/api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  76. want:
  77. err: failed to call endpoint
  78. ---
  79. case: error no secret err
  80. args:
  81. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  82. key: testkey
  83. version: 1
  84. statuscode: 404
  85. response: not found
  86. want:
  87. path: /api/getsecret?id=testkey&version=1
  88. err: ` + esv1.NoSecretErr.Error() + `
  89. ---
  90. case: error server error
  91. args:
  92. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  93. key: testkey
  94. version: 1
  95. statuscode: 500
  96. response: server error
  97. want:
  98. path: /api/getsecret?id=testkey&version=1
  99. err: endpoint gave error 500
  100. ---
  101. case: error bad json
  102. args:
  103. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  104. key: testkey
  105. version: 1
  106. jsonpath: $.result.thesecret
  107. response: '{"result":{"thesecret":"secret-value"}'
  108. want:
  109. path: /api/getsecret?id=testkey&version=1
  110. err: failed to parse response json
  111. ---
  112. case: error bad jsonpath
  113. args:
  114. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  115. key: testkey
  116. version: 1
  117. jsonpath: $.result.thesecret
  118. response: '{"result":{"nosecret":"secret-value"}}'
  119. want:
  120. path: /api/getsecret?id=testkey&version=1
  121. err: failed to get response path
  122. ---
  123. case: pull data out of map
  124. args:
  125. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  126. key: testkey
  127. version: 1
  128. jsonpath: $.result.thesecret
  129. response: '{"result":{"thesecret":{"one":"secret-value"}}}'
  130. want:
  131. path: /api/getsecret?id=testkey&version=1
  132. err: ''
  133. result: '{"one":"secret-value"}'
  134. ---
  135. case: error timeout
  136. args:
  137. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  138. key: testkey
  139. version: 1
  140. response: secret-value
  141. timeout: 0.01ms
  142. want:
  143. path: /api/getsecret?id=testkey&version=1
  144. err: context deadline exceeded
  145. ---
  146. case: good plaintext
  147. args:
  148. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  149. key: testkey
  150. version: 1
  151. response: secret-value
  152. want:
  153. path: /api/getsecret?id=testkey&version=1
  154. err: ''
  155. result: secret-value
  156. ---
  157. case: good json
  158. args:
  159. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  160. key: testkey
  161. version: 1
  162. jsonpath: $.result.thesecret
  163. response: '{"result":{"thesecret":"secret-value"}}'
  164. want:
  165. path: /api/getsecret?id=testkey&version=1
  166. err: ''
  167. result: secret-value
  168. ---
  169. case: good json map
  170. args:
  171. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  172. key: testkey
  173. version: 1
  174. jsonpath: $.result
  175. response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value"}}'
  176. want:
  177. path: /api/getsecret?id=testkey&version=1
  178. err: ''
  179. resultmap:
  180. thesecret: secret-value
  181. alsosecret: another-value
  182. ---
  183. case: templated jsonpath good json map
  184. args:
  185. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  186. key: testkey
  187. version: 1
  188. jsonpath: $.{{printf "result" }}
  189. response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value"}}'
  190. want:
  191. path: /api/getsecret?id=testkey&version=1
  192. err: ''
  193. resultmap:
  194. thesecret: secret-value
  195. alsosecret: another-value
  196. ---
  197. case: templated jsonpath invalid template
  198. args:
  199. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  200. key: testkey
  201. version: 1
  202. jsonpath: $.{{printf 'result' }}
  203. response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value"}}'
  204. want:
  205. path: /api/getsecret?id=testkey&version=1
  206. err: "cannot get templated json path"
  207. resultmap:
  208. thesecret: secret-value
  209. alsosecret: another-value
  210. ---
  211. case: good json map string
  212. args:
  213. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  214. key: testkey
  215. version: 1
  216. response: '{"thesecret":"secret-value","alsosecret":"another-value"}'
  217. want:
  218. path: /api/getsecret?id=testkey&version=1
  219. err: ''
  220. resultmap:
  221. thesecret: secret-value
  222. alsosecret: another-value
  223. ---
  224. case: error json map string
  225. args:
  226. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  227. key: testkey
  228. version: 1
  229. response: 'some simple string'
  230. want:
  231. path: /api/getsecret?id=testkey&version=1
  232. err: "failed to parse response json: invalid character"
  233. resultmap: {}
  234. ---
  235. case: error json map
  236. args:
  237. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  238. key: testkey
  239. version: 1
  240. jsonpath: $.result.thesecret
  241. response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value"}}'
  242. want:
  243. path: /api/getsecret?id=testkey&version=1
  244. err: "failed to parse response json from jsonpath"
  245. resultmap: {}
  246. ---
  247. case: good json with good templated jsonpath
  248. args:
  249. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  250. key: testkey
  251. property: thesecret
  252. version: 1
  253. jsonpath: $.result.{{ .remoteRef.property }}
  254. response: '{"result":{"thesecret":"secret-value"}}'
  255. want:
  256. path: /api/getsecret?id=testkey&version=1
  257. err: ''
  258. result: secret-value
  259. ---
  260. case: good json with jsonpath filter
  261. args:
  262. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  263. key: testkey
  264. version: 1
  265. jsonpath: $.secrets[?@.name=="thesecret"].value
  266. response: '{"secrets": [{"name": "thesecret", "value": "secret-value"}, {"name": "alsosecret", "value": "another-value"}]}'
  267. want:
  268. path: /api/getsecret?id=testkey&version=1
  269. err: ''
  270. result: secret-value
  271. ---
  272. case: good json with bad templated jsonpath
  273. args:
  274. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  275. key: testkey
  276. property: thesecret
  277. version: 1
  278. jsonpath: $.result.{{ .remoteRef.property }
  279. response: '{"result":{"thesecret":"secret-value"}}'
  280. want:
  281. path: /api/getsecret?id=testkey&version=1
  282. err: 'template: webhooktemplate:1: unexpected "}" in operand'
  283. ---
  284. case: error with jsonpath filter empty results
  285. args:
  286. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  287. key: testkey
  288. version: 1
  289. jsonpath: $.secrets[?@.name=="thebadsecret"].value
  290. response: '{"secrets": [{"name": "thesecret", "value": "secret-value"}, {"name": "alsosecret", "value": "another-value"}]}'
  291. want:
  292. path: /api/getsecret?id=testkey&version=1
  293. err: "filter worked but didn't get any result"
  294. ---
  295. case: success with jsonpath filter and result array
  296. args:
  297. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  298. key: testkey
  299. version: 1
  300. jsonpath: $..name
  301. response: '{"secrets": [{"name": "thesecret", "value": "secret-value"}, {"name": "alsosecret", "value": "another-value"}]}'
  302. want:
  303. path: /api/getsecret?id=testkey&version=1
  304. err: ''
  305. result: 'thesecret'
  306. ---
  307. case: success with jsonpath filter and result array of ints
  308. args:
  309. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  310. key: testkey
  311. version: 1
  312. jsonpath: $..name
  313. response: '{"secrets": [{"name": 123, "value": "secret-value"}, {"name": 456, "value": "another-value"}]}'
  314. want:
  315. path: /api/getsecret?id=testkey&version=1
  316. err: ''
  317. result: 123
  318. ---
  319. case: support backslash
  320. args:
  321. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  322. key: testkey
  323. version: 1
  324. jsonpath: $.refresh_token
  325. response: '{"access_token":"REDACTED","refresh_token":"RE\/DACTED=="}'
  326. want:
  327. path: /api/getsecret?id=testkey&version=1
  328. err: ''
  329. result: "RE/DACTED=="
  330. ---
  331. case: good json with mixed fields and jsonpath filter
  332. args:
  333. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  334. key: testkey
  335. version: 1
  336. jsonpath: $.result.thesecret
  337. response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value", "id": 1234, "weight": 1.5}}'
  338. want:
  339. path: /api/getsecret?id=testkey&version=1
  340. err: ''
  341. result: secret-value
  342. ---
  343. case: good json with mixed fields to map
  344. args:
  345. url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
  346. key: testkey
  347. version: 1
  348. jsonpath: $.result
  349. response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value", "id": 1234, "weight": 1.5}}'
  350. want:
  351. path: /api/getsecret?id=testkey&version=1
  352. err: ''
  353. resultmap:
  354. thesecret: secret-value
  355. alsosecret: another-value
  356. id: 1234
  357. weight: 1.5
  358. ---
  359. case: only url encoding for url templates
  360. args:
  361. url: /api/getsecrets?folder={{ .remoteRef.key }}
  362. body: '{"folder": "{{ .remoteRef.key }}"}'
  363. key: /myapp/secrets
  364. want:
  365. path: /api/getsecrets?folder=%2Fmyapp%2Fsecrets
  366. body: '{"folder": "/myapp/secrets"}'
  367. ---
  368. case: namespace template in headers
  369. args:
  370. url: /api/getsecret?id={{ .remoteRef.key }}
  371. key: testkey
  372. response: secret-value
  373. want:
  374. path: /api/getsecret?id=testkey
  375. err: ''
  376. result: secret-value
  377. ---
  378. case: namespace template in url
  379. args:
  380. url: /api/getsecret?id={{ .remoteRef.key }}&namespace={{ .remoteRef.namespace }}
  381. key: testkey
  382. response: secret-value
  383. want:
  384. path: /api/getsecret?id=testkey&namespace=testnamespace
  385. err: ''
  386. result: secret-value
  387. `
  388. func TestWebhookGetSecret(t *testing.T) {
  389. ydec := yaml.NewDecoder(bytes.NewReader([]byte(testCases)))
  390. for {
  391. var tc testCase
  392. if err := ydec.Decode(&tc); err != nil {
  393. if !errors.Is(err, io.EOF) {
  394. t.Errorf("testcase decode error %v", err)
  395. }
  396. break
  397. }
  398. runTestCase(tc, t)
  399. }
  400. }
  401. var testCasesPushSecret = `
  402. ---
  403. case: secret key not found
  404. args:
  405. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}&secret={{ .remoteRef.secretKey }}
  406. key: testkey
  407. secretkey: not-found
  408. pushsecret: true
  409. secret:
  410. name: test-secret
  411. data:
  412. secretkey: value
  413. want:
  414. path: /api/pushsecret?id=testkey&secret=not-found
  415. err: 'failed to find secret key in secret with key: not-found'
  416. ---
  417. case: default body good json
  418. args:
  419. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}&secret={{ .remoteRef.secretKey }}
  420. key: testkey
  421. secretkey: secretkey
  422. pushsecret: true
  423. secret:
  424. name: test-secret
  425. data:
  426. secretkey: value
  427. want:
  428. path: /api/pushsecret?id=testkey&secret=secretkey
  429. body: 'value'
  430. err: ''
  431. ---
  432. case: good json
  433. args:
  434. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}&secret={{ .remoteRef.secretKey }}
  435. key: testkey
  436. body: 'pre {{ .remoteRef.remoteKey }} {{ .remoteRef.testkey }} post'
  437. secretkey: secretkey
  438. pushsecret: true
  439. secret:
  440. name: test-secret
  441. data:
  442. secretkey: value
  443. want:
  444. path: /api/pushsecret?id=testkey&secret=secretkey
  445. body: 'pre testkey value post'
  446. err: ''
  447. ---
  448. case: empty body
  449. args:
  450. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}
  451. key: testkey
  452. body: '{{ "" }}'
  453. pushsecret: true
  454. secret:
  455. name: test-secret
  456. data:
  457. secretkey: value
  458. want:
  459. path: /api/pushsecret?id=testkey
  460. body: ''
  461. err: ''
  462. ---
  463. case: default body pushing without secret key
  464. args:
  465. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}
  466. key: testkey
  467. pushsecret: true
  468. secret:
  469. name: test-secret
  470. data:
  471. secretkey: value
  472. want:
  473. path: /api/pushsecret?id=testkey
  474. body: '{"secretkey":"value"}'
  475. err: ''
  476. ---
  477. case: pushing without secret key
  478. args:
  479. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}
  480. key: testkey
  481. body: 'pre {{ .remoteRef.remoteKey }} {{ index (.remoteRef.testkey | fromJson) "secretkey" }} post'
  482. pushsecret: true
  483. secret:
  484. name: test-secret
  485. data:
  486. secretkey: value
  487. want:
  488. path: /api/pushsecret?id=testkey
  489. body: 'pre testkey value post'
  490. err: ''
  491. ---
  492. case: pushing without secret key with dynamic resolution
  493. args:
  494. url: /api/pushsecret?id={{ .remoteRef.remoteKey }}
  495. key: testkey
  496. body: 'pre {{ .remoteRef.remoteKey }} {{ index (index .remoteRef .remoteRef.remoteKey | fromJson) "secretkey" }} post'
  497. pushsecret: true
  498. secret:
  499. name: test-secret
  500. data:
  501. secretkey: value
  502. want:
  503. path: /api/pushsecret?id=testkey
  504. body: 'pre testkey value post'
  505. err: ''
  506. `
  507. func TestWebhookPushSecret(t *testing.T) {
  508. ydec := yaml.NewDecoder(bytes.NewReader([]byte(testCasesPushSecret)))
  509. for {
  510. var tc testCase
  511. if err := ydec.Decode(&tc); err != nil {
  512. if !errors.Is(err, io.EOF) {
  513. t.Errorf("testcase decode error %v", err)
  514. }
  515. break
  516. }
  517. runTestCase(tc, t)
  518. }
  519. }
  520. func testCaseServer(tc testCase, t *testing.T) *httptest.Server {
  521. // Start a new server for every test case because the server wants to check the expected api path
  522. return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
  523. if tc.Want.Path != "" && req.URL.String() != tc.Want.Path {
  524. t.Errorf("%s: unexpected api path: %s, expected %s", tc.Case, req.URL.String(), tc.Want.Path)
  525. }
  526. if tc.Want.Body != nil {
  527. b, _ := io.ReadAll(req.Body)
  528. if tc.Want.Body != nil && string(b) != *tc.Want.Body {
  529. t.Errorf("%s: unexpected body: %s, expected %s", tc.Case, string(b), *tc.Want.Body)
  530. }
  531. }
  532. if tc.Args.StatusCode != 0 {
  533. rw.WriteHeader(tc.Args.StatusCode)
  534. }
  535. rw.Write([]byte(tc.Args.Response))
  536. }))
  537. }
  538. func parseTimeout(timeout string) (*metav1.Duration, error) {
  539. if timeout == "" {
  540. return nil, nil
  541. }
  542. dur, err := time.ParseDuration(timeout)
  543. if err != nil {
  544. return nil, err
  545. }
  546. return &metav1.Duration{Duration: dur}, nil
  547. }
  548. func runTestCase(tc testCase, t *testing.T) {
  549. t.Run(tc.Case, func(t *testing.T) {
  550. ts := testCaseServer(tc, t)
  551. defer ts.Close()
  552. testStore := makeClusterSecretStore(ts.URL, tc.Args)
  553. var err error
  554. timeout, err := parseTimeout(tc.Args.Timeout)
  555. if err != nil {
  556. t.Errorf("%s: error parsing timeout '%s': %s", tc.Case, tc.Args.Timeout, err.Error())
  557. return
  558. }
  559. testStore.Spec.Provider.Webhook.Timeout = timeout
  560. testProv := &Provider{}
  561. client, err := testProv.NewClient(context.Background(), testStore, nil, "testnamespace")
  562. if err != nil {
  563. t.Errorf("%s: error creating client: %s", tc.Case, err.Error())
  564. return
  565. }
  566. if tc.Want.ResultMap != nil && !tc.Args.PushSecret {
  567. testGetSecretMap(tc, t, client)
  568. } else if !tc.Args.PushSecret {
  569. testGetSecret(tc, t, client)
  570. } else {
  571. testPushSecret(tc, t, client)
  572. }
  573. })
  574. }
  575. func testGetSecretMap(tc testCase, t *testing.T, client esv1.SecretsClient) {
  576. testRef := esv1.ExternalSecretDataRemoteRef{
  577. Key: tc.Args.Key,
  578. Version: tc.Args.Version,
  579. }
  580. secretmap, err := client.GetSecretMap(context.Background(), testRef)
  581. errStr := ""
  582. if err != nil {
  583. errStr = err.Error()
  584. }
  585. if (tc.Want.Err == "") != (errStr == "") || !strings.Contains(errStr, tc.Want.Err) {
  586. t.Errorf("%s: unexpected error: '%s' (expected '%s')", tc.Case, errStr, tc.Want.Err)
  587. }
  588. if err == nil {
  589. for wantkey, wantval := range tc.Want.ResultMap {
  590. gotval, ok := secretmap[wantkey]
  591. if !ok {
  592. t.Errorf("%s: unexpected response: wanted key '%s' not found", tc.Case, wantkey)
  593. } else if string(gotval) != wantval {
  594. t.Errorf("%s: unexpected response: key '%s' = '%s' (expected '%s')", tc.Case, wantkey, wantval, gotval)
  595. }
  596. }
  597. }
  598. }
  599. func testGetSecret(tc testCase, t *testing.T, client esv1.SecretsClient) {
  600. testRef := esv1.ExternalSecretDataRemoteRef{
  601. Key: tc.Args.Key,
  602. Property: tc.Args.Property,
  603. Version: tc.Args.Version,
  604. }
  605. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  606. defer cancel()
  607. secret, err := client.GetSecret(ctx, testRef)
  608. errStr := ""
  609. if err != nil {
  610. errStr = err.Error()
  611. }
  612. if !strings.Contains(errStr, tc.Want.Err) {
  613. t.Errorf("%s: unexpected error: '%s' (expected '%s')", tc.Case, errStr, tc.Want.Err)
  614. }
  615. if err == nil && string(secret) != tc.Want.Result {
  616. t.Errorf("%s: unexpected response: '%s' (expected '%s')", tc.Case, secret, tc.Want.Result)
  617. }
  618. }
  619. func testPushSecret(tc testCase, t *testing.T, client esv1.SecretsClient) {
  620. testRef := v1alpha1.PushSecretData{
  621. Match: v1alpha1.PushSecretMatch{
  622. SecretKey: tc.Args.SecretKey,
  623. RemoteRef: v1alpha1.PushSecretRemoteRef{
  624. RemoteKey: tc.Args.Key,
  625. },
  626. },
  627. }
  628. data := map[string][]byte{}
  629. for k, v := range tc.Args.Secret.Data {
  630. data[k] = []byte(v)
  631. }
  632. sec := &corev1.Secret{
  633. ObjectMeta: metav1.ObjectMeta{
  634. Name: tc.Args.Secret.Name,
  635. },
  636. Data: data,
  637. }
  638. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  639. defer cancel()
  640. err := client.PushSecret(ctx, sec, testRef)
  641. errStr := ""
  642. if err != nil {
  643. errStr = err.Error()
  644. }
  645. if tc.Want.Err == "" && errStr != "" {
  646. t.Errorf("%s: unexpected error: '%s' (expected '%s')", tc.Case, errStr, tc.Want.Err)
  647. }
  648. if !strings.Contains(errStr, tc.Want.Err) {
  649. t.Errorf("%s: unexpected error: '%s' (expected '%s')", tc.Case, errStr, tc.Want.Err)
  650. }
  651. }
  652. func makeClusterSecretStore(url string, args args) *esv1.ClusterSecretStore {
  653. store := &esv1.ClusterSecretStore{
  654. TypeMeta: metav1.TypeMeta{
  655. Kind: "ClusterSecretStore",
  656. },
  657. ObjectMeta: metav1.ObjectMeta{
  658. Name: "wehbook-store",
  659. Namespace: "default",
  660. },
  661. Spec: esv1.SecretStoreSpec{
  662. Provider: &esv1.SecretStoreProvider{
  663. Webhook: &esv1.WebhookProvider{
  664. URL: url + args.URL,
  665. Body: args.Body,
  666. Headers: map[string]string{
  667. "Content-Type": "application.json",
  668. "X-SecretKey": "{{ .remoteRef.key }}",
  669. "X-Kubernetes-Namespace": "{{ .remoteRef.namespace }}",
  670. },
  671. Result: esv1.WebhookResult{
  672. JSONPath: args.JSONPath,
  673. },
  674. },
  675. },
  676. },
  677. }
  678. return store
  679. }