client_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  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 onepasswordsdk
  13. import (
  14. "context"
  15. "errors"
  16. "testing"
  17. "github.com/1password/onepassword-sdk-go"
  18. "github.com/stretchr/testify/assert"
  19. "github.com/stretchr/testify/require"
  20. corev1 "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  24. )
  25. func TestProviderGetSecret(t *testing.T) {
  26. tests := []struct {
  27. name string
  28. ref v1.ExternalSecretDataRemoteRef
  29. want []byte
  30. assertError func(t *testing.T, err error)
  31. client func() *onepassword.Client
  32. }{
  33. {
  34. name: "get secret successfully",
  35. client: func() *onepassword.Client {
  36. fc := &fakeClient{
  37. resolveResult: "secret",
  38. }
  39. return &onepassword.Client{
  40. SecretsAPI: fc,
  41. VaultsAPI: fc,
  42. }
  43. },
  44. assertError: func(t *testing.T, err error) {
  45. require.NoError(t, err)
  46. },
  47. ref: v1.ExternalSecretDataRemoteRef{
  48. Key: "secret",
  49. },
  50. want: []byte("secret"),
  51. },
  52. {
  53. name: "get secret with error",
  54. client: func() *onepassword.Client {
  55. fc := &fakeClient{
  56. resolveError: errors.New("fobar"),
  57. }
  58. return &onepassword.Client{
  59. SecretsAPI: fc,
  60. VaultsAPI: fc,
  61. }
  62. },
  63. assertError: func(t *testing.T, err error) {
  64. require.ErrorContains(t, err, "fobar")
  65. },
  66. ref: v1.ExternalSecretDataRemoteRef{
  67. Key: "secret",
  68. },
  69. },
  70. {
  71. name: "get secret version not implemented",
  72. client: func() *onepassword.Client {
  73. fc := &fakeClient{
  74. resolveResult: "secret",
  75. }
  76. return &onepassword.Client{
  77. SecretsAPI: fc,
  78. VaultsAPI: fc,
  79. }
  80. },
  81. ref: v1.ExternalSecretDataRemoteRef{
  82. Key: "secret",
  83. Version: "1",
  84. },
  85. assertError: func(t *testing.T, err error) {
  86. require.ErrorContains(t, err, "is not implemented in the 1Password SDK provider")
  87. },
  88. },
  89. }
  90. for _, tt := range tests {
  91. t.Run(tt.name, func(t *testing.T) {
  92. p := &Provider{
  93. client: tt.client(),
  94. vaultPrefix: "op://vault/",
  95. }
  96. got, err := p.GetSecret(context.Background(), tt.ref)
  97. tt.assertError(t, err)
  98. require.Equal(t, string(got), string(tt.want))
  99. })
  100. }
  101. }
  102. func TestProviderGetSecretMap(t *testing.T) {
  103. tests := []struct {
  104. name string
  105. ref v1.ExternalSecretDataRemoteRef
  106. want map[string][]byte
  107. assertError func(t *testing.T, err error)
  108. client func() *onepassword.Client
  109. }{
  110. {
  111. name: "get secret successfully for files",
  112. client: func() *onepassword.Client {
  113. fc := &fakeClient{}
  114. fl := &fakeLister{
  115. listAllResult: []onepassword.ItemOverview{
  116. {
  117. ID: "test-item-id",
  118. Title: "key",
  119. Category: "login",
  120. VaultID: "vault-id",
  121. },
  122. },
  123. getResult: onepassword.Item{
  124. ID: "test-item-id",
  125. Title: "key",
  126. Category: "login",
  127. VaultID: "vault-id",
  128. Files: []onepassword.ItemFile{
  129. {
  130. Attributes: onepassword.FileAttributes{
  131. Name: "name",
  132. ID: "id",
  133. },
  134. FieldID: "field-id",
  135. },
  136. },
  137. },
  138. fileLister: &fakeFileLister{
  139. readContent: []byte("content"),
  140. },
  141. }
  142. return &onepassword.Client{
  143. SecretsAPI: fc,
  144. ItemsAPI: fl,
  145. VaultsAPI: fc,
  146. }
  147. },
  148. assertError: func(t *testing.T, err error) {
  149. require.NoError(t, err)
  150. },
  151. ref: v1.ExternalSecretDataRemoteRef{
  152. Key: "key",
  153. Property: "file/name",
  154. },
  155. want: map[string][]byte{
  156. "name": []byte("content"),
  157. },
  158. },
  159. {
  160. name: "get secret successfully for fields",
  161. client: func() *onepassword.Client {
  162. fc := &fakeClient{}
  163. fl := &fakeLister{
  164. listAllResult: []onepassword.ItemOverview{
  165. {
  166. ID: "test-item-id",
  167. Title: "key",
  168. Category: "login",
  169. VaultID: "vault-id",
  170. },
  171. },
  172. getResult: onepassword.Item{
  173. ID: "test-item-id",
  174. Title: "key",
  175. Category: "login",
  176. VaultID: "vault-id",
  177. Fields: []onepassword.ItemField{
  178. {
  179. ID: "field-id",
  180. Title: "name",
  181. FieldType: onepassword.ItemFieldTypeConcealed,
  182. Value: "value",
  183. },
  184. },
  185. },
  186. fileLister: &fakeFileLister{
  187. readContent: []byte("content"),
  188. },
  189. }
  190. return &onepassword.Client{
  191. SecretsAPI: fc,
  192. ItemsAPI: fl,
  193. VaultsAPI: fc,
  194. }
  195. },
  196. assertError: func(t *testing.T, err error) {
  197. require.NoError(t, err)
  198. },
  199. ref: v1.ExternalSecretDataRemoteRef{
  200. Key: "key",
  201. Property: "field/name",
  202. },
  203. want: map[string][]byte{
  204. "name": []byte("value"),
  205. },
  206. },
  207. {
  208. name: "get secret fails with fields with same title",
  209. client: func() *onepassword.Client {
  210. fc := &fakeClient{}
  211. fl := &fakeLister{
  212. listAllResult: []onepassword.ItemOverview{
  213. {
  214. ID: "test-item-id",
  215. Title: "key",
  216. Category: "login",
  217. VaultID: "vault-id",
  218. },
  219. },
  220. getResult: onepassword.Item{
  221. ID: "test-item-id",
  222. Title: "key",
  223. Category: "login",
  224. VaultID: "vault-id",
  225. Fields: []onepassword.ItemField{
  226. {
  227. ID: "field-id",
  228. Title: "name",
  229. FieldType: onepassword.ItemFieldTypeConcealed,
  230. Value: "value",
  231. },
  232. {
  233. ID: "field-id",
  234. Title: "name",
  235. FieldType: onepassword.ItemFieldTypeConcealed,
  236. Value: "value",
  237. },
  238. },
  239. },
  240. fileLister: &fakeFileLister{
  241. readContent: []byte("content"),
  242. },
  243. }
  244. return &onepassword.Client{
  245. SecretsAPI: fc,
  246. ItemsAPI: fl,
  247. VaultsAPI: fc,
  248. }
  249. },
  250. assertError: func(t *testing.T, err error) {
  251. require.ErrorContains(t, err, "found more than 1 fields with title 'name' in 'key', got 2")
  252. },
  253. ref: v1.ExternalSecretDataRemoteRef{
  254. Key: "key",
  255. Property: "field/name",
  256. },
  257. },
  258. }
  259. for _, tt := range tests {
  260. t.Run(tt.name, func(t *testing.T) {
  261. p := &Provider{
  262. client: tt.client(),
  263. vaultPrefix: "op://vault/",
  264. }
  265. got, err := p.GetSecretMap(context.Background(), tt.ref)
  266. tt.assertError(t, err)
  267. require.Equal(t, tt.want, got)
  268. })
  269. }
  270. }
  271. func TestProviderValidate(t *testing.T) {
  272. tests := []struct {
  273. name string
  274. want v1.ValidationResult
  275. assertError func(t *testing.T, err error)
  276. client func() *onepassword.Client
  277. vaultPrefix string
  278. }{
  279. {
  280. name: "validate successfully",
  281. client: func() *onepassword.Client {
  282. fc := &fakeClient{
  283. listAllResult: []onepassword.VaultOverview{
  284. {
  285. ID: "test",
  286. Title: "test",
  287. },
  288. },
  289. }
  290. return &onepassword.Client{
  291. SecretsAPI: fc,
  292. VaultsAPI: fc,
  293. }
  294. },
  295. want: v1.ValidationResultReady,
  296. assertError: func(t *testing.T, err error) {
  297. require.NoError(t, err)
  298. },
  299. vaultPrefix: "op://vault/",
  300. },
  301. {
  302. name: "validate error",
  303. client: func() *onepassword.Client {
  304. fc := &fakeClient{
  305. listAllResult: []onepassword.VaultOverview{},
  306. listAllError: errors.New("no vaults found when listing"),
  307. }
  308. return &onepassword.Client{
  309. SecretsAPI: fc,
  310. VaultsAPI: fc,
  311. }
  312. },
  313. want: v1.ValidationResultError,
  314. assertError: func(t *testing.T, err error) {
  315. require.ErrorContains(t, err, "no vaults found when listing")
  316. },
  317. vaultPrefix: "op://vault/",
  318. },
  319. {
  320. name: "validate error missing vault prefix",
  321. client: func() *onepassword.Client {
  322. fc := &fakeClient{
  323. listAllResult: []onepassword.VaultOverview{},
  324. listAllError: errors.New("no vaults found when listing"),
  325. }
  326. return &onepassword.Client{
  327. SecretsAPI: fc,
  328. VaultsAPI: fc,
  329. }
  330. },
  331. want: v1.ValidationResultError,
  332. assertError: func(t *testing.T, err error) {
  333. require.ErrorContains(t, err, "no vaults found when listing")
  334. },
  335. },
  336. }
  337. for _, tt := range tests {
  338. t.Run(tt.name, func(t *testing.T) {
  339. p := &Provider{
  340. client: tt.client(),
  341. vaultPrefix: tt.vaultPrefix,
  342. }
  343. got, err := p.Validate()
  344. tt.assertError(t, err)
  345. require.Equal(t, got, tt.want)
  346. })
  347. }
  348. }
  349. func TestPushSecret(t *testing.T) {
  350. fc := &fakeClient{
  351. listAllResult: []onepassword.VaultOverview{
  352. {
  353. ID: "test",
  354. Title: "test",
  355. },
  356. },
  357. }
  358. tests := []struct {
  359. name string
  360. ref v1alpha1.PushSecretData
  361. secret *corev1.Secret
  362. assertError func(t *testing.T, err error)
  363. lister func() *fakeLister
  364. assertLister func(t *testing.T, lister *fakeLister)
  365. }{
  366. {
  367. name: "create is called",
  368. lister: func() *fakeLister {
  369. return &fakeLister{
  370. listAllResult: []onepassword.ItemOverview{},
  371. }
  372. },
  373. secret: &corev1.Secret{
  374. Data: map[string][]byte{
  375. "foo": []byte("bar"),
  376. },
  377. ObjectMeta: metav1.ObjectMeta{
  378. Name: "secret",
  379. Namespace: "default",
  380. },
  381. },
  382. ref: v1alpha1.PushSecretData{
  383. Match: v1alpha1.PushSecretMatch{
  384. SecretKey: "foo",
  385. RemoteRef: v1alpha1.PushSecretRemoteRef{
  386. RemoteKey: "key",
  387. },
  388. },
  389. },
  390. assertError: func(t *testing.T, err error) {
  391. require.NoError(t, err)
  392. },
  393. assertLister: func(t *testing.T, lister *fakeLister) {
  394. assert.True(t, lister.createCalled)
  395. },
  396. },
  397. {
  398. name: "update is called",
  399. lister: func() *fakeLister {
  400. return &fakeLister{
  401. listAllResult: []onepassword.ItemOverview{
  402. {
  403. ID: "test-item-id",
  404. Title: "key",
  405. Category: "login",
  406. VaultID: "vault-id",
  407. },
  408. },
  409. }
  410. },
  411. secret: &corev1.Secret{
  412. Data: map[string][]byte{
  413. "foo": []byte("bar"),
  414. },
  415. ObjectMeta: metav1.ObjectMeta{
  416. Name: "secret",
  417. Namespace: "default",
  418. },
  419. },
  420. ref: v1alpha1.PushSecretData{
  421. Match: v1alpha1.PushSecretMatch{
  422. SecretKey: "foo",
  423. RemoteRef: v1alpha1.PushSecretRemoteRef{
  424. RemoteKey: "key",
  425. },
  426. },
  427. },
  428. assertError: func(t *testing.T, err error) {
  429. require.NoError(t, err)
  430. },
  431. assertLister: func(t *testing.T, lister *fakeLister) {
  432. assert.True(t, lister.putCalled)
  433. },
  434. },
  435. }
  436. for _, tt := range tests {
  437. t.Run(tt.name, func(t *testing.T) {
  438. ctx := context.Background()
  439. lister := tt.lister()
  440. p := &Provider{
  441. client: &onepassword.Client{
  442. SecretsAPI: fc,
  443. VaultsAPI: fc,
  444. ItemsAPI: lister,
  445. },
  446. }
  447. err := p.PushSecret(ctx, tt.secret, tt.ref)
  448. tt.assertError(t, err)
  449. tt.assertLister(t, lister)
  450. })
  451. }
  452. }
  453. func TestDeleteItemField(t *testing.T) {
  454. fc := &fakeClient{
  455. listAllResult: []onepassword.VaultOverview{
  456. {
  457. ID: "test",
  458. Title: "test",
  459. },
  460. },
  461. }
  462. testCases := []struct {
  463. name string
  464. lister func() *fakeLister
  465. ref *v1alpha1.PushSecretRemoteRef
  466. assertError func(t *testing.T, err error)
  467. assertLister func(t *testing.T, lister *fakeLister)
  468. }{
  469. {
  470. name: "update is called",
  471. ref: &v1alpha1.PushSecretRemoteRef{
  472. RemoteKey: "key",
  473. Property: "password",
  474. },
  475. assertLister: func(t *testing.T, lister *fakeLister) {
  476. require.True(t, lister.putCalled)
  477. },
  478. lister: func() *fakeLister {
  479. fl := &fakeLister{
  480. listAllResult: []onepassword.ItemOverview{
  481. {
  482. ID: "test-item-id",
  483. Title: "key",
  484. Category: "login",
  485. VaultID: "vault-id",
  486. },
  487. },
  488. getResult: onepassword.Item{
  489. ID: "test-item-id",
  490. Title: "key",
  491. Category: "login",
  492. VaultID: "vault-id",
  493. Fields: []onepassword.ItemField{
  494. {
  495. ID: "field-1",
  496. Title: "password",
  497. FieldType: onepassword.ItemFieldTypeConcealed,
  498. Value: "password",
  499. },
  500. {
  501. ID: "field-2",
  502. Title: "other-field",
  503. FieldType: onepassword.ItemFieldTypeConcealed,
  504. Value: "username",
  505. },
  506. },
  507. },
  508. }
  509. return fl
  510. },
  511. assertError: func(t *testing.T, err error) {
  512. require.NoError(t, err)
  513. },
  514. },
  515. {
  516. name: "delete is called",
  517. ref: &v1alpha1.PushSecretRemoteRef{
  518. RemoteKey: "key",
  519. Property: "password",
  520. },
  521. assertLister: func(t *testing.T, lister *fakeLister) {
  522. require.True(t, lister.deleteCalled, "delete should have been called as the item should have existed")
  523. },
  524. lister: func() *fakeLister {
  525. fl := &fakeLister{
  526. listAllResult: []onepassword.ItemOverview{
  527. {
  528. ID: "test-item-id",
  529. Title: "key",
  530. Category: "login",
  531. VaultID: "vault-id",
  532. },
  533. },
  534. getResult: onepassword.Item{
  535. ID: "test-item-id",
  536. Title: "key",
  537. Category: "login",
  538. VaultID: "vault-id",
  539. Fields: []onepassword.ItemField{
  540. {
  541. ID: "field-1",
  542. Title: "password",
  543. FieldType: onepassword.ItemFieldTypeConcealed,
  544. Value: "password",
  545. },
  546. },
  547. },
  548. }
  549. return fl
  550. },
  551. assertError: func(t *testing.T, err error) {
  552. require.NoError(t, err)
  553. },
  554. },
  555. }
  556. for _, testCase := range testCases {
  557. t.Run(testCase.name, func(t *testing.T) {
  558. ctx := context.Background()
  559. lister := testCase.lister()
  560. p := &Provider{
  561. client: &onepassword.Client{
  562. SecretsAPI: fc,
  563. VaultsAPI: fc,
  564. ItemsAPI: lister,
  565. },
  566. }
  567. testCase.assertError(t, p.DeleteSecret(ctx, testCase.ref))
  568. testCase.assertLister(t, lister)
  569. })
  570. }
  571. }
  572. func TestGetVault(t *testing.T) {
  573. fc := &fakeClient{
  574. listAllResult: []onepassword.VaultOverview{
  575. {
  576. ID: "vault-id",
  577. Title: "vault-title",
  578. },
  579. },
  580. }
  581. p := &Provider{
  582. client: &onepassword.Client{
  583. VaultsAPI: fc,
  584. },
  585. }
  586. titleOrUuids := []string{"vault-title", "vault-id"}
  587. for _, titleOrUuid := range titleOrUuids {
  588. t.Run(titleOrUuid, func(t *testing.T) {
  589. vaultID, err := p.GetVault(context.Background(), titleOrUuid)
  590. require.NoError(t, err)
  591. require.Equal(t, fc.listAllResult[0].ID, vaultID)
  592. })
  593. }
  594. }
  595. type fakeLister struct {
  596. listAllResult []onepassword.ItemOverview
  597. createCalled bool
  598. putCalled bool
  599. deleteCalled bool
  600. getResult onepassword.Item
  601. fileLister onepassword.ItemsFilesAPI
  602. }
  603. func (f *fakeLister) Create(ctx context.Context, params onepassword.ItemCreateParams) (onepassword.Item, error) {
  604. f.createCalled = true
  605. return onepassword.Item{}, nil
  606. }
  607. func (f *fakeLister) Get(ctx context.Context, vaultID, itemID string) (onepassword.Item, error) {
  608. return f.getResult, nil
  609. }
  610. func (f *fakeLister) Put(ctx context.Context, item onepassword.Item) (onepassword.Item, error) {
  611. f.putCalled = true
  612. return onepassword.Item{}, nil
  613. }
  614. func (f *fakeLister) Delete(ctx context.Context, vaultID, itemID string) error {
  615. f.deleteCalled = true
  616. return nil
  617. }
  618. func (f *fakeLister) Archive(ctx context.Context, vaultID, itemID string) error {
  619. return nil
  620. }
  621. func (f *fakeLister) List(ctx context.Context, vaultID string, opts ...onepassword.ItemListFilter) ([]onepassword.ItemOverview, error) {
  622. return f.listAllResult, nil
  623. }
  624. func (f *fakeLister) Shares() onepassword.ItemsSharesAPI {
  625. return nil
  626. }
  627. func (f *fakeLister) Files() onepassword.ItemsFilesAPI {
  628. return f.fileLister
  629. }
  630. type fakeFileLister struct {
  631. readContent []byte
  632. }
  633. func (f *fakeFileLister) Attach(ctx context.Context, item onepassword.Item, fileParams onepassword.FileCreateParams) (onepassword.Item, error) {
  634. return onepassword.Item{}, nil
  635. }
  636. func (f *fakeFileLister) Read(ctx context.Context, vaultID, itemID string, attr onepassword.FileAttributes) ([]byte, error) {
  637. return f.readContent, nil
  638. }
  639. func (f *fakeFileLister) Delete(ctx context.Context, item onepassword.Item, sectionID, fieldID string) (onepassword.Item, error) {
  640. return onepassword.Item{}, nil
  641. }
  642. func (f *fakeFileLister) ReplaceDocument(ctx context.Context, item onepassword.Item, docParams onepassword.DocumentCreateParams) (onepassword.Item, error) {
  643. return onepassword.Item{}, nil
  644. }
  645. var _ onepassword.ItemsFilesAPI = (*fakeFileLister)(nil)
  646. type fakeClient struct {
  647. resolveResult string
  648. resolveError error
  649. resolveAll onepassword.ResolveAllResponse
  650. resolveAllError error
  651. listAllResult []onepassword.VaultOverview
  652. listAllError error
  653. }
  654. func (f *fakeClient) List(ctx context.Context) ([]onepassword.VaultOverview, error) {
  655. return f.listAllResult, f.listAllError
  656. }
  657. func (f *fakeClient) Resolve(ctx context.Context, secretReference string) (string, error) {
  658. return f.resolveResult, f.resolveError
  659. }
  660. func (f *fakeClient) ResolveAll(ctx context.Context, secretReferences []string) (onepassword.ResolveAllResponse, error) {
  661. return f.resolveAll, f.resolveAllError
  662. }
  663. var _ onepassword.SecretsAPI = &fakeClient{}
  664. var _ onepassword.VaultsAPI = &fakeClient{}
  665. var _ onepassword.ItemsAPI = &fakeLister{}