provider_test.go 14 KB

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