client_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  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 keepersecurity
  13. import (
  14. "context"
  15. "errors"
  16. "fmt"
  17. "reflect"
  18. "testing"
  19. ksm "github.com/keeper-security/secrets-manager-go/core"
  20. corev1 "k8s.io/api/core/v1"
  21. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  22. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  23. "github.com/external-secrets/external-secrets/pkg/provider/keepersecurity/fake"
  24. testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
  25. )
  26. const (
  27. folderID = "a8ekf031k"
  28. validExistingRecord = "record0/login"
  29. invalidRecord = "record5/login"
  30. outputRecord0 = "{\"title\":\"record0\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":null,\"files\":null}"
  31. outputRecord1 = "{\"title\":\"record1\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":null,\"files\":null}"
  32. outputRecord2 = "{\"title\":\"record2\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}],\"custom\":null,\"files\":null}"
  33. record0 = "record0"
  34. record1 = "record1"
  35. record2 = "record2"
  36. LoginKey = "login"
  37. PasswordKey = "password"
  38. RecordNameFormat = "record%d"
  39. )
  40. func TestClientDeleteSecret(t *testing.T) {
  41. type fields struct {
  42. ksmClient SecurityClient
  43. folderID string
  44. }
  45. type args struct {
  46. ctx context.Context
  47. remoteRef v1beta1.PushSecretRemoteRef
  48. }
  49. tests := []struct {
  50. name string
  51. fields fields
  52. args args
  53. wantErr bool
  54. }{
  55. {
  56. name: "Delete valid secret",
  57. fields: fields{
  58. ksmClient: &fake.MockKeeperClient{
  59. DeleteSecretsFn: func(recrecordUids []string) (map[string]string, error) {
  60. return map[string]string{
  61. record0: record0,
  62. }, nil
  63. },
  64. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  65. return generateRecords()[0], nil
  66. },
  67. },
  68. folderID: folderID,
  69. },
  70. args: args{
  71. context.Background(),
  72. &v1alpha1.PushSecretRemoteRef{
  73. RemoteKey: validExistingRecord,
  74. },
  75. },
  76. wantErr: false,
  77. },
  78. {
  79. name: "Delete invalid secret type",
  80. fields: fields{
  81. ksmClient: &fake.MockKeeperClient{
  82. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  83. return generateRecords()[1], nil
  84. },
  85. },
  86. folderID: folderID,
  87. },
  88. args: args{
  89. context.Background(),
  90. &v1alpha1.PushSecretRemoteRef{
  91. RemoteKey: validExistingRecord,
  92. },
  93. },
  94. wantErr: true,
  95. },
  96. {
  97. name: "Delete non existing secret",
  98. fields: fields{
  99. ksmClient: &fake.MockKeeperClient{
  100. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  101. return nil, errors.New("failed")
  102. },
  103. },
  104. folderID: folderID,
  105. },
  106. args: args{
  107. context.Background(),
  108. &v1alpha1.PushSecretRemoteRef{
  109. RemoteKey: invalidRecord,
  110. },
  111. },
  112. wantErr: true,
  113. },
  114. }
  115. for _, tt := range tests {
  116. t.Run(tt.name, func(t *testing.T) {
  117. c := &Client{
  118. ksmClient: tt.fields.ksmClient,
  119. folderID: tt.fields.folderID,
  120. }
  121. if err := c.DeleteSecret(tt.args.ctx, tt.args.remoteRef); (err != nil) != tt.wantErr {
  122. t.Errorf("DeleteSecret() error = %v, wantErr %v", err, tt.wantErr)
  123. }
  124. })
  125. }
  126. }
  127. func TestClientGetAllSecrets(t *testing.T) {
  128. type fields struct {
  129. ksmClient SecurityClient
  130. folderID string
  131. }
  132. type args struct {
  133. ctx context.Context
  134. ref v1beta1.ExternalSecretFind
  135. }
  136. var path = "path_to_fail"
  137. tests := []struct {
  138. name string
  139. fields fields
  140. args args
  141. want map[string][]byte
  142. wantErr bool
  143. }{
  144. {
  145. name: "Tags not Implemented",
  146. fields: fields{
  147. ksmClient: &fake.MockKeeperClient{},
  148. folderID: folderID,
  149. },
  150. args: args{
  151. ctx: context.Background(),
  152. ref: v1beta1.ExternalSecretFind{
  153. Tags: map[string]string{
  154. "xxx": "yyy",
  155. },
  156. },
  157. },
  158. wantErr: true,
  159. },
  160. {
  161. name: "Path not Implemented",
  162. fields: fields{
  163. ksmClient: &fake.MockKeeperClient{},
  164. folderID: folderID,
  165. },
  166. args: args{
  167. ctx: context.Background(),
  168. ref: v1beta1.ExternalSecretFind{
  169. Path: &path,
  170. },
  171. },
  172. wantErr: true,
  173. },
  174. {
  175. name: "Get secrets with matching regex",
  176. fields: fields{
  177. ksmClient: &fake.MockKeeperClient{
  178. GetSecretsFn: func(strings []string) ([]*ksm.Record, error) {
  179. return generateRecords(), nil
  180. },
  181. },
  182. folderID: folderID,
  183. },
  184. args: args{
  185. ctx: context.Background(),
  186. ref: v1beta1.ExternalSecretFind{
  187. Name: &v1beta1.FindName{
  188. RegExp: "record",
  189. },
  190. },
  191. },
  192. want: map[string][]byte{
  193. record0: []byte(outputRecord0),
  194. record1: []byte(outputRecord1),
  195. record2: []byte(outputRecord2),
  196. },
  197. wantErr: false,
  198. },
  199. {
  200. name: "Get 1 secret with matching regex",
  201. fields: fields{
  202. ksmClient: &fake.MockKeeperClient{
  203. GetSecretsFn: func(strings []string) ([]*ksm.Record, error) {
  204. return generateRecords(), nil
  205. },
  206. },
  207. folderID: folderID,
  208. },
  209. args: args{
  210. ctx: context.Background(),
  211. ref: v1beta1.ExternalSecretFind{
  212. Name: &v1beta1.FindName{
  213. RegExp: record0,
  214. },
  215. },
  216. },
  217. want: map[string][]byte{
  218. record0: []byte(outputRecord0),
  219. },
  220. wantErr: false,
  221. },
  222. }
  223. for _, tt := range tests {
  224. t.Run(tt.name, func(t *testing.T) {
  225. c := &Client{
  226. ksmClient: tt.fields.ksmClient,
  227. folderID: tt.fields.folderID,
  228. }
  229. got, err := c.GetAllSecrets(tt.args.ctx, tt.args.ref)
  230. if (err != nil) != tt.wantErr {
  231. t.Errorf("GetAllSecrets() error = %v, wantErr %v", err, tt.wantErr)
  232. return
  233. }
  234. if !reflect.DeepEqual(got, tt.want) {
  235. t.Errorf("GetAllSecrets() got = %v, want %v", got, tt.want)
  236. }
  237. })
  238. }
  239. }
  240. func TestClientGetSecret(t *testing.T) {
  241. type fields struct {
  242. ksmClient SecurityClient
  243. folderID string
  244. }
  245. type args struct {
  246. ctx context.Context
  247. ref v1beta1.ExternalSecretDataRemoteRef
  248. }
  249. tests := []struct {
  250. name string
  251. fields fields
  252. args args
  253. want []byte
  254. wantErr bool
  255. }{
  256. {
  257. name: "Get Secret with a property",
  258. fields: fields{
  259. ksmClient: &fake.MockKeeperClient{
  260. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  261. return []*ksm.Record{generateRecords()[0]}, nil
  262. },
  263. },
  264. folderID: folderID,
  265. },
  266. args: args{
  267. ctx: context.Background(),
  268. ref: v1beta1.ExternalSecretDataRemoteRef{
  269. Key: record0,
  270. Property: LoginKey,
  271. },
  272. },
  273. want: []byte("foo"),
  274. wantErr: false,
  275. },
  276. {
  277. name: "Get Secret without property",
  278. fields: fields{
  279. ksmClient: &fake.MockKeeperClient{
  280. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  281. return []*ksm.Record{generateRecords()[0]}, nil
  282. },
  283. },
  284. folderID: folderID,
  285. },
  286. args: args{
  287. ctx: context.Background(),
  288. ref: v1beta1.ExternalSecretDataRemoteRef{
  289. Key: record0,
  290. },
  291. },
  292. want: []byte(outputRecord0),
  293. wantErr: false,
  294. },
  295. {
  296. name: "Get non existing secret",
  297. fields: fields{
  298. ksmClient: &fake.MockKeeperClient{
  299. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  300. return nil, errors.New("not found")
  301. },
  302. },
  303. folderID: folderID,
  304. },
  305. args: args{
  306. ctx: context.Background(),
  307. ref: v1beta1.ExternalSecretDataRemoteRef{
  308. Key: "record5",
  309. },
  310. },
  311. wantErr: true,
  312. },
  313. {
  314. name: "Get valid secret with non existing property",
  315. fields: fields{
  316. ksmClient: &fake.MockKeeperClient{
  317. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  318. return []*ksm.Record{generateRecords()[0]}, nil
  319. },
  320. },
  321. folderID: folderID,
  322. },
  323. args: args{
  324. ctx: context.Background(),
  325. ref: v1beta1.ExternalSecretDataRemoteRef{
  326. Key: record0,
  327. Property: "invalid",
  328. },
  329. },
  330. wantErr: true,
  331. },
  332. }
  333. for _, tt := range tests {
  334. t.Run(tt.name, func(t *testing.T) {
  335. c := &Client{
  336. ksmClient: tt.fields.ksmClient,
  337. folderID: tt.fields.folderID,
  338. }
  339. got, err := c.GetSecret(tt.args.ctx, tt.args.ref)
  340. if (err != nil) != tt.wantErr {
  341. t.Errorf("GetSecret() error = %v, wantErr %v", err, tt.wantErr)
  342. return
  343. }
  344. if !reflect.DeepEqual(got, tt.want) {
  345. t.Errorf("GetSecret() got = %v, want %v", got, tt.want)
  346. }
  347. })
  348. }
  349. }
  350. func TestClientGetSecretMap(t *testing.T) {
  351. type fields struct {
  352. ksmClient SecurityClient
  353. folderID string
  354. }
  355. type args struct {
  356. ctx context.Context
  357. ref v1beta1.ExternalSecretDataRemoteRef
  358. }
  359. tests := []struct {
  360. name string
  361. fields fields
  362. args args
  363. want map[string][]byte
  364. wantErr bool
  365. }{
  366. {
  367. name: "Get Secret with valid property",
  368. fields: fields{
  369. ksmClient: &fake.MockKeeperClient{
  370. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  371. return []*ksm.Record{generateRecords()[0]}, nil
  372. },
  373. },
  374. folderID: folderID,
  375. },
  376. args: args{
  377. ctx: context.Background(),
  378. ref: v1beta1.ExternalSecretDataRemoteRef{
  379. Key: record0,
  380. Property: LoginKey,
  381. },
  382. },
  383. want: map[string][]byte{
  384. LoginKey: []byte("foo"),
  385. },
  386. wantErr: false,
  387. },
  388. {
  389. name: "Get Secret without property",
  390. fields: fields{
  391. ksmClient: &fake.MockKeeperClient{
  392. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  393. return []*ksm.Record{generateRecords()[0]}, nil
  394. },
  395. },
  396. folderID: folderID,
  397. },
  398. args: args{
  399. ctx: context.Background(),
  400. ref: v1beta1.ExternalSecretDataRemoteRef{
  401. Key: record0,
  402. },
  403. },
  404. want: map[string][]byte{
  405. LoginKey: []byte("foo"),
  406. PasswordKey: []byte("bar"),
  407. },
  408. wantErr: false,
  409. },
  410. {
  411. name: "Get non existing secret",
  412. fields: fields{
  413. ksmClient: &fake.MockKeeperClient{
  414. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  415. return nil, errors.New("not found")
  416. },
  417. },
  418. folderID: folderID,
  419. },
  420. args: args{
  421. ctx: context.Background(),
  422. ref: v1beta1.ExternalSecretDataRemoteRef{
  423. Key: "record5",
  424. },
  425. },
  426. wantErr: true,
  427. },
  428. {
  429. name: "Get Secret with invalid property",
  430. fields: fields{
  431. ksmClient: &fake.MockKeeperClient{
  432. GetSecretsFn: func(filter []string) ([]*ksm.Record, error) {
  433. return []*ksm.Record{generateRecords()[0]}, nil
  434. },
  435. },
  436. folderID: folderID,
  437. },
  438. args: args{
  439. ctx: context.Background(),
  440. ref: v1beta1.ExternalSecretDataRemoteRef{
  441. Key: record0,
  442. Property: "invalid",
  443. },
  444. },
  445. wantErr: true,
  446. },
  447. }
  448. for _, tt := range tests {
  449. t.Run(tt.name, func(t *testing.T) {
  450. c := &Client{
  451. ksmClient: tt.fields.ksmClient,
  452. folderID: tt.fields.folderID,
  453. }
  454. got, err := c.GetSecretMap(tt.args.ctx, tt.args.ref)
  455. if (err != nil) != tt.wantErr {
  456. t.Errorf("GetSecretMap() error = %v, wantErr %v", err, tt.wantErr)
  457. return
  458. }
  459. if !reflect.DeepEqual(got, tt.want) {
  460. t.Errorf("GetSecretMap() got = %v, want %v", got, tt.want)
  461. }
  462. })
  463. }
  464. }
  465. func TestClientPushSecret(t *testing.T) {
  466. secretKey := "secret-key"
  467. type fields struct {
  468. ksmClient SecurityClient
  469. folderID string
  470. }
  471. type args struct {
  472. value []byte
  473. data testingfake.PushSecretData
  474. }
  475. tests := []struct {
  476. name string
  477. fields fields
  478. args args
  479. wantErr bool
  480. }{
  481. {
  482. name: "Invalid remote ref",
  483. fields: fields{
  484. ksmClient: &fake.MockKeeperClient{},
  485. folderID: folderID,
  486. },
  487. args: args{
  488. data: testingfake.PushSecretData{
  489. SecretKey: secretKey,
  490. RemoteKey: record0,
  491. },
  492. value: []byte("foo"),
  493. },
  494. wantErr: true,
  495. },
  496. {
  497. name: "Push new valid secret",
  498. fields: fields{
  499. ksmClient: &fake.MockKeeperClient{
  500. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  501. return nil, errors.New("NotFound")
  502. },
  503. CreateSecretWithRecordDataFn: func(recUID, folderUid string, recordData *ksm.RecordCreate) (string, error) {
  504. return "record5", nil
  505. },
  506. },
  507. folderID: folderID,
  508. },
  509. args: args{
  510. data: testingfake.PushSecretData{
  511. SecretKey: secretKey,
  512. RemoteKey: invalidRecord,
  513. },
  514. value: []byte("foo"),
  515. },
  516. wantErr: false,
  517. },
  518. {
  519. name: "Push existing valid secret",
  520. fields: fields{
  521. ksmClient: &fake.MockKeeperClient{
  522. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  523. return generateRecords()[0], nil
  524. },
  525. SaveFn: func(record *ksm.Record) error {
  526. return nil
  527. },
  528. },
  529. folderID: folderID,
  530. },
  531. args: args{
  532. data: testingfake.PushSecretData{
  533. SecretKey: secretKey,
  534. RemoteKey: validExistingRecord,
  535. },
  536. value: []byte("foo2"),
  537. },
  538. wantErr: false,
  539. },
  540. {
  541. name: "Push existing invalid secret",
  542. fields: fields{
  543. ksmClient: &fake.MockKeeperClient{
  544. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  545. return generateRecords()[1], nil
  546. },
  547. SaveFn: func(record *ksm.Record) error {
  548. return nil
  549. },
  550. },
  551. folderID: folderID,
  552. },
  553. args: args{
  554. data: testingfake.PushSecretData{
  555. SecretKey: secretKey,
  556. RemoteKey: validExistingRecord,
  557. },
  558. value: []byte("foo2"),
  559. },
  560. wantErr: true,
  561. },
  562. {
  563. name: "Unable to push new valid secret",
  564. fields: fields{
  565. ksmClient: &fake.MockKeeperClient{
  566. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  567. return nil, errors.New("NotFound")
  568. },
  569. CreateSecretWithRecordDataFn: func(recUID, folderUID string, recordData *ksm.RecordCreate) (string, error) {
  570. return "", errors.New("Unable to push")
  571. },
  572. },
  573. folderID: folderID,
  574. },
  575. args: args{
  576. data: testingfake.PushSecretData{
  577. SecretKey: secretKey,
  578. RemoteKey: invalidRecord,
  579. },
  580. value: []byte("foo"),
  581. },
  582. wantErr: true,
  583. },
  584. {
  585. name: "Unable to save existing valid secret",
  586. fields: fields{
  587. ksmClient: &fake.MockKeeperClient{
  588. GetSecretByTitleFn: func(recordTitle string) (*ksm.Record, error) {
  589. return generateRecords()[0], nil
  590. },
  591. SaveFn: func(record *ksm.Record) error {
  592. return errors.New("Unable to save")
  593. },
  594. },
  595. folderID: folderID,
  596. },
  597. args: args{
  598. data: testingfake.PushSecretData{
  599. SecretKey: secretKey,
  600. RemoteKey: validExistingRecord,
  601. },
  602. value: []byte("foo2"),
  603. },
  604. wantErr: true,
  605. },
  606. }
  607. for _, tt := range tests {
  608. t.Run(tt.name, func(t *testing.T) {
  609. c := &Client{
  610. ksmClient: tt.fields.ksmClient,
  611. folderID: tt.fields.folderID,
  612. }
  613. s := &corev1.Secret{Data: map[string][]byte{secretKey: tt.args.value}}
  614. if err := c.PushSecret(context.Background(), s, tt.args.data); (err != nil) != tt.wantErr {
  615. t.Errorf("PushSecret() error = %v, wantErr %v", err, tt.wantErr)
  616. }
  617. })
  618. }
  619. }
  620. func generateRecords() []*ksm.Record {
  621. var records []*ksm.Record
  622. for i := 0; i < 3; i++ {
  623. var record ksm.Record
  624. if i == 0 {
  625. record = ksm.Record{
  626. Uid: fmt.Sprintf(RecordNameFormat, i),
  627. RecordDict: map[string]interface{}{
  628. "type": externalSecretType,
  629. "folderUID": folderID,
  630. },
  631. }
  632. } else {
  633. record = ksm.Record{
  634. Uid: fmt.Sprintf(RecordNameFormat, i),
  635. RecordDict: map[string]interface{}{
  636. "type": LoginType,
  637. "folderUID": folderID,
  638. },
  639. }
  640. }
  641. sec := fmt.Sprintf("{\"title\":\"record%d\",\"type\":\"login\",\"fields\":[{\"type\":\"login\",\"value\":[\"foo\"]},{\"type\":\"password\",\"value\":[\"bar\"]}]}", i)
  642. record.SetTitle(fmt.Sprintf(RecordNameFormat, i))
  643. record.SetStandardFieldValue(LoginKey, "foo")
  644. record.SetStandardFieldValue(PasswordKey, "bar")
  645. record.RawJson = sec
  646. records = append(records, &record)
  647. }
  648. return records
  649. }