client_test.go 13 KB

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