chef_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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 chef
  14. import (
  15. "bytes"
  16. "context"
  17. "errors"
  18. "fmt"
  19. "strings"
  20. "testing"
  21. "time"
  22. "github.com/go-chef/chef"
  23. corev1 "k8s.io/api/core/v1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
  28. fake "github.com/external-secrets/external-secrets/providers/v1/chef/fake"
  29. "github.com/external-secrets/external-secrets/runtime/esutils"
  30. )
  31. const (
  32. name = "chef-demo-user"
  33. baseURL = "https://chef.cloudant.com/organizations/myorg/"
  34. noEndSlashInvalidBaseURL = "no end slash invalid base URL"
  35. baseInvalidURL = "invalid base URL/"
  36. authName = "chef-demo-auth-name"
  37. authKey = "chef-demo-auth-key"
  38. authNamespace = "chef-demo-auth-namespace"
  39. kind = "SecretStore"
  40. apiversion = "external-secrets.io/v1"
  41. databagName = "databag01"
  42. )
  43. type chefTestCase struct {
  44. mockClient *fake.ChefMockClient
  45. databagName string
  46. databagItemName string
  47. property string
  48. ref *esv1.ExternalSecretDataRemoteRef
  49. apiErr error
  50. expectError string
  51. expectedData map[string][]byte
  52. expectedByte []byte
  53. }
  54. type ValidateStoreTestCase struct {
  55. store *esv1.SecretStore
  56. err error
  57. }
  58. // type storeModifier func(*esv1.SecretStore) *esv1.SecretStore
  59. func makeValidChefTestCase() *chefTestCase {
  60. smtc := chefTestCase{
  61. mockClient: &fake.ChefMockClient{},
  62. databagName: "databag01",
  63. databagItemName: "item01",
  64. property: "",
  65. apiErr: nil,
  66. expectError: "",
  67. expectedData: map[string][]byte{"item01": []byte(`"https://chef.com/organizations/dev/data/databag01/item01"`)},
  68. expectedByte: []byte(`{"item01":"{\"id\":\"databag01-item01\",\"some_key\":\"fe7f29ede349519a1\",\"some_password\":\"dolphin_123zc\",\"some_username\":\"testuser\"}"}`),
  69. }
  70. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, smtc.property)
  71. smtc.mockClient.WithListItems(smtc.databagName, smtc.apiErr)
  72. smtc.mockClient.WithItem(smtc.databagName, smtc.databagItemName, smtc.apiErr)
  73. return &smtc
  74. }
  75. func makeInValidChefTestCase() *chefTestCase {
  76. smtc := chefTestCase{
  77. mockClient: &fake.ChefMockClient{},
  78. databagName: "databag01",
  79. databagItemName: "item03",
  80. property: "",
  81. apiErr: errors.New("unable to convert databagItem into JSON"),
  82. expectError: "unable to convert databagItem into JSON",
  83. expectedData: nil,
  84. expectedByte: nil,
  85. }
  86. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, smtc.property)
  87. smtc.mockClient.WithListItems(smtc.databagName, smtc.apiErr)
  88. smtc.mockClient.WithItem(smtc.databagName, smtc.databagItemName, smtc.apiErr)
  89. return &smtc
  90. }
  91. func makeValidRef(databag, dataitem, property string) *esv1.ExternalSecretDataRemoteRef {
  92. return &esv1.ExternalSecretDataRemoteRef{
  93. Key: databag + "/" + dataitem,
  94. Property: property,
  95. }
  96. }
  97. func makeinValidRef() *esv1.ExternalSecretDataRemoteRef {
  98. return &esv1.ExternalSecretDataRemoteRef{
  99. Key: "",
  100. }
  101. }
  102. func makeValidRefForGetSecretMap(databag string) *esv1.ExternalSecretDataRemoteRef {
  103. return &esv1.ExternalSecretDataRemoteRef{
  104. Key: databag,
  105. }
  106. }
  107. func makeValidChefTestCaseCustom(tweaks ...func(smtc *chefTestCase)) *chefTestCase {
  108. smtc := makeValidChefTestCase()
  109. for _, fn := range tweaks {
  110. fn(smtc)
  111. }
  112. return smtc
  113. }
  114. func TestChefGetSecret(t *testing.T) {
  115. nilClient := func(smtc *chefTestCase) {
  116. smtc.mockClient = nil
  117. smtc.expectedByte = nil
  118. smtc.expectError = "chef provider is not initialized"
  119. }
  120. invalidDatabagName := func(smtc *chefTestCase) {
  121. smtc.databagName = "databag02"
  122. smtc.expectedByte = nil
  123. smtc.ref = makeinValidRef()
  124. smtc.expectError = "invalid key format in data section. Expected value 'databagName/databagItemName'"
  125. }
  126. invalidDatabagItemName := func(smtc *chefTestCase) {
  127. smtc.expectError = "data bag item item02 not found in data bag databag01"
  128. smtc.databagName = databagName
  129. smtc.databagItemName = "item02"
  130. smtc.expectedByte = nil
  131. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "")
  132. }
  133. noProperty := func(smtc *chefTestCase) {
  134. smtc.expectError = "property findProperty not found in data bag item"
  135. smtc.databagName = databagName
  136. smtc.databagItemName = "item01"
  137. smtc.expectedByte = nil
  138. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "findProperty")
  139. }
  140. withProperty := func(smtc *chefTestCase) {
  141. smtc.expectedByte = []byte("foundProperty")
  142. smtc.databagName = "databag03"
  143. smtc.databagItemName = "item03"
  144. smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "findProperty")
  145. }
  146. successCases := []*chefTestCase{
  147. makeValidChefTestCase(),
  148. makeValidChefTestCaseCustom(nilClient),
  149. makeValidChefTestCaseCustom(invalidDatabagName),
  150. makeValidChefTestCaseCustom(invalidDatabagItemName),
  151. makeValidChefTestCaseCustom(noProperty),
  152. makeValidChefTestCaseCustom(withProperty),
  153. makeInValidChefTestCase(),
  154. }
  155. sm := Providerchef{
  156. databagService: &chef.DataBagService{},
  157. }
  158. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  159. defer cancel()
  160. for k, v := range successCases {
  161. sm.databagService = v.mockClient
  162. out, err := sm.GetSecret(ctx, *v.ref)
  163. if err != nil && !esutils.ErrorContains(err, v.expectError) {
  164. t.Errorf("[case %d] expected error: %v, got: %v", k, v.expectError, err)
  165. } else if v.expectError != "" && err == nil {
  166. t.Errorf("[case %d] expected error: %v, got: nil", k, v.expectError)
  167. }
  168. if !bytes.Equal(out, v.expectedByte) {
  169. t.Errorf("[case %d] expected secret: %s, got: %s", k, v.expectedByte, out)
  170. }
  171. }
  172. }
  173. func TestChefGetSecretMap(t *testing.T) {
  174. nilClient := func(smtc *chefTestCase) {
  175. smtc.mockClient = nil
  176. smtc.expectedByte = nil
  177. smtc.expectError = "chef provider is not initialized"
  178. }
  179. databagHasSlash := func(smtc *chefTestCase) {
  180. smtc.expectedByte = nil
  181. smtc.ref = makeinValidRef()
  182. smtc.ref.Key = "data/Bag02"
  183. smtc.expectError = "invalid key format in dataForm section. Expected only 'databagName'"
  184. }
  185. withProperty := func(smtc *chefTestCase) {
  186. smtc.expectedByte = []byte(`{"item01":"{\"id\":\"databag01-item01\",\"some_key\":\"fe7f29ede349519a1\",\"some_password\":\"dolphin_123zc\",\"some_username\":\"testuser\"}"}`)
  187. smtc.databagName = databagName
  188. smtc.ref = makeValidRefForGetSecretMap(smtc.databagName)
  189. }
  190. withProperty2 := func(smtc *chefTestCase) {
  191. smtc.expectError = "unable to list items in data bag 123, may be given data bag doesn't exists or it is empty"
  192. smtc.expectedByte = nil
  193. smtc.databagName = "123"
  194. smtc.ref = makeValidRefForGetSecretMap(smtc.databagName)
  195. }
  196. successCases := []*chefTestCase{
  197. makeValidChefTestCaseCustom(nilClient),
  198. makeValidChefTestCaseCustom(databagHasSlash),
  199. makeValidChefTestCaseCustom(withProperty),
  200. makeValidChefTestCaseCustom(withProperty2),
  201. }
  202. pc := Providerchef{
  203. databagService: &chef.DataBagService{},
  204. }
  205. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  206. defer cancel()
  207. for k, v := range successCases {
  208. pc.databagService = v.mockClient
  209. out, err := pc.GetSecretMap(ctx, *v.ref)
  210. if err != nil && !esutils.ErrorContains(err, v.expectError) {
  211. t.Errorf("[case %d] expected error: %v, got: %v", k, v.expectError, err)
  212. } else if v.expectError != "" && err == nil {
  213. t.Errorf("[case %d] expected error: %v, got: nil", k, v.expectError)
  214. }
  215. if !bytes.Equal(out["item01"], v.expectedByte) {
  216. t.Errorf("[case %d] unexpected secret: expected %s, got %s", k, v.expectedByte, out)
  217. }
  218. }
  219. }
  220. func makeSecretStore(name, baseURL string, auth *esv1.ChefAuth) *esv1.SecretStore {
  221. store := &esv1.SecretStore{
  222. Spec: esv1.SecretStoreSpec{
  223. Provider: &esv1.SecretStoreProvider{
  224. Chef: &esv1.ChefProvider{
  225. UserName: name,
  226. ServerURL: baseURL,
  227. Auth: auth,
  228. },
  229. },
  230. },
  231. }
  232. return store
  233. }
  234. func makeAuth(name, namespace, key string) *esv1.ChefAuth {
  235. return &esv1.ChefAuth{
  236. SecretRef: esv1.ChefAuthSecretRef{
  237. SecretKey: v1.SecretKeySelector{
  238. Name: name,
  239. Key: key,
  240. Namespace: &namespace,
  241. },
  242. },
  243. }
  244. }
  245. func TestValidateStore(t *testing.T) {
  246. testCases := []ValidateStoreTestCase{
  247. {
  248. store: makeSecretStore("", baseURL, makeAuth(authName, authNamespace, authKey)),
  249. err: errors.New("received invalid Chef SecretStore resource: missing username"),
  250. },
  251. {
  252. store: makeSecretStore(name, "", makeAuth(authName, authNamespace, authKey)),
  253. err: errors.New("received invalid Chef SecretStore resource: missing serverurl"),
  254. },
  255. {
  256. store: makeSecretStore(name, baseURL, nil),
  257. err: errors.New("received invalid Chef SecretStore resource: cannot initialize Chef Client: no valid authType was specified"),
  258. },
  259. {
  260. store: makeSecretStore(name, baseInvalidURL, makeAuth(authName, authNamespace, authKey)),
  261. err: errors.New("received invalid Chef SecretStore resource: invalid serverurl: parse \"invalid base URL/\": invalid URI for request"),
  262. },
  263. {
  264. store: makeSecretStore(name, noEndSlashInvalidBaseURL, makeAuth(authName, authNamespace, authKey)),
  265. err: errors.New("received invalid Chef SecretStore resource: serverurl does not end with slash(/)"),
  266. },
  267. {
  268. store: makeSecretStore(name, baseURL, makeAuth(authName, authNamespace, "")),
  269. err: errors.New("received invalid Chef SecretStore resource: missing Secret Key"),
  270. },
  271. {
  272. store: makeSecretStore(name, baseURL, makeAuth(authName, authNamespace, authKey)),
  273. err: errors.New("received invalid Chef SecretStore resource: namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"),
  274. },
  275. {
  276. store: &esv1.SecretStore{
  277. Spec: esv1.SecretStoreSpec{
  278. Provider: nil,
  279. },
  280. },
  281. err: errors.New("received invalid Chef SecretStore resource: missing provider"),
  282. },
  283. {
  284. store: &esv1.SecretStore{
  285. Spec: esv1.SecretStoreSpec{
  286. Provider: &esv1.SecretStoreProvider{
  287. Chef: nil,
  288. },
  289. },
  290. },
  291. err: errors.New("received invalid Chef SecretStore resource: missing chef provider"),
  292. },
  293. }
  294. pc := Providerchef{}
  295. for _, tc := range testCases {
  296. _, err := pc.ValidateStore(tc.store)
  297. if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
  298. t.Errorf("test failed! want: %v, got: %v", tc.err, err)
  299. } else if tc.err == nil && err != nil {
  300. t.Errorf("want: nil got: err %v", err)
  301. } else if tc.err != nil && err == nil {
  302. t.Errorf("want: err %v got: nil", tc.err)
  303. }
  304. }
  305. }
  306. func TestNewClient(t *testing.T) {
  307. store := &esv1.SecretStore{TypeMeta: metav1.TypeMeta{Kind: "ClusterSecretStore"},
  308. Spec: esv1.SecretStoreSpec{
  309. Provider: &esv1.SecretStoreProvider{
  310. Chef: &esv1.ChefProvider{
  311. Auth: makeAuth(authName, authNamespace, authKey),
  312. UserName: name,
  313. ServerURL: baseURL,
  314. },
  315. },
  316. },
  317. }
  318. expected := fmt.Sprintf("could not fetch SecretKey Secret: secrets %q not found", authName)
  319. expectedMissingStore := "missing or invalid spec: missing store"
  320. ctx := context.TODO()
  321. kube := clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
  322. ObjectMeta: metav1.ObjectMeta{
  323. Name: "creds",
  324. Namespace: "default",
  325. }, TypeMeta: metav1.TypeMeta{
  326. Kind: kind,
  327. APIVersion: apiversion,
  328. },
  329. }).Build()
  330. pc := Providerchef{databagService: &fake.ChefMockClient{}}
  331. _, errMissingStore := pc.NewClient(ctx, nil, kube, "default")
  332. if !ErrorContains(errMissingStore, expectedMissingStore) {
  333. t.Errorf("CheckNewClient unexpected error: %s, expected: '%s'", errMissingStore.Error(), expectedMissingStore)
  334. }
  335. _, err := pc.NewClient(ctx, store, kube, "default")
  336. if !ErrorContains(err, expected) {
  337. t.Errorf("CheckNewClient unexpected error: %s, expected: '%s'", err.Error(), expected)
  338. }
  339. }
  340. func ErrorContains(out error, want string) bool {
  341. if out == nil {
  342. return want == ""
  343. }
  344. if want == "" {
  345. return false
  346. }
  347. return strings.Contains(out.Error(), want)
  348. }
  349. func TestValidate(t *testing.T) {
  350. pc := Providerchef{}
  351. var mockClient *fake.ChefMockClient
  352. pc.userService = mockClient
  353. pc.clientName = "correctUser"
  354. _, err := pc.Validate()
  355. t.Log("Error: ", err)
  356. pc.clientName = "wrongUser"
  357. _, err = pc.Validate()
  358. t.Log("Error: ", err)
  359. }
  360. func TestCapabilities(t *testing.T) {
  361. pc := Providerchef{}
  362. capabilities := pc.Capabilities()
  363. t.Log(capabilities)
  364. }
  365. // Test Cases To be added when Close function is implemented.
  366. func TestClose(_ *testing.T) {
  367. pc := Providerchef{}
  368. pc.Close(context.Background())
  369. }
  370. // Test Cases To be added when GetAllSecrets function is implemented.
  371. func TestGetAllSecrets(_ *testing.T) {
  372. pc := Providerchef{}
  373. pc.GetAllSecrets(context.Background(), esv1.ExternalSecretFind{})
  374. }
  375. // Test Cases To be implemented when DeleteSecret function is implemented.
  376. func TestDeleteSecret(_ *testing.T) {
  377. pc := Providerchef{}
  378. pc.DeleteSecret(context.Background(), nil)
  379. }
  380. // Test Cases To be implemented when PushSecret function is implemented.
  381. func TestPushSecret(_ *testing.T) {
  382. pc := Providerchef{}
  383. pc.PushSecret(context.Background(), &corev1.Secret{}, nil)
  384. }