doppler_test.go 14 KB

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