vault_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 vault
  13. import (
  14. "bytes"
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io/ioutil"
  20. "net/http"
  21. "testing"
  22. "github.com/crossplane/crossplane-runtime/pkg/test"
  23. "github.com/google/go-cmp/cmp"
  24. vault "github.com/hashicorp/vault/api"
  25. corev1 "k8s.io/api/core/v1"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  28. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  29. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  30. "github.com/external-secrets/external-secrets/pkg/provider/vault/fake"
  31. )
  32. func makeValidSecretStore() *esv1alpha1.SecretStore {
  33. return &esv1alpha1.SecretStore{
  34. ObjectMeta: metav1.ObjectMeta{
  35. Name: "vault-store",
  36. Namespace: "default",
  37. },
  38. Spec: esv1alpha1.SecretStoreSpec{
  39. Provider: &esv1alpha1.SecretStoreProvider{
  40. Vault: &esv1alpha1.VaultProvider{
  41. Server: "vault.example.com",
  42. Path: "secret",
  43. Version: esv1alpha1.VaultKVStoreV2,
  44. Auth: esv1alpha1.VaultAuth{
  45. Kubernetes: &esv1alpha1.VaultKubernetesAuth{
  46. Path: "kubernetes",
  47. Role: "kubernetes-auth-role",
  48. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  49. Name: "example-sa",
  50. },
  51. },
  52. },
  53. },
  54. },
  55. },
  56. }
  57. }
  58. type secretStoreTweakFn func(s *esv1alpha1.SecretStore)
  59. func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1alpha1.SecretStore {
  60. store := makeValidSecretStore()
  61. for _, fn := range tweaks {
  62. fn(store)
  63. }
  64. return store
  65. }
  66. func newVaultResponse(data *vault.Secret) *vault.Response {
  67. jsonData, _ := json.Marshal(data)
  68. return &vault.Response{
  69. Response: &http.Response{
  70. Body: ioutil.NopCloser(bytes.NewReader(jsonData)),
  71. },
  72. }
  73. }
  74. func newVaultTokenIDResponse(token string) *vault.Response {
  75. return newVaultResponse(&vault.Secret{
  76. Data: map[string]interface{}{
  77. "id": token,
  78. },
  79. })
  80. }
  81. func TestNewVault(t *testing.T) {
  82. errBoom := errors.New("boom")
  83. secretData := []byte("some-creds")
  84. type args struct {
  85. newClientFunc func(c *vault.Config) (Client, error)
  86. store esv1alpha1.GenericStore
  87. kube kclient.Client
  88. ns string
  89. }
  90. type want struct {
  91. err error
  92. }
  93. cases := map[string]struct {
  94. reason string
  95. args args
  96. want want
  97. }{
  98. "InvalidVaultStore": {
  99. reason: "Should return error if given an invalid vault store.",
  100. args: args{
  101. store: &esv1alpha1.SecretStore{},
  102. },
  103. want: want{
  104. err: errors.New(errVaultStore),
  105. },
  106. },
  107. "AddVaultStoreCertsError": {
  108. reason: "Should return error if given an invalid CA certificate.",
  109. args: args{
  110. store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
  111. s.Spec.Provider.Vault.CABundle = []byte("badcertdata")
  112. }),
  113. },
  114. want: want{
  115. err: errors.New(errVaultCert),
  116. },
  117. },
  118. "VaultAuthFormatError": {
  119. reason: "Should return error if no valid authentication method is given.",
  120. args: args{
  121. store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
  122. s.Spec.Provider.Vault.Auth = esv1alpha1.VaultAuth{}
  123. }),
  124. },
  125. want: want{
  126. err: errors.New(errAuthFormat),
  127. },
  128. },
  129. "GetKubeServiceAccountError": {
  130. reason: "Should return error if fetching kubernetes secret fails.",
  131. args: args{
  132. store: makeSecretStore(),
  133. kube: &test.MockClient{
  134. MockGet: test.NewMockGetFn(errBoom),
  135. },
  136. },
  137. want: want{
  138. err: fmt.Errorf(errGetKubeSA, "example-sa", errBoom),
  139. },
  140. },
  141. "GetKubeSecretError": {
  142. reason: "Should return error if fetching kubernetes secret fails.",
  143. args: args{
  144. store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
  145. s.Spec.Provider.Vault.Auth.Kubernetes.ServiceAccountRef = nil
  146. s.Spec.Provider.Vault.Auth.Kubernetes.SecretRef = &esmeta.SecretKeySelector{
  147. Name: "vault-secret",
  148. Key: "key",
  149. }
  150. }),
  151. kube: &test.MockClient{
  152. MockGet: test.NewMockGetFn(errBoom),
  153. },
  154. },
  155. want: want{
  156. err: fmt.Errorf(errGetKubeSecret, "vault-secret", errBoom),
  157. },
  158. },
  159. "SuccessfulVaultStore": {
  160. reason: "Should return a Vault provider successfully",
  161. args: args{
  162. store: makeSecretStore(),
  163. kube: &test.MockClient{
  164. MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
  165. if o, ok := obj.(*corev1.ServiceAccount); ok {
  166. o.Secrets = []corev1.ObjectReference{
  167. {
  168. Name: "example-secret-token",
  169. },
  170. }
  171. return nil
  172. }
  173. if o, ok := obj.(*corev1.Secret); ok {
  174. o.Data = map[string][]byte{
  175. "token": secretData,
  176. }
  177. return nil
  178. }
  179. return nil
  180. }),
  181. },
  182. newClientFunc: func(c *vault.Config) (Client, error) {
  183. return &fake.VaultClient{
  184. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  185. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
  186. newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error {
  187. kubeRole := makeValidSecretStore().Spec.Provider.Vault.Auth.Kubernetes.Role
  188. want := kubeParameters(kubeRole, string(secretData))
  189. if diff := cmp.Diff(want, got.Obj); diff != "" {
  190. t.Errorf("RawRequestWithContext(...): -want, +got:\n%s", diff)
  191. }
  192. return nil
  193. }),
  194. MockSetToken: fake.NewSetTokenFn(),
  195. }, nil
  196. },
  197. },
  198. want: want{
  199. err: nil,
  200. },
  201. },
  202. }
  203. for name, tc := range cases {
  204. t.Run(name, func(t *testing.T) {
  205. conn := &connector{
  206. newVaultClient: tc.args.newClientFunc,
  207. }
  208. if tc.args.newClientFunc == nil {
  209. conn.newVaultClient = newVaultClient
  210. }
  211. _, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
  212. if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
  213. t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff)
  214. }
  215. })
  216. }
  217. }
  218. func TestGetSecretMap(t *testing.T) {
  219. errBoom := errors.New("boom")
  220. type args struct {
  221. store *esv1alpha1.VaultProvider
  222. kube kclient.Client
  223. vClient Client
  224. ns string
  225. data esv1alpha1.ExternalSecretDataRemoteRef
  226. }
  227. type want struct {
  228. err error
  229. }
  230. cases := map[string]struct {
  231. reason string
  232. args args
  233. want want
  234. }{
  235. "ReadSecretError": {
  236. reason: "Should return error if vault client fails to read secret.",
  237. args: args{
  238. store: makeSecretStore().Spec.Provider.Vault,
  239. vClient: &fake.VaultClient{
  240. MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
  241. MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom),
  242. },
  243. },
  244. want: want{
  245. err: fmt.Errorf(errReadSecret, errBoom),
  246. },
  247. },
  248. }
  249. for name, tc := range cases {
  250. t.Run(name, func(t *testing.T) {
  251. vStore := &client{
  252. kube: tc.args.kube,
  253. client: tc.args.vClient,
  254. store: tc.args.store,
  255. namespace: tc.args.ns,
  256. }
  257. _, err := vStore.GetSecretMap(context.Background(), tc.args.data)
  258. if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
  259. t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
  260. }
  261. })
  262. }
  263. }