client_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  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. "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. type fakeLister struct {
  573. listAllResult []onepassword.ItemOverview
  574. createCalled bool
  575. putCalled bool
  576. deleteCalled bool
  577. getResult onepassword.Item
  578. fileLister onepassword.ItemsFilesAPI
  579. }
  580. func (f *fakeLister) Create(ctx context.Context, params onepassword.ItemCreateParams) (onepassword.Item, error) {
  581. f.createCalled = true
  582. return onepassword.Item{}, nil
  583. }
  584. func (f *fakeLister) Get(ctx context.Context, vaultID, itemID string) (onepassword.Item, error) {
  585. return f.getResult, nil
  586. }
  587. func (f *fakeLister) Put(ctx context.Context, item onepassword.Item) (onepassword.Item, error) {
  588. f.putCalled = true
  589. return onepassword.Item{}, nil
  590. }
  591. func (f *fakeLister) Delete(ctx context.Context, vaultID, itemID string) error {
  592. f.deleteCalled = true
  593. return nil
  594. }
  595. func (f *fakeLister) Archive(ctx context.Context, vaultID, itemID string) error {
  596. return nil
  597. }
  598. func (f *fakeLister) List(ctx context.Context, vaultID string, opts ...onepassword.ItemListFilter) ([]onepassword.ItemOverview, error) {
  599. return f.listAllResult, nil
  600. }
  601. func (f *fakeLister) Shares() onepassword.ItemsSharesAPI {
  602. return nil
  603. }
  604. func (f *fakeLister) Files() onepassword.ItemsFilesAPI {
  605. return f.fileLister
  606. }
  607. type fakeFileLister struct {
  608. readContent []byte
  609. }
  610. func (f *fakeFileLister) Attach(ctx context.Context, item onepassword.Item, fileParams onepassword.FileCreateParams) (onepassword.Item, error) {
  611. return onepassword.Item{}, nil
  612. }
  613. func (f *fakeFileLister) Read(ctx context.Context, vaultID, itemID string, attr onepassword.FileAttributes) ([]byte, error) {
  614. return f.readContent, nil
  615. }
  616. func (f *fakeFileLister) Delete(ctx context.Context, item onepassword.Item, sectionID, fieldID string) (onepassword.Item, error) {
  617. return onepassword.Item{}, nil
  618. }
  619. func (f *fakeFileLister) ReplaceDocument(ctx context.Context, item onepassword.Item, docParams onepassword.DocumentCreateParams) (onepassword.Item, error) {
  620. return onepassword.Item{}, nil
  621. }
  622. var _ onepassword.ItemsFilesAPI = (*fakeFileLister)(nil)
  623. type fakeClient struct {
  624. resolveResult string
  625. resolveError error
  626. resolveAll onepassword.ResolveAllResponse
  627. resolveAllError error
  628. listAllResult []onepassword.VaultOverview
  629. listAllError error
  630. }
  631. func (f *fakeClient) List(ctx context.Context) ([]onepassword.VaultOverview, error) {
  632. return f.listAllResult, f.listAllError
  633. }
  634. func (f *fakeClient) Resolve(ctx context.Context, secretReference string) (string, error) {
  635. return f.resolveResult, f.resolveError
  636. }
  637. func (f *fakeClient) ResolveAll(ctx context.Context, secretReferences []string) (onepassword.ResolveAllResponse, error) {
  638. return f.resolveAll, f.resolveAllError
  639. }
  640. var _ onepassword.SecretsAPI = &fakeClient{}
  641. var _ onepassword.VaultsAPI = &fakeClient{}
  642. var _ onepassword.ItemsAPI = &fakeLister{}