client_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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",
  112. client: func() *onepassword.Client {
  113. fc := &fakeClient{
  114. resolveResult: `{"key": "value"}`,
  115. }
  116. return &onepassword.Client{
  117. SecretsAPI: fc,
  118. VaultsAPI: fc,
  119. }
  120. },
  121. assertError: func(t *testing.T, err error) {
  122. require.NoError(t, err)
  123. },
  124. ref: v1.ExternalSecretDataRemoteRef{
  125. Key: "secret",
  126. },
  127. want: map[string][]byte{
  128. "key": []byte("value"),
  129. },
  130. },
  131. }
  132. for _, tt := range tests {
  133. t.Run(tt.name, func(t *testing.T) {
  134. p := &Provider{
  135. client: tt.client(),
  136. vaultPrefix: "op://vault/",
  137. }
  138. got, err := p.GetSecretMap(context.Background(), tt.ref)
  139. tt.assertError(t, err)
  140. require.Equal(t, got, tt.want)
  141. })
  142. }
  143. }
  144. func TestProviderValidate(t *testing.T) {
  145. tests := []struct {
  146. name string
  147. want v1.ValidationResult
  148. assertError func(t *testing.T, err error)
  149. client func() *onepassword.Client
  150. vaultPrefix string
  151. }{
  152. {
  153. name: "validate successfully",
  154. client: func() *onepassword.Client {
  155. fc := &fakeClient{
  156. listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
  157. []onepassword.VaultOverview{
  158. {
  159. ID: "test",
  160. Title: "test",
  161. },
  162. },
  163. ),
  164. }
  165. return &onepassword.Client{
  166. SecretsAPI: fc,
  167. VaultsAPI: fc,
  168. }
  169. },
  170. want: v1.ValidationResultReady,
  171. assertError: func(t *testing.T, err error) {
  172. require.NoError(t, err)
  173. },
  174. vaultPrefix: "op://vault/",
  175. },
  176. {
  177. name: "validate error",
  178. client: func() *onepassword.Client {
  179. fc := &fakeClient{
  180. listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
  181. []onepassword.VaultOverview{},
  182. ),
  183. }
  184. return &onepassword.Client{
  185. SecretsAPI: fc,
  186. VaultsAPI: fc,
  187. }
  188. },
  189. want: v1.ValidationResultError,
  190. assertError: func(t *testing.T, err error) {
  191. require.ErrorContains(t, err, "no vaults found when listing")
  192. },
  193. vaultPrefix: "op://vault/",
  194. },
  195. {
  196. name: "validate error missing vault prefix",
  197. client: func() *onepassword.Client {
  198. fc := &fakeClient{
  199. listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
  200. []onepassword.VaultOverview{},
  201. ),
  202. }
  203. return &onepassword.Client{
  204. SecretsAPI: fc,
  205. VaultsAPI: fc,
  206. }
  207. },
  208. want: v1.ValidationResultError,
  209. assertError: func(t *testing.T, err error) {
  210. require.ErrorContains(t, err, "no vaults found when listing")
  211. },
  212. },
  213. }
  214. for _, tt := range tests {
  215. t.Run(tt.name, func(t *testing.T) {
  216. p := &Provider{
  217. client: tt.client(),
  218. vaultPrefix: tt.vaultPrefix,
  219. }
  220. got, err := p.Validate()
  221. tt.assertError(t, err)
  222. require.Equal(t, got, tt.want)
  223. })
  224. }
  225. }
  226. func TestPushSecret(t *testing.T) {
  227. fc := &fakeClient{
  228. listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
  229. []onepassword.VaultOverview{
  230. {
  231. ID: "test",
  232. Title: "test",
  233. },
  234. },
  235. ),
  236. }
  237. tests := []struct {
  238. name string
  239. ref v1alpha1.PushSecretData
  240. secret *corev1.Secret
  241. assertError func(t *testing.T, err error)
  242. lister func() *fakeLister
  243. assertLister func(t *testing.T, lister *fakeLister)
  244. }{
  245. {
  246. name: "create is called",
  247. lister: func() *fakeLister {
  248. return &fakeLister{
  249. listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
  250. []onepassword.ItemOverview{},
  251. ),
  252. }
  253. },
  254. secret: &corev1.Secret{
  255. Data: map[string][]byte{
  256. "foo": []byte("bar"),
  257. },
  258. ObjectMeta: metav1.ObjectMeta{
  259. Name: "secret",
  260. Namespace: "default",
  261. },
  262. },
  263. ref: v1alpha1.PushSecretData{
  264. Match: v1alpha1.PushSecretMatch{
  265. SecretKey: "foo",
  266. RemoteRef: v1alpha1.PushSecretRemoteRef{
  267. RemoteKey: "key",
  268. },
  269. },
  270. },
  271. assertError: func(t *testing.T, err error) {
  272. require.NoError(t, err)
  273. },
  274. assertLister: func(t *testing.T, lister *fakeLister) {
  275. assert.True(t, lister.createCalled)
  276. },
  277. },
  278. {
  279. name: "update is called",
  280. lister: func() *fakeLister {
  281. return &fakeLister{
  282. listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
  283. []onepassword.ItemOverview{
  284. {
  285. ID: "test-item-id",
  286. Title: "key",
  287. Category: "login",
  288. VaultID: "vault-id",
  289. },
  290. },
  291. ),
  292. }
  293. },
  294. secret: &corev1.Secret{
  295. Data: map[string][]byte{
  296. "foo": []byte("bar"),
  297. },
  298. ObjectMeta: metav1.ObjectMeta{
  299. Name: "secret",
  300. Namespace: "default",
  301. },
  302. },
  303. ref: v1alpha1.PushSecretData{
  304. Match: v1alpha1.PushSecretMatch{
  305. SecretKey: "foo",
  306. RemoteRef: v1alpha1.PushSecretRemoteRef{
  307. RemoteKey: "key",
  308. },
  309. },
  310. },
  311. assertError: func(t *testing.T, err error) {
  312. require.NoError(t, err)
  313. },
  314. assertLister: func(t *testing.T, lister *fakeLister) {
  315. assert.True(t, lister.putCalled)
  316. },
  317. },
  318. }
  319. for _, tt := range tests {
  320. t.Run(tt.name, func(t *testing.T) {
  321. ctx := context.Background()
  322. lister := tt.lister()
  323. p := &Provider{
  324. client: &onepassword.Client{
  325. SecretsAPI: fc,
  326. VaultsAPI: fc,
  327. ItemsAPI: lister,
  328. },
  329. }
  330. err := p.PushSecret(ctx, tt.secret, tt.ref)
  331. tt.assertError(t, err)
  332. tt.assertLister(t, lister)
  333. })
  334. }
  335. }
  336. func TestDeleteItemField(t *testing.T) {
  337. fc := &fakeClient{
  338. listAllResult: onepassword.NewIterator[onepassword.VaultOverview](
  339. []onepassword.VaultOverview{
  340. {
  341. ID: "test",
  342. Title: "test",
  343. },
  344. },
  345. ),
  346. }
  347. testCases := []struct {
  348. name string
  349. lister func() *fakeLister
  350. ref *v1alpha1.PushSecretRemoteRef
  351. assertError func(t *testing.T, err error)
  352. assertLister func(t *testing.T, lister *fakeLister)
  353. }{
  354. {
  355. name: "update is called",
  356. ref: &v1alpha1.PushSecretRemoteRef{
  357. RemoteKey: "key",
  358. Property: "password",
  359. },
  360. assertLister: func(t *testing.T, lister *fakeLister) {
  361. require.True(t, lister.putCalled)
  362. },
  363. lister: func() *fakeLister {
  364. fl := &fakeLister{
  365. listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
  366. []onepassword.ItemOverview{
  367. {
  368. ID: "test-item-id",
  369. Title: "key",
  370. Category: "login",
  371. VaultID: "vault-id",
  372. },
  373. },
  374. ),
  375. getResult: onepassword.Item{
  376. ID: "test-item-id",
  377. Title: "key",
  378. Category: "login",
  379. VaultID: "vault-id",
  380. Fields: []onepassword.ItemField{
  381. {
  382. ID: "field-1",
  383. Title: "password",
  384. FieldType: onepassword.ItemFieldTypeConcealed,
  385. Value: "password",
  386. },
  387. {
  388. ID: "field-2",
  389. Title: "other-field",
  390. FieldType: onepassword.ItemFieldTypeConcealed,
  391. Value: "username",
  392. },
  393. },
  394. },
  395. }
  396. return fl
  397. },
  398. assertError: func(t *testing.T, err error) {
  399. require.NoError(t, err)
  400. },
  401. },
  402. {
  403. name: "delete is called",
  404. ref: &v1alpha1.PushSecretRemoteRef{
  405. RemoteKey: "key",
  406. Property: "password",
  407. },
  408. assertLister: func(t *testing.T, lister *fakeLister) {
  409. require.True(t, lister.deleteCalled, "delete should have been called as the item should have existed")
  410. },
  411. lister: func() *fakeLister {
  412. fl := &fakeLister{
  413. listAllResult: onepassword.NewIterator[onepassword.ItemOverview](
  414. []onepassword.ItemOverview{
  415. {
  416. ID: "test-item-id",
  417. Title: "key",
  418. Category: "login",
  419. VaultID: "vault-id",
  420. },
  421. },
  422. ),
  423. getResult: onepassword.Item{
  424. ID: "test-item-id",
  425. Title: "key",
  426. Category: "login",
  427. VaultID: "vault-id",
  428. Fields: []onepassword.ItemField{
  429. {
  430. ID: "field-1",
  431. Title: "password",
  432. FieldType: onepassword.ItemFieldTypeConcealed,
  433. Value: "password",
  434. },
  435. },
  436. },
  437. }
  438. return fl
  439. },
  440. assertError: func(t *testing.T, err error) {
  441. require.NoError(t, err)
  442. },
  443. },
  444. }
  445. for _, testCase := range testCases {
  446. t.Run(testCase.name, func(t *testing.T) {
  447. ctx := context.Background()
  448. lister := testCase.lister()
  449. p := &Provider{
  450. client: &onepassword.Client{
  451. SecretsAPI: fc,
  452. VaultsAPI: fc,
  453. ItemsAPI: lister,
  454. },
  455. }
  456. testCase.assertError(t, p.DeleteSecret(ctx, testCase.ref))
  457. testCase.assertLister(t, lister)
  458. })
  459. }
  460. }
  461. type fakeLister struct {
  462. listAllResult *onepassword.Iterator[onepassword.ItemOverview]
  463. createCalled bool
  464. putCalled bool
  465. deleteCalled bool
  466. getResult onepassword.Item
  467. }
  468. func (f *fakeLister) Create(ctx context.Context, params onepassword.ItemCreateParams) (onepassword.Item, error) {
  469. f.createCalled = true
  470. return onepassword.Item{}, nil
  471. }
  472. func (f *fakeLister) Get(ctx context.Context, vaultID, itemID string) (onepassword.Item, error) {
  473. return f.getResult, nil
  474. }
  475. func (f *fakeLister) Put(ctx context.Context, item onepassword.Item) (onepassword.Item, error) {
  476. f.putCalled = true
  477. return onepassword.Item{}, nil
  478. }
  479. func (f *fakeLister) Delete(ctx context.Context, vaultID, itemID string) error {
  480. f.deleteCalled = true
  481. return nil
  482. }
  483. func (f *fakeLister) Archive(ctx context.Context, vaultID, itemID string) error {
  484. return nil
  485. }
  486. func (f *fakeLister) ListAll(ctx context.Context, vaultID string) (*onepassword.Iterator[onepassword.ItemOverview], error) {
  487. return f.listAllResult, nil
  488. }
  489. func (f *fakeLister) Shares() onepassword.ItemsSharesAPI {
  490. return nil
  491. }
  492. func (f *fakeLister) Files() onepassword.ItemsFilesAPI {
  493. return nil
  494. }
  495. type fakeClient struct {
  496. resolveResult string
  497. resolveError error
  498. resolveAll onepassword.ResolveAllResponse
  499. resolveAllError error
  500. listAllResult *onepassword.Iterator[onepassword.VaultOverview]
  501. listAllError error
  502. }
  503. func (f *fakeClient) ListAll(ctx context.Context) (*onepassword.Iterator[onepassword.VaultOverview], error) {
  504. return f.listAllResult, f.listAllError
  505. }
  506. func (f *fakeClient) Resolve(ctx context.Context, secretReference string) (string, error) {
  507. return f.resolveResult, f.resolveError
  508. }
  509. func (f *fakeClient) ResolveAll(ctx context.Context, secretReferences []string) (onepassword.ResolveAllResponse, error) {
  510. return f.resolveAll, f.resolveAllError
  511. }
  512. var _ onepassword.SecretsAPI = &fakeClient{}
  513. var _ onepassword.VaultsAPI = &fakeClient{}
  514. var _ onepassword.ItemsAPI = &fakeLister{}