client_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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. }
  279. type args struct {
  280. ctx context.Context
  281. ref esv1.ExternalSecretDataRemoteRef
  282. }
  283. tests := []struct {
  284. name string
  285. fields fields
  286. args args
  287. want []byte
  288. wantErr bool
  289. }{
  290. {
  291. name: "Get Secret with a property (no label)",
  292. fields: fields{
  293. ksmClient: &fake.MockKeeperClient{
  294. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  295. return []*ksm.Record{generateRecords()[0]}, nil
  296. },
  297. },
  298. folderID: folderID,
  299. },
  300. args: args{
  301. ctx: context.Background(),
  302. ref: esv1.ExternalSecretDataRemoteRef{
  303. Key: record0,
  304. Property: LoginKey,
  305. },
  306. },
  307. want: []byte("foo"),
  308. wantErr: false,
  309. },
  310. {
  311. name: "Get Secret without property (no label)",
  312. fields: fields{
  313. ksmClient: &fake.MockKeeperClient{
  314. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  315. return []*ksm.Record{generateRecords()[0]}, nil
  316. },
  317. },
  318. folderID: folderID,
  319. },
  320. args: args{
  321. ctx: context.Background(),
  322. ref: esv1.ExternalSecretDataRemoteRef{
  323. Key: record0,
  324. },
  325. },
  326. want: []byte(outputRecord0),
  327. wantErr: false,
  328. },
  329. {
  330. name: "Get Secret with a property using label",
  331. fields: fields{
  332. ksmClient: &fake.MockKeeperClient{
  333. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  334. return []*ksm.Record{generateRecordWithLabels()}, nil
  335. },
  336. },
  337. folderID: folderID,
  338. },
  339. args: args{
  340. ctx: context.Background(),
  341. ref: esv1.ExternalSecretDataRemoteRef{
  342. Key: recordWithLabels,
  343. Property: UsernameLabel,
  344. },
  345. },
  346. want: []byte("foo"),
  347. wantErr: false,
  348. },
  349. {
  350. name: "Get Secret with a property using type when label exists",
  351. fields: fields{
  352. ksmClient: &fake.MockKeeperClient{
  353. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  354. return []*ksm.Record{generateRecordWithLabels()}, nil
  355. },
  356. },
  357. folderID: folderID,
  358. },
  359. args: args{
  360. ctx: context.Background(),
  361. ref: esv1.ExternalSecretDataRemoteRef{
  362. Key: recordWithLabels,
  363. Property: LoginKey, // Try to access by type when label exists
  364. },
  365. },
  366. wantErr: true, // Should fail because label takes precedence
  367. },
  368. {
  369. name: "Get Secret without property (with labels)",
  370. fields: fields{
  371. ksmClient: &fake.MockKeeperClient{
  372. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  373. return []*ksm.Record{generateRecordWithLabels()}, nil
  374. },
  375. },
  376. folderID: folderID,
  377. },
  378. args: args{
  379. ctx: context.Background(),
  380. ref: esv1.ExternalSecretDataRemoteRef{
  381. Key: recordWithLabels,
  382. },
  383. },
  384. want: []byte(outputRecordWithLabels),
  385. wantErr: false,
  386. },
  387. {
  388. name: "Get secret with multiple matches by ID",
  389. fields: fields{
  390. ksmClient: &fake.MockKeeperClient{
  391. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  392. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  393. },
  394. },
  395. folderID: folderID,
  396. },
  397. args: args{
  398. ctx: context.Background(),
  399. ref: esv1.ExternalSecretDataRemoteRef{
  400. Key: record0,
  401. },
  402. },
  403. want: []byte(outputRecord0),
  404. wantErr: false,
  405. },
  406. {
  407. name: "Get non existing secret",
  408. fields: fields{
  409. ksmClient: &fake.MockKeeperClient{
  410. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  411. return nil, errors.New("not found")
  412. },
  413. },
  414. folderID: folderID,
  415. },
  416. args: args{
  417. ctx: context.Background(),
  418. ref: esv1.ExternalSecretDataRemoteRef{
  419. Key: "record5",
  420. },
  421. },
  422. wantErr: true,
  423. },
  424. {
  425. name: "Get valid secret with non existing property",
  426. fields: fields{
  427. ksmClient: &fake.MockKeeperClient{
  428. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  429. return []*ksm.Record{generateRecords()[0]}, nil
  430. },
  431. },
  432. folderID: folderID,
  433. },
  434. args: args{
  435. ctx: context.Background(),
  436. ref: esv1.ExternalSecretDataRemoteRef{
  437. Key: record0,
  438. Property: "invalid",
  439. },
  440. },
  441. wantErr: true,
  442. },
  443. }
  444. for _, tt := range tests {
  445. t.Run(tt.name, func(t *testing.T) {
  446. c := &Client{
  447. ksmClient: tt.fields.ksmClient,
  448. folderID: tt.fields.folderID,
  449. }
  450. got, err := c.GetSecret(tt.args.ctx, tt.args.ref)
  451. if (err != nil) != tt.wantErr {
  452. t.Errorf("GetSecret() error = %v, wantErr %v", err, tt.wantErr)
  453. return
  454. }
  455. if !reflect.DeepEqual(got, tt.want) {
  456. t.Errorf("GetSecret() got = %v, want %v", got, tt.want)
  457. }
  458. })
  459. }
  460. }
  461. func TestClientGetSecretMap(t *testing.T) {
  462. type fields struct {
  463. ksmClient SecurityClient
  464. folderID string
  465. }
  466. type args struct {
  467. ctx context.Context
  468. ref esv1.ExternalSecretDataRemoteRef
  469. }
  470. tests := []struct {
  471. name string
  472. fields fields
  473. args args
  474. want map[string][]byte
  475. wantErr bool
  476. }{
  477. {
  478. name: "Get Secret with valid property (no label)",
  479. fields: fields{
  480. ksmClient: &fake.MockKeeperClient{
  481. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  482. return []*ksm.Record{generateRecords()[0]}, nil
  483. },
  484. },
  485. folderID: folderID,
  486. },
  487. args: args{
  488. ctx: context.Background(),
  489. ref: esv1.ExternalSecretDataRemoteRef{
  490. Key: record0,
  491. Property: LoginKey,
  492. },
  493. },
  494. want: map[string][]byte{
  495. LoginKey: []byte("foo"),
  496. },
  497. wantErr: false,
  498. },
  499. {
  500. name: "Get Secret without property (no label)",
  501. fields: fields{
  502. ksmClient: &fake.MockKeeperClient{
  503. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  504. return []*ksm.Record{generateRecords()[0]}, nil
  505. },
  506. },
  507. folderID: folderID,
  508. },
  509. args: args{
  510. ctx: context.Background(),
  511. ref: esv1.ExternalSecretDataRemoteRef{
  512. Key: record0,
  513. },
  514. },
  515. want: map[string][]byte{
  516. LoginKey: []byte("foo"),
  517. PasswordKey: []byte("bar"),
  518. fmt.Sprintf(HostKeyFormat, 0): []byte("{\"hostName\":\"mysql\",\"port\":\"3306\"}"),
  519. },
  520. wantErr: false,
  521. },
  522. {
  523. name: "Get Secret with valid property using label",
  524. fields: fields{
  525. ksmClient: &fake.MockKeeperClient{
  526. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  527. return []*ksm.Record{generateRecordWithLabels()}, nil
  528. },
  529. },
  530. folderID: folderID,
  531. },
  532. args: args{
  533. ctx: context.Background(),
  534. ref: esv1.ExternalSecretDataRemoteRef{
  535. Key: recordWithLabels,
  536. Property: UsernameLabel,
  537. },
  538. },
  539. want: map[string][]byte{
  540. UsernameLabel: []byte("foo"),
  541. },
  542. wantErr: false,
  543. },
  544. {
  545. name: "Get Secret without property (with labels)",
  546. fields: fields{
  547. ksmClient: &fake.MockKeeperClient{
  548. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  549. return []*ksm.Record{generateRecordWithLabels()}, nil
  550. },
  551. },
  552. folderID: folderID,
  553. },
  554. args: args{
  555. ctx: context.Background(),
  556. ref: esv1.ExternalSecretDataRemoteRef{
  557. Key: recordWithLabels,
  558. },
  559. },
  560. want: map[string][]byte{
  561. UsernameLabel: []byte("foo"),
  562. PassLabel: []byte("bar"),
  563. fmt.Sprintf(HostKeyFormat, 0): []byte("{\"hostName\":\"mysql\",\"port\":\"3306\"}"),
  564. },
  565. wantErr: false,
  566. },
  567. {
  568. name: "Get non existing secret",
  569. fields: fields{
  570. ksmClient: &fake.MockKeeperClient{
  571. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  572. return nil, errors.New("not found")
  573. },
  574. },
  575. folderID: folderID,
  576. },
  577. args: args{
  578. ctx: context.Background(),
  579. ref: esv1.ExternalSecretDataRemoteRef{
  580. Key: "record5",
  581. },
  582. },
  583. wantErr: true,
  584. },
  585. {
  586. name: "Get Secret with invalid property",
  587. fields: fields{
  588. ksmClient: &fake.MockKeeperClient{
  589. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  590. return []*ksm.Record{generateRecords()[0]}, nil
  591. },
  592. },
  593. folderID: folderID,
  594. },
  595. args: args{
  596. ctx: context.Background(),
  597. ref: esv1.ExternalSecretDataRemoteRef{
  598. Key: record0,
  599. Property: "invalid",
  600. },
  601. },
  602. wantErr: true,
  603. },
  604. }
  605. for _, tt := range tests {
  606. t.Run(tt.name, func(t *testing.T) {
  607. c := &Client{
  608. ksmClient: tt.fields.ksmClient,
  609. folderID: tt.fields.folderID,
  610. }
  611. got, err := c.GetSecretMap(tt.args.ctx, tt.args.ref)
  612. if (err != nil) != tt.wantErr {
  613. t.Errorf("GetSecretMap() error = %v, wantErr %v", err, tt.wantErr)
  614. return
  615. }
  616. if !reflect.DeepEqual(got, tt.want) {
  617. t.Errorf("GetSecretMap() got = %v, want %v", got, tt.want)
  618. }
  619. })
  620. }
  621. }
  622. func TestClientPushSecret(t *testing.T) {
  623. secretKey := "secret-key"
  624. type fields struct {
  625. ksmClient SecurityClient
  626. folderID string
  627. }
  628. type args struct {
  629. value []byte
  630. data testingfake.PushSecretData
  631. }
  632. tests := []struct {
  633. name string
  634. fields fields
  635. args args
  636. wantErr bool
  637. }{
  638. {
  639. name: "Invalid remote ref",
  640. fields: fields{
  641. ksmClient: &fake.MockKeeperClient{},
  642. folderID: folderID,
  643. },
  644. args: args{
  645. data: testingfake.PushSecretData{
  646. SecretKey: secretKey,
  647. RemoteKey: record0,
  648. },
  649. value: []byte("foo"),
  650. },
  651. wantErr: true,
  652. },
  653. {
  654. name: "Push new valid secret",
  655. fields: fields{
  656. ksmClient: &fake.MockKeeperClient{
  657. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  658. return generateRecords()[0:0], nil
  659. },
  660. CreateSecretWithRecordDataFn: func(recUID, folderUid string, recordData *ksm.RecordCreate) (string, error) {
  661. return "record5", nil
  662. },
  663. },
  664. folderID: folderID,
  665. },
  666. args: args{
  667. data: testingfake.PushSecretData{
  668. SecretKey: secretKey,
  669. RemoteKey: invalidRecord,
  670. },
  671. value: []byte("foo"),
  672. },
  673. wantErr: false,
  674. },
  675. {
  676. name: "Push existing valid secret",
  677. fields: fields{
  678. ksmClient: &fake.MockKeeperClient{
  679. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  680. return generateRecords()[0:1], nil
  681. },
  682. SaveFn: func(record *ksm.Record) error {
  683. return nil
  684. },
  685. },
  686. folderID: folderID,
  687. },
  688. args: args{
  689. data: testingfake.PushSecretData{
  690. SecretKey: secretKey,
  691. RemoteKey: validExistingRecord,
  692. },
  693. value: []byte("foo2"),
  694. },
  695. wantErr: false,
  696. },
  697. {
  698. name: "Unable to push new valid secret with multiple matches by Name",
  699. fields: fields{
  700. ksmClient: &fake.MockKeeperClient{
  701. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  702. return []*ksm.Record{generateRecords()[0], generateRecords()[0]}, nil
  703. },
  704. },
  705. folderID: folderID,
  706. },
  707. args: args{
  708. data: testingfake.PushSecretData{
  709. SecretKey: secretKey,
  710. RemoteKey: validExistingRecord,
  711. },
  712. value: []byte("foo"),
  713. },
  714. wantErr: true,
  715. },
  716. {
  717. name: "Unable to push new valid secret",
  718. fields: fields{
  719. ksmClient: &fake.MockKeeperClient{
  720. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  721. return nil, errors.New("NotFound")
  722. },
  723. CreateSecretWithRecordDataFn: func(recUID, folderUID string, recordData *ksm.RecordCreate) (string, error) {
  724. return "", errors.New("Unable to push")
  725. },
  726. },
  727. folderID: folderID,
  728. },
  729. args: args{
  730. data: testingfake.PushSecretData{
  731. SecretKey: secretKey,
  732. RemoteKey: invalidRecord,
  733. },
  734. value: []byte("foo"),
  735. },
  736. wantErr: true,
  737. },
  738. {
  739. name: "Unable to save existing valid secret",
  740. fields: fields{
  741. ksmClient: &fake.MockKeeperClient{
  742. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  743. return generateRecords()[0], nil
  744. },
  745. GetSecretsByTitleFn: func(recordTitle string) (records []*ksm.Record, err error) {
  746. return generateRecords()[0:1], nil
  747. },
  748. SaveFn: func(record *ksm.Record) error {
  749. return errors.New("Unable to save")
  750. },
  751. },
  752. folderID: folderID,
  753. },
  754. args: args{
  755. data: testingfake.PushSecretData{
  756. SecretKey: secretKey,
  757. RemoteKey: validExistingRecord,
  758. },
  759. value: []byte("foo2"),
  760. },
  761. wantErr: true,
  762. },
  763. }
  764. for _, tt := range tests {
  765. t.Run(tt.name, func(t *testing.T) {
  766. c := &Client{
  767. ksmClient: tt.fields.ksmClient,
  768. folderID: tt.fields.folderID,
  769. }
  770. s := &corev1.Secret{Data: map[string][]byte{secretKey: tt.args.value}}
  771. if err := c.PushSecret(context.Background(), s, tt.args.data); (err != nil) != tt.wantErr {
  772. t.Errorf("PushSecret() error = %v, wantErr %v", err, tt.wantErr)
  773. }
  774. })
  775. }
  776. }
  777. func generateRecords() []*ksm.Record {
  778. var records []*ksm.Record
  779. for i := 0; i < 3; i++ {
  780. var record ksm.Record
  781. if i == 0 {
  782. record = ksm.Record{
  783. Uid: fmt.Sprintf(RecordNameFormat, i),
  784. RecordDict: map[string]any{
  785. "type": externalSecretType,
  786. "folderUID": folderID,
  787. },
  788. }
  789. } else {
  790. record = ksm.Record{
  791. Uid: fmt.Sprintf(RecordNameFormat, i),
  792. RecordDict: map[string]any{
  793. "type": LoginType,
  794. "folderUID": folderID,
  795. },
  796. }
  797. }
  798. sec := fmt.Sprintf("{\"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\"}]}]}", i, i)
  799. record.SetTitle(fmt.Sprintf(RecordNameFormat, i))
  800. record.SetStandardFieldValue(LoginKey, "foo")
  801. record.SetStandardFieldValue(PasswordKey, "bar")
  802. record.RawJson = sec
  803. records = append(records, &record)
  804. }
  805. return records
  806. }
  807. func generateRecordWithLabels() *ksm.Record {
  808. record := ksm.Record{
  809. Uid: recordWithLabels,
  810. RecordDict: map[string]any{
  811. "type": externalSecretType,
  812. "folderUID": folderID,
  813. },
  814. }
  815. // Fields with labels - using label as key
  816. sec := fmt.Sprintf("{\"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\"}]}]}", recordWithLabels, UsernameLabel, PassLabel)
  817. record.SetTitle(recordWithLabels)
  818. record.SetStandardFieldValue(LoginKey, "foo")
  819. record.SetStandardFieldValue(PasswordKey, "bar")
  820. record.RawJson = sec
  821. return &record
  822. }