Browse Source

Merge branch 'main' into k8s_common_types_e2e_tests

KianTigger 5 years ago
parent
commit
3eeb69c897

+ 19 - 1
apis/externalsecrets/v1alpha1/secretstore_vault_types.go

@@ -62,7 +62,7 @@ type VaultProvider struct {
 }
 
 // VaultAuth is the configuration used to authenticate with a Vault server.
-// Only one of `tokenSecretRef`, `appRole`,  `kubernetes`, `ldap` or `jwt`
+// Only one of `tokenSecretRef`, `appRole`,  `kubernetes`, `ldap`, `jwt` or `cert`
 // can be specified.
 type VaultAuth struct {
 	// TokenSecretRef authenticates with Vault by presenting a token.
@@ -88,6 +88,11 @@ type VaultAuth struct {
 	// JWT/OIDC authentication method
 	// +optional
 	Jwt *VaultJwtAuth `json:"jwt,omitempty"`
+
+	// Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate
+	// Cert authentication method
+	// +optional
+	Cert *VaultCertAuth `json:"cert,omitempty"`
 }
 
 // VaultAppRole authenticates with Vault using the App Role auth mechanism,
@@ -161,3 +166,16 @@ type VaultJwtAuth struct {
 	// authenticate with Vault using the JWT/OIDC authentication method
 	SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
 }
+
+// VaultJwtAuth authenticates with Vault using the JWT/OIDC authentication
+// method, with the role name and token stored in a Kubernetes Secret resource.
+type VaultCertAuth struct {
+	// ClientCert is a certificate to authenticate using the Cert Vault
+	// authentication method
+	// +optional
+	ClientCert esmeta.SecretKeySelector `json:"clientCert,omitempty"`
+
+	// SecretRef to a key in a Secret resource containing client private key to
+	// authenticate with Vault using the Cert authentication method
+	SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
+}

+ 22 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -833,6 +833,11 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) {
 		*out = new(VaultJwtAuth)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Cert != nil {
+		in, out := &in.Cert, &out.Cert
+		*out = new(VaultCertAuth)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth.
@@ -845,6 +850,23 @@ func (in *VaultAuth) DeepCopy() *VaultAuth {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VaultCertAuth) DeepCopyInto(out *VaultCertAuth) {
+	*out = *in
+	in.ClientCert.DeepCopyInto(&out.ClientCert)
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultCertAuth.
+func (in *VaultCertAuth) DeepCopy() *VaultCertAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(VaultCertAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *VaultJwtAuth) DeepCopyInto(out *VaultJwtAuth) {
 	*out = *in

+ 53 - 0
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -359,6 +359,59 @@ spec:
                             - roleId
                             - secretRef
                             type: object
+                          cert:
+                            description: Cert authenticates with TLS Certificates
+                              by passing client certificate, private key and ca certificate
+                              Cert authentication method
+                            properties:
+                              clientCert:
+                                description: ClientCert is a certificate to authenticate
+                                  using the Cert Vault authentication method
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                              secretRef:
+                                description: SecretRef to a key in a Secret resource
+                                  containing client private key to authenticate with
+                                  Vault using the Cert authentication method
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                            type: object
                           jwt:
                             description: Jwt authenticates with Vault by passing role
                               and JWT token using the JWT/OIDC authentication method

+ 53 - 0
deploy/crds/external-secrets.io_secretstores.yaml

@@ -359,6 +359,59 @@ spec:
                             - roleId
                             - secretRef
                             type: object
+                          cert:
+                            description: Cert authenticates with TLS Certificates
+                              by passing client certificate, private key and ca certificate
+                              Cert authentication method
+                            properties:
+                              clientCert:
+                                description: ClientCert is a certificate to authenticate
+                                  using the Cert Vault authentication method
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                              secretRef:
+                                description: SecretRef to a key in a Secret resource
+                                  containing client private key to authenticate with
+                                  Vault using the Cert authentication method
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                required:
+                                - name
+                                type: object
+                            type: object
                           jwt:
                             description: Jwt authenticates with Vault by passing role
                               and JWT token using the JWT/OIDC authentication method

+ 543 - 543
pkg/provider/vault/vault.go

@@ -1,543 +1,543 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package vault
-
-import (
-	"context"
-	"crypto/x509"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"os"
-	"strings"
-
-	"github.com/go-logr/logr"
-	vault "github.com/hashicorp/vault/api"
-	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/types"
-	ctrl "sigs.k8s.io/controller-runtime"
-	kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
-	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
-	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/schema"
-)
-
-var (
-	_ provider.Provider      = &connector{}
-	_ provider.SecretsClient = &client{}
-)
-
-const (
-	serviceAccTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
-
-	errVaultStore     = "received invalid Vault SecretStore resource: %w"
-	errVaultClient    = "cannot setup new vault client: %w"
-	errVaultCert      = "cannot set Vault CA certificate: %w"
-	errReadSecret     = "cannot read secret data from Vault: %w"
-	errAuthFormat     = "cannot initialize Vault client: no valid auth method specified: %w"
-	errDataField      = "failed to find data field: %v"
-	errJSONUnmarshall = "failed to unmarshall JSON: %v"
-	errSecretFormat   = "secret data not in expected format: %v"
-	errVaultToken     = "cannot parse Vault authentication token: %w"
-	errVaultReqParams = "cannot set Vault request parameters: %w"
-	errVaultRequest   = "error from Vault request: %w"
-	errVaultResponse  = "cannot parse Vault response: %w"
-	errServiceAccount = "cannot read Kubernetes service account token from file system: %w"
-
-	errGetKubeSA        = "cannot get Kubernetes service account %q: %w"
-	errGetKubeSASecrets = "cannot find secrets bound to service account: %q"
-	errGetKubeSANoToken = "cannot find token in secrets bound to service account: %q"
-
-	errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
-	errSecretKeyFmt  = "cannot find secret data for key: %q"
-)
-
-type Client interface {
-	NewRequest(method, requestPath string) *vault.Request
-	RawRequestWithContext(ctx context.Context, r *vault.Request) (*vault.Response, error)
-	SetToken(v string)
-	SetNamespace(namespace string)
-}
-
-type client struct {
-	kube      kclient.Client
-	store     *esv1alpha1.VaultProvider
-	log       logr.Logger
-	client    Client
-	namespace string
-	storeKind string
-}
-
-func init() {
-	schema.Register(&connector{
-		newVaultClient: newVaultClient,
-	}, &esv1alpha1.SecretStoreProvider{
-		Vault: &esv1alpha1.VaultProvider{},
-	})
-}
-
-func newVaultClient(c *vault.Config) (Client, error) {
-	return vault.NewClient(c)
-}
-
-type connector struct {
-	newVaultClient func(c *vault.Config) (Client, error)
-}
-
-func (c *connector) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
-	storeSpec := store.GetSpec()
-	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
-		return nil, errors.New(errVaultStore)
-	}
-	vaultSpec := storeSpec.Provider.Vault
-
-	vStore := &client{
-		kube:      kube,
-		store:     vaultSpec,
-		log:       ctrl.Log.WithName("provider").WithName("vault"),
-		namespace: namespace,
-		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
-	}
-
-	cfg, err := vStore.newConfig()
-	if err != nil {
-		return nil, err
-	}
-
-	client, err := c.newVaultClient(cfg)
-	if err != nil {
-		return nil, fmt.Errorf(errVaultClient, err)
-	}
-
-	if vaultSpec.Namespace != nil {
-		client.SetNamespace(*vaultSpec.Namespace)
-	}
-
-	if err := vStore.setAuth(ctx, client); err != nil {
-		return nil, err
-	}
-
-	vStore.client = client
-	return vStore, nil
-}
-
-func (v *client) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	data, err := v.readSecret(ctx, ref.Key, ref.Version)
-	if err != nil {
-		return nil, err
-	}
-	value, exists := data[ref.Property]
-	if !exists {
-		return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
-	}
-	return value, nil
-}
-
-func (v *client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	return v.readSecret(ctx, ref.Key, ref.Version)
-}
-
-func (v *client) Close() error {
-	return nil
-}
-
-func (v *client) readSecret(ctx context.Context, path, version string) (map[string][]byte, error) {
-	kvPath := v.store.Path
-
-	if v.store.Version == esv1alpha1.VaultKVStoreV2 {
-		if !strings.HasSuffix(kvPath, "/data") {
-			kvPath = fmt.Sprintf("%s/data", kvPath)
-		}
-	}
-
-	// path formated according to vault docs for v1 and v2 API
-	// v1: https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret
-	// v2: https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
-	req := v.client.NewRequest(http.MethodGet, fmt.Sprintf("/v1/%s/%s", kvPath, path))
-	if version != "" {
-		req.Params.Set("version", version)
-	}
-
-	resp, err := v.client.RawRequestWithContext(ctx, req)
-	if err != nil {
-		return nil, fmt.Errorf(errReadSecret, err)
-	}
-
-	vaultSecret, err := vault.ParseSecret(resp.Body)
-	if err != nil {
-		return nil, err
-	}
-
-	secretData := vaultSecret.Data
-	if v.store.Version == esv1alpha1.VaultKVStoreV2 {
-		// Vault KV2 has data embedded within sub-field
-		// reference - https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
-		dataInt, ok := vaultSecret.Data["data"]
-
-		if !ok {
-			return nil, errors.New(errDataField)
-		}
-		secretData, ok = dataInt.(map[string]interface{})
-		if !ok {
-			return nil, errors.New(errJSONUnmarshall)
-		}
-	}
-
-	byteMap := make(map[string][]byte, len(secretData))
-	for k, v := range secretData {
-		switch t := v.(type) {
-		case string:
-			byteMap[k] = []byte(t)
-		case []byte:
-			byteMap[k] = t
-		default:
-			return nil, errors.New(errSecretFormat)
-		}
-	}
-
-	return byteMap, nil
-}
-
-func (v *client) newConfig() (*vault.Config, error) {
-	cfg := vault.DefaultConfig()
-	cfg.Address = v.store.Server
-
-	if len(v.store.CABundle) == 0 {
-		return cfg, nil
-	}
-
-	caCertPool := x509.NewCertPool()
-	ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
-	if !ok {
-		return nil, errors.New(errVaultCert)
-	}
-
-	if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
-		transport.TLSClientConfig.RootCAs = caCertPool
-	}
-
-	return cfg, nil
-}
-
-func (v *client) setAuth(ctx context.Context, client Client) error {
-	tokenRef := v.store.Auth.TokenSecretRef
-	if tokenRef != nil {
-		token, err := v.secretKeyRef(ctx, tokenRef)
-		if err != nil {
-			return err
-		}
-		client.SetToken(token)
-		return nil
-	}
-
-	appRole := v.store.Auth.AppRole
-	if appRole != nil {
-		token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole)
-		if err != nil {
-			return err
-		}
-		client.SetToken(token)
-		return nil
-	}
-
-	kubernetesAuth := v.store.Auth.Kubernetes
-	if kubernetesAuth != nil {
-		token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth)
-		if err != nil {
-			return err
-		}
-		client.SetToken(token)
-		return nil
-	}
-
-	ldapAuth := v.store.Auth.Ldap
-	if ldapAuth != nil {
-		token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth)
-		if err != nil {
-			return err
-		}
-		client.SetToken(token)
-		return nil
-	}
-
-	jwtAuth := v.store.Auth.Jwt
-	if jwtAuth != nil {
-		token, err := v.requestTokenWithJwtAuth(ctx, client, jwtAuth)
-		if err != nil {
-			return err
-		}
-		client.SetToken(token)
-		return nil
-	}
-
-	return errors.New(errAuthFormat)
-}
-
-func (v *client) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) {
-	serviceAccount := &corev1.ServiceAccount{}
-	ref := types.NamespacedName{
-		Namespace: v.namespace,
-		Name:      serviceAccountRef.Name,
-	}
-	if (v.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
-		(serviceAccountRef.Namespace != nil) {
-		ref.Namespace = *serviceAccountRef.Namespace
-	}
-	err := v.kube.Get(ctx, ref, serviceAccount)
-	if err != nil {
-		return "", fmt.Errorf(errGetKubeSA, ref.Name, err)
-	}
-	if len(serviceAccount.Secrets) == 0 {
-		return "", fmt.Errorf(errGetKubeSASecrets, ref.Name)
-	}
-	for _, tokenRef := range serviceAccount.Secrets {
-		retval, err := v.secretKeyRef(ctx, &esmeta.SecretKeySelector{
-			Name:      tokenRef.Name,
-			Namespace: &ref.Namespace,
-			Key:       "token",
-		})
-
-		if err != nil {
-			continue
-		}
-
-		return retval, nil
-	}
-	return "", fmt.Errorf(errGetKubeSANoToken, ref.Name)
-}
-
-func (v *client) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
-	secret := &corev1.Secret{}
-	ref := types.NamespacedName{
-		Namespace: v.namespace,
-		Name:      secretRef.Name,
-	}
-	if (v.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
-		(secretRef.Namespace != nil) {
-		ref.Namespace = *secretRef.Namespace
-	}
-	err := v.kube.Get(ctx, ref, secret)
-	if err != nil {
-		return "", fmt.Errorf(errGetKubeSecret, ref.Name, err)
-	}
-
-	keyBytes, ok := secret.Data[secretRef.Key]
-	if !ok {
-		return "", fmt.Errorf(errSecretKeyFmt, secretRef.Key)
-	}
-
-	value := string(keyBytes)
-	valueStr := strings.TrimSpace(value)
-	return valueStr, nil
-}
-
-// appRoleParameters creates the required body for Vault AppRole Auth.
-// Reference - https://www.vaultproject.io/api-docs/auth/approle#login-with-approle
-func appRoleParameters(role, secret string) map[string]string {
-	return map[string]string{
-		"role_id":   role,
-		"secret_id": secret,
-	}
-}
-
-func (v *client) requestTokenWithAppRoleRef(ctx context.Context, client Client, appRole *esv1alpha1.VaultAppRole) (string, error) {
-	roleID := strings.TrimSpace(appRole.RoleID)
-
-	secretID, err := v.secretKeyRef(ctx, &appRole.SecretRef)
-	if err != nil {
-		return "", err
-	}
-
-	parameters := appRoleParameters(roleID, secretID)
-	url := strings.Join([]string{"/v1", "auth", appRole.Path, "login"}, "/")
-	request := client.NewRequest("POST", url)
-
-	err = request.SetJSONBody(parameters)
-	if err != nil {
-		return "", fmt.Errorf(errVaultReqParams, err)
-	}
-
-	resp, err := client.RawRequestWithContext(ctx, request)
-	if err != nil {
-		return "", fmt.Errorf(errVaultRequest, err)
-	}
-
-	defer resp.Body.Close()
-
-	vaultResult := vault.Secret{}
-	if err = resp.DecodeJSON(&vaultResult); err != nil {
-		return "", fmt.Errorf(errVaultResponse, err)
-	}
-
-	token, err := vaultResult.TokenID()
-	if err != nil {
-		return "", fmt.Errorf(errVaultToken, err)
-	}
-
-	return token, nil
-}
-
-// kubeParameters creates the required body for Vault Kubernetes auth.
-// Reference - https://www.vaultproject.io/api/auth/kubernetes#login
-func kubeParameters(role, jwt string) map[string]string {
-	return map[string]string{
-		"role": role,
-		"jwt":  jwt,
-	}
-}
-
-func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) {
-	jwtString := ""
-	if kubernetesAuth.ServiceAccountRef != nil {
-		jwt, err := v.secretKeyRefForServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
-		if err != nil {
-			return "", err
-		}
-		jwtString = jwt
-	} else if kubernetesAuth.SecretRef != nil {
-		tokenRef := kubernetesAuth.SecretRef
-		if tokenRef.Key == "" {
-			tokenRef = kubernetesAuth.SecretRef.DeepCopy()
-			tokenRef.Key = "token"
-		}
-		jwt, err := v.secretKeyRef(ctx, tokenRef)
-		if err != nil {
-			return "", err
-		}
-		jwtString = jwt
-	} else {
-		// Kubernetes authentication is specified, but without a referenced
-		// Kubernetes secret. We check if the file path for in-cluster service account
-		// exists and attempt to use the token for Vault Kubernetes auth.
-		if _, err := os.Stat(serviceAccTokenPath); err != nil {
-			return "", fmt.Errorf(errServiceAccount, err)
-		}
-		jwtByte, err := ioutil.ReadFile(serviceAccTokenPath)
-		if err != nil {
-			return "", fmt.Errorf(errServiceAccount, err)
-		}
-		jwtString = string(jwtByte)
-	}
-
-	parameters := kubeParameters(kubernetesAuth.Role, jwtString)
-	url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/")
-	request := client.NewRequest("POST", url)
-
-	err := request.SetJSONBody(parameters)
-	if err != nil {
-		return "", fmt.Errorf(errVaultReqParams, err)
-	}
-
-	resp, err := client.RawRequestWithContext(ctx, request)
-	if err != nil {
-		return "", fmt.Errorf(errVaultRequest, err)
-	}
-
-	defer resp.Body.Close()
-	vaultResult := vault.Secret{}
-	err = resp.DecodeJSON(&vaultResult)
-	if err != nil {
-		return "", fmt.Errorf(errVaultResponse, err)
-	}
-
-	token, err := vaultResult.TokenID()
-	if err != nil {
-		return "", fmt.Errorf(errVaultToken, err)
-	}
-
-	return token, nil
-}
-
-func (v *client) requestTokenWithLdapAuth(ctx context.Context, client Client, ldapAuth *esv1alpha1.VaultLdapAuth) (string, error) {
-	username := strings.TrimSpace(ldapAuth.Username)
-
-	password, err := v.secretKeyRef(ctx, &ldapAuth.SecretRef)
-	if err != nil {
-		return "", err
-	}
-
-	parameters := map[string]string{
-		"password": password,
-	}
-	url := strings.Join([]string{"/v1", "auth", "ldap", "login", username}, "/")
-	request := client.NewRequest("POST", url)
-
-	err = request.SetJSONBody(parameters)
-	if err != nil {
-		return "", fmt.Errorf(errVaultReqParams, err)
-	}
-
-	resp, err := client.RawRequestWithContext(ctx, request)
-	if err != nil {
-		return "", fmt.Errorf(errVaultRequest, err)
-	}
-
-	defer resp.Body.Close()
-
-	vaultResult := vault.Secret{}
-	if err = resp.DecodeJSON(&vaultResult); err != nil {
-		return "", fmt.Errorf(errVaultResponse, err)
-	}
-
-	token, err := vaultResult.TokenID()
-	if err != nil {
-		return "", fmt.Errorf(errVaultToken, err)
-	}
-
-	return token, nil
-}
-
-func (v *client) requestTokenWithJwtAuth(ctx context.Context, client Client, jwtAuth *esv1alpha1.VaultJwtAuth) (string, error) {
-	role := strings.TrimSpace(jwtAuth.Role)
-
-	jwt, err := v.secretKeyRef(ctx, &jwtAuth.SecretRef)
-	if err != nil {
-		return "", err
-	}
-
-	parameters := map[string]string{
-		"role": role,
-		"jwt":  jwt,
-	}
-	url := strings.Join([]string{"/v1", "auth", "jwt", "login"}, "/")
-	request := client.NewRequest("POST", url)
-
-	err = request.SetJSONBody(parameters)
-	if err != nil {
-		return "", fmt.Errorf(errVaultReqParams, err)
-	}
-
-	resp, err := client.RawRequestWithContext(ctx, request)
-	if err != nil {
-		return "", fmt.Errorf(errVaultRequest, err)
-	}
-
-	defer resp.Body.Close()
-
-	vaultResult := vault.Secret{}
-	if err = resp.DecodeJSON(&vaultResult); err != nil {
-		return "", fmt.Errorf(errVaultResponse, err)
-	}
-
-	token, err := vaultResult.TokenID()
-	if err != nil {
-		return "", fmt.Errorf(errVaultToken, err)
-	}
-
-	return token, nil
-}
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package vault
+
+import (
+	"context"
+	"crypto/x509"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/go-logr/logr"
+	vault "github.com/hashicorp/vault/api"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	ctrl "sigs.k8s.io/controller-runtime"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/provider"
+	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+)
+
+var (
+	_ provider.Provider      = &connector{}
+	_ provider.SecretsClient = &client{}
+)
+
+const (
+	serviceAccTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
+
+	errVaultStore     = "received invalid Vault SecretStore resource: %w"
+	errVaultClient    = "cannot setup new vault client: %w"
+	errVaultCert      = "cannot set Vault CA certificate: %w"
+	errReadSecret     = "cannot read secret data from Vault: %w"
+	errAuthFormat     = "cannot initialize Vault client: no valid auth method specified: %w"
+	errDataField      = "failed to find data field: %v"
+	errJSONUnmarshall = "failed to unmarshall JSON: %v"
+	errSecretFormat   = "secret data not in expected format: %v"
+	errVaultToken     = "cannot parse Vault authentication token: %w"
+	errVaultReqParams = "cannot set Vault request parameters: %w"
+	errVaultRequest   = "error from Vault request: %w"
+	errVaultResponse  = "cannot parse Vault response: %w"
+	errServiceAccount = "cannot read Kubernetes service account token from file system: %w"
+
+	errGetKubeSA        = "cannot get Kubernetes service account %q: %w"
+	errGetKubeSASecrets = "cannot find secrets bound to service account: %q"
+	errGetKubeSANoToken = "cannot find token in secrets bound to service account: %q"
+
+	errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
+	errSecretKeyFmt  = "cannot find secret data for key: %q"
+)
+
+type Client interface {
+	NewRequest(method, requestPath string) *vault.Request
+	RawRequestWithContext(ctx context.Context, r *vault.Request) (*vault.Response, error)
+	SetToken(v string)
+	SetNamespace(namespace string)
+}
+
+type client struct {
+	kube      kclient.Client
+	store     *esv1alpha1.VaultProvider
+	log       logr.Logger
+	client    Client
+	namespace string
+	storeKind string
+}
+
+func init() {
+	schema.Register(&connector{
+		newVaultClient: newVaultClient,
+	}, &esv1alpha1.SecretStoreProvider{
+		Vault: &esv1alpha1.VaultProvider{},
+	})
+}
+
+func newVaultClient(c *vault.Config) (Client, error) {
+	return vault.NewClient(c)
+}
+
+type connector struct {
+	newVaultClient func(c *vault.Config) (Client, error)
+}
+
+func (c *connector) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+	storeSpec := store.GetSpec()
+	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
+		return nil, errors.New(errVaultStore)
+	}
+	vaultSpec := storeSpec.Provider.Vault
+
+	vStore := &client{
+		kube:      kube,
+		store:     vaultSpec,
+		log:       ctrl.Log.WithName("provider").WithName("vault"),
+		namespace: namespace,
+		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
+	}
+
+	cfg, err := vStore.newConfig()
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := c.newVaultClient(cfg)
+	if err != nil {
+		return nil, fmt.Errorf(errVaultClient, err)
+	}
+
+	if vaultSpec.Namespace != nil {
+		client.SetNamespace(*vaultSpec.Namespace)
+	}
+
+	if err := vStore.setAuth(ctx, client); err != nil {
+		return nil, err
+	}
+
+	vStore.client = client
+	return vStore, nil
+}
+
+func (v *client) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	data, err := v.readSecret(ctx, ref.Key, ref.Version)
+	if err != nil {
+		return nil, err
+	}
+	value, exists := data[ref.Property]
+	if !exists {
+		return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
+	}
+	return value, nil
+}
+
+func (v *client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	return v.readSecret(ctx, ref.Key, ref.Version)
+}
+
+func (v *client) Close() error {
+	return nil
+}
+
+func (v *client) readSecret(ctx context.Context, path, version string) (map[string][]byte, error) {
+	kvPath := v.store.Path
+
+	if v.store.Version == esv1alpha1.VaultKVStoreV2 {
+		if !strings.HasSuffix(kvPath, "/data") {
+			kvPath = fmt.Sprintf("%s/data", kvPath)
+		}
+	}
+
+	// path formated according to vault docs for v1 and v2 API
+	// v1: https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret
+	// v2: https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
+	req := v.client.NewRequest(http.MethodGet, fmt.Sprintf("/v1/%s/%s", kvPath, path))
+	if version != "" {
+		req.Params.Set("version", version)
+	}
+
+	resp, err := v.client.RawRequestWithContext(ctx, req)
+	if err != nil {
+		return nil, fmt.Errorf(errReadSecret, err)
+	}
+
+	vaultSecret, err := vault.ParseSecret(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	secretData := vaultSecret.Data
+	if v.store.Version == esv1alpha1.VaultKVStoreV2 {
+		// Vault KV2 has data embedded within sub-field
+		// reference - https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
+		dataInt, ok := vaultSecret.Data["data"]
+
+		if !ok {
+			return nil, errors.New(errDataField)
+		}
+		secretData, ok = dataInt.(map[string]interface{})
+		if !ok {
+			return nil, errors.New(errJSONUnmarshall)
+		}
+	}
+
+	byteMap := make(map[string][]byte, len(secretData))
+	for k, v := range secretData {
+		switch t := v.(type) {
+		case string:
+			byteMap[k] = []byte(t)
+		case []byte:
+			byteMap[k] = t
+		default:
+			return nil, errors.New(errSecretFormat)
+		}
+	}
+
+	return byteMap, nil
+}
+
+func (v *client) newConfig() (*vault.Config, error) {
+	cfg := vault.DefaultConfig()
+	cfg.Address = v.store.Server
+
+	if len(v.store.CABundle) == 0 {
+		return cfg, nil
+	}
+
+	caCertPool := x509.NewCertPool()
+	ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
+	if !ok {
+		return nil, errors.New(errVaultCert)
+	}
+
+	if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
+		transport.TLSClientConfig.RootCAs = caCertPool
+	}
+
+	return cfg, nil
+}
+
+func (v *client) setAuth(ctx context.Context, client Client) error {
+	tokenRef := v.store.Auth.TokenSecretRef
+	if tokenRef != nil {
+		token, err := v.secretKeyRef(ctx, tokenRef)
+		if err != nil {
+			return err
+		}
+		client.SetToken(token)
+		return nil
+	}
+
+	appRole := v.store.Auth.AppRole
+	if appRole != nil {
+		token, err := v.requestTokenWithAppRoleRef(ctx, client, appRole)
+		if err != nil {
+			return err
+		}
+		client.SetToken(token)
+		return nil
+	}
+
+	kubernetesAuth := v.store.Auth.Kubernetes
+	if kubernetesAuth != nil {
+		token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth)
+		if err != nil {
+			return err
+		}
+		client.SetToken(token)
+		return nil
+	}
+
+	ldapAuth := v.store.Auth.Ldap
+	if ldapAuth != nil {
+		token, err := v.requestTokenWithLdapAuth(ctx, client, ldapAuth)
+		if err != nil {
+			return err
+		}
+		client.SetToken(token)
+		return nil
+	}
+
+	jwtAuth := v.store.Auth.Jwt
+	if jwtAuth != nil {
+		token, err := v.requestTokenWithJwtAuth(ctx, client, jwtAuth)
+		if err != nil {
+			return err
+		}
+		client.SetToken(token)
+		return nil
+	}
+
+	return errors.New(errAuthFormat)
+}
+
+func (v *client) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) {
+	serviceAccount := &corev1.ServiceAccount{}
+	ref := types.NamespacedName{
+		Namespace: v.namespace,
+		Name:      serviceAccountRef.Name,
+	}
+	if (v.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
+		(serviceAccountRef.Namespace != nil) {
+		ref.Namespace = *serviceAccountRef.Namespace
+	}
+	err := v.kube.Get(ctx, ref, serviceAccount)
+	if err != nil {
+		return "", fmt.Errorf(errGetKubeSA, ref.Name, err)
+	}
+	if len(serviceAccount.Secrets) == 0 {
+		return "", fmt.Errorf(errGetKubeSASecrets, ref.Name)
+	}
+	for _, tokenRef := range serviceAccount.Secrets {
+		retval, err := v.secretKeyRef(ctx, &esmeta.SecretKeySelector{
+			Name:      tokenRef.Name,
+			Namespace: &ref.Namespace,
+			Key:       "token",
+		})
+
+		if err != nil {
+			continue
+		}
+
+		return retval, nil
+	}
+	return "", fmt.Errorf(errGetKubeSANoToken, ref.Name)
+}
+
+func (v *client) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
+	secret := &corev1.Secret{}
+	ref := types.NamespacedName{
+		Namespace: v.namespace,
+		Name:      secretRef.Name,
+	}
+	if (v.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
+		(secretRef.Namespace != nil) {
+		ref.Namespace = *secretRef.Namespace
+	}
+	err := v.kube.Get(ctx, ref, secret)
+	if err != nil {
+		return "", fmt.Errorf(errGetKubeSecret, ref.Name, err)
+	}
+
+	keyBytes, ok := secret.Data[secretRef.Key]
+	if !ok {
+		return "", fmt.Errorf(errSecretKeyFmt, secretRef.Key)
+	}
+
+	value := string(keyBytes)
+	valueStr := strings.TrimSpace(value)
+	return valueStr, nil
+}
+
+// appRoleParameters creates the required body for Vault AppRole Auth.
+// Reference - https://www.vaultproject.io/api-docs/auth/approle#login-with-approle
+func appRoleParameters(role, secret string) map[string]string {
+	return map[string]string{
+		"role_id":   role,
+		"secret_id": secret,
+	}
+}
+
+func (v *client) requestTokenWithAppRoleRef(ctx context.Context, client Client, appRole *esv1alpha1.VaultAppRole) (string, error) {
+	roleID := strings.TrimSpace(appRole.RoleID)
+
+	secretID, err := v.secretKeyRef(ctx, &appRole.SecretRef)
+	if err != nil {
+		return "", err
+	}
+
+	parameters := appRoleParameters(roleID, secretID)
+	url := strings.Join([]string{"/v1", "auth", appRole.Path, "login"}, "/")
+	request := client.NewRequest("POST", url)
+
+	err = request.SetJSONBody(parameters)
+	if err != nil {
+		return "", fmt.Errorf(errVaultReqParams, err)
+	}
+
+	resp, err := client.RawRequestWithContext(ctx, request)
+	if err != nil {
+		return "", fmt.Errorf(errVaultRequest, err)
+	}
+
+	defer resp.Body.Close()
+
+	vaultResult := vault.Secret{}
+	if err = resp.DecodeJSON(&vaultResult); err != nil {
+		return "", fmt.Errorf(errVaultResponse, err)
+	}
+
+	token, err := vaultResult.TokenID()
+	if err != nil {
+		return "", fmt.Errorf(errVaultToken, err)
+	}
+
+	return token, nil
+}
+
+// kubeParameters creates the required body for Vault Kubernetes auth.
+// Reference - https://www.vaultproject.io/api/auth/kubernetes#login
+func kubeParameters(role, jwt string) map[string]string {
+	return map[string]string{
+		"role": role,
+		"jwt":  jwt,
+	}
+}
+
+func (v *client) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *esv1alpha1.VaultKubernetesAuth) (string, error) {
+	jwtString := ""
+	if kubernetesAuth.ServiceAccountRef != nil {
+		jwt, err := v.secretKeyRefForServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
+		if err != nil {
+			return "", err
+		}
+		jwtString = jwt
+	} else if kubernetesAuth.SecretRef != nil {
+		tokenRef := kubernetesAuth.SecretRef
+		if tokenRef.Key == "" {
+			tokenRef = kubernetesAuth.SecretRef.DeepCopy()
+			tokenRef.Key = "token"
+		}
+		jwt, err := v.secretKeyRef(ctx, tokenRef)
+		if err != nil {
+			return "", err
+		}
+		jwtString = jwt
+	} else {
+		// Kubernetes authentication is specified, but without a referenced
+		// Kubernetes secret. We check if the file path for in-cluster service account
+		// exists and attempt to use the token for Vault Kubernetes auth.
+		if _, err := os.Stat(serviceAccTokenPath); err != nil {
+			return "", fmt.Errorf(errServiceAccount, err)
+		}
+		jwtByte, err := ioutil.ReadFile(serviceAccTokenPath)
+		if err != nil {
+			return "", fmt.Errorf(errServiceAccount, err)
+		}
+		jwtString = string(jwtByte)
+	}
+
+	parameters := kubeParameters(kubernetesAuth.Role, jwtString)
+	url := strings.Join([]string{"/v1", "auth", kubernetesAuth.Path, "login"}, "/")
+	request := client.NewRequest("POST", url)
+
+	err := request.SetJSONBody(parameters)
+	if err != nil {
+		return "", fmt.Errorf(errVaultReqParams, err)
+	}
+
+	resp, err := client.RawRequestWithContext(ctx, request)
+	if err != nil {
+		return "", fmt.Errorf(errVaultRequest, err)
+	}
+
+	defer resp.Body.Close()
+	vaultResult := vault.Secret{}
+	err = resp.DecodeJSON(&vaultResult)
+	if err != nil {
+		return "", fmt.Errorf(errVaultResponse, err)
+	}
+
+	token, err := vaultResult.TokenID()
+	if err != nil {
+		return "", fmt.Errorf(errVaultToken, err)
+	}
+
+	return token, nil
+}
+
+func (v *client) requestTokenWithLdapAuth(ctx context.Context, client Client, ldapAuth *esv1alpha1.VaultLdapAuth) (string, error) {
+	username := strings.TrimSpace(ldapAuth.Username)
+
+	password, err := v.secretKeyRef(ctx, &ldapAuth.SecretRef)
+	if err != nil {
+		return "", err
+	}
+
+	parameters := map[string]string{
+		"password": password,
+	}
+	url := strings.Join([]string{"/v1", "auth", "ldap", "login", username}, "/")
+	request := client.NewRequest("POST", url)
+
+	err = request.SetJSONBody(parameters)
+	if err != nil {
+		return "", fmt.Errorf(errVaultReqParams, err)
+	}
+
+	resp, err := client.RawRequestWithContext(ctx, request)
+	if err != nil {
+		return "", fmt.Errorf(errVaultRequest, err)
+	}
+
+	defer resp.Body.Close()
+
+	vaultResult := vault.Secret{}
+	if err = resp.DecodeJSON(&vaultResult); err != nil {
+		return "", fmt.Errorf(errVaultResponse, err)
+	}
+
+	token, err := vaultResult.TokenID()
+	if err != nil {
+		return "", fmt.Errorf(errVaultToken, err)
+	}
+
+	return token, nil
+}
+
+func (v *client) requestTokenWithJwtAuth(ctx context.Context, client Client, jwtAuth *esv1alpha1.VaultJwtAuth) (string, error) {
+	role := strings.TrimSpace(jwtAuth.Role)
+
+	jwt, err := v.secretKeyRef(ctx, &jwtAuth.SecretRef)
+	if err != nil {
+		return "", err
+	}
+
+	parameters := map[string]string{
+		"role": role,
+		"jwt":  jwt,
+	}
+	url := strings.Join([]string{"/v1", "auth", "jwt", "login"}, "/")
+	request := client.NewRequest("POST", url)
+
+	err = request.SetJSONBody(parameters)
+	if err != nil {
+		return "", fmt.Errorf(errVaultReqParams, err)
+	}
+
+	resp, err := client.RawRequestWithContext(ctx, request)
+	if err != nil {
+		return "", fmt.Errorf(errVaultRequest, err)
+	}
+
+	defer resp.Body.Close()
+
+	vaultResult := vault.Secret{}
+	if err = resp.DecodeJSON(&vaultResult); err != nil {
+		return "", fmt.Errorf(errVaultResponse, err)
+	}
+
+	token, err := vaultResult.TokenID()
+	if err != nil {
+		return "", fmt.Errorf(errVaultToken, err)
+	}
+
+	return token, nil
+}

