provider_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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 ibm
  13. import (
  14. "context"
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. "testing"
  19. "github.com/IBM/go-sdk-core/v5/core"
  20. sm "github.com/IBM/secrets-manager-go-sdk/secretsmanagerv1"
  21. "github.com/crossplane/crossplane-runtime/pkg/test"
  22. corev1 "k8s.io/api/core/v1"
  23. utilpointer "k8s.io/utils/pointer"
  24. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  25. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  26. v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. fakesm "github.com/external-secrets/external-secrets/pkg/provider/ibm/fake"
  28. )
  29. type secretManagerTestCase struct {
  30. mockClient *fakesm.IBMMockClient
  31. apiInput *sm.GetSecretOptions
  32. apiOutput *sm.GetSecret
  33. ref *esv1alpha1.ExternalSecretDataRemoteRef
  34. refFrom *esv1alpha1.ExternalSecretDataFromRemoteRef
  35. serviceURL *string
  36. apiErr error
  37. expectError string
  38. expectedSecret string
  39. // for testing secretmap
  40. expectedData map[string][]byte
  41. }
  42. func makeValidSecretManagerTestCase() *secretManagerTestCase {
  43. smtc := secretManagerTestCase{
  44. mockClient: &fakesm.IBMMockClient{},
  45. apiInput: makeValidAPIInput(),
  46. ref: makeValidRef(),
  47. refFrom: makeValidRefFrom(),
  48. apiOutput: makeValidAPIOutput(),
  49. serviceURL: nil,
  50. apiErr: nil,
  51. expectError: "",
  52. expectedSecret: "",
  53. expectedData: map[string][]byte{},
  54. }
  55. smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput, smtc.apiErr)
  56. return &smtc
  57. }
  58. func makeValidRef() *esv1alpha1.ExternalSecretDataRemoteRef {
  59. return &esv1alpha1.ExternalSecretDataRemoteRef{
  60. Key: "test-secret",
  61. Version: "default",
  62. }
  63. }
  64. func makeValidRefFrom() *esv1alpha1.ExternalSecretDataFromRemoteRef {
  65. return &esv1alpha1.ExternalSecretDataFromRemoteRef{
  66. Extract: esv1alpha1.ExternalSecretExtract{
  67. Key: "test-secret",
  68. Version: "default",
  69. },
  70. }
  71. }
  72. func makeValidAPIInput() *sm.GetSecretOptions {
  73. return &sm.GetSecretOptions{
  74. SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
  75. ID: utilpointer.StringPtr("test-secret"),
  76. }
  77. }
  78. func makeValidAPIOutput() *sm.GetSecret {
  79. secretData := make(map[string]interface{})
  80. secretData["payload"] = ""
  81. return &sm.GetSecret{
  82. Resources: []sm.SecretResourceIntf{
  83. &sm.SecretResource{
  84. SecretType: utilpointer.StringPtr("testytype"),
  85. Name: utilpointer.StringPtr("testyname"),
  86. SecretData: secretData,
  87. },
  88. },
  89. }
  90. }
  91. func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
  92. smtc := makeValidSecretManagerTestCase()
  93. for _, fn := range tweaks {
  94. fn(smtc)
  95. }
  96. smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput, smtc.apiErr)
  97. return smtc
  98. }
  99. // This case can be shared by both GetSecret and GetSecretMap tests.
  100. // bad case: set apiErr.
  101. var setAPIErr = func(smtc *secretManagerTestCase) {
  102. smtc.apiErr = fmt.Errorf("oh no")
  103. smtc.expectError = "oh no"
  104. }
  105. var setNilMockClient = func(smtc *secretManagerTestCase) {
  106. smtc.mockClient = nil
  107. smtc.expectError = errUninitalizedIBMProvider
  108. }
  109. // test the sm<->gcp interface
  110. // make sure correct values are passed and errors are handled accordingly.
  111. func TestIBMSecretManagerGetSecret(t *testing.T) {
  112. secretData := make(map[string]interface{})
  113. secretString := "changedvalue"
  114. secretPassword := "P@ssw0rd"
  115. secretAPIKey := "01234567890"
  116. secretCertificate := "certificate_value"
  117. secretData["payload"] = secretString
  118. secretData["password"] = secretPassword
  119. secretData["certificate"] = secretCertificate
  120. // good case: default version is set
  121. // key is passed in, output is sent back
  122. setSecretString := func(smtc *secretManagerTestCase) {
  123. resources := []sm.SecretResourceIntf{
  124. &sm.SecretResource{
  125. SecretType: utilpointer.StringPtr("testytype"),
  126. Name: utilpointer.StringPtr("testyname"),
  127. SecretData: secretData,
  128. }}
  129. smtc.apiOutput.Resources = resources
  130. smtc.expectedSecret = secretString
  131. }
  132. // good case: custom version set
  133. setCustomKey := func(smtc *secretManagerTestCase) {
  134. resources := []sm.SecretResourceIntf{
  135. &sm.SecretResource{
  136. SecretType: utilpointer.StringPtr("testytype"),
  137. Name: utilpointer.StringPtr("testyname"),
  138. SecretData: secretData,
  139. }}
  140. smtc.ref.Key = "testyname"
  141. smtc.apiInput.ID = utilpointer.StringPtr("testyname")
  142. smtc.apiOutput.Resources = resources
  143. smtc.expectedSecret = secretString
  144. }
  145. // bad case: username_password type without property
  146. secretUserPass := "username_password/test-secret"
  147. badSecretUserPass := func(smtc *secretManagerTestCase) {
  148. resources := []sm.SecretResourceIntf{
  149. &sm.SecretResource{
  150. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  151. Name: utilpointer.StringPtr("testyname"),
  152. SecretData: secretData,
  153. }}
  154. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
  155. smtc.apiOutput.Resources = resources
  156. smtc.ref.Key = secretUserPass
  157. smtc.expectError = "remoteRef.property required for secret type username_password"
  158. }
  159. // good case: username_password type with property
  160. setSecretUserPass := func(smtc *secretManagerTestCase) {
  161. resources := []sm.SecretResourceIntf{
  162. &sm.SecretResource{
  163. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  164. Name: utilpointer.StringPtr("testyname"),
  165. SecretData: secretData,
  166. }}
  167. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
  168. smtc.apiOutput.Resources = resources
  169. smtc.ref.Key = secretUserPass
  170. smtc.ref.Property = "password"
  171. smtc.expectedSecret = secretPassword
  172. }
  173. // good case: iam_credenatials type
  174. setSecretIam := func(smtc *secretManagerTestCase) {
  175. resources := []sm.SecretResourceIntf{
  176. &sm.SecretResource{
  177. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
  178. Name: utilpointer.StringPtr("testyname"),
  179. APIKey: utilpointer.StringPtr(secretAPIKey),
  180. }}
  181. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst)
  182. smtc.apiOutput.Resources = resources
  183. smtc.ref.Key = "iam_credentials/test-secret"
  184. smtc.expectedSecret = secretAPIKey
  185. }
  186. // good case: imported_cert type with property
  187. secretCert := "imported_cert/test-secret"
  188. setSecretCert := func(smtc *secretManagerTestCase) {
  189. resources := []sm.SecretResourceIntf{
  190. &sm.SecretResource{
  191. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  192. Name: utilpointer.StringPtr("testyname"),
  193. SecretData: secretData,
  194. }}
  195. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
  196. smtc.apiOutput.Resources = resources
  197. smtc.ref.Key = secretCert
  198. smtc.ref.Property = "certificate"
  199. smtc.expectedSecret = secretCertificate
  200. }
  201. // bad case: imported_cert type without property
  202. badSecretCert := func(smtc *secretManagerTestCase) {
  203. resources := []sm.SecretResourceIntf{
  204. &sm.SecretResource{
  205. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  206. Name: utilpointer.StringPtr("testyname"),
  207. SecretData: secretData,
  208. }}
  209. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
  210. smtc.apiOutput.Resources = resources
  211. smtc.ref.Key = secretCert
  212. smtc.expectError = "remoteref.Property required for secret type imported_cert"
  213. }
  214. successCases := []*secretManagerTestCase{
  215. makeValidSecretManagerTestCase(),
  216. makeValidSecretManagerTestCaseCustom(setSecretString),
  217. makeValidSecretManagerTestCaseCustom(setCustomKey),
  218. makeValidSecretManagerTestCaseCustom(setAPIErr),
  219. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  220. makeValidSecretManagerTestCaseCustom(badSecretUserPass),
  221. makeValidSecretManagerTestCaseCustom(setSecretUserPass),
  222. makeValidSecretManagerTestCaseCustom(setSecretIam),
  223. makeValidSecretManagerTestCaseCustom(setSecretCert),
  224. makeValidSecretManagerTestCaseCustom(badSecretCert),
  225. }
  226. sm := providerIBM{}
  227. for k, v := range successCases {
  228. sm.IBMClient = v.mockClient
  229. out, err := sm.GetSecret(context.Background(), *v.ref)
  230. if !ErrorContains(err, v.expectError) {
  231. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
  232. }
  233. if string(out) != v.expectedSecret {
  234. t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
  235. }
  236. }
  237. }
  238. func TestGetSecretMap(t *testing.T) {
  239. secretUsername := "user1"
  240. secretPassword := "P@ssw0rd"
  241. secretAPIKey := "01234567890"
  242. secretCertificate := "certificate_value"
  243. secretPrivateKey := "private_key_value"
  244. secretIntermediate := "intermediate_value"
  245. // good case: default version & deserialization
  246. setDeserialization := func(smtc *secretManagerTestCase) {
  247. secretData := make(map[string]interface{})
  248. secretData["payload"] = `{"foo":"bar"}`
  249. resources := []sm.SecretResourceIntf{
  250. &sm.SecretResource{
  251. SecretType: utilpointer.StringPtr("testytype"),
  252. Name: utilpointer.StringPtr("testyname"),
  253. SecretData: secretData,
  254. }}
  255. smtc.apiOutput.Resources = resources
  256. smtc.expectedData["foo"] = []byte("bar")
  257. }
  258. // bad case: invalid json
  259. setInvalidJSON := func(smtc *secretManagerTestCase) {
  260. secretData := make(map[string]interface{})
  261. secretData["payload"] = `-----------------`
  262. resources := []sm.SecretResourceIntf{
  263. &sm.SecretResource{
  264. SecretType: utilpointer.StringPtr("testytype"),
  265. Name: utilpointer.StringPtr("testyname"),
  266. SecretData: secretData,
  267. }}
  268. smtc.apiOutput.Resources = resources
  269. smtc.expectError = "unable to unmarshal secret: invalid character '-' in numeric literal"
  270. }
  271. // good case: username_password
  272. setSecretUserPass := func(smtc *secretManagerTestCase) {
  273. secretData := make(map[string]interface{})
  274. secretData["username"] = secretUsername
  275. secretData["password"] = secretPassword
  276. resources := []sm.SecretResourceIntf{
  277. &sm.SecretResource{
  278. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst),
  279. Name: utilpointer.StringPtr("testyname"),
  280. SecretData: secretData,
  281. }}
  282. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeUsernamePasswordConst)
  283. smtc.apiOutput.Resources = resources
  284. smtc.refFrom.Extract.Key = "username_password/test-secret"
  285. smtc.expectedData["username"] = []byte(secretUsername)
  286. smtc.expectedData["password"] = []byte(secretPassword)
  287. }
  288. // good case: iam_credentials
  289. setSecretIam := func(smtc *secretManagerTestCase) {
  290. resources := []sm.SecretResourceIntf{
  291. &sm.SecretResource{
  292. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst),
  293. Name: utilpointer.StringPtr("testyname"),
  294. APIKey: utilpointer.StringPtr(secretAPIKey),
  295. }}
  296. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeIamCredentialsConst)
  297. smtc.apiOutput.Resources = resources
  298. smtc.refFrom.Extract.Key = "iam_credentials/test-secret"
  299. smtc.expectedData["apikey"] = []byte(secretAPIKey)
  300. }
  301. // good case: imported_cert
  302. setSecretCert := func(smtc *secretManagerTestCase) {
  303. secretData := make(map[string]interface{})
  304. secretData["certificate"] = secretCertificate
  305. secretData["private_key"] = secretPrivateKey
  306. secretData["intermediate"] = secretIntermediate
  307. resources := []sm.SecretResourceIntf{
  308. &sm.SecretResource{
  309. SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
  310. Name: utilpointer.StringPtr("testyname"),
  311. SecretData: secretData,
  312. }}
  313. smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
  314. smtc.apiOutput.Resources = resources
  315. smtc.refFrom.Extract.Key = "imported_cert/test-secret"
  316. smtc.expectedData["certificate"] = []byte(secretCertificate)
  317. smtc.expectedData["private_key"] = []byte(secretPrivateKey)
  318. smtc.expectedData["intermediate"] = []byte(secretIntermediate)
  319. }
  320. successCases := []*secretManagerTestCase{
  321. makeValidSecretManagerTestCaseCustom(setDeserialization),
  322. makeValidSecretManagerTestCaseCustom(setInvalidJSON),
  323. makeValidSecretManagerTestCaseCustom(setNilMockClient),
  324. makeValidSecretManagerTestCaseCustom(setAPIErr),
  325. makeValidSecretManagerTestCaseCustom(setSecretUserPass),
  326. makeValidSecretManagerTestCaseCustom(setSecretIam),
  327. makeValidSecretManagerTestCaseCustom(setSecretCert),
  328. }
  329. sm := providerIBM{}
  330. for k, v := range successCases {
  331. sm.IBMClient = v.mockClient
  332. out, err := sm.GetSecretMap(context.Background(), *v.refFrom)
  333. if !ErrorContains(err, v.expectError) {
  334. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
  335. }
  336. if err == nil && !reflect.DeepEqual(out, v.expectedData) {
  337. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
  338. }
  339. }
  340. }
  341. func TestValidRetryInput(t *testing.T) {
  342. sm := providerIBM{}
  343. invalid := "Invalid"
  344. serviceURL := "http://fake-service-url.cool"
  345. spec := &esv1alpha1.SecretStore{
  346. Spec: esv1alpha1.SecretStoreSpec{
  347. Provider: &esv1alpha1.SecretStoreProvider{
  348. IBM: &esv1alpha1.IBMProvider{
  349. Auth: esv1alpha1.IBMAuth{
  350. SecretRef: esv1alpha1.IBMAuthSecretRef{
  351. SecretAPIKey: v1.SecretKeySelector{
  352. Name: "fake-secret",
  353. Key: "fake-key",
  354. },
  355. },
  356. },
  357. ServiceURL: &serviceURL,
  358. },
  359. },
  360. RetrySettings: &esv1alpha1.SecretStoreRetrySettings{
  361. RetryInterval: &invalid,
  362. },
  363. },
  364. }
  365. expected := fmt.Sprintf("cannot setup new ibm client: time: invalid duration %q", invalid)
  366. ctx := context.TODO()
  367. kube := &test.MockClient{
  368. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  369. if o, ok := obj.(*corev1.Secret); ok {
  370. o.Data = map[string][]byte{
  371. "fake-key": []byte("ImAFakeApiKey"),
  372. }
  373. return nil
  374. }
  375. return nil
  376. }),
  377. }
  378. _, err := sm.NewClient(ctx, spec, kube, "default")
  379. if !ErrorContains(err, expected) {
  380. t.Errorf("CheckValidRetryInput unexpected error: %s, expected: '%s'", err.Error(), expected)
  381. }
  382. }
  383. func ErrorContains(out error, want string) bool {
  384. if out == nil {
  385. return want == ""
  386. }
  387. if want == "" {
  388. return false
  389. }
  390. return strings.Contains(out.Error(), want)
  391. }