akeyless_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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 akeyless
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "strings"
  19. "testing"
  20. "github.com/akeylesslabs/akeyless-go/v4"
  21. "github.com/stretchr/testify/require"
  22. corev1 "k8s.io/api/core/v1"
  23. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  25. fakeakeyless "github.com/external-secrets/external-secrets/pkg/provider/akeyless/fake"
  26. testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
  27. )
  28. type akeylessTestCase struct {
  29. testName string
  30. mockClient *fakeakeyless.AkeylessMockClient
  31. apiInput *fakeakeyless.Input
  32. apiOutput *fakeakeyless.Output
  33. ref *esv1.ExternalSecretDataRemoteRef
  34. input any
  35. input2 any
  36. expectError string
  37. expectedVal any
  38. expectedSecret string
  39. }
  40. const fmtExpectedError = "unexpected error: %s, expected: '%s'"
  41. func (a *akeylessTestCase) SetMockClient(c *fakeakeyless.AkeylessMockClient) *akeylessTestCase {
  42. a.mockClient = c
  43. return a
  44. }
  45. func (a *akeylessTestCase) SetExpectErr(err string) *akeylessTestCase {
  46. a.expectError = err
  47. return a
  48. }
  49. func (a *akeylessTestCase) SetExpectVal(val any) *akeylessTestCase {
  50. a.expectedVal = val
  51. return a
  52. }
  53. func (a *akeylessTestCase) SetExpectInput(input any) *akeylessTestCase {
  54. a.input = input
  55. return a
  56. }
  57. func (a *akeylessTestCase) SetExpectInput2(input any) *akeylessTestCase {
  58. a.input2 = input
  59. return a
  60. }
  61. func makeValidAkeylessTestCase(testName string) *akeylessTestCase {
  62. smtc := akeylessTestCase{
  63. testName: testName,
  64. mockClient: &fakeakeyless.AkeylessMockClient{},
  65. apiInput: makeValidInput(),
  66. ref: makeValidRef(),
  67. apiOutput: makeValidOutput(),
  68. expectError: "",
  69. expectedSecret: "",
  70. }
  71. smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
  72. return &smtc
  73. }
  74. func nilProviderTestCase() *akeylessTestCase {
  75. return makeValidAkeylessTestCase("nil provider").SetMockClient(nil).SetExpectErr(errUninitalizedAkeylessProvider)
  76. }
  77. func failGetTestCase() *akeylessTestCase {
  78. return makeValidAkeylessTestCase("fail GetSecret").SetExpectVal(false).SetExpectErr("fail get").
  79. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "", errors.New("fail get") }))
  80. }
  81. func makeValidRef() *esv1.ExternalSecretDataRemoteRef {
  82. return &esv1.ExternalSecretDataRemoteRef{
  83. Key: "test-secret",
  84. Version: "1",
  85. }
  86. }
  87. func makeValidInput() *fakeakeyless.Input {
  88. return &fakeakeyless.Input{
  89. SecretName: "name",
  90. Version: 0,
  91. Token: "token",
  92. }
  93. }
  94. func makeValidOutput() *fakeakeyless.Output {
  95. return &fakeakeyless.Output{
  96. Value: "secret-val",
  97. Err: nil,
  98. }
  99. }
  100. func makeValidAkeylessTestCaseCustom(tweaks ...func(smtc *akeylessTestCase)) *akeylessTestCase {
  101. smtc := makeValidAkeylessTestCase("")
  102. for _, fn := range tweaks {
  103. fn(smtc)
  104. }
  105. smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
  106. return smtc
  107. }
  108. // This case can be shared by both GetSecret and GetSecretMap tests.
  109. // bad case: set apiErr.
  110. var setAPIErr = func(smtc *akeylessTestCase) {
  111. smtc.apiOutput.Err = errors.New("oh no")
  112. smtc.expectError = "oh no"
  113. }
  114. var setNilMockClient = func(smtc *akeylessTestCase) {
  115. smtc.mockClient = nil
  116. smtc.expectError = errUninitalizedAkeylessProvider
  117. }
  118. func TestAkeylessGetSecret(t *testing.T) {
  119. secretValue := "changedvalue"
  120. // good case: default version is set
  121. // key is passed in, output is sent back
  122. setSecretString := func(smtc *akeylessTestCase) {
  123. smtc.apiOutput = &fakeakeyless.Output{
  124. Value: secretValue,
  125. Err: nil,
  126. }
  127. smtc.expectedSecret = secretValue
  128. }
  129. successCases := []*akeylessTestCase{
  130. makeValidAkeylessTestCaseCustom(setAPIErr),
  131. makeValidAkeylessTestCaseCustom(setSecretString),
  132. makeValidAkeylessTestCaseCustom(setNilMockClient),
  133. }
  134. sm := Akeyless{}
  135. for _, v := range successCases {
  136. sm.Client = v.mockClient
  137. out, err := sm.GetSecret(context.Background(), *v.ref)
  138. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  139. require.Equal(t, string(out), v.expectedSecret)
  140. }
  141. }
  142. func TestValidateStore(t *testing.T) {
  143. provider := Provider{}
  144. akeylessGWApiURL := ""
  145. t.Run("secret auth", func(t *testing.T) {
  146. store := &esv1.SecretStore{
  147. Spec: esv1.SecretStoreSpec{
  148. Provider: &esv1.SecretStoreProvider{
  149. Akeyless: &esv1.AkeylessProvider{
  150. AkeylessGWApiURL: &akeylessGWApiURL,
  151. Auth: &esv1.AkeylessAuth{
  152. SecretRef: esv1.AkeylessAuthSecretRef{
  153. AccessID: esmeta.SecretKeySelector{
  154. Name: "accessId",
  155. Key: "key-1",
  156. },
  157. AccessType: esmeta.SecretKeySelector{
  158. Name: "accessId",
  159. Key: "key-1",
  160. },
  161. AccessTypeParam: esmeta.SecretKeySelector{
  162. Name: "accessId",
  163. Key: "key-1",
  164. },
  165. },
  166. },
  167. },
  168. },
  169. },
  170. }
  171. _, err := provider.ValidateStore(store)
  172. require.NoError(t, err)
  173. })
  174. t.Run("k8s auth", func(t *testing.T) {
  175. store := &esv1.SecretStore{
  176. Spec: esv1.SecretStoreSpec{
  177. Provider: &esv1.SecretStoreProvider{
  178. Akeyless: &esv1.AkeylessProvider{
  179. AkeylessGWApiURL: &akeylessGWApiURL,
  180. Auth: &esv1.AkeylessAuth{
  181. KubernetesAuth: &esv1.AkeylessKubernetesAuth{
  182. K8sConfName: "name",
  183. AccessID: "id",
  184. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  185. Name: "name",
  186. },
  187. },
  188. },
  189. },
  190. },
  191. },
  192. }
  193. _, err := provider.ValidateStore(store)
  194. require.NoError(t, err)
  195. })
  196. t.Run("bad conf auth", func(t *testing.T) {
  197. store := &esv1.SecretStore{
  198. Spec: esv1.SecretStoreSpec{
  199. Provider: &esv1.SecretStoreProvider{
  200. Akeyless: &esv1.AkeylessProvider{
  201. AkeylessGWApiURL: &akeylessGWApiURL,
  202. Auth: &esv1.AkeylessAuth{},
  203. },
  204. },
  205. },
  206. }
  207. _, err := provider.ValidateStore(store)
  208. require.Error(t, err)
  209. })
  210. t.Run("bad k8s conf auth", func(t *testing.T) {
  211. store := &esv1.SecretStore{
  212. Spec: esv1.SecretStoreSpec{
  213. Provider: &esv1.SecretStoreProvider{
  214. Akeyless: &esv1.AkeylessProvider{
  215. AkeylessGWApiURL: &akeylessGWApiURL,
  216. Auth: &esv1.AkeylessAuth{
  217. KubernetesAuth: &esv1.AkeylessKubernetesAuth{
  218. AccessID: "id",
  219. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  220. Name: "name",
  221. },
  222. },
  223. },
  224. },
  225. },
  226. },
  227. }
  228. _, err := provider.ValidateStore(store)
  229. require.Error(t, err)
  230. })
  231. }
  232. func TestGetSecretMap(t *testing.T) {
  233. // good case: default version & deserialization
  234. setDeserialization := func(smtc *akeylessTestCase) {
  235. smtc.apiOutput.Value = `{"foo":"bar"}`
  236. smtc.expectedVal = map[string][]byte{"foo": []byte("bar")}
  237. }
  238. // bad case: invalid json
  239. setInvalidJSON := func(smtc *akeylessTestCase) {
  240. smtc.apiOutput.Value = `-----------------`
  241. smtc.expectError = "unable to unmarshal secret"
  242. }
  243. successCases := []*akeylessTestCase{
  244. makeValidAkeylessTestCaseCustom(setDeserialization),
  245. makeValidAkeylessTestCaseCustom(setInvalidJSON).SetExpectVal(map[string][]byte(nil)),
  246. makeValidAkeylessTestCaseCustom(setAPIErr).SetExpectVal(map[string][]byte(nil)),
  247. makeValidAkeylessTestCaseCustom(setNilMockClient).SetExpectVal(map[string][]byte(nil)),
  248. }
  249. sm := Akeyless{}
  250. for _, v := range successCases {
  251. sm.Client = v.mockClient
  252. out, err := sm.GetSecretMap(context.Background(), *v.ref)
  253. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  254. require.Equal(t, v.expectedVal.(map[string][]byte), out)
  255. }
  256. }
  257. func ErrorContains(out error, want string) bool {
  258. if out == nil {
  259. return want == ""
  260. }
  261. if want == "" {
  262. return false
  263. }
  264. return strings.Contains(out.Error(), want)
  265. }
  266. func TestSecretExists(t *testing.T) {
  267. testCases := []*akeylessTestCase{
  268. nilProviderTestCase().SetExpectVal(false),
  269. makeValidAkeylessTestCase("no secret").SetExpectVal(false).
  270. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "", ErrItemNotExists })),
  271. failGetTestCase(),
  272. makeValidAkeylessTestCase("success without property").SetExpectVal(true).SetExpectInput(&testingfake.PushSecretData{Property: ""}).
  273. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "my secret", nil })),
  274. makeValidAkeylessTestCase("fail unmarshal").SetExpectVal(false).SetExpectErr("invalid character 'd' looking for beginning of value").SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
  275. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "daenerys", nil })),
  276. makeValidAkeylessTestCase("no property").SetExpectVal(false).SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
  277. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"propa": "a"}`, nil })),
  278. makeValidAkeylessTestCase("success with property").SetExpectVal(true).SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
  279. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"prop": "a"}`, nil })),
  280. }
  281. sm := Akeyless{}
  282. t.Parallel()
  283. for _, v := range testCases {
  284. t.Run(v.testName, func(t *testing.T) {
  285. sm.Client = v.mockClient
  286. if v.input == nil {
  287. v.input = &testingfake.PushSecretData{}
  288. }
  289. out, err := sm.SecretExists(context.Background(), v.input.(esv1.PushSecretRemoteRef))
  290. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  291. require.Equal(t, out, v.expectedVal.(bool))
  292. })
  293. }
  294. }
  295. func TestPushSecret(t *testing.T) {
  296. testCases := []*akeylessTestCase{
  297. nilProviderTestCase(),
  298. failGetTestCase(),
  299. makeValidAkeylessTestCase("fail unmarshal").SetExpectErr("invalid character 'm' looking for beginning of value").
  300. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "morgoth", nil })),
  301. makeValidAkeylessTestCase("create new secret").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
  302. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return "", ErrItemNotExists }).
  303. SetCreateSecretFn(func(_ context.Context, _ string, data string) error {
  304. if data != `{"test":"test"}` {
  305. return errors.New("secret is not good")
  306. }
  307. return nil
  308. })),
  309. makeValidAkeylessTestCase("update secret").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test2": []byte("test2")}}).
  310. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"test2":"untest"}`, nil }).
  311. SetUpdateSecretFn(func(_ context.Context, _ string, data string) error {
  312. if data != `{"test2":"test2"}` {
  313. return errors.New("secret is not good")
  314. }
  315. return nil
  316. })),
  317. makeValidAkeylessTestCase("shouldnt update").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
  318. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"test":"test"}`, nil })),
  319. makeValidAkeylessTestCase("merge secret maps").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
  320. SetExpectInput2(&testingfake.PushSecretData{Property: "test", SecretKey: "test"}).
  321. SetMockClient(fakeakeyless.New().SetGetSecretFn(func(_ string, _ int32) (string, error) { return `{"test2":"test2"}`, nil }).
  322. SetUpdateSecretFn(func(_ context.Context, _ string, data string) error {
  323. expected := `{"test":"test","test2":"test2"}`
  324. if data != expected {
  325. return fmt.Errorf("secret %s expected %s", data, expected)
  326. }
  327. return nil
  328. })),
  329. }
  330. sm := Akeyless{}
  331. t.Parallel()
  332. for _, v := range testCases {
  333. t.Run(v.testName, func(t *testing.T) {
  334. sm.Client = v.mockClient
  335. if v.input == nil {
  336. v.input = &corev1.Secret{}
  337. }
  338. if v.input2 == nil {
  339. v.input2 = &testingfake.PushSecretData{}
  340. }
  341. err := sm.PushSecret(context.Background(), v.input.(*corev1.Secret), v.input2.(esv1.PushSecretData))
  342. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  343. })
  344. }
  345. }
  346. func TestDeleteSecret(t *testing.T) {
  347. testCases := []*akeylessTestCase{
  348. nilProviderTestCase(),
  349. makeValidAkeylessTestCase("fail describe").SetExpectErr("err desc").
  350. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) { return nil, errors.New("err desc") })),
  351. makeValidAkeylessTestCase("no such item").
  352. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) { return nil, nil })),
  353. makeValidAkeylessTestCase("tags nil").
  354. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) { return &akeyless.Item{}, nil })),
  355. makeValidAkeylessTestCase("no external secret managed tags").
  356. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  357. return &akeyless.Item{ItemTags: &[]string{"some-random-tag"}}, nil
  358. })),
  359. makeValidAkeylessTestCase("delete whole secret").SetExpectInput(&testingfake.PushSecretData{RemoteKey: "42"}).
  360. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  361. return &akeyless.Item{ItemTags: &[]string{extSecretManagedTag}}, nil
  362. }).SetDeleteSecretFn(func(_ context.Context, remoteKey string) error {
  363. if remoteKey != "42" {
  364. return fmt.Errorf("remote key %s expected %s", remoteKey, "42")
  365. }
  366. return nil
  367. })),
  368. makeValidAkeylessTestCase("delete property of secret").SetExpectInput(&testingfake.PushSecretData{Property: "Foo"}).
  369. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  370. return &akeyless.Item{ItemTags: &[]string{extSecretManagedTag}}, nil
  371. }).SetGetSecretFn(func(_ string, _ int32) (string, error) {
  372. return `{"Dio": "Brando", "Foo": "Fighters"}`, nil
  373. }).
  374. SetUpdateSecretFn(func(_ context.Context, _ string, data string) error {
  375. expected := `{"Dio":"Brando"}`
  376. if data != expected {
  377. return fmt.Errorf("secret %s expected %s", data, expected)
  378. }
  379. return nil
  380. })),
  381. makeValidAkeylessTestCase("delete secret if one property left").SetExpectInput(&testingfake.PushSecretData{RemoteKey: "Rings", Property: "Annatar"}).
  382. SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(_ context.Context, _ string) (*akeyless.Item, error) {
  383. return &akeyless.Item{ItemTags: &[]string{extSecretManagedTag}}, nil
  384. }).SetGetSecretFn(func(_ string, _ int32) (string, error) {
  385. return `{"Annatar": "The Lord of Gifts"}`, nil
  386. }).
  387. SetDeleteSecretFn(func(_ context.Context, remoteKey string) error {
  388. if remoteKey != "Rings" {
  389. return fmt.Errorf("remote key %s expected %s", remoteKey, "Annatar")
  390. }
  391. return nil
  392. })),
  393. }
  394. sm := Akeyless{}
  395. t.Parallel()
  396. for _, v := range testCases {
  397. t.Run(v.testName, func(t *testing.T) {
  398. sm.Client = v.mockClient
  399. if v.input == nil {
  400. v.input = &testingfake.PushSecretData{}
  401. }
  402. err := sm.DeleteSecret(context.Background(), v.input.(esv1.PushSecretData))
  403. require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
  404. })
  405. }
  406. }