client_test.go 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  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 keepersecurity
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "reflect"
  19. "testing"
  20. ksm "github.com/keeper-security/secrets-manager-go/core"
  21. corev1 "k8s.io/api/core/v1"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  24. "github.com/external-secrets/external-secrets/providers/v1/keepersecurity/fake"
  25. testingfake "github.com/external-secrets/external-secrets/runtime/testing/fake"
  26. )
  27. const (
  28. folderID = "a8ekf031k"
  29. validExistingRecord = "record0/login"
  30. invalidRecord = "record5/login"
  31. outputRecord0 = "{\"title\":\"record0\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host0\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
  32. outputRecord1 = "{\"title\":\"record1\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host1\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
  33. outputRecord2 = "{\"title\":\"record2\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host2\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
  34. outputRecordWithLabels = "{\"title\":\"recordWithLabels\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"label\":\"username\",\"value\":[\"foo\"]},{\"type\":\"password\",\"label\":\"pass\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host0\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}],\"files\":null}"
  35. record0 = "record0"
  36. record1 = "record1"
  37. record2 = "record2"
  38. recordWithLabels = "recordWithLabels"
  39. LoginKey = "login"
  40. PasswordKey = "password"
  41. HostKeyFormat = "host%d"
  42. RecordNameFormat = "record%d"
  43. UsernameLabel = "username"
  44. PassLabel = "pass"
  45. )
  46. func TestClientDeleteSecret(t *testing.T) {
  47. type fields struct {
  48. ksmClient SecurityClient
  49. folderID string
  50. }
  51. type args struct {
  52. ctx context.Context
  53. remoteRef esv1.PushSecretRemoteRef
  54. }
  55. tests := []struct {
  56. name string
  57. fields fields
  58. args args
  59. wantErr bool
  60. }{
  61. {
  62. name: "Delete valid secret",
  63. fields: fields{
  64. ksmClient: &fake.MockKeeperClient{
  65. DeleteSecretsFn: func(recrecordUids []string) (map[string]string, error) {
  66. return map[string]string{
  67. record0: record0,
  68. }, nil
  69. },
  70. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  71. return generateRecords()[:1], nil
  72. },
  73. },
  74. folderID: folderID,
  75. },
  76. args: args{
  77. context.Background(),
  78. &v1alpha1.PushSecretRemoteRef{
  79. RemoteKey: validExistingRecord,
  80. },
  81. },
  82. wantErr: false,
  83. },
  84. {
  85. name: "Delete secret with multiple matches by Name",
  86. fields: fields{
  87. ksmClient: &fake.MockKeeperClient{
  88. DeleteSecretsFn: func(recrecordUids []string) (map[string]string, error) {
  89. return map[string]string{
  90. record0: record0,
  91. }, nil
  92. },
  93. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  94. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  95. },
  96. },
  97. folderID: folderID,
  98. },
  99. args: args{
  100. context.Background(),
  101. &v1alpha1.PushSecretRemoteRef{
  102. RemoteKey: validExistingRecord,
  103. },
  104. },
  105. wantErr: true,
  106. },
  107. {
  108. name: "Delete non existing secret",
  109. fields: fields{
  110. ksmClient: &fake.MockKeeperClient{
  111. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  112. return nil, errors.New("failed")
  113. },
  114. },
  115. folderID: folderID,
  116. },
  117. args: args{
  118. context.Background(),
  119. &v1alpha1.PushSecretRemoteRef{
  120. RemoteKey: invalidRecord,
  121. },
  122. },
  123. wantErr: true,
  124. },
  125. }
  126. for _, tt := range tests {
  127. t.Run(tt.name, func(t *testing.T) {
  128. c := &Client{
  129. ksmClient: tt.fields.ksmClient,
  130. folderID: tt.fields.folderID,
  131. }
  132. if err := c.DeleteSecret(tt.args.ctx, tt.args.remoteRef); (err != nil) != tt.wantErr {
  133. t.Errorf("DeleteSecret() error = %v, wantErr %v", err, tt.wantErr)
  134. }
  135. })
  136. }
  137. }
  138. func TestClientGetAllSecrets(t *testing.T) {
  139. type fields struct {
  140. ksmClient SecurityClient
  141. folderID string
  142. }
  143. type args struct {
  144. ctx context.Context
  145. ref esv1.ExternalSecretFind
  146. }
  147. var path = "path_to_fail"
  148. tests := []struct {
  149. name string
  150. fields fields
  151. args args
  152. want map[string][]byte
  153. wantErr bool
  154. }{
  155. {
  156. name: "Tags not Implemented",
  157. fields: fields{
  158. ksmClient: &fake.MockKeeperClient{},
  159. folderID: folderID,
  160. },
  161. args: args{
  162. ctx: context.Background(),
  163. ref: esv1.ExternalSecretFind{
  164. Tags: map[string]string{
  165. "xxx": "yyy",
  166. },
  167. },
  168. },
  169. wantErr: true,
  170. },
  171. {
  172. name: "Path not Implemented",
  173. fields: fields{
  174. ksmClient: &fake.MockKeeperClient{},
  175. folderID: folderID,
  176. },
  177. args: args{
  178. ctx: context.Background(),
  179. ref: esv1.ExternalSecretFind{
  180. Path: &path,
  181. },
  182. },
  183. wantErr: true,
  184. },
  185. {
  186. name: "Get secrets with matching regex",
  187. fields: fields{
  188. ksmClient: &fake.MockKeeperClient{
  189. GetSecretsFn: func(strings []string) ([]*ksm.Record, error) {
  190. return generateRecords(), nil
  191. },
  192. },
  193. folderID: folderID,
  194. },
  195. args: args{
  196. ctx: context.Background(),
  197. ref: esv1.ExternalSecretFind{
  198. Name: &esv1.FindName{
  199. RegExp: "record",
  200. },
  201. },
  202. },
  203. want: map[string][]byte{
  204. record0: []byte(outputRecord0),
  205. record1: []byte(outputRecord1),
  206. record2: []byte(outputRecord2),
  207. },
  208. wantErr: false,
  209. },
  210. {
  211. name: "Get 1 secret with matching regex",
  212. fields: fields{
  213. ksmClient: &fake.MockKeeperClient{
  214. GetSecretsFn: func(strings []string) ([]*ksm.Record, error) {
  215. return generateRecords(), nil
  216. },
  217. },
  218. folderID: folderID,
  219. },
  220. args: args{
  221. ctx: context.Background(),
  222. ref: esv1.ExternalSecretFind{
  223. Name: &esv1.FindName{
  224. RegExp: record0,
  225. },
  226. },
  227. },
  228. want: map[string][]byte{
  229. record0: []byte(outputRecord0),
  230. },
  231. wantErr: false,
  232. },
  233. {
  234. name: "Get secrets with labels using matching regex",
  235. fields: fields{
  236. ksmClient: &fake.MockKeeperClient{
  237. GetSecretsFn: func(strings []string) ([]*ksm.Record, error) {
  238. return []*ksm.Record{generateRecordWithLabels()}, nil
  239. },
  240. },
  241. folderID: folderID,
  242. },
  243. args: args{
  244. ctx: context.Background(),
  245. ref: esv1.ExternalSecretFind{
  246. Name: &esv1.FindName{
  247. RegExp: recordWithLabels,
  248. },
  249. },
  250. },
  251. want: map[string][]byte{
  252. recordWithLabels: []byte(outputRecordWithLabels),
  253. },
  254. wantErr: false,
  255. },
  256. }
  257. for _, tt := range tests {
  258. t.Run(tt.name, func(t *testing.T) {
  259. c := &Client{
  260. ksmClient: tt.fields.ksmClient,
  261. folderID: tt.fields.folderID,
  262. }
  263. got, err := c.GetAllSecrets(tt.args.ctx, tt.args.ref)
  264. if (err != nil) != tt.wantErr {
  265. t.Errorf("GetAllSecrets() error = %v, wantErr %v", err, tt.wantErr)
  266. return
  267. }
  268. if !reflect.DeepEqual(got, tt.want) {
  269. t.Errorf("GetAllSecrets() got = %v, want %v", got, tt.want)
  270. }
  271. })
  272. }
  273. }
  274. func TestClientGetSecret(t *testing.T) {
  275. type fields struct {
  276. ksmClient SecurityClient
  277. folderID string
  278. getByTitleFallback bool
  279. }
  280. type args struct {
  281. ctx context.Context
  282. ref esv1.ExternalSecretDataRemoteRef
  283. }
  284. tests := []struct {
  285. name string
  286. fields fields
  287. args args
  288. want []byte
  289. wantErr bool
  290. // wantNoSecretErr is asserted for every case, so a failure that is not a
  291. // missing record must not carry the sentinel either.
  292. wantNoSecretErr bool
  293. }{
  294. {
  295. name: "Get Secret with a property (no label)",
  296. fields: fields{
  297. ksmClient: &fake.MockKeeperClient{
  298. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  299. return []*ksm.Record{generateRecords()[0]}, nil
  300. },
  301. },
  302. folderID: folderID,
  303. getByTitleFallback: false,
  304. },
  305. args: args{
  306. ctx: context.Background(),
  307. ref: esv1.ExternalSecretDataRemoteRef{
  308. Key: record0,
  309. Property: LoginKey,
  310. },
  311. },
  312. want: []byte("foo"),
  313. wantErr: false,
  314. },
  315. {
  316. name: "Get Secret without property (no label)",
  317. fields: fields{
  318. ksmClient: &fake.MockKeeperClient{
  319. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  320. return []*ksm.Record{generateRecords()[0]}, nil
  321. },
  322. },
  323. folderID: folderID,
  324. getByTitleFallback: false,
  325. },
  326. args: args{
  327. ctx: context.Background(),
  328. ref: esv1.ExternalSecretDataRemoteRef{
  329. Key: record0,
  330. },
  331. },
  332. want: []byte(outputRecord0),
  333. wantErr: false,
  334. },
  335. {
  336. name: "Get Secret with a property using label",
  337. fields: fields{
  338. ksmClient: &fake.MockKeeperClient{
  339. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  340. return []*ksm.Record{generateRecordWithLabels()}, nil
  341. },
  342. },
  343. folderID: folderID,
  344. getByTitleFallback: false,
  345. },
  346. args: args{
  347. ctx: context.Background(),
  348. ref: esv1.ExternalSecretDataRemoteRef{
  349. Key: recordWithLabels,
  350. Property: UsernameLabel,
  351. },
  352. },
  353. want: []byte("foo"),
  354. wantErr: false,
  355. },
  356. {
  357. name: "Get Secret with a property using type when label exists",
  358. fields: fields{
  359. ksmClient: &fake.MockKeeperClient{
  360. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  361. return []*ksm.Record{generateRecordWithLabels()}, nil
  362. },
  363. },
  364. folderID: folderID,
  365. getByTitleFallback: false,
  366. },
  367. args: args{
  368. ctx: context.Background(),
  369. ref: esv1.ExternalSecretDataRemoteRef{
  370. Key: recordWithLabels,
  371. Property: LoginKey, // Try to access by type when label exists
  372. },
  373. },
  374. wantErr: true, // Should fail because label takes precedence
  375. },
  376. {
  377. name: "Get Secret without property (with labels)",
  378. fields: fields{
  379. ksmClient: &fake.MockKeeperClient{
  380. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  381. return []*ksm.Record{generateRecordWithLabels()}, nil
  382. },
  383. },
  384. folderID: folderID,
  385. getByTitleFallback: false,
  386. },
  387. args: args{
  388. ctx: context.Background(),
  389. ref: esv1.ExternalSecretDataRemoteRef{
  390. Key: recordWithLabels,
  391. },
  392. },
  393. want: []byte(outputRecordWithLabels),
  394. wantErr: false,
  395. },
  396. {
  397. name: "Get secret with multiple matches by ID",
  398. fields: fields{
  399. ksmClient: &fake.MockKeeperClient{
  400. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  401. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  402. },
  403. },
  404. folderID: folderID,
  405. getByTitleFallback: false,
  406. },
  407. args: args{
  408. ctx: context.Background(),
  409. ref: esv1.ExternalSecretDataRemoteRef{
  410. Key: record0,
  411. },
  412. },
  413. want: []byte(outputRecord0),
  414. wantErr: false,
  415. },
  416. {
  417. name: "Get secret by ID",
  418. fields: fields{
  419. ksmClient: &fake.MockKeeperClient{
  420. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  421. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  422. },
  423. },
  424. folderID: folderID,
  425. getByTitleFallback: false,
  426. },
  427. args: args{
  428. ctx: context.Background(),
  429. ref: esv1.ExternalSecretDataRemoteRef{
  430. Key: record0,
  431. },
  432. },
  433. want: []byte(outputRecord0),
  434. wantErr: false,
  435. },
  436. {
  437. name: "Get secret by ID (with fallback)",
  438. fields: fields{
  439. ksmClient: &fake.MockKeeperClient{
  440. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  441. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  442. },
  443. },
  444. folderID: folderID,
  445. getByTitleFallback: true,
  446. },
  447. args: args{
  448. ctx: context.Background(),
  449. ref: esv1.ExternalSecretDataRemoteRef{
  450. Key: record0,
  451. },
  452. },
  453. want: []byte(outputRecord0),
  454. wantErr: false,
  455. },
  456. {
  457. name: "Get non existing secret with client error",
  458. fields: fields{
  459. ksmClient: &fake.MockKeeperClient{
  460. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  461. return nil, errors.New("not found")
  462. },
  463. },
  464. folderID: folderID,
  465. getByTitleFallback: false,
  466. },
  467. args: args{
  468. ctx: context.Background(),
  469. ref: esv1.ExternalSecretDataRemoteRef{
  470. Key: "record5",
  471. },
  472. },
  473. wantErr: true,
  474. },
  475. {
  476. name: "Get non existing secret",
  477. fields: fields{
  478. ksmClient: &fake.MockKeeperClient{
  479. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  480. return []*ksm.Record{}, nil
  481. },
  482. },
  483. folderID: folderID,
  484. getByTitleFallback: false,
  485. },
  486. args: args{
  487. ctx: context.Background(),
  488. ref: esv1.ExternalSecretDataRemoteRef{
  489. Key: "record5",
  490. },
  491. },
  492. wantErr: true,
  493. wantNoSecretErr: true,
  494. },
  495. {
  496. name: "Get non existing secret with fallback",
  497. fields: fields{
  498. ksmClient: &fake.MockKeeperClient{
  499. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  500. return []*ksm.Record{}, nil
  501. },
  502. GetSecretsByTitleFn: func(recordTitle string) ([]*ksm.Record, error) {
  503. return []*ksm.Record{}, nil
  504. },
  505. },
  506. folderID: folderID,
  507. getByTitleFallback: true,
  508. },
  509. args: args{
  510. ctx: context.Background(),
  511. ref: esv1.ExternalSecretDataRemoteRef{
  512. Key: "record5",
  513. },
  514. },
  515. wantErr: true,
  516. wantNoSecretErr: true,
  517. },
  518. {
  519. name: "Get valid secret with non existing property",
  520. fields: fields{
  521. ksmClient: &fake.MockKeeperClient{
  522. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  523. return []*ksm.Record{generateRecords()[0]}, nil
  524. },
  525. },
  526. folderID: folderID,
  527. getByTitleFallback: false,
  528. },
  529. args: args{
  530. ctx: context.Background(),
  531. ref: esv1.ExternalSecretDataRemoteRef{
  532. Key: record0,
  533. Property: "invalid",
  534. },
  535. },
  536. wantErr: true,
  537. },
  538. {
  539. name: "Get secret by name",
  540. fields: fields{
  541. ksmClient: &fake.MockKeeperClient{
  542. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  543. // Return empty list to trigger name lookup
  544. return []*ksm.Record{}, nil
  545. },
  546. GetSecretsByTitleFn: func(recordTitle string) ([]*ksm.Record, error) {
  547. return []*ksm.Record{generateRecords()[0]}, nil
  548. },
  549. },
  550. folderID: folderID,
  551. getByTitleFallback: true,
  552. },
  553. args: args{
  554. ctx: context.Background(),
  555. ref: esv1.ExternalSecretDataRemoteRef{
  556. Key: record0,
  557. },
  558. },
  559. want: []byte(outputRecord0),
  560. wantErr: false,
  561. },
  562. {
  563. name: "Get secret by name with multiple matches",
  564. fields: fields{
  565. ksmClient: &fake.MockKeeperClient{
  566. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  567. // Return empty list to trigger name lookup
  568. return []*ksm.Record{}, nil
  569. },
  570. GetSecretsByTitleFn: func(recordTitle string) ([]*ksm.Record, error) {
  571. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  572. },
  573. },
  574. folderID: folderID,
  575. getByTitleFallback: true,
  576. },
  577. args: args{
  578. ctx: context.Background(),
  579. ref: esv1.ExternalSecretDataRemoteRef{
  580. Key: record0,
  581. },
  582. },
  583. wantErr: true,
  584. },
  585. }
  586. for _, tt := range tests {
  587. t.Run(tt.name, func(t *testing.T) {
  588. c := &Client{
  589. ksmClient: tt.fields.ksmClient,
  590. folderID: tt.fields.folderID,
  591. getByTitleFallback: tt.fields.getByTitleFallback,
  592. }
  593. got, err := c.GetSecret(tt.args.ctx, tt.args.ref)
  594. if (err != nil) != tt.wantErr {
  595. t.Errorf("GetSecret() error = %v, wantErr %v", err, tt.wantErr)
  596. return
  597. }
  598. if isNoSecret := errors.Is(err, esv1.NoSecretErr); isNoSecret != tt.wantNoSecretErr {
  599. t.Errorf("GetSecret() errors.Is(err, NoSecretErr) = %v, want %v (err = %v)", isNoSecret, tt.wantNoSecretErr, err)
  600. }
  601. if !reflect.DeepEqual(got, tt.want) {
  602. t.Errorf("GetSecret() got = %v, want %v", got, tt.want)
  603. }
  604. })
  605. }
  606. }
  607. func TestClientGetSecretMap(t *testing.T) {
  608. type fields struct {
  609. ksmClient SecurityClient
  610. folderID string
  611. }
  612. type args struct {
  613. ctx context.Context
  614. ref esv1.ExternalSecretDataRemoteRef
  615. }
  616. tests := []struct {
  617. name string
  618. fields fields
  619. args args
  620. want map[string][]byte
  621. wantErr bool
  622. wantNoSecretErr bool
  623. }{
  624. {
  625. name: "Get Secret with valid property (no label)",
  626. fields: fields{
  627. ksmClient: &fake.MockKeeperClient{
  628. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  629. return []*ksm.Record{generateRecords()[0]}, nil
  630. },
  631. },
  632. folderID: folderID,
  633. },
  634. args: args{
  635. ctx: context.Background(),
  636. ref: esv1.ExternalSecretDataRemoteRef{
  637. Key: record0,
  638. Property: LoginKey,
  639. },
  640. },
  641. want: map[string][]byte{
  642. LoginKey: []byte("foo"),
  643. },
  644. wantErr: false,
  645. },
  646. {
  647. name: "Get Secret without property (no label)",
  648. fields: fields{
  649. ksmClient: &fake.MockKeeperClient{
  650. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  651. return []*ksm.Record{generateRecords()[0]}, nil
  652. },
  653. },
  654. folderID: folderID,
  655. },
  656. args: args{
  657. ctx: context.Background(),
  658. ref: esv1.ExternalSecretDataRemoteRef{
  659. Key: record0,
  660. },
  661. },
  662. want: map[string][]byte{
  663. LoginKey: []byte("foo"),
  664. PasswordKey: []byte("bar"),
  665. fmt.Sprintf(HostKeyFormat, 0): []byte("{\"hostName\":\"mysql\",\"port\":\"3306\"}"),
  666. },
  667. wantErr: false,
  668. },
  669. {
  670. name: "Get Secret with valid property using label",
  671. fields: fields{
  672. ksmClient: &fake.MockKeeperClient{
  673. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  674. return []*ksm.Record{generateRecordWithLabels()}, nil
  675. },
  676. },
  677. folderID: folderID,
  678. },
  679. args: args{
  680. ctx: context.Background(),
  681. ref: esv1.ExternalSecretDataRemoteRef{
  682. Key: recordWithLabels,
  683. Property: UsernameLabel,
  684. },
  685. },
  686. want: map[string][]byte{
  687. UsernameLabel: []byte("foo"),
  688. },
  689. wantErr: false,
  690. },
  691. {
  692. name: "Get Secret without property (with labels)",
  693. fields: fields{
  694. ksmClient: &fake.MockKeeperClient{
  695. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  696. return []*ksm.Record{generateRecordWithLabels()}, nil
  697. },
  698. },
  699. folderID: folderID,
  700. },
  701. args: args{
  702. ctx: context.Background(),
  703. ref: esv1.ExternalSecretDataRemoteRef{
  704. Key: recordWithLabels,
  705. },
  706. },
  707. want: map[string][]byte{
  708. UsernameLabel: []byte("foo"),
  709. PassLabel: []byte("bar"),
  710. fmt.Sprintf(HostKeyFormat, 0): []byte("{\"hostName\":\"mysql\",\"port\":\"3306\"}"),
  711. },
  712. wantErr: false,
  713. },
  714. {
  715. // The API call itself fails here, so this must not be reported as a
  716. // missing record.
  717. name: "Get secret when the API call fails",
  718. fields: fields{
  719. ksmClient: &fake.MockKeeperClient{
  720. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  721. return nil, errors.New("keeper API unavailable")
  722. },
  723. },
  724. folderID: folderID,
  725. },
  726. args: args{
  727. ctx: context.Background(),
  728. ref: esv1.ExternalSecretDataRemoteRef{
  729. Key: "record5",
  730. },
  731. },
  732. wantErr: true,
  733. },
  734. {
  735. name: "Get non existing secret",
  736. fields: fields{
  737. ksmClient: &fake.MockKeeperClient{
  738. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  739. return []*ksm.Record{}, nil
  740. },
  741. },
  742. folderID: folderID,
  743. },
  744. args: args{
  745. ctx: context.Background(),
  746. ref: esv1.ExternalSecretDataRemoteRef{
  747. Key: "record5",
  748. },
  749. },
  750. wantErr: true,
  751. wantNoSecretErr: true,
  752. },
  753. {
  754. name: "Get Secret with invalid property",
  755. fields: fields{
  756. ksmClient: &fake.MockKeeperClient{
  757. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  758. return []*ksm.Record{generateRecords()[0]}, nil
  759. },
  760. },
  761. folderID: folderID,
  762. },
  763. args: args{
  764. ctx: context.Background(),
  765. ref: esv1.ExternalSecretDataRemoteRef{
  766. Key: record0,
  767. Property: "invalid",
  768. },
  769. },
  770. wantErr: true,
  771. },
  772. }
  773. for _, tt := range tests {
  774. t.Run(tt.name, func(t *testing.T) {
  775. c := &Client{
  776. ksmClient: tt.fields.ksmClient,
  777. folderID: tt.fields.folderID,
  778. }
  779. got, err := c.GetSecretMap(tt.args.ctx, tt.args.ref)
  780. if (err != nil) != tt.wantErr {
  781. t.Errorf("GetSecretMap() error = %v, wantErr %v", err, tt.wantErr)
  782. return
  783. }
  784. if isNoSecret := errors.Is(err, esv1.NoSecretErr); isNoSecret != tt.wantNoSecretErr {
  785. t.Errorf("GetSecretMap() errors.Is(err, NoSecretErr) = %v, want %v (err = %v)", isNoSecret, tt.wantNoSecretErr, err)
  786. }
  787. if !reflect.DeepEqual(got, tt.want) {
  788. t.Errorf("GetSecretMap() got = %v, want %v", got, tt.want)
  789. }
  790. })
  791. }
  792. }
  793. func TestClientPushSecret(t *testing.T) {
  794. secretKey := "secret-key"
  795. type fields struct {
  796. ksmClient SecurityClient
  797. folderID string
  798. }
  799. type args struct {
  800. value []byte
  801. data testingfake.PushSecretData
  802. }
  803. tests := []struct {
  804. name string
  805. fields fields
  806. args args
  807. wantErr bool
  808. }{
  809. {
  810. name: "Invalid remote ref",
  811. fields: fields{
  812. ksmClient: &fake.MockKeeperClient{},
  813. folderID: folderID,
  814. },
  815. args: args{
  816. data: testingfake.PushSecretData{
  817. SecretKey: secretKey,
  818. RemoteKey: record0,
  819. },
  820. value: []byte("foo"),
  821. },
  822. wantErr: true,
  823. },
  824. {
  825. name: "Push new valid secret",
  826. fields: fields{
  827. ksmClient: &fake.MockKeeperClient{
  828. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  829. return generateRecords()[0:0], nil
  830. },
  831. CreateSecretWithRecordDataFn: func(recUID, folderUid string, recordData *ksm.RecordCreate) (string, error) {
  832. return "record5", nil
  833. },
  834. },
  835. folderID: folderID,
  836. },
  837. args: args{
  838. data: testingfake.PushSecretData{
  839. SecretKey: secretKey,
  840. RemoteKey: invalidRecord,
  841. },
  842. value: []byte("foo"),
  843. },
  844. wantErr: false,
  845. },
  846. {
  847. name: "Push existing valid secret",
  848. fields: fields{
  849. ksmClient: &fake.MockKeeperClient{
  850. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  851. return generateRecords()[0:1], nil
  852. },
  853. SaveFn: func(record *ksm.Record) error {
  854. return nil
  855. },
  856. },
  857. folderID: folderID,
  858. },
  859. args: args{
  860. data: testingfake.PushSecretData{
  861. SecretKey: secretKey,
  862. RemoteKey: validExistingRecord,
  863. },
  864. value: []byte("foo2"),
  865. },
  866. wantErr: false,
  867. },
  868. {
  869. name: "Unable to push new valid secret with multiple matches by Name",
  870. fields: fields{
  871. ksmClient: &fake.MockKeeperClient{
  872. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  873. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  874. },
  875. },
  876. folderID: folderID,
  877. },
  878. args: args{
  879. data: testingfake.PushSecretData{
  880. SecretKey: secretKey,
  881. RemoteKey: validExistingRecord,
  882. },
  883. value: []byte("foo"),
  884. },
  885. wantErr: true,
  886. },
  887. {
  888. name: "Unable to push new valid secret",
  889. fields: fields{
  890. ksmClient: &fake.MockKeeperClient{
  891. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  892. return nil, errors.New("NotFound")
  893. },
  894. CreateSecretWithRecordDataFn: func(recUID, folderUID string, recordData *ksm.RecordCreate) (string, error) {
  895. return "", errors.New("Unable to push")
  896. },
  897. },
  898. folderID: folderID,
  899. },
  900. args: args{
  901. data: testingfake.PushSecretData{
  902. SecretKey: secretKey,
  903. RemoteKey: invalidRecord,
  904. },
  905. value: []byte("foo"),
  906. },
  907. wantErr: true,
  908. },
  909. {
  910. name: "Push new secret fails without folderID",
  911. fields: fields{
  912. ksmClient: &fake.MockKeeperClient{
  913. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  914. return generateRecords()[0:0], nil
  915. },
  916. },
  917. folderID: "",
  918. },
  919. args: args{
  920. data: testingfake.PushSecretData{
  921. SecretKey: secretKey,
  922. RemoteKey: invalidRecord,
  923. },
  924. value: []byte("foo"),
  925. },
  926. wantErr: true,
  927. },
  928. {
  929. name: "Unable to save existing valid secret",
  930. fields: fields{
  931. ksmClient: &fake.MockKeeperClient{
  932. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  933. return generateRecords()[0], nil
  934. },
  935. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  936. return generateRecords()[0:1], nil
  937. },
  938. SaveFn: func(record *ksm.Record) error {
  939. return errors.New("Unable to save")
  940. },
  941. },
  942. folderID: folderID,
  943. },
  944. args: args{
  945. data: testingfake.PushSecretData{
  946. SecretKey: secretKey,
  947. RemoteKey: validExistingRecord,
  948. },
  949. value: []byte("foo2"),
  950. },
  951. wantErr: true,
  952. },
  953. }
  954. for _, tt := range tests {
  955. t.Run(tt.name, func(t *testing.T) {
  956. c := &Client{
  957. ksmClient: tt.fields.ksmClient,
  958. folderID: tt.fields.folderID,
  959. }
  960. s := &corev1.Secret{Data: map[string][]byte{secretKey: tt.args.value}}
  961. if err := c.PushSecret(context.Background(), s, tt.args.data); (err != nil) != tt.wantErr {
  962. t.Errorf("PushSecret() error = %v, wantErr %v", err, tt.wantErr)
  963. }
  964. })
  965. }
  966. }
  967. func generateRecords() []*ksm.Record {
  968. records := make([]*ksm.Record, 0, 3)
  969. for i := range 3 {
  970. var record ksm.Record
  971. if i == 0 {
  972. record = ksm.Record{
  973. Uid: fmt.Sprintf(RecordNameFormat, i),
  974. RecordDict: map[string]any{
  975. "type": externalSecretType,
  976. "folderUID": folderID,
  977. },
  978. }
  979. } else {
  980. record = ksm.Record{
  981. Uid: fmt.Sprintf(RecordNameFormat, i),
  982. RecordDict: map[string]any{
  983. "type": LoginType,
  984. "folderUID": folderID,
  985. },
  986. }
  987. }
  988. sec := fmt.Sprintf(
  989. "{\"title\":\"record%d\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host%d\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}]}",
  990. i,
  991. i,
  992. )
  993. record.SetTitle(fmt.Sprintf(RecordNameFormat, i))
  994. record.SetStandardFieldValue(LoginKey, "foo")
  995. record.SetStandardFieldValue(PasswordKey, "bar")
  996. record.RawJson = sec
  997. records = append(records, &record)
  998. }
  999. return records
  1000. }
  1001. func generateRecordWithLabels() *ksm.Record {
  1002. record := ksm.Record{
  1003. Uid: recordWithLabels,
  1004. RecordDict: map[string]any{
  1005. "type": externalSecretType,
  1006. "folderUID": folderID,
  1007. },
  1008. }
  1009. // Fields with labels - using label as key
  1010. sec := fmt.Sprintf(
  1011. "{\"title\":%q,\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"label\":%q,\"value\":[\"foo\"]},{\"type\":\"password\",\"label\":%q,\"value\":[\"bar\"]}],\"custom\":[{\"type\":\"host\",\"label\":\"host0\",\"value\":[{\"hostName\":\"mysql\",\"port\":\"3306\"}]}]}",
  1012. recordWithLabels,
  1013. UsernameLabel,
  1014. PassLabel,
  1015. )
  1016. record.SetTitle(recordWithLabels)
  1017. record.SetStandardFieldValue(LoginKey, "foo")
  1018. record.SetStandardFieldValue(PasswordKey, "bar")
  1019. record.RawJson = sec
  1020. return &record
  1021. }