akeyless_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. /*
  2. Copyright © The ESO Authors
  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 akeyless
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "net/http/httptest"
  20. "strings"
  21. "testing"
  22. "github.com/akeylesslabs/akeyless-go/v4"
  23. "github.com/stretchr/testify/require"
  24. corev1 "k8s.io/api/core/v1"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  28. fakeakeyless "github.com/external-secrets/external-secrets/providers/v1/akeyless/fake"
  29. testingfake "github.com/external-secrets/external-secrets/runtime/testing/fake"
  30. )
  31. type akeylessTestCase struct {
  32. testName string
  33. mockClient *fakeakeyless.AkeylessMockClient
  34. apiInput *fakeakeyless.Input
  35. apiOutput *fakeakeyless.Output
  36. ref *esv1.ExternalSecretDataRemoteRef
  37. input any
  38. input2 any
  39. expectError string
  40. expectedVal any
  41. expectedSecret string
  42. }
  43. const fmtExpectedError = "unexpected error: %s, expected: '%s'"
  44. func (a *akeylessTestCase) SetMockClient(c *fakeakeyless.AkeylessMockClient) *akeylessTestCase {
  45. a.mockClient = c
  46. return a
  47. }
  48. func (a *akeylessTestCase) SetExpectErr(err string) *akeylessTestCase {
  49. a.expectError = err
  50. return a
  51. }
  52. func (a *akeylessTestCase) SetExpectVal(val any) *akeylessTestCase {
  53. a.expectedVal = val
  54. return a
  55. }
  56. func (a *akeylessTestCase) SetExpectInput(input any) *akeylessTestCase {
  57. a.input = input
  58. return a
  59. }
  60. func (a *akeylessTestCase) SetExpectInput2(input any) *akeylessTestCase {
  61. a.input2 = input
  62. return a
  63. }
  64. func makeValidAkeylessTestCase(testName string) *akeylessTestCase {
  65. smtc := akeylessTestCase{
  66. testName: testName,
  67. mockClient: &fakeakeyless.AkeylessMockClient{},
  68. apiInput: makeValidInput(),
  69. ref: makeValidRef(),
  70. apiOutput: makeValidOutput(),
  71. expectError: "",
  72. expectedSecret: "",
  73. }
  74. smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
  75. return &smtc
  76. }
  77. func nilProviderTestCase() *akeylessTestCase {
  78. return makeValidAkeylessTestCase("nil provider").SetMockClient(nil).SetExpectErr(errUninitalizedAkeylessProvider)
  79. }
  80. func failGetTestCase() *akeylessTestCase {
  81. return makeValidAkeylessTestCase("fail GetSecret").SetExpectVal(false).SetExpectErr("fail get").
  82. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "", errors.New("fail get") }))
  83. }
  84. func makeValidRef() *esv1.ExternalSecretDataRemoteRef {
  85. return &esv1.ExternalSecretDataRemoteRef{
  86. Key: "test-secret",
  87. Version: "1",
  88. }
  89. }
  90. func makeValidInput() *fakeakeyless.Input {
  91. return &fakeakeyless.Input{
  92. SecretName: "name",
  93. Version: 0,
  94. Token: "token",
  95. }
  96. }
  97. func makeValidOutput() *fakeakeyless.Output {
  98. return &fakeakeyless.Output{
  99. Value: "secret-val",
  100. Err: nil,
  101. }
  102. }
  103. func makeValidAkeylessTestCaseCustom(tweaks ...func(smtc *akeylessTestCase)) *akeylessTestCase {
  104. smtc := makeValidAkeylessTestCase("")
  105. for _, fn := range tweaks {
  106. fn(smtc)
  107. }
  108. smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
  109. return smtc
  110. }
  111. // This case can be shared by both GetSecret and GetSecretMap tests.
  112. // bad case: set apiErr.
  113. var setAPIErr = func(smtc *akeylessTestCase) {
  114. smtc.apiOutput.Err = errors.New("oh no")
  115. smtc.expectError = "oh no"
  116. }
  117. var setNilMockClient = func(smtc *akeylessTestCase) {
  118. smtc.mockClient = nil
  119. smtc.expectError = errUninitalizedAkeylessProvider
  120. }
  121. func TestAkeylessGetSecret(t *testing.T) {
  122. secretValue := "changedvalue"
  123. // good case: default version is set
  124. // key is passed in, output is sent back
  125. setSecretString := func(smtc *akeylessTestCase) {
  126. smtc.apiOutput = &fakeakeyless.Output{
  127. Value: secretValue,
  128. Err: nil,
  129. }
  130. smtc.expectedSecret = secretValue
  131. }
  132. successCases := []*akeylessTestCase{
  133. makeValidAkeylessTestCaseCustom(setAPIErr),
  134. makeValidAkeylessTestCaseCustom(setSecretString),
  135. makeValidAkeylessTestCaseCustom(setNilMockClient),
  136. }
  137. sm := Akeyless{}
  138. for _, v := range successCases {
  139. sm.Client = v.mockClient
  140. out, err := sm.GetSecret(context.Background(), *v.ref)
  141. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  142. require.Equal(t, string(out), v.expectedSecret)
  143. }
  144. }
  145. func TestValidateStore(t *testing.T) {
  146. provider := Provider{}
  147. akeylessGWApiURL := ""
  148. t.Run("secret auth", func(t *testing.T) {
  149. store := &esv1.SecretStore{
  150. Spec: esv1.SecretStoreSpec{
  151. Provider: &esv1.SecretStoreProvider{
  152. Akeyless: &esv1.AkeylessProvider{
  153. AkeylessGWApiURL: &akeylessGWApiURL,
  154. Auth: &esv1.AkeylessAuth{
  155. SecretRef: esv1.AkeylessAuthSecretRef{
  156. AccessID: esmeta.SecretKeySelector{
  157. Name: "accessId",
  158. Key: "key-1",
  159. },
  160. AccessType: esmeta.SecretKeySelector{
  161. Name: "accessId",
  162. Key: "key-1",
  163. },
  164. AccessTypeParam: esmeta.SecretKeySelector{
  165. Name: "accessId",
  166. Key: "key-1",
  167. },
  168. },
  169. },
  170. },
  171. },
  172. },
  173. }
  174. _, err := provider.ValidateStore(store)
  175. require.NoError(t, err)
  176. })
  177. t.Run("secret auth with serviceAccountRef", func(t *testing.T) {
  178. store := &esv1.SecretStore{
  179. Spec: esv1.SecretStoreSpec{
  180. Provider: &esv1.SecretStoreProvider{
  181. Akeyless: &esv1.AkeylessProvider{
  182. AkeylessGWApiURL: &akeylessGWApiURL,
  183. Auth: &esv1.AkeylessAuth{
  184. SecretRef: esv1.AkeylessAuthSecretRef{
  185. AccessID: esmeta.SecretKeySelector{
  186. Name: "accessId",
  187. Key: "key-1",
  188. },
  189. AccessType: esmeta.SecretKeySelector{
  190. Name: "accessId",
  191. Key: "key-1",
  192. },
  193. },
  194. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  195. Name: "akeyless-wi-sa",
  196. },
  197. },
  198. },
  199. },
  200. },
  201. }
  202. _, err := provider.ValidateStore(store)
  203. require.NoError(t, err)
  204. })
  205. t.Run("secret auth with serviceAccountRef in different namespace", func(t *testing.T) {
  206. ns := "other-ns"
  207. store := &esv1.SecretStore{
  208. ObjectMeta: metav1.ObjectMeta{
  209. Namespace: "app-test",
  210. },
  211. TypeMeta: metav1.TypeMeta{
  212. Kind: esv1.SecretStoreKind,
  213. },
  214. Spec: esv1.SecretStoreSpec{
  215. Provider: &esv1.SecretStoreProvider{
  216. Akeyless: &esv1.AkeylessProvider{
  217. AkeylessGWApiURL: &akeylessGWApiURL,
  218. Auth: &esv1.AkeylessAuth{
  219. SecretRef: esv1.AkeylessAuthSecretRef{
  220. AccessID: esmeta.SecretKeySelector{
  221. Name: "accessId",
  222. Key: "key-1",
  223. },
  224. AccessType: esmeta.SecretKeySelector{
  225. Name: "accessId",
  226. Key: "key-1",
  227. },
  228. },
  229. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  230. Name: "akeyless-wi-sa",
  231. Namespace: &ns,
  232. },
  233. },
  234. },
  235. },
  236. },
  237. }
  238. _, err := provider.ValidateStore(store)
  239. require.Error(t, err)
  240. })
  241. t.Run("cluster secret auth with serviceAccountRef namespace", func(t *testing.T) {
  242. ns := "app-test"
  243. store := &esv1.ClusterSecretStore{
  244. TypeMeta: metav1.TypeMeta{
  245. Kind: esv1.ClusterSecretStoreKind,
  246. },
  247. Spec: esv1.SecretStoreSpec{
  248. Provider: &esv1.SecretStoreProvider{
  249. Akeyless: &esv1.AkeylessProvider{
  250. AkeylessGWApiURL: &akeylessGWApiURL,
  251. Auth: &esv1.AkeylessAuth{
  252. SecretRef: esv1.AkeylessAuthSecretRef{
  253. AccessID: esmeta.SecretKeySelector{
  254. Name: "accessId",
  255. Key: "key-1",
  256. Namespace: &ns,
  257. },
  258. AccessType: esmeta.SecretKeySelector{
  259. Name: "accessId",
  260. Key: "key-1",
  261. Namespace: &ns,
  262. },
  263. },
  264. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  265. Name: "akeyless-wi-sa",
  266. Namespace: &ns,
  267. },
  268. },
  269. },
  270. },
  271. },
  272. }
  273. _, err := provider.ValidateStore(store)
  274. require.NoError(t, err)
  275. })
  276. t.Run("k8s auth", func(t *testing.T) {
  277. store := &esv1.SecretStore{
  278. Spec: esv1.SecretStoreSpec{
  279. Provider: &esv1.SecretStoreProvider{
  280. Akeyless: &esv1.AkeylessProvider{
  281. AkeylessGWApiURL: &akeylessGWApiURL,
  282. Auth: &esv1.AkeylessAuth{
  283. KubernetesAuth: &esv1.AkeylessKubernetesAuth{
  284. K8sConfName: "name",
  285. AccessID: "id",
  286. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  287. Name: "name",
  288. },
  289. },
  290. },
  291. },
  292. },
  293. },
  294. }
  295. _, err := provider.ValidateStore(store)
  296. require.NoError(t, err)
  297. })
  298. t.Run("bad conf auth", func(t *testing.T) {
  299. store := &esv1.SecretStore{
  300. Spec: esv1.SecretStoreSpec{
  301. Provider: &esv1.SecretStoreProvider{
  302. Akeyless: &esv1.AkeylessProvider{
  303. AkeylessGWApiURL: &akeylessGWApiURL,
  304. Auth: &esv1.AkeylessAuth{},
  305. },
  306. },
  307. },
  308. }
  309. _, err := provider.ValidateStore(store)
  310. require.Error(t, err)
  311. })
  312. t.Run("bad k8s conf auth", func(t *testing.T) {
  313. store := &esv1.SecretStore{
  314. Spec: esv1.SecretStoreSpec{
  315. Provider: &esv1.SecretStoreProvider{
  316. Akeyless: &esv1.AkeylessProvider{
  317. AkeylessGWApiURL: &akeylessGWApiURL,
  318. Auth: &esv1.AkeylessAuth{
  319. KubernetesAuth: &esv1.AkeylessKubernetesAuth{
  320. AccessID: "id",
  321. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  322. Name: "name",
  323. },
  324. },
  325. },
  326. },
  327. },
  328. },
  329. }
  330. _, err := provider.ValidateStore(store)
  331. require.Error(t, err)
  332. })
  333. }
  334. func TestGetSecretMap(t *testing.T) {
  335. // good case: default version & deserialization
  336. setDeserialization := func(smtc *akeylessTestCase) {
  337. smtc.apiOutput.Value = `{"foo":"bar"}`
  338. smtc.expectedVal = map[string][]byte{"foo": []byte("bar")}
  339. }
  340. // good case: nested json values are kept as raw json bytes
  341. setNestedJSON := func(smtc *akeylessTestCase) {
  342. smtc.apiOutput.Value = `{"foobar":{"baz":"nestedval"}}`
  343. smtc.expectedVal = map[string][]byte{"foobar": []byte(`{"baz":"nestedval"}`)}
  344. }
  345. // good case: extract a nested json object into multiple keys
  346. setExtractProperty := func(smtc *akeylessTestCase) {
  347. smtc.apiOutput.Value = `{"db":{"username":"my_user","password":"my_pass"},"apiKey":"myApiKey"}`
  348. smtc.ref.Property = "db"
  349. smtc.expectedVal = map[string][]byte{
  350. "username": []byte("my_user"),
  351. "password": []byte("my_pass"),
  352. }
  353. }
  354. // good case: extract property with non-string values
  355. setExtractPropertyWithNonStringValues := func(smtc *akeylessTestCase) {
  356. smtc.apiOutput.Value = `{"db":{"username":"my_user","port":5432}}`
  357. smtc.ref.Property = "db"
  358. smtc.expectedVal = map[string][]byte{
  359. "username": []byte("my_user"),
  360. "port": []byte("5432"),
  361. }
  362. }
  363. // bad case: invalid json
  364. setInvalidJSON := func(smtc *akeylessTestCase) {
  365. smtc.apiOutput.Value = `-----------------`
  366. smtc.expectError = "unable to unmarshal secret"
  367. }
  368. successCases := []*akeylessTestCase{
  369. makeValidAkeylessTestCaseCustom(setDeserialization),
  370. makeValidAkeylessTestCaseCustom(setNestedJSON),
  371. makeValidAkeylessTestCaseCustom(setExtractProperty),
  372. makeValidAkeylessTestCaseCustom(setExtractPropertyWithNonStringValues),
  373. makeValidAkeylessTestCaseCustom(setInvalidJSON).SetExpectVal(map[string][]byte(nil)),
  374. makeValidAkeylessTestCaseCustom(setAPIErr).SetExpectVal(map[string][]byte(nil)),
  375. makeValidAkeylessTestCaseCustom(setNilMockClient).SetExpectVal(map[string][]byte(nil)),
  376. }
  377. sm := Akeyless{}
  378. for _, v := range successCases {
  379. sm.Client = v.mockClient
  380. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  381. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  382. require.Equal(t, v.expectedVal.(map[string][]byte), out)
  383. }
  384. }
  385. func ErrorContains(out error, want string) bool {
  386. if out == nil {
  387. return want == ""
  388. }
  389. if want == "" {
  390. return false
  391. }
  392. return strings.Contains(out.Error(), want)
  393. }
  394. func TestSecretExists(t *testing.T) {
  395. testCases := []*akeylessTestCase{
  396. nilProviderTestCase().SetExpectVal(false),
  397. makeValidAkeylessTestCase("no secret").SetExpectVal(false).
  398. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "", ErrItemNotExists })),
  399. failGetTestCase(),
  400. makeValidAkeylessTestCase("success without property").SetExpectVal(true).SetExpectInput(&testingfake.PushSecretData{Property: ""}).
  401. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "my secret", nil })),
  402. makeValidAkeylessTestCase(
  403. "fail unmarshal",
  404. ).SetExpectVal(false).
  405. SetExpectErr("failed to unmarshal secret: invalid JSON format").
  406. SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
  407. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "daenerys", nil })),
  408. makeValidAkeylessTestCase("no property").SetExpectVal(false).SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
  409. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"propa": "a"}`, nil })),
  410. makeValidAkeylessTestCase("success with property").SetExpectVal(true).SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
  411. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"prop": "a"}`, nil })),
  412. }
  413. sm := Akeyless{}
  414. t.Parallel()
  415. for _, v := range testCases {
  416. t.Run(v.testName, func(t *testing.T) {
  417. sm.Client = v.mockClient
  418. if v.input == nil {
  419. v.input = &testingfake.PushSecretData{}
  420. }
  421. out, err := sm.SecretExists(context.Background(), v.input.(esv1.PushSecretRemoteRef))
  422. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  423. require.Equal(t, out, v.expectedVal.(bool))
  424. })
  425. }
  426. }
  427. func TestPushSecret(t *testing.T) {
  428. testCases := []*akeylessTestCase{
  429. nilProviderTestCase(),
  430. failGetTestCase(),
  431. makeValidAkeylessTestCase("fail unmarshal").SetExpectErr("failed to unmarshal remote secret: invalid JSON format").
  432. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "morgoth", nil })),
  433. makeValidAkeylessTestCase("create new secret").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
  434. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "", ErrItemNotExists }).
  435. SetCreateSecretFn(func(_ context.Context, _ string, data string) error {
  436. if data != `{"test":"test"}` {
  437. return errors.New("secret is not good")
  438. }
  439. return nil
  440. })),
  441. makeValidAkeylessTestCase("update secret").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test2": []byte("test2")}}).
  442. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"test2":"untest"}`, nil }).
  443. SetUpdateSecretFn(func(_ context.Context, _ string, data string) error {
  444. if data != `{"test2":"test2"}` {
  445. return errors.New("secret is not good")
  446. }
  447. return nil
  448. })),
  449. makeValidAkeylessTestCase("shouldnt update").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
  450. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"test":"test"}`, nil })),
  451. makeValidAkeylessTestCase("merge secret maps").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
  452. SetExpectInput2(&testingfake.PushSecretData{Property: "test", SecretKey: "test"}).
  453. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"test2":"test2"}`, nil }).
  454. SetUpdateSecretFn(func(_ context.Context, _ string, data string) error {
  455. expected := `{"test":"test","test2":"test2"}`
  456. if data != expected {
  457. return fmt.Errorf("secret %s expected %s", data, expected)
  458. }
  459. return nil
  460. })),
  461. }
  462. sm := Akeyless{}
  463. t.Parallel()
  464. for _, v := range testCases {
  465. t.Run(v.testName, func(t *testing.T) {
  466. sm.Client = v.mockClient
  467. if v.input == nil {
  468. v.input = &corev1.Secret{}
  469. }
  470. if v.input2 == nil {
  471. v.input2 = &testingfake.PushSecretData{}
  472. }
  473. err := sm.PushSecret(context.Background(), v.input.(*corev1.Secret), v.input2.(esv1.PushSecretData))
  474. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  475. })
  476. }
  477. }
  478. func TestDeleteSecret(t *testing.T) {
  479. testCases := []*akeylessTestCase{
  480. nilProviderTestCase(),
  481. makeValidAkeylessTestCase("fail describe").SetExpectErr("err desc").
  482. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) { return nil, errors.New("err desc") })),
  483. makeValidAkeylessTestCase("no such item").
  484. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) { return nil, nil })),
  485. makeValidAkeylessTestCase("tags nil").
  486. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) { return &akeyless.Item{}, nil })),
  487. makeValidAkeylessTestCase("no external secret managed tags").
  488. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  489. return &akeyless.Item{ItemTags: &[]string{"some-random-tag"}}, nil
  490. })),
  491. makeValidAkeylessTestCase("delete whole secret").SetExpectInput(&testingfake.PushSecretData{RemoteKey: "42"}).
  492. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  493. return &akeyless.Item{ItemTags: &[]string{extSecretManagedTag}}, nil
  494. }).SetDeleteSecretFn(func(_ context.Context, remoteKey string) error {
  495. if remoteKey != "42" {
  496. return fmt.Errorf("remote key %s expected %s", remoteKey, "42")
  497. }
  498. return nil
  499. })),
  500. makeValidAkeylessTestCase("delete property of secret").SetExpectInput(&testingfake.PushSecretData{Property: "Foo"}).
  501. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  502. return &akeyless.Item{ItemTags: &[]string{extSecretManagedTag}}, nil
  503. }).SetGetSecretFn(func(_ string, _ int32) (string, error) {
  504. return `{"Dio": "Brando", "Foo": "Fighters"}`, nil
  505. }).
  506. SetUpdateSecretFn(func(_ context.Context, _ string, data string) error {
  507. expected := `{"Dio":"Brando"}`
  508. if data != expected {
  509. return fmt.Errorf("secret %s expected %s", data, expected)
  510. }
  511. return nil
  512. })),
  513. makeValidAkeylessTestCase("delete secret if one property left").SetExpectInput(&testingfake.PushSecretData{RemoteKey: "Rings", Property: "Annatar"}).
  514. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  515. return &akeyless.Item{ItemTags: &[]string{extSecretManagedTag}}, nil
  516. }).SetGetSecretFn(func(_ string, _ int32) (string, error) {
  517. return `{"Annatar": "The Lord of Gifts"}`, nil
  518. }).
  519. SetDeleteSecretFn(func(_ context.Context, remoteKey string) error {
  520. if remoteKey != "Rings" {
  521. return fmt.Errorf("remote key %s expected %s", remoteKey, "Annatar")
  522. }
  523. return nil
  524. })),
  525. }
  526. sm := Akeyless{}
  527. t.Parallel()
  528. for _, v := range testCases {
  529. t.Run(v.testName, func(t *testing.T) {
  530. sm.Client = v.mockClient
  531. if v.input == nil {
  532. v.input = &testingfake.PushSecretData{}
  533. }
  534. err := sm.DeleteSecret(context.Background(), v.input.(esv1.PushSecretData))
  535. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  536. })
  537. }
  538. }
  539. func TestCapabilities(t *testing.T) {
  540. // The provider implements PushSecret, DeleteSecret, and SecretExists, so it
  541. // must advertise ReadWrite; otherwise ESO skips push operations entirely.
  542. p := &Provider{}
  543. require.Equal(t, esv1.SecretStoreReadWrite, p.Capabilities())
  544. }
  545. // newDescribeItemServer serves a single canned response for /describe-item and
  546. // returns an akeylessBase wired to it.
  547. func newDescribeItemServer(t *testing.T, status int, body string) *akeylessBase {
  548. t.Helper()
  549. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
  550. w.Header().Set("Content-Type", "application/json")
  551. w.WriteHeader(status)
  552. _, _ = w.Write([]byte(body))
  553. }))
  554. t.Cleanup(srv.Close)
  555. return &akeylessBase{
  556. RestAPI: akeyless.NewAPIClient(&akeyless.Configuration{
  557. Servers: []akeyless.ServerConfiguration{{URL: srv.URL}},
  558. }).V2Api,
  559. }
  560. }
  561. func describeItemCtx() context.Context {
  562. return context.WithValue(context.Background(), aKeylessToken, "t-test-token")
  563. }
  564. // TestDescribeItemMapsAPIStatus pins which Akeyless responses mean "absent".
  565. // Akeyless answers 404 only for a caller allowed to know an item is missing,
  566. // and 401 for one that is not, so the HTTP status is the discriminator. The
  567. // wording of the error body is not part of the contract and is not matched on.
  568. func TestDescribeItemMapsAPIStatus(t *testing.T) {
  569. const notFoundBody = `{"error":"failed to obtain item description: Desc: Failed to get item. ` +
  570. `Status 404 Not Found, Error: NotFound. Message: account id: acc-x, access id: p-y. ` +
  571. `failed to obtain item /some/item"}`
  572. const deniedBody = `{"error":"failed to obtain item description: Desc: Failed to get item. ` +
  573. `Status 401 Unauthorized, Error: UnauthorizedAccess. Message: account id: acc-x, ` +
  574. `access id: p-y. unauthorized access for access id p-y"}`
  575. tests := []struct {
  576. name string
  577. status int
  578. body string
  579. notExists bool
  580. wantMessage string
  581. }{
  582. {
  583. name: "404 means the item is absent",
  584. status: http.StatusNotFound,
  585. body: notFoundBody,
  586. notExists: true,
  587. },
  588. {
  589. name: "401 is an authorization failure, not an absent item",
  590. status: http.StatusUnauthorized,
  591. body: deniedBody,
  592. wantMessage: "UnauthorizedAccess",
  593. },
  594. {
  595. name: "5xx is a server failure, not an absent item",
  596. status: http.StatusInternalServerError,
  597. body: `{"error":"internal server error"}`,
  598. wantMessage: "internal server error",
  599. },
  600. }
  601. for _, tt := range tests {
  602. t.Run(tt.name, func(t *testing.T) {
  603. a := newDescribeItemServer(t, tt.status, tt.body)
  604. item, err := a.DescribeItem(describeItemCtx(), "/some/item")
  605. require.Error(t, err)
  606. require.Nil(t, item)
  607. if tt.notExists {
  608. require.ErrorIs(t, err, ErrItemNotExists)
  609. return
  610. }
  611. // The caller must not mistake this for an absent item, and the
  612. // operator needs the reason Akeyless gave.
  613. require.NotErrorIs(t, err, ErrItemNotExists)
  614. require.Contains(t, err.Error(), tt.wantMessage)
  615. })
  616. }
  617. }
  618. // TestGetSecretByTypeSurfacesAuthFailure covers the path from the bug report:
  619. // a denied describe used to reach the caller as ErrItemNotExists, which made
  620. // SecretExists report absence and PushSecret attempt a create.
  621. func TestGetSecretByTypeSurfacesAuthFailure(t *testing.T) {
  622. a := newDescribeItemServer(t, http.StatusUnauthorized,
  623. `{"error":"Status 401 Unauthorized, Error: UnauthorizedAccess. Message: sub claim mismatch"}`)
  624. _, err := a.GetSecretByType(describeItemCtx(), "/some/item", 0)
  625. require.Error(t, err)
  626. require.NotErrorIs(t, err, ErrItemNotExists)
  627. require.Contains(t, err.Error(), "UnauthorizedAccess")
  628. }
  629. // TestDescribeItemSuccess guards the happy path, since the fix reorders the
  630. // error branch that precedes it.
  631. func TestDescribeItemSuccess(t *testing.T) {
  632. a := newDescribeItemServer(t, http.StatusOK,
  633. `{"item_name":"/some/item","item_type":"STATIC_SECRET","last_version":3}`)
  634. item, err := a.DescribeItem(describeItemCtx(), "/some/item")
  635. require.NoError(t, err)
  636. require.Equal(t, "/some/item", item.GetItemName())
  637. require.Equal(t, "STATIC_SECRET", item.GetItemType())
  638. }