akeyless_test.go 16 KB

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