onboardbase_test.go 11 KB

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