client_test.go 16 KB

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