onboardbase_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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 impliec.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package onboardbase
  13. import (
  14. "context"
  15. "errors"
  16. "strings"
  17. "testing"
  18. "github.com/google/go-cmp/cmp"
  19. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  20. v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
  21. "github.com/external-secrets/external-secrets/pkg/provider/onboardbase/client"
  22. "github.com/external-secrets/external-secrets/pkg/provider/onboardbase/fake"
  23. )
  24. const (
  25. validSecretName = "API_KEY"
  26. validSecretValue = "3a3ea4f5"
  27. onboardbaseProject = "ONBOARDBASE_PROJECT"
  28. onboardbaseEnvironment = "development"
  29. onboardbaseProjectVal = "payments-service"
  30. missingSecret = "INVALID_NAME"
  31. invalidSecret = "unknown_project"
  32. missingSecretErr = "could not get secret"
  33. )
  34. type onboardbaseTestCase struct {
  35. label string
  36. fakeClient *fake.OnboardbaseClient
  37. request client.SecretRequest
  38. response *client.SecretResponse
  39. remoteRef *esv1beta1.ExternalSecretDataRemoteRef
  40. PushSecretRemoteRef esv1beta1.PushSecretRemoteRef
  41. apiErr error
  42. expectError string
  43. expectedSecret string
  44. expectedData map[string][]byte
  45. }
  46. func makeValidAPIRequest() client.SecretRequest {
  47. return client.SecretRequest{
  48. Name: validSecretName,
  49. }
  50. }
  51. func makeValidAPIOutput() *client.SecretResponse {
  52. return &client.SecretResponse{
  53. Name: validSecretName,
  54. Value: validSecretValue,
  55. }
  56. }
  57. func makeValidRemoteRef() *esv1beta1.ExternalSecretDataRemoteRef {
  58. return &esv1beta1.ExternalSecretDataRemoteRef{
  59. Key: validSecretName,
  60. }
  61. }
  62. type pushRemoteRef struct {
  63. secretKey string
  64. }
  65. func (pRef pushRemoteRef) GetProperty() string {
  66. return ""
  67. }
  68. func (pRef pushRemoteRef) GetRemoteKey() string {
  69. return pRef.secretKey
  70. }
  71. func makeValidPushRemoteRef(key string) esv1beta1.PushSecretRemoteRef {
  72. return pushRemoteRef{
  73. secretKey: key,
  74. }
  75. }
  76. func makeValidOnboardbaseTestCase() *onboardbaseTestCase {
  77. return &onboardbaseTestCase{
  78. fakeClient: &fake.OnboardbaseClient{},
  79. request: makeValidAPIRequest(),
  80. response: makeValidAPIOutput(),
  81. remoteRef: makeValidRemoteRef(),
  82. PushSecretRemoteRef: makeValidPushRemoteRef(validSecretName),
  83. apiErr: nil,
  84. expectError: "",
  85. expectedSecret: "",
  86. expectedData: make(map[string][]byte),
  87. }
  88. }
  89. func makeValidOnboardbaseTestCaseCustom(tweaks ...func(pstc *onboardbaseTestCase)) *onboardbaseTestCase {
  90. pstc := makeValidOnboardbaseTestCase()
  91. for _, fn := range tweaks {
  92. fn(pstc)
  93. }
  94. pstc.fakeClient.WithValue(pstc.request, pstc.response, pstc.apiErr)
  95. return pstc
  96. }
  97. func TestGetSecret(t *testing.T) {
  98. setSecret := func(pstc *onboardbaseTestCase) {
  99. pstc.label = "set secret"
  100. pstc.request.Name = onboardbaseProject
  101. pstc.response.Name = onboardbaseProject
  102. pstc.response.Value = onboardbaseProjectVal
  103. pstc.expectedSecret = onboardbaseProjectVal
  104. pstc.remoteRef.Key = onboardbaseProject
  105. }
  106. setMissingSecret := func(pstc *onboardbaseTestCase) {
  107. pstc.label = "invalid missing secret"
  108. pstc.remoteRef.Key = missingSecret
  109. pstc.request.Name = missingSecret
  110. pstc.response = nil
  111. pstc.expectError = missingSecretErr
  112. pstc.apiErr = errors.New("")
  113. }
  114. setInvalidSecret := func(pstc *onboardbaseTestCase) {
  115. pstc.label = "invalid secret name format"
  116. pstc.remoteRef.Key = invalidSecret
  117. pstc.request.Name = invalidSecret
  118. pstc.response = nil
  119. pstc.expectError = missingSecretErr
  120. pstc.apiErr = errors.New("")
  121. }
  122. setClientError := func(pstc *onboardbaseTestCase) {
  123. pstc.label = "invalid client error"
  124. pstc.response = &client.SecretResponse{}
  125. pstc.expectError = missingSecretErr
  126. pstc.apiErr = errors.New("")
  127. }
  128. testCases := []*onboardbaseTestCase{
  129. makeValidOnboardbaseTestCaseCustom(setSecret),
  130. makeValidOnboardbaseTestCaseCustom(setMissingSecret),
  131. makeValidOnboardbaseTestCaseCustom(setInvalidSecret),
  132. makeValidOnboardbaseTestCaseCustom(setClientError),
  133. }
  134. c := Client{}
  135. for k, tc := range testCases {
  136. c.onboardbase = tc.fakeClient
  137. out, err := c.GetSecret(context.Background(), *tc.remoteRef)
  138. if !ErrorContains(err, tc.expectError) {
  139. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
  140. }
  141. if err == nil && !cmp.Equal(string(out), tc.expectedSecret) {
  142. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, tc.expectedSecret, string(out))
  143. }
  144. }
  145. }
  146. func TestDeleteSecret(t *testing.T) {
  147. setMissingSecret := func(pstc *onboardbaseTestCase) {
  148. pstc.label = "invalid missing secret"
  149. pstc.remoteRef.Key = missingSecret
  150. pstc.PushSecretRemoteRef = makeValidPushRemoteRef(missingSecret)
  151. pstc.request.Name = missingSecret
  152. pstc.response = nil
  153. pstc.expectError = missingSecretErr
  154. pstc.apiErr = errors.New("")
  155. }
  156. setInvalidSecret := func(pstc *onboardbaseTestCase) {
  157. pstc.label = "invalid secret name format"
  158. pstc.remoteRef.Key = invalidSecret
  159. pstc.PushSecretRemoteRef = makeValidPushRemoteRef(invalidSecret)
  160. pstc.request.Name = invalidSecret
  161. pstc.response = nil
  162. pstc.expectError = missingSecretErr
  163. pstc.apiErr = errors.New("")
  164. }
  165. deleteSecret := func(pstc *onboardbaseTestCase) {
  166. pstc.label = "delete secret successfully"
  167. pstc.remoteRef.Key = validSecretName
  168. pstc.PushSecretRemoteRef = makeValidPushRemoteRef(validSecretName)
  169. pstc.request.Name = validSecretName
  170. pstc.response = nil
  171. pstc.apiErr = nil
  172. }
  173. testCases := []*onboardbaseTestCase{
  174. makeValidOnboardbaseTestCaseCustom(setMissingSecret),
  175. makeValidOnboardbaseTestCaseCustom(setInvalidSecret),
  176. makeValidOnboardbaseTestCaseCustom(deleteSecret),
  177. }
  178. c := Client{}
  179. for k, tc := range testCases {
  180. c.onboardbase = tc.fakeClient
  181. err := c.DeleteSecret(context.Background(), tc.PushSecretRemoteRef)
  182. if err != nil && !ErrorContains(err, tc.expectError) {
  183. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
  184. }
  185. }
  186. }
  187. func TestGetSecretMap(t *testing.T) {
  188. simpleJSON := func(pstc *onboardbaseTestCase) {
  189. pstc.label = "valid unmarshalling"
  190. pstc.response.Value = `{"API_KEY":"3a3ea4f5"}`
  191. pstc.expectedData["API_KEY"] = []byte("3a3ea4f5")
  192. }
  193. complexJSON := func(pstc *onboardbaseTestCase) {
  194. pstc.label = "valid unmarshalling for nested json"
  195. pstc.response.Value = `{"API_KEY": "3a3ea4fs5", "AUTH_SA": {"appID": "a1ea-48bd-8749-b6f5ec3c5a1f"}}`
  196. pstc.expectedData["API_KEY"] = []byte("3a3ea4fs5")
  197. pstc.expectedData["AUTH_SA"] = []byte(`{"appID": "a1ea-48bd-8749-b6f5ec3c5a1f"}`)
  198. }
  199. setInvalidJSON := func(pstc *onboardbaseTestCase) {
  200. pstc.label = "invalid json"
  201. pstc.response.Value = `{"API_KEY": "3a3ea4f`
  202. pstc.expectError = "unable to unmarshal secret"
  203. }
  204. setAPIError := func(pstc *onboardbaseTestCase) {
  205. pstc.label = "client error"
  206. pstc.response = &client.SecretResponse{}
  207. pstc.expectError = missingSecretErr
  208. pstc.apiErr = errors.New("")
  209. }
  210. testCases := []*onboardbaseTestCase{
  211. makeValidOnboardbaseTestCaseCustom(simpleJSON),
  212. makeValidOnboardbaseTestCaseCustom(complexJSON),
  213. makeValidOnboardbaseTestCaseCustom(setInvalidJSON),
  214. makeValidOnboardbaseTestCaseCustom(setAPIError),
  215. }
  216. d := Client{}
  217. for k, tc := range testCases {
  218. t.Run(tc.label, func(t *testing.T) {
  219. d.onboardbase = tc.fakeClient
  220. out, err := d.GetSecretMap(context.Background(), *tc.remoteRef)
  221. if !ErrorContains(err, tc.expectError) {
  222. t.Errorf("[%d] unexpected error: %q, expected: %q", k, err.Error(), tc.expectError)
  223. }
  224. if err == nil && !cmp.Equal(out, tc.expectedData) {
  225. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, tc.expectedData, out)
  226. }
  227. })
  228. }
  229. }
  230. func ErrorContains(out error, want string) bool {
  231. if out == nil {
  232. return want == ""
  233. }
  234. if want == "" {
  235. return false
  236. }
  237. return strings.Contains(out.Error(), want)
  238. }
  239. type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
  240. func makeSecretStore(fn ...storeModifier) *esv1beta1.SecretStore {
  241. store := &esv1beta1.SecretStore{
  242. Spec: esv1beta1.SecretStoreSpec{
  243. Provider: &esv1beta1.SecretStoreProvider{
  244. Onboardbase: &esv1beta1.OnboardbaseProvider{
  245. Auth: &esv1beta1.OnboardbaseAuthSecretRef{},
  246. },
  247. },
  248. },
  249. }
  250. for _, f := range fn {
  251. store = f(store)
  252. }
  253. return store
  254. }
  255. func withAuth(name, key string, namespace *string, passcode string) storeModifier {
  256. return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
  257. store.Spec.Provider.Onboardbase.Auth.OnboardbaseAPIKeyRef = v1.SecretKeySelector{
  258. Name: name,
  259. Key: key,
  260. Namespace: namespace,
  261. }
  262. store.Spec.Provider.Onboardbase.Auth.OnboardbasePasscodeRef = v1.SecretKeySelector{
  263. Name: passcode,
  264. Key: passcode,
  265. Namespace: namespace,
  266. }
  267. return store
  268. }
  269. }
  270. type ValidateStoreTestCase struct {
  271. label string
  272. store *esv1beta1.SecretStore
  273. err error
  274. }
  275. func TestValidateStore(t *testing.T) {
  276. namespace := "ns"
  277. secretName := "onboardbase-api-key-secret"
  278. testCases := []ValidateStoreTestCase{
  279. {
  280. label: "invalid store missing onboardbaseAPIKey.name",
  281. store: makeSecretStore(withAuth("", "", nil, "")),
  282. err: errors.New("invalid store: onboardbaseAPIKey.name cannot be empty"),
  283. },
  284. {
  285. label: "invalid store missing onboardbasePasscode.name",
  286. store: makeSecretStore(withAuth(secretName, "", nil, "")),
  287. err: errors.New("invalid store: onboardbasePasscode.name cannot be empty"),
  288. },
  289. {
  290. label: "invalid store namespace not allowed",
  291. store: makeSecretStore(withAuth(secretName, "", &namespace, "passcode")),
  292. err: errors.New("invalid store: namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  293. },
  294. {
  295. label: "valid provide optional onboardbaseAPIKey.key",
  296. store: makeSecretStore(withAuth(secretName, "customSecretKey", nil, "passcode")),
  297. err: nil,
  298. },
  299. {
  300. label: "valid namespace not set",
  301. store: makeSecretStore(withAuth(secretName, "", nil, "passcode")),
  302. err: nil,
  303. },
  304. }
  305. p := Provider{}
  306. for _, tc := range testCases {
  307. t.Run(tc.label, func(t *testing.T) {
  308. _, err := p.ValidateStore(tc.store)
  309. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  310. t.Errorf("test failed! want %v, got %v", tc.err, err)
  311. } else if tc.err == nil && err != nil {
  312. t.Errorf("want nil got err %v", err)
  313. } else if tc.err != nil && err == nil {
  314. t.Errorf("want err %v got nil", tc.err)
  315. }
  316. })
  317. }
  318. }