Browse Source

Adding GetAllSecrets for Hashicorp Vault

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Gustavo Carvalho 4 years ago
parent
commit
2f23fd28ed

+ 3 - 0
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -175,6 +175,9 @@ type ExternalSecretDataFromRemoteRef struct {
 // +kubebuilder:validation:MinProperties=1
 // +kubebuilder:validation:MinProperties=1
 // +kubebuilder:validation:MaxProperties=1
 // +kubebuilder:validation:MaxProperties=1
 type ExternalSecretFind struct {
 type ExternalSecretFind struct {
+	// A root path to start the find operations.
+	// +optional
+	Path *string `json:"path,omitempty"`
 	// Finds secrets based on the name.
 	// Finds secrets based on the name.
 	// +optional
 	// +optional
 	Name *FindName `json:"name,omitempty"`
 	Name *FindName `json:"name,omitempty"`

+ 5 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -462,6 +462,11 @@ func (in *ExternalSecretDataRemoteRef) DeepCopy() *ExternalSecretDataRemoteRef {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ExternalSecretFind) DeepCopyInto(out *ExternalSecretFind) {
 func (in *ExternalSecretFind) DeepCopyInto(out *ExternalSecretFind) {
 	*out = *in
 	*out = *in
+	if in.Path != nil {
+		in, out := &in.Path, &out.Path
+		*out = new(string)
+		**out = **in
+	}
 	if in.Name != nil {
 	if in.Name != nil {
 		in, out := &in.Name, &out.Name
 		in, out := &in.Name, &out.Name
 		*out = new(FindName)
 		*out = new(FindName)

+ 3 - 0
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -353,6 +353,9 @@ spec:
                               description: Finds secrets base
                               description: Finds secrets base
                               type: string
                               type: string
                           type: object
                           type: object
+                        path:
+                          description: A root path to start the find operations.
+                          type: string
                         tags:
                         tags:
                           additionalProperties:
                           additionalProperties:
                             type: string
                             type: string

+ 3 - 0
deploy/crds/bundle.yaml

@@ -2278,6 +2278,9 @@ spec:
                                 description: Finds secrets base
                                 description: Finds secrets base
                                 type: string
                                 type: string
                             type: object
                             type: object
+                          path:
+                            description: A root path to start the find operations.
+                            type: string
                           tags:
                           tags:
                             additionalProperties:
                             additionalProperties:
                               type: string
                               type: string

+ 34 - 0
pkg/provider/vault/fake/vault.go

@@ -16,6 +16,8 @@ package fake
 
 
 import (
 import (
 	"context"
 	"context"
+	"net/url"
+	"strings"
 
 
 	vault "github.com/hashicorp/vault/api"
 	vault "github.com/hashicorp/vault/api"
 )
 )
@@ -40,6 +42,17 @@ func NewMockNewRequestFn(req *vault.Request) MockNewRequestFn {
 	}
 	}
 }
 }
 
 
+func NewMockNewRequestListFn(req *vault.Request) MockNewRequestFn {
+	return func(method, requestPath string) *vault.Request {
+		urlPath := url.URL{
+			Path: requestPath,
+		}
+		req.URL = &urlPath
+		req.Params = make(url.Values)
+		return req
+	}
+}
+
 // An RequestFn operates on the supplied Request. You might use an RequestFn to
 // An RequestFn operates on the supplied Request. You might use an RequestFn to
 // test or update the contents of an Request.
 // test or update the contents of an Request.
 type RequestFn func(req *vault.Request) error
 type RequestFn func(req *vault.Request) error
@@ -55,6 +68,27 @@ func NewMockRawRequestWithContextFn(res *vault.Response, err error, ofn ...Reque
 	}
 	}
 }
 }
 
 
+type VaultListResponse struct {
+	Metadata *vault.Response
+	Data     *vault.Response
+}
+
+func NewMockRawRequestListWithContextFn(res map[string]VaultListResponse, err error) MockRawRequestWithContextFn {
+	return func(_ context.Context, r *vault.Request) (*vault.Response, error) {
+		pathList := strings.Split(r.URL.Path, "/")
+		path := "default"
+		if pathList[4] != "" {
+			path = strings.Join(pathList[4:], "/")
+		}
+		if strings.Contains(r.URL.Path, "metadata") {
+			resp := res[path].Metadata
+			return resp, err
+		}
+		resp := res[path].Data
+		return resp, err
+	}
+}
+
 func NewSetTokenFn(ofn ...func(v string)) MockSetTokenFn {
 func NewSetTokenFn(ofn ...func(v string)) MockSetTokenFn {
 	return func(v string) {
 	return func(v string) {
 		for _, fn := range ofn {
 		for _, fn := range ofn {

+ 74 - 35
pkg/provider/vault/vault.go

@@ -49,25 +49,27 @@ var (
 const (
 const (
 	serviceAccTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
 	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"
-	errInvalidCredentials = "invalid vault credentials: %w"
-	errDataField          = "failed to find data field"
-	errJSONUnmarshall     = "failed to unmarshall JSON"
-	errSecretFormat       = "secret data not in expected format"
-	errUnexpectedKey      = "unexpected key in data: %s"
-	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"
+	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"
+	errInvalidCredentials   = "invalid vault credentials: %w"
+	errDataField            = "failed to find data field"
+	errJSONUnmarshall       = "failed to unmarshall JSON"
+	errPathInvalid          = "provided Path isn't a valid kv v2 path"
+	errSecretFormat         = "secret data not in expected format"
+	errUnexpectedKey        = "unexpected key in data: %s"
+	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"
+	errDuplicateSecret      = "duplicate secret found: %s"
+	errServiceAccount       = "cannot read Kubernetes service account token from file system: %w"
+	errUnsupportedKvVersion = "cannot perform find operations with kv version v1"
+	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"
 	errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
 	errSecretKeyFmt  = "cannot find secret data for key: %q"
 	errSecretKeyFmt  = "cannot find secret data for key: %q"
@@ -231,20 +233,27 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
 
 
 // Empty GetAllSecrets.
 // Empty GetAllSecrets.
 // GetAllSecrets
 // GetAllSecrets
-// First load all secrets from secretStore path configuration
-// Then, gets secrets from a matching name or matching custom_metadata
+// First load all secrets from secretStore path configuration.
+// Then, gets secrets from a matching name or matching custom_metadata.
 func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
-	potentialSecrets, err := v.listSecrets(ctx, "")
+	if v.store.Version == esv1beta1.VaultKVStoreV1 {
+		return nil, errors.New(errUnsupportedKvVersion)
+	}
+	searchPath := ""
+	if ref.Path != nil {
+		searchPath = *ref.Path + "/"
+	}
+	potentialSecrets, err := v.listSecrets(ctx, searchPath)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	if ref.Name != nil {
 	if ref.Name != nil {
-		return v.findSecretsFromName(ctx, potentialSecrets, *ref.Name)
+		return v.findSecretsFromName(ctx, potentialSecrets, *ref.Name, searchPath)
 	}
 	}
-	return v.findSecretsFromTags(ctx, potentialSecrets, ref.Tags)
+	return v.findSecretsFromTags(ctx, potentialSecrets, ref.Tags, searchPath)
 }
 }
 
 
-func (v *client) findSecretsFromTags(ctx context.Context, candidates []string, tags map[string]string) (map[string][]byte, error) {
+func (v *client) findSecretsFromTags(ctx context.Context, candidates []string, tags map[string]string, removeFromName string) (map[string][]byte, error) {
 	secrets := make(map[string][]byte)
 	secrets := make(map[string][]byte)
 	for _, name := range candidates {
 	for _, name := range candidates {
 		match := true
 		match := true
@@ -264,14 +273,20 @@ func (v *client) findSecretsFromTags(ctx context.Context, candidates []string, t
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
-			newName := strings.ReplaceAll(name, "/", "-")
+			if removeFromName != "" {
+				name = strings.TrimPrefix(name, removeFromName)
+			}
+			newName := utils.ConvertName(name)
+			if _, exists := secrets[newName]; exists {
+				return nil, fmt.Errorf(errDuplicateSecret, newName)
+			}
 			secrets[newName] = secret
 			secrets[newName] = secret
 		}
 		}
 	}
 	}
 	return secrets, nil
 	return secrets, nil
 }
 }
 
 
-func (v *client) findSecretsFromName(ctx context.Context, candidates []string, ref esv1beta1.FindName) (map[string][]byte, error) {
+func (v *client) findSecretsFromName(ctx context.Context, candidates []string, ref esv1beta1.FindName, removeFromName string) (map[string][]byte, error) {
 	secrets := make(map[string][]byte)
 	secrets := make(map[string][]byte)
 	for _, name := range candidates {
 	for _, name := range candidates {
 		ok, err := regexp.MatchString(ref.RegExp, name)
 		ok, err := regexp.MatchString(ref.RegExp, name)
@@ -283,7 +298,13 @@ func (v *client) findSecretsFromName(ctx context.Context, candidates []string, r
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
-			newName := strings.ReplaceAll(name, "/", "-")
+			if removeFromName != "" {
+				name = strings.TrimPrefix(name, removeFromName)
+			}
+			newName := utils.ConvertName(name)
+			if _, exists := secrets[newName]; exists {
+				return nil, fmt.Errorf(errDuplicateSecret, newName)
+			}
 			secrets[newName] = secret
 			secrets[newName] = secret
 		}
 		}
 	}
 	}
@@ -292,7 +313,10 @@ func (v *client) findSecretsFromName(ctx context.Context, candidates []string, r
 
 
 func (v *client) listSecrets(ctx context.Context, path string) ([]string, error) {
 func (v *client) listSecrets(ctx context.Context, path string) ([]string, error) {
 	secrets := make([]string, 0)
 	secrets := make([]string, 0)
-	url := "/v1/" + *v.store.Path + "/metadata/" + path
+	url, err := v.buildMetadataPath(path)
+	if err != nil {
+		return nil, err
+	}
 	r := v.client.NewRequest(http.MethodGet, url)
 	r := v.client.NewRequest(http.MethodGet, url)
 	r.Params.Set("list", "true")
 	r.Params.Set("list", "true")
 	resp, err := v.client.RawRequestWithContext(ctx, r)
 	resp, err := v.client.RawRequestWithContext(ctx, r)
@@ -315,15 +339,14 @@ func (v *client) listSecrets(ctx context.Context, path string) ([]string, error)
 			fullPath = strPath
 			fullPath = strPath
 		}
 		}
 		// Recurrently find secrets
 		// Recurrently find secrets
-		if strings.HasSuffix(p.(string), "/") {
-			var partial = make([]string, 0)
-			partial, err = v.listSecrets(ctx, fullPath)
+		if !strings.HasSuffix(p.(string), "/") {
+			secrets = append(secrets, fullPath)
+		} else {
+			partial, err := v.listSecrets(ctx, fullPath)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 			secrets = append(secrets, partial...)
 			secrets = append(secrets, partial...)
-		} else {
-			secrets = append(secrets, fullPath)
 		}
 		}
 	}
 	}
 	return secrets, nil
 	return secrets, nil
@@ -331,7 +354,10 @@ func (v *client) listSecrets(ctx context.Context, path string) ([]string, error)
 
 
 func (v *client) readSecretMetadata(ctx context.Context, path string) (map[string]string, error) {
 func (v *client) readSecretMetadata(ctx context.Context, path string) (map[string]string, error) {
 	metadata := make(map[string]string)
 	metadata := make(map[string]string)
-	url := "/v1/" + *v.store.Path + "/metadata/" + path
+	url, err := v.buildMetadataPath(path)
+	if err != nil {
+		return nil, err
+	}
 	r := v.client.NewRequest(http.MethodGet, url)
 	r := v.client.NewRequest(http.MethodGet, url)
 	resp, err := v.client.RawRequestWithContext(ctx, r)
 	resp, err := v.client.RawRequestWithContext(ctx, r)
 	if err != nil {
 	if err != nil {
@@ -459,6 +485,19 @@ func (v *client) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
+func (v *client) buildMetadataPath(path string) (string, error) {
+	var url string
+	if v.store.Path == nil && !strings.Contains(path, "data") {
+		return "", fmt.Errorf(errPathInvalid)
+	}
+	if v.store.Path == nil {
+		path = strings.Replace(path, "data", "metadata", 1)
+		url = fmt.Sprintf("/v1/%s", path)
+	} else {
+		url = fmt.Sprintf("/v1/%s/metadata/%s", *v.store.Path, path)
+	}
+	return url, nil
+}
 func (v *client) buildPath(path string) string {
 func (v *client) buildPath(path string) string {
 	optionalMount := v.store.Path
 	optionalMount := v.store.Path
 	origPath := strings.Split(path, "/")
 	origPath := strings.Split(path, "/")

+ 143 - 141
pkg/provider/vault/vault_test.go

@@ -186,6 +186,24 @@ func newVaultResponseWithData(data map[string]interface{}) *vault.Response {
 	})
 	})
 }
 }
 
 
+func newVaultResponseWithMetadata(content map[string]interface{}) map[string]fake.VaultListResponse {
+	ans := make(map[string]fake.VaultListResponse)
+	for k, v := range content {
+		t := v.(map[string]interface{})
+		m := t["metadata"].(map[string]interface{})
+		listResponse := fake.VaultListResponse{
+			Data: newVaultResponse(&vault.Secret{
+				Data: t,
+			}),
+			Metadata: newVaultResponse(&vault.Secret{
+				Data: m,
+			}),
+		}
+		ans[k] = listResponse
+	}
+	return ans
+}
+
 func newVaultTokenIDResponse(token string) *vault.Response {
 func newVaultTokenIDResponse(token string) *vault.Response {
 	return newVaultResponseWithData(map[string]interface{}{
 	return newVaultResponseWithData(map[string]interface{}{
 		"id": token,
 		"id": token,
@@ -984,23 +1002,91 @@ func TestGetSecretMap(t *testing.T) {
 }
 }
 
 
 func TestGetAllSecrets(t *testing.T) {
 func TestGetAllSecrets(t *testing.T) {
-	errBoom := errors.New("boom")
+	secret1Bytes := []byte("{\"access_key\":\"access_key\",\"access_secret\":\"access_secret\"}")
+	secret2Bytes := []byte("{\"access_key\":\"access_key2\",\"access_secret\":\"access_secret2\"}")
+	path1Bytes := []byte("{\"access_key\":\"path1\",\"access_secret\":\"path1\"}")
+	path2Bytes := []byte("{\"access_key\":\"path2\",\"access_secret\":\"path2\"}")
+	tagBytes := []byte("{\"access_key\":\"unfetched\",\"access_secret\":\"unfetched\"}")
+	path := "path"
 	secret := map[string]interface{}{
 	secret := map[string]interface{}{
-		"access_key":    "access_key",
-		"access_secret": "access_secret",
-	}
-	secondSecret := map[string]interface{}{
-		"access_key":    "access_key2",
-		"access_secret": "access_secret2",
-		"token":         nil,
+		"secret1": map[string]interface{}{
+			"data": map[string]interface{}{
+				"access_key":    "access_key",
+				"access_secret": "access_secret",
+			},
+			"metadata": map[string]interface{}{
+				"custom_metadata": map[string]interface{}{
+					"foo": "bar",
+				},
+			},
+		},
+		"secret2": map[string]interface{}{
+			"data": map[string]interface{}{
+				"access_key":    "access_key2",
+				"access_secret": "access_secret2",
+			},
+			"metadata": map[string]interface{}{
+				"custom_metadata": map[string]interface{}{
+					"foo": "baz",
+				},
+			},
+		},
+		"tag": map[string]interface{}{
+			"data": map[string]interface{}{
+				"access_key":    "unfetched",
+				"access_secret": "unfetched",
+			},
+			"metadata": map[string]interface{}{
+				"custom_metadata": map[string]interface{}{
+					"foo": "baz",
+				},
+			},
+		},
+		"path/1": map[string]interface{}{
+			"data": map[string]interface{}{
+				"access_key":    "path1",
+				"access_secret": "path1",
+			},
+			"metadata": map[string]interface{}{
+				"custom_metadata": map[string]interface{}{
+					"foo": "path",
+				},
+			},
+		},
+		"path/2": map[string]interface{}{
+			"data": map[string]interface{}{
+				"access_key":    "path2",
+				"access_secret": "path2",
+			},
+			"metadata": map[string]interface{}{
+				"custom_metadata": map[string]interface{}{
+					"foo": "path",
+				},
+			},
+		},
+		"default": map[string]interface{}{
+			"data": map[string]interface{}{
+				"empty": "true",
+			},
+			"metadata": map[string]interface{}{
+				"keys": []string{"secret1", "secret2", "tag", "path/"},
+			},
+		},
+		"path/": map[string]interface{}{
+			"data": map[string]interface{}{
+				"empty": "true",
+			},
+			"metadata": map[string]interface{}{
+				"keys": []string{"1", "2"},
+			},
+		},
 	}
 	}
-
 	type args struct {
 	type args struct {
 		store   *esv1beta1.VaultProvider
 		store   *esv1beta1.VaultProvider
 		kube    kclient.Client
 		kube    kclient.Client
 		vClient Client
 		vClient Client
 		ns      string
 		ns      string
-		data    esv1beta1.ExternalSecretDataRemoteRef
+		data    esv1beta1.ExternalSecretFind
 	}
 	}
 
 
 	type want struct {
 	type want struct {
@@ -1013,181 +1099,97 @@ func TestGetAllSecrets(t *testing.T) {
 		args   args
 		args   args
 		want   want
 		want   want
 	}{
 	}{
-		"ReadSecretKV2": {
-			reason: "Should Map the Secret with DataFrom.Find.Name",
-			args: args{
-				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
-				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(secret), nil,
-					),
-				},
-			},
-			want: want{
-				err: nil,
-				val: map[string][]byte{
-					"access_key":    []byte("access_key"),
-					"access_secret": []byte("access_secret"),
-				},
-			},
-		},
-		"ReadSecretKV2": {
-			reason: "Should map the secret even if it has a nil value",
+		"FindByName": {
+			reason: "should map multiple secrets matching name",
 			args: args{
 			args: args{
 				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
 				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
 				vClient: &fake.VaultClient{
 				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(
-							map[string]interface{}{
-								"data": secret,
-							},
-						), nil,
+					MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}),
+					MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn(
+						newVaultResponseWithMetadata(secret), nil,
 					),
 					),
 				},
 				},
-			},
-			want: want{
-				err: nil,
-				val: map[string][]byte{
-					"access_key":    []byte("access_key"),
-					"access_secret": []byte("access_secret"),
-				},
-			},
-		},
-		"ReadSecretWithNilValueKV1": {
-			reason: "Should map the secret even if it has a nil value",
-			args: args{
-				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
-				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(secretWithNilVal), nil,
-					),
+				data: esv1beta1.ExternalSecretFind{
+					Name: &esv1beta1.FindName{
+						RegExp: "secret.*",
+					},
 				},
 				},
 			},
 			},
 			want: want{
 			want: want{
 				err: nil,
 				err: nil,
 				val: map[string][]byte{
 				val: map[string][]byte{
-					"access_key":    []byte("access_key"),
-					"access_secret": []byte("access_secret"),
-					"token":         []byte(nil),
+					"secret1": secret1Bytes,
+					"secret2": secret2Bytes,
 				},
 				},
 			},
 			},
 		},
 		},
-		"ReadSecretWithNilValueKV2": {
-			reason: "Should map the secret even if it has a nil value",
+		"FindByTag": {
+			reason: "should map multiple secrets matching tags",
 			args: args{
 			args: args{
 				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
 				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
 				vClient: &fake.VaultClient{
 				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(
-							map[string]interface{}{
-								"data": secretWithNilVal,
-							},
-						), nil,
+					MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}),
+					MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn(
+						newVaultResponseWithMetadata(secret), nil,
 					),
 					),
 				},
 				},
-			},
-			want: want{
-				err: nil,
-				val: map[string][]byte{
-					"access_key":    []byte("access_key"),
-					"access_secret": []byte("access_secret"),
-					"token":         []byte(nil),
-				},
-			},
-		},
-		"ReadSecretWithTypesKV2": {
-			reason: "Should map the secret even if it has other types",
-			args: args{
-				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
-				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(
-							map[string]interface{}{
-								"data": secretWithTypes,
-							},
-						), nil,
-					),
+				data: esv1beta1.ExternalSecretFind{
+					Tags: map[string]string{
+						"foo": "baz",
+					},
 				},
 				},
 			},
 			},
 			want: want{
 			want: want{
 				err: nil,
 				err: nil,
 				val: map[string][]byte{
 				val: map[string][]byte{
-					"access_secret": []byte("access_secret"),
-					"f32":           []byte("2.12"),
-					"f64":           []byte("2.1234534153423423"),
-					"int":           []byte("42"),
-					"bool":          []byte("true"),
-					"bt":            []byte("Zm9vYmFy"), // base64
+					"tag":     tagBytes,
+					"secret2": secret2Bytes,
 				},
 				},
 			},
 			},
 		},
 		},
-		"ReadNestedSecret": {
-			reason: "Should map the secret for deeply nested property",
+		"FilterByPath": {
+			reason: "should filter secrets based on path",
 			args: args{
 			args: args{
 				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
 				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
-				data: esv1beta1.ExternalSecretDataRemoteRef{
-					Property: "nested",
-				},
 				vClient: &fake.VaultClient{
 				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(
-							map[string]interface{}{
-								"data": secretWithNestedVal,
-							},
-						), nil,
+					MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}),
+					MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn(
+						newVaultResponseWithMetadata(secret), nil,
 					),
 					),
 				},
 				},
+				data: esv1beta1.ExternalSecretFind{
+					Path: &path,
+					Tags: map[string]string{
+						"foo": "path",
+					},
+				},
 			},
 			},
 			want: want{
 			want: want{
 				err: nil,
 				err: nil,
 				val: map[string][]byte{
 				val: map[string][]byte{
-					"foo": []byte(`{"mhkeih":"yada yada","oke":"yup"}`),
+					"1": path1Bytes,
+					"2": path2Bytes,
 				},
 				},
 			},
 			},
 		},
 		},
-		"ReadDeeplyNestedSecret": {
-			reason: "Should map the secret for deeply nested property",
+		"FailIfKv1": {
+			reason: "should not work if using kv1 store",
 			args: args{
 			args: args{
-				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
-				data: esv1beta1.ExternalSecretDataRemoteRef{
-					Property: "nested.foo",
-				},
+				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
 				vClient: &fake.VaultClient{
 				vClient: &fake.VaultClient{
-					MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
-					MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
-						newVaultResponseWithData(
-							map[string]interface{}{
-								"data": secretWithNestedVal,
-							},
-						), nil,
+					MockNewRequest: fake.NewMockNewRequestListFn(&vault.Request{}),
+					MockRawRequestWithContext: fake.NewMockRawRequestListWithContextFn(
+						newVaultResponseWithMetadata(secret), nil,
 					),
 					),
 				},
 				},
-			},
-			want: want{
-				err: nil,
-				val: map[string][]byte{
-					"oke":    []byte("yup"),
-					"mhkeih": []byte("yada yada"),
-				},
-			},
-		},
-		"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),
+				data: esv1beta1.ExternalSecretFind{
+					Tags: map[string]string{
+						"foo": "baz",
+					},
 				},
 				},
 			},
 			},
 			want: want{
 			want: want{
-				err: fmt.Errorf(errReadSecret, errBoom),
+				err: errors.New(errUnsupportedKvVersion),
 			},
 			},
 		},
 		},
 	}
 	}