+ 411 - 287
pkg/provider/vault/vault_test.go

@@ -1,287 +1,411 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package vault
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"testing"
-
-	"github.com/crossplane/crossplane-runtime/pkg/test"
-	"github.com/google/go-cmp/cmp"
-	vault "github.com/hashicorp/vault/api"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
-	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
-	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	"github.com/external-secrets/external-secrets/pkg/provider/vault/fake"
-)
-
-func makeValidSecretStore() *esv1alpha1.SecretStore {
-	return &esv1alpha1.SecretStore{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "vault-store",
-			Namespace: "default",
-		},
-		Spec: esv1alpha1.SecretStoreSpec{
-			Provider: &esv1alpha1.SecretStoreProvider{
-				Vault: &esv1alpha1.VaultProvider{
-					Server:  "vault.example.com",
-					Path:    "secret",
-					Version: esv1alpha1.VaultKVStoreV2,
-					Auth: esv1alpha1.VaultAuth{
-						Kubernetes: &esv1alpha1.VaultKubernetesAuth{
-							Path: "kubernetes",
-							Role: "kubernetes-auth-role",
-							ServiceAccountRef: &esmeta.ServiceAccountSelector{
-								Name: "example-sa",
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-}
-
-type secretStoreTweakFn func(s *esv1alpha1.SecretStore)
-
-func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1alpha1.SecretStore {
-	store := makeValidSecretStore()
-
-	for _, fn := range tweaks {
-		fn(store)
-	}
-
-	return store
-}
-
-func newVaultResponse(data *vault.Secret) *vault.Response {
-	jsonData, _ := json.Marshal(data)
-	return &vault.Response{
-		Response: &http.Response{
-			Body: ioutil.NopCloser(bytes.NewReader(jsonData)),
-		},
-	}
-}
-
-func newVaultTokenIDResponse(token string) *vault.Response {
-	return newVaultResponse(&vault.Secret{
-		Data: map[string]interface{}{
-			"id": token,
-		},
-	})
-}
-
-func TestNewVault(t *testing.T) {
-	errBoom := errors.New("boom")
-	secretData := []byte("some-creds")
-
-	type args struct {
-		newClientFunc func(c *vault.Config) (Client, error)
-		store         esv1alpha1.GenericStore
-		kube          kclient.Client
-		ns            string
-	}
-
-	type want struct {
-		err error
-	}
-
-	cases := map[string]struct {
-		reason string
-		args   args
-		want   want
-	}{
-		"InvalidVaultStore": {
-			reason: "Should return error if given an invalid vault store.",
-			args: args{
-				store: &esv1alpha1.SecretStore{},
-			},
-			want: want{
-				err: errors.New(errVaultStore),
-			},
-		},
-		"AddVaultStoreCertsError": {
-			reason: "Should return error if given an invalid CA certificate.",
-			args: args{
-				store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
-					s.Spec.Provider.Vault.CABundle = []byte("badcertdata")
-				}),
-			},
-			want: want{
-				err: errors.New(errVaultCert),
-			},
-		},
-		"VaultAuthFormatError": {
-			reason: "Should return error if no valid authentication method is given.",
-			args: args{
-				store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
-					s.Spec.Provider.Vault.Auth = esv1alpha1.VaultAuth{}
-				}),
-			},
-			want: want{
-				err: errors.New(errAuthFormat),
-			},
-		},
-		"GetKubeServiceAccountError": {
-			reason: "Should return error if fetching kubernetes secret fails.",
-			args: args{
-				store: makeSecretStore(),
-				kube: &test.MockClient{
-					MockGet: test.NewMockGetFn(errBoom),
-				},
-			},
-			want: want{
-				err: fmt.Errorf(errGetKubeSA, "example-sa", errBoom),
-			},
-		},
-		"GetKubeSecretError": {
-			reason: "Should return error if fetching kubernetes secret fails.",
-			args: args{
-				store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
-					s.Spec.Provider.Vault.Auth.Kubernetes.ServiceAccountRef = nil
-					s.Spec.Provider.Vault.Auth.Kubernetes.SecretRef = &esmeta.SecretKeySelector{
-						Name: "vault-secret",
-						Key:  "key",
-					}
-				}),
-				kube: &test.MockClient{
-					MockGet: test.NewMockGetFn(errBoom),
-				},
-			},
-			want: want{
-				err: fmt.Errorf(errGetKubeSecret, "vault-secret", errBoom),
-			},
-		},
-		"SuccessfulVaultStore": {
-			reason: "Should return a Vault provider successfully",
-			args: args{
-				store: makeSecretStore(),
-				kube: &test.MockClient{
-					MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
-						if o, ok := obj.(*corev1.ServiceAccount); ok {
-							o.Secrets = []corev1.ObjectReference{
-								{
-									Name: "example-secret-token",
-								},
-							}
-							return nil
-						}
-						if o, ok := obj.(*corev1.Secret); ok {
-							o.Data = map[string][]byte{
-								"token": secretData,
-							}
-							return nil
-						}
-						return nil
-					}),
-				},
-				newClientFunc: func(c *vault.Config) (Client, error) {
-					return &fake.VaultClient{
-						MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-						MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-							newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error {
-								kubeRole := makeValidSecretStore().Spec.Provider.Vault.Auth.Kubernetes.Role
-								want := kubeParameters(kubeRole, string(secretData))
-								if diff := cmp.Diff(want, got.Obj); diff != "" {
-									t.Errorf("RawRequestWithContext(...): -want, +got:\n%s", diff)
-								}
-
-								return nil
-							}),
-						MockSetToken: fake.NewSetTokenFn(),
-					}, nil
-				},
-			},
-			want: want{
-				err: nil,
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			conn := &connector{
-				newVaultClient: tc.args.newClientFunc,
-			}
-			if tc.args.newClientFunc == nil {
-				conn.newVaultClient = newVaultClient
-			}
-			_, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
-			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
-				t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff)
-			}
-		})
-	}
-}
-
-func TestGetSecretMap(t *testing.T) {
-	errBoom := errors.New("boom")
-
-	type args struct {
-		store   *esv1alpha1.VaultProvider
-		kube    kclient.Client
-		vClient Client
-		ns      string
-		data    esv1alpha1.ExternalSecretDataRemoteRef
-	}
-
-	type want struct {
-		err error
-	}
-
-	cases := map[string]struct {
-		reason string
-		args   args
-		want   want
-	}{
-		"ReadSecretError": {
-			reason: "Should return error if vault client fails to read secret.",
-			args: args{
-				store: makeSecretStore().Spec.Provider.Vault,
-				vClient: &fake.VaultClient{
-					MockNewRequest:            fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom),
-				},
-			},
-			want: want{
-				err: fmt.Errorf(errReadSecret, errBoom),
-			},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			vStore := &client{
-				kube:      tc.args.kube,
-				client:    tc.args.vClient,
-				store:     tc.args.store,
-				namespace: tc.args.ns,
-			}
-			_, err := vStore.GetSecretMap(context.Background(), tc.args.data)
-			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
-				t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
-			}
-		})
-	}
-}
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package vault
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"testing"
+
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+	"github.com/google/go-cmp/cmp"
+	vault "github.com/hashicorp/vault/api"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/provider/vault/fake"
+)
+
+func makeValidSecretStore() *esv1alpha1.SecretStore {
+	return &esv1alpha1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "vault-store",
+			Namespace: "default",
+		},
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				Vault: &esv1alpha1.VaultProvider{
+					Server:  "vault.example.com",
+					Path:    "secret",
+					Version: esv1alpha1.VaultKVStoreV2,
+					Auth: esv1alpha1.VaultAuth{
+						Kubernetes: &esv1alpha1.VaultKubernetesAuth{
+							Path: "kubernetes",
+							Role: "kubernetes-auth-role",
+							ServiceAccountRef: &esmeta.ServiceAccountSelector{
+								Name: "example-sa",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func makeValidSecretStoreWithCerts() *esv1alpha1.SecretStore {
+	return &esv1alpha1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "vault-store",
+			Namespace: "default",
+		},
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				Vault: &esv1alpha1.VaultProvider{
+					Server:  "vault.example.com",
+					Path:    "secret",
+					Version: esv1alpha1.VaultKVStoreV2,
+					Auth: esv1alpha1.VaultAuth{
+						Cert: &esv1alpha1.VaultCertAuth{
+							ClientCert: esmeta.SecretKeySelector{
+								Name: "tls-auth-certs",
+								Key:  "tls.crt",
+							},
+							SecretRef: esmeta.SecretKeySelector{
+								Name: "tls-auth-certs",
+								Key:  "tls.key",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+type secretStoreTweakFn func(s *esv1alpha1.SecretStore)
+
+func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1alpha1.SecretStore {
+	store := makeValidSecretStore()
+
+	for _, fn := range tweaks {
+		fn(store)
+	}
+
+	return store
+}
+
+func newVaultResponse(data *vault.Secret) *vault.Response {
+	jsonData, _ := json.Marshal(data)
+	return &vault.Response{
+		Response: &http.Response{
+			Body: ioutil.NopCloser(bytes.NewReader(jsonData)),
+		},
+	}
+}
+
+func newVaultTokenIDResponse(token string) *vault.Response {
+	return newVaultResponse(&vault.Secret{
+		Data: map[string]interface{}{
+			"id": token,
+		},
+	})
+}
+
+func TestNewVault(t *testing.T) {
+	errBoom := errors.New("boom")
+	secretData := []byte("some-creds")
+	secretClientKey := []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArfZ4HV1obFVlVNiA24tX/UOakqRnEtWXpIvaOsMaPGvvODgGe4XnyJGO32idPv85sIr7vDH9p+OhactVlJV1fu5SZoZ7pg4jTCLqVDCb3IRD++yik2Sw58YayNe3HiaCTsJQWeMXLzfaqOeyk6bEpBCJo09+3QxUWxijgJ7YZCb+Gi8pf3ZWeSZG+rGNNvXHmTs1Yu1H849SYXu+uJOd/R3ZSTw8CxFe4eTLgbCnPf6tgA8Sg2hc+CAZxunPP2JLZWbiJXxjNRoypso6MAJ1FRkx5sTJiLg6UoLvd95/S/lCVOR2PDlM1hg7ox8VEd4QHky7tLx7gji/5hHQKJQSTwIDAQABAoIBAQCYPICQ8hVX+MNcpLrfZenycR7sBYNOMC0silbH5cUn6yzFfgHuRxi3pOnrCJnTb3cE0BvMbdMVAVdYReD2znSsR9NEdZvvjZ/GGSgH1SIQsI7t//+mDQ/jRLJb4KsXb4vJcLLwdpLrd22bMmhMXjzndrF8gSz8NLX9omozPM8RlLxjzPzYOdlX/Zw8V68qQH2Ic04KbtnCwyAUIgAJxYtn/uYB8lzILBkyzQqwhQKkDDZQ0wbZT0hP6z+HgsdifwQvHG1GZAgCuzzyXrL/4TgDaDhYdMVoBA4+HPmzqm5MkBvjH4oqroxjRofUroVix0OGXZJMI1OJ0z/ubzmwCq5BAoGBANqbwzAydUJs0P+GFL94K/Y6tXULKA2c9N0crbxoxheobRpuJvhpW1ZE/9UGpaYX1Rw3nW4x+Jwvt83YkgHAlR4LgEwDvdJPZobybfqifQDiraUO0t62Crn8mSxOsFCugtRIFniwnX67w3uKxiSdCZYbJGs9JEDTpxRG/PSWq3QlAoGBAMu3zOv1PJAhOky7VcxFxWQPEMY+t2PA/sneD01/qgGuhlTwL4QlpywmBqXcI070dcvcBkP0flnWI7y5cnuE1+55twmsrvfaS8s1+AYje0b35DsaF2vtKuJrXC0AGKP+/eiycd9cbvVW2GWOxE7Ui76Mj95MARK8ZNjt0wJagQhjAoGASm9dD80uhhadN1RFPkjB1054OMk6sx/tdFhug8e9I5MSyzwUguME2aQW5EcmIh7dToVVUo8rUqsgz7NdS8FyRM+vuLJRcQneJDbp4bxwCdwlOh2JCZI8psVutlp4yJATNgrxs9iXV+7BChDflNnvyK+nP+iKrpQiwNHHEdU3vg0CgYEAvEpwD4+loJn1psJn9NxwK6F5IaMKIhtZ4/9pKXpcCh3jb1JouL2MnFOxRVAJGor87aW57Mlol2RDt8W4OM56PqMlOL3xIokUEQka66GT6e5pdu8QwuJ9BrWwhq9WFw4yZQe6FHb836qbbJLegvYVC9QjjZW2UDjtBUwcAkrghH0CgYBUMmMOCwIfMEtMaWxZRGdxRabazLhn7TXhBpVTuv7WouPaXYd7ZGjCTMKAuVa/E4afBlxgemnqBuX90gHpK/dDmn9l+lp8GZey0grJ7G0x5HEMiKziaX5PrgAcKbQ70m9ZNZ1deYhsC05X8rHNexZB6ns7Yms9L7qnlAy51ZH2zw==
+-----END RSA PRIVATE KEY-----`)
+	clientCrt := []byte(`-----BEGIN CERTIFICATE-----
+MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZhdWx0LWNhMB4XDTIxMDcyMDA4MTQxM1oXDTIyMDcyMDA4MTQxM1owFzEVMBMGA1UEAwwMdmF1bHQtY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArfZ4HV1obFVlVNiA24tX/UOakqRnEtWXpIvaOsMaPGvvODgGe4XnyJGO32idPv85sIr7vDH9p+OhactVlJV1fu5SZoZ7pg4jTCLqVDCb3IRD++yik2Sw58YayNe3HiaCTsJQWeMXLzfaqOeyk6bEpBCJo09+3QxUWxijgJ7YZCb+Gi8pf3ZWeSZG+rGNNvXHmTs1Yu1H849SYXu+uJOd/R3ZSTw8CxFe4eTLgbCnPf6tgA8Sg2hc+CAZxunPP2JLZWbiJXxjNRoypso6MAJ1FRkx5sTJiLg6UoLvd95/S/lCVOR2PDlM1hg7ox8VEd4QHky7tLx7gji/5hHQKJQSTwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAsDYKtzScIA7bqIOmqF8rr+oLSjRhPt5OfT+KGNdXk8G3VAy1ED2tyCHaRNC7dPLq4EvcxbIXQnXPy1iZMofriGbFPAcQ2fyWUesAD6bYSpI+bYxwz6Ebb93hU5nc/FyXg8yh0kgiGbY3MrACPjxqP2+z5kcOC3u3hx3SZylgW7TeOXDTdqSbNfH1b+1rR/bVNgQQshjhU9d+c4Yv/t0u07uykBhHLWZDSnYiAeOZ8+mWuOSDkcZHE1zznx74fWgtN0zRDtr0L0w9evT9R2CnNSZGxXcEQxAlQ7SL/Jyw82TFCGEw0L4jj7jjvx0N5J8KX/DulUDE9vuVyQEJ88Epe
+-----END CERTIFICATE-----
+`)
+
+	type args struct {
+		newClientFunc func(c *vault.Config) (Client, error)
+		store         esv1alpha1.GenericStore
+		kube          kclient.Client
+		ns            string
+	}
+
+	type want struct {
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"InvalidVaultStore": {
+			reason: "Should return error if given an invalid vault store.",
+			args: args{
+				store: &esv1alpha1.SecretStore{},
+			},
+			want: want{
+				err: errors.New(errVaultStore),
+			},
+		},
+		"AddVaultStoreCertsError": {
+			reason: "Should return error if given an invalid CA certificate.",
+			args: args{
+				store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
+					s.Spec.Provider.Vault.CABundle = []byte("badcertdata")
+				}),
+			},
+			want: want{
+				err: errors.New(errVaultCert),
+			},
+		},
+		"VaultAuthFormatError": {
+			reason: "Should return error if no valid authentication method is given.",
+			args: args{
+				store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
+					s.Spec.Provider.Vault.Auth = esv1alpha1.VaultAuth{}
+				}),
+			},
+			want: want{
+				err: errors.New(errAuthFormat),
+			},
+		},
+		"GetKubeServiceAccountError": {
+			reason: "Should return error if fetching kubernetes secret fails.",
+			args: args{
+				store: makeSecretStore(),
+				kube: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
+				},
+			},
+			want: want{
+				err: fmt.Errorf(errGetKubeSA, "example-sa", errBoom),
+			},
+		},
+		"GetKubeSecretError": {
+			reason: "Should return error if fetching kubernetes secret fails.",
+			args: args{
+				store: makeSecretStore(func(s *esv1alpha1.SecretStore) {
+					s.Spec.Provider.Vault.Auth.Kubernetes.ServiceAccountRef = nil
+					s.Spec.Provider.Vault.Auth.Kubernetes.SecretRef = &esmeta.SecretKeySelector{
+						Name: "vault-secret",
+						Key:  "key",
+					}
+				}),
+				kube: &test.MockClient{
+					MockGet: test.NewMockGetFn(errBoom),
+				},
+			},
+			want: want{
+				err: fmt.Errorf(errGetKubeSecret, "vault-secret", errBoom),
+			},
+		},
+		"SuccessfulVaultStore": {
+			reason: "Should return a Vault provider successfully",
+			args: args{
+				store: makeSecretStore(),
+				kube: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
+						if o, ok := obj.(*corev1.ServiceAccount); ok {
+							o.Secrets = []corev1.ObjectReference{
+								{
+									Name: "example-secret-token",
+								},
+							}
+							return nil
+						}
+						if o, ok := obj.(*corev1.Secret); ok {
+							o.Data = map[string][]byte{
+								"token": secretData,
+							}
+							return nil
+						}
+						return nil
+					}),
+				},
+				newClientFunc: func(c *vault.Config) (Client, error) {
+					return &fake.VaultClient{
+						MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
+						MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
+							newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error {
+								kubeRole := makeValidSecretStore().Spec.Provider.Vault.Auth.Kubernetes.Role
+								want := kubeParameters(kubeRole, string(secretData))
+								if diff := cmp.Diff(want, got.Obj); diff != "" {
+									t.Errorf("RawRequestWithContext(...): -want, +got:\n%s", diff)
+								}
+
+								return nil
+							}),
+						MockSetToken: fake.NewSetTokenFn(),
+					}, nil
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SuccessfulVaultStoreWithCertAuth": {
+			reason: "Should return a Vault provider successfully",
+			args: args{
+				store: makeValidSecretStoreWithCerts(),
+				kube: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
+						if o, ok := obj.(*corev1.Secret); ok {
+							o.Data = map[string][]byte{
+								"tls.key": secretClientKey,
+								"tls.crt": clientCrt,
+							}
+							return nil
+						}
+						return nil
+					}),
+				},
+				newClientFunc: func(c *vault.Config) (Client, error) {
+					return &fake.VaultClient{
+						MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
+						MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
+							newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
+						MockSetToken: fake.NewSetTokenFn(),
+					}, nil
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"GetCertificateFormatError": {
+			reason: "Should return error if client certificate is in wrong format.",
+			args: args{
+				store: makeValidSecretStoreWithCerts(),
+				kube: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
+						if o, ok := obj.(*corev1.Secret); ok {
+							o.Data = map[string][]byte{
+								"tls.key": secretClientKey,
+								"tls.crt": []byte("cert with mistak"),
+							}
+							return nil
+						}
+						return nil
+					}),
+				},
+				newClientFunc: func(c *vault.Config) (Client, error) {
+					return &fake.VaultClient{
+						MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
+						MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
+							newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
+						MockSetToken: fake.NewSetTokenFn(),
+					}, nil
+				},
+			},
+			want: want{
+				err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in certificate input"),
+			},
+		},
+		"GetKeyFormatError": {
+			reason: "Should return error if client key is in wrong format.",
+			args: args{
+				store: makeValidSecretStoreWithCerts(),
+				kube: &test.MockClient{
+					MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
+						if o, ok := obj.(*corev1.Secret); ok {
+							o.Data = map[string][]byte{
+								"tls.key": []byte("key with mistake"),
+								"tls.crt": clientCrt,
+							}
+							return nil
+						}
+						return nil
+					}),
+				},
+				newClientFunc: func(c *vault.Config) (Client, error) {
+					return &fake.VaultClient{
+						MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
+						MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
+							newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
+						MockSetToken: fake.NewSetTokenFn(),
+					}, nil
+				},
+			},
+			want: want{
+				err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in key input"),
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			conn := &connector{
+				newVaultClient: tc.args.newClientFunc,
+			}
+			if tc.args.newClientFunc == nil {
+				conn.newVaultClient = newVaultClient
+			}
+			_, err := conn.NewClient(context.Background(), tc.args.store, tc.args.kube, tc.args.ns)
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nvault.New(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}
+
+func TestGetSecretMap(t *testing.T) {
+	errBoom := errors.New("boom")
+
+	type args struct {
+		store   *esv1alpha1.VaultProvider
+		kube    kclient.Client
+		vClient Client
+		ns      string
+		data    esv1alpha1.ExternalSecretDataRemoteRef
+	}
+
+	type want struct {
+		err error
+	}
+
+	cases := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"ReadSecretError": {
+			reason: "Should return error if vault client fails to read secret.",
+			args: args{
+				store: makeSecretStore().Spec.Provider.Vault,
+				vClient: &fake.VaultClient{
+					MockNewRequest:            fake.NewMockNewRequestFn(&vault.Request{}),
+					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom),
+				},
+			},
+			want: want{
+				err: fmt.Errorf(errReadSecret, errBoom),
+			},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			vStore := &client{
+				kube:      tc.args.kube,
+				client:    tc.args.vClient,
+				store:     tc.args.store,
+				namespace: tc.args.ns,
+			}
+			_, err := vStore.GetSecretMap(context.Background(), tc.args.data)
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
+			}
+		})
+	}
+}