client_test.go 17 KB

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