@@ -1200,7 +1202,7 @@ func TestGetAllSecrets(t *testing.T) {
 				store:     tc.args.store,
 				store:     tc.args.store,
 				namespace: tc.args.ns,
 				namespace: tc.args.ns,
 			}
 			}
-			val, err := vStore.GetSecretMap(context.Background(), tc.args.data)
+			val, err := vStore.GetAllSecrets(context.Background(), tc.args.data)
 			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
 			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)
 				t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
 			}
 			}

+ 21 - 0
pkg/utils/utils.go

@@ -22,6 +22,8 @@ import (
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
 
 
+	"unicode"
+
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 )
 )
@@ -34,6 +36,25 @@ func MergeByteMap(dst, src map[string][]byte) map[string][]byte {
 	return dst
 	return dst
 }
 }
 
 
+// ConvertName converts a string into a secret-key compatible string.
+// Replaces any non-alphanumeric characters with its unicode code.
+func ConvertName(in string) string {
+	out := make([]string, len(in))
+	rs := []rune(in)
+	for k, r := range rs {
+		if !unicode.IsNumber(r) &&
+			!unicode.IsLetter(r) &&
+			r != '-' &&
+			r != '.' &&
+			r != '_' {
+			out[k] = fmt.Sprintf("_U%04x_", r)
+		} else {
+			out[k] = string(r)
+		}
+	}
+	return strings.Join(out, "")
+}
+
 // MergeStringMap performs a deep clone from src to dest.
 // MergeStringMap performs a deep clone from src to dest.
 func MergeStringMap(dest, src map[string]string) {
 func MergeStringMap(dest, src map[string]string) {
 	for k, v := range src {
 	for k, v := range src {