doppler_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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 doppler
  13. import (
  14. "context"
  15. "errors"
  16. "strings"
  17. "testing"
  18. "github.com/google/go-cmp/cmp"
  19. corev1 "k8s.io/api/core/v1"
  20. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  21. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  22. v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
  23. "github.com/external-secrets/external-secrets/pkg/provider/doppler/client"
  24. "github.com/external-secrets/external-secrets/pkg/provider/doppler/fake"
  25. )
  26. const (
  27. validSecretName = "API_KEY"
  28. validSecretValue = "3a3ea4f5"
  29. validRemoteKey = "REMOTE_KEY"
  30. dopplerProject = "DOPPLER_PROJECT"
  31. dopplerProjectVal = "auth-api"
  32. missingSecret = "INVALID_NAME"
  33. invalidSecret = "doppler_project"
  34. invalidRemoteKey = "INVALID_REMOTE_KEY"
  35. missingSecretErr = "could not get secret"
  36. missingDeleteErr = "could not delete secrets"
  37. missingPushErr = "could not push secrets"
  38. )
  39. type dopplerTestCase struct {
  40. label string
  41. fakeClient *fake.DopplerClient
  42. request client.SecretRequest
  43. response *client.SecretResponse
  44. remoteRef *esv1.ExternalSecretDataRemoteRef
  45. apiErr error
  46. expectError string
  47. expectedSecret string
  48. expectedData map[string][]byte
  49. }
  50. type updateSecretCase struct {
  51. label string
  52. fakeClient *fake.DopplerClient
  53. request client.UpdateSecretsRequest
  54. remoteRef *esv1alpha1.PushSecretRemoteRef
  55. secret corev1.Secret
  56. secretData esv1.PushSecretData
  57. apiErr error
  58. expectError string
  59. }
  60. func makeValidAPIRequest() client.SecretRequest {
  61. return client.SecretRequest{
  62. Name: validSecretName,
  63. }
  64. }
  65. func makeValidPushRequest() client.UpdateSecretsRequest {
  66. return client.UpdateSecretsRequest{
  67. Secrets: client.Secrets{
  68. validRemoteKey: validSecretValue,
  69. },
  70. }
  71. }
  72. func makeValidDeleteRequest() client.UpdateSecretsRequest {
  73. return client.UpdateSecretsRequest{
  74. ChangeRequests: []client.Change{
  75. {
  76. Name: validRemoteKey,
  77. OriginalName: validRemoteKey,
  78. ShouldDelete: true,
  79. },
  80. },
  81. }
  82. }
  83. func makeValidAPIOutput() *client.SecretResponse {
  84. return &client.SecretResponse{
  85. Name: validSecretName,
  86. Value: validSecretValue,
  87. }
  88. }
  89. func makeValidRemoteRef() *esv1.ExternalSecretDataRemoteRef {
  90. return &esv1.ExternalSecretDataRemoteRef{
  91. Key: validSecretName,
  92. }
  93. }
  94. func makeValidPushRemoteRef() *esv1alpha1.PushSecretRemoteRef {
  95. return &esv1alpha1.PushSecretRemoteRef{
  96. RemoteKey: validRemoteKey,
  97. }
  98. }
  99. func makeValidSecret() corev1.Secret {
  100. return corev1.Secret{
  101. Data: map[string][]byte{
  102. validSecretName: []byte(validSecretValue),
  103. },
  104. }
  105. }
  106. func makeValidSecretData() esv1alpha1.PushSecretData {
  107. return makeSecretData(validSecretName, *makeValidPushRemoteRef())
  108. }
  109. func makeSecretData(key string, ref esv1alpha1.PushSecretRemoteRef) esv1alpha1.PushSecretData {
  110. return esv1alpha1.PushSecretData{
  111. Match: esv1alpha1.PushSecretMatch{
  112. SecretKey: key,
  113. RemoteRef: ref,
  114. },
  115. }
  116. }
  117. func makeValidDopplerTestCase() *dopplerTestCase {
  118. return &dopplerTestCase{
  119. fakeClient: &fake.DopplerClient{},
  120. request: makeValidAPIRequest(),
  121. response: makeValidAPIOutput(),
  122. remoteRef: makeValidRemoteRef(),
  123. apiErr: nil,
  124. expectError: "",
  125. expectedSecret: "",
  126. expectedData: make(map[string][]byte),
  127. }
  128. }
  129. func makeValidUpdateSecretTestCase() *updateSecretCase {
  130. return &updateSecretCase{
  131. fakeClient: &fake.DopplerClient{},
  132. remoteRef: makeValidPushRemoteRef(),
  133. secret: makeValidSecret(),
  134. secretData: makeValidSecretData(),
  135. apiErr: nil,
  136. expectError: "",
  137. }
  138. }
  139. func makeValidDopplerTestCaseCustom(tweaks ...func(pstc *dopplerTestCase)) *dopplerTestCase {
  140. pstc := makeValidDopplerTestCase()
  141. for _, fn := range tweaks {
  142. fn(pstc)
  143. }
  144. pstc.fakeClient.WithValue(pstc.request, pstc.response, pstc.apiErr)
  145. return pstc
  146. }
  147. func makeValidUpdateSecretCaseCustom(tweaks ...func(pstc *updateSecretCase)) *updateSecretCase {
  148. pstc := makeValidUpdateSecretTestCase()
  149. for _, fn := range tweaks {
  150. fn(pstc)
  151. }
  152. pstc.fakeClient.WithUpdateValue(pstc.request, pstc.apiErr)
  153. return pstc
  154. }
  155. func TestGetSecret(t *testing.T) {
  156. setSecret := func(pstc *dopplerTestCase) {
  157. pstc.label = "set secret"
  158. pstc.request.Name = dopplerProject
  159. pstc.response.Name = dopplerProject
  160. pstc.response.Value = dopplerProjectVal
  161. pstc.expectedSecret = dopplerProjectVal
  162. pstc.remoteRef.Key = dopplerProject
  163. }
  164. setMissingSecret := func(pstc *dopplerTestCase) {
  165. pstc.label = "invalid missing secret"
  166. pstc.remoteRef.Key = missingSecret
  167. pstc.request.Name = missingSecret
  168. pstc.response = nil
  169. pstc.expectError = missingSecretErr
  170. pstc.apiErr = errors.New("")
  171. }
  172. setInvalidSecret := func(pstc *dopplerTestCase) {
  173. pstc.label = "invalid secret name format"
  174. pstc.remoteRef.Key = invalidSecret
  175. pstc.request.Name = invalidSecret
  176. pstc.response = nil
  177. pstc.expectError = missingSecretErr
  178. pstc.apiErr = errors.New("")
  179. }
  180. setClientError := func(pstc *dopplerTestCase) {
  181. pstc.label = "invalid client error" //nolint:goconst
  182. pstc.response = &client.SecretResponse{}
  183. pstc.expectError = missingSecretErr
  184. pstc.apiErr = errors.New("")
  185. }
  186. testCases := []*dopplerTestCase{
  187. makeValidDopplerTestCaseCustom(setSecret),
  188. makeValidDopplerTestCaseCustom(setMissingSecret),
  189. makeValidDopplerTestCaseCustom(setInvalidSecret),
  190. makeValidDopplerTestCaseCustom(setClientError),
  191. }
  192. c := Client{}
  193. for k, tc := range testCases {
  194. c.doppler = tc.fakeClient
  195. out, err := c.GetSecret(context.Background(), *tc.remoteRef)
  196. if !ErrorContains(err, tc.expectError) {
  197. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
  198. }
  199. if err == nil && !cmp.Equal(string(out), tc.expectedSecret) {
  200. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, tc.expectedSecret, string(out))
  201. }
  202. }
  203. }
  204. func TestGetSecretMap(t *testing.T) {
  205. simpleJSON := func(pstc *dopplerTestCase) {
  206. pstc.label = "valid unmarshalling"
  207. pstc.response.Value = `{"API_KEY":"3a3ea4f5"}`
  208. pstc.expectedData["API_KEY"] = []byte("3a3ea4f5")
  209. }
  210. complexJSON := func(pstc *dopplerTestCase) {
  211. pstc.label = "valid unmarshalling for nested json"
  212. pstc.response.Value = `{"API_KEY": "3a3ea4f5", "AUTH_SA": {"appID": "a1ea-48bd-8749-b6f5ec3c5a1f"}}`
  213. pstc.expectedData["API_KEY"] = []byte("3a3ea4f5")
  214. pstc.expectedData["AUTH_SA"] = []byte(`{"appID": "a1ea-48bd-8749-b6f5ec3c5a1f"}`)
  215. }
  216. setInvalidJSON := func(pstc *dopplerTestCase) {
  217. pstc.label = "invalid json"
  218. pstc.response.Value = `{"API_KEY": "3a3ea4f`
  219. pstc.expectError = "unable to unmarshal secret"
  220. }
  221. setAPIError := func(pstc *dopplerTestCase) {
  222. pstc.label = "client error"
  223. pstc.response = &client.SecretResponse{}
  224. pstc.expectError = missingSecretErr
  225. pstc.apiErr = errors.New("")
  226. }
  227. testCases := []*dopplerTestCase{
  228. makeValidDopplerTestCaseCustom(simpleJSON),
  229. makeValidDopplerTestCaseCustom(complexJSON),
  230. makeValidDopplerTestCaseCustom(setInvalidJSON),
  231. makeValidDopplerTestCaseCustom(setAPIError),
  232. }
  233. d := Client{}
  234. for k, tc := range testCases {
  235. t.Run(tc.label, func(t *testing.T) {
  236. d.doppler = tc.fakeClient
  237. out, err := d.GetSecretMap(context.Background(), *tc.remoteRef)
  238. if !ErrorContains(err, tc.expectError) {
  239. t.Errorf("[%d] unexpected error: %q, expected: %q", k, err.Error(), tc.expectError)
  240. }
  241. if err == nil && !cmp.Equal(out, tc.expectedData) {
  242. t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, tc.expectedData, out)
  243. }
  244. })
  245. }
  246. }
  247. func ErrorContains(out error, want string) bool {
  248. if out == nil {
  249. return want == ""
  250. }
  251. if want == "" {
  252. return false
  253. }
  254. return strings.Contains(out.Error(), want)
  255. }
  256. func TestDeleteSecret(t *testing.T) {
  257. deleteSecret := func(pstc *updateSecretCase) {
  258. pstc.label = "delete secret"
  259. pstc.request = makeValidDeleteRequest()
  260. }
  261. deleteMissingSecret := func(pstc *updateSecretCase) {
  262. pstc.label = "delete missing secret"
  263. pstc.request = makeValidDeleteRequest()
  264. pstc.remoteRef.RemoteKey = invalidRemoteKey
  265. pstc.expectError = missingDeleteErr
  266. pstc.apiErr = errors.New("")
  267. }
  268. setClientError := func(pstc *updateSecretCase) {
  269. pstc.label = "invalid client error"
  270. pstc.request = makeValidDeleteRequest()
  271. pstc.expectError = missingDeleteErr
  272. pstc.apiErr = errors.New("")
  273. }
  274. testCases := []*updateSecretCase{
  275. makeValidUpdateSecretCaseCustom(deleteSecret),
  276. makeValidUpdateSecretCaseCustom(deleteMissingSecret),
  277. makeValidUpdateSecretCaseCustom(setClientError),
  278. }
  279. c := Client{}
  280. for k, tc := range testCases {
  281. c.doppler = tc.fakeClient
  282. err := c.DeleteSecret(context.Background(), tc.remoteRef)
  283. if !ErrorContains(err, tc.expectError) {
  284. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
  285. }
  286. }
  287. }
  288. func TestPushSecret(t *testing.T) {
  289. pushSecret := func(pstc *updateSecretCase) {
  290. pstc.label = "push secret"
  291. pstc.request = makeValidPushRequest()
  292. }
  293. pushMissingSecretKey := func(pstc *updateSecretCase) {
  294. pstc.label = "push missing secret key"
  295. pstc.secretData = makeSecretData(invalidSecret, *makeValidPushRemoteRef())
  296. pstc.expectError = missingPushErr
  297. pstc.apiErr = errors.New("")
  298. }
  299. pushMissingRemoteSecret := func(pstc *updateSecretCase) {
  300. pstc.label = "push missing remote secret"
  301. pstc.secretData = makeSecretData(
  302. validSecretName,
  303. esv1alpha1.PushSecretRemoteRef{
  304. RemoteKey: invalidRemoteKey,
  305. },
  306. )
  307. pstc.expectError = missingPushErr
  308. pstc.apiErr = errors.New("")
  309. }
  310. setClientError := func(pstc *updateSecretCase) {
  311. pstc.label = "invalid client error"
  312. pstc.expectError = missingPushErr
  313. pstc.apiErr = errors.New("")
  314. }
  315. testCases := []*updateSecretCase{
  316. makeValidUpdateSecretCaseCustom(pushSecret),
  317. makeValidUpdateSecretCaseCustom(pushMissingSecretKey),
  318. makeValidUpdateSecretCaseCustom(pushMissingRemoteSecret),
  319. makeValidUpdateSecretCaseCustom(setClientError),
  320. }
  321. c := Client{}
  322. for k, tc := range testCases {
  323. c.doppler = tc.fakeClient
  324. err := c.PushSecret(context.Background(), &tc.secret, tc.secretData)
  325. if !ErrorContains(err, tc.expectError) {
  326. t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), tc.expectError)
  327. }
  328. }
  329. }
  330. type storeModifier func(*esv1.SecretStore) *esv1.SecretStore
  331. func makeSecretStore(fn ...storeModifier) *esv1.SecretStore {
  332. store := &esv1.SecretStore{
  333. Spec: esv1.SecretStoreSpec{
  334. Provider: &esv1.SecretStoreProvider{
  335. Doppler: &esv1.DopplerProvider{
  336. Auth: &esv1.DopplerAuth{},
  337. },
  338. },
  339. },
  340. }
  341. for _, f := range fn {
  342. store = f(store)
  343. }
  344. return store
  345. }
  346. func withAuth(name, key string, namespace *string) storeModifier {
  347. return func(store *esv1.SecretStore) *esv1.SecretStore {
  348. store.Spec.Provider.Doppler.Auth.SecretRef.DopplerToken = v1.SecretKeySelector{
  349. Name: name,
  350. Key: key,
  351. Namespace: namespace,
  352. }
  353. return store
  354. }
  355. }
  356. type ValidateStoreTestCase struct {
  357. label string
  358. store *esv1.SecretStore
  359. err error
  360. }
  361. func TestValidateStore(t *testing.T) {
  362. namespace := "ns"
  363. secretName := "doppler-token-secret"
  364. testCases := []ValidateStoreTestCase{
  365. {
  366. label: "invalid store missing dopplerToken.name",
  367. store: makeSecretStore(withAuth("", "", nil)),
  368. err: errors.New("invalid store: dopplerToken.name cannot be empty"),
  369. },
  370. {
  371. label: "invalid store namespace not allowed",
  372. store: makeSecretStore(withAuth(secretName, "", &namespace)),
  373. err: errors.New("invalid store: namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  374. },
  375. {
  376. label: "valid provide optional dopplerToken.key",
  377. store: makeSecretStore(withAuth(secretName, "customSecretKey", nil)),
  378. err: nil,
  379. },
  380. {
  381. label: "valid namespace not set",
  382. store: makeSecretStore(withAuth(secretName, "", nil)),
  383. err: nil,
  384. },
  385. }
  386. p := Provider{}
  387. for _, tc := range testCases {
  388. t.Run(tc.label, func(t *testing.T) {
  389. _, err := p.ValidateStore(tc.store)
  390. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  391. t.Errorf("test failed! want %v, got %v", tc.err, err)
  392. } else if tc.err == nil && err != nil {
  393. t.Errorf("want nil got err %v", err)
  394. } else if tc.err != nil && err == nil {
  395. t.Errorf("want err %v got nil", tc.err)
  396. }
  397. })
  398. }
  399. }