Repository: external-secrets/external-secrets (go)

Issue:
[Infisical] accessing nested items doesn't let you choose folder
**Describe the bug**
When trying to access nested items (SecretStore has `recursive: true` set), it's not possible to choose the path where a secret should lie.
With `data`, I can only fetch secrets of root layer.
With `dataFrom`, I can fetch secrets from the whole recursive filestructure specified in the SecretStore, but I cannot provide a path, so when a key is used multiple times, it's random which secret is fetched.

**To Reproduce**
Steps to reproduce the behavior:
Infisical Version: 1.3.0
External-Secrets Version: 0.12.1
Kubernetes Version: v1.29.11
1. Create a Test Secrets Project in Infisical called test-for-github with the following Secrets Layout:
  ```
  SECRET_LAYER_0 -> Value: SECRET_LAYER_0
  folder0
    UNIQUE_SECRET -> Value: UNIQUE_SECRET
    SECRET_LAYER_1 -> Value: SECRET_LAYER_1_0
  folder1
    SECRET_LAYER_1 -> Value: SECRET_LAYER_1_1
  ```
2. Create a Machine identity with role Admin (just for testing, reduce it if you want) to the Project.
3. Add a Secret with the Client Credentials to the Kubernetes Test Namespace (for naming, see SecretStore Manifest)
4. Create a SecretStore like this:
  ```
  apiVersion: external-secrets.io/v1beta1
  kind: SecretStore
  metadata:
    name: test-for-github
  spec:
    provider:
      infisical:
        auth:
          universalAuthCredentials:
            clientId:
              key: clientId
              name: infisical-creds
            clientSecret:
              key: clientSecret
              name: infisical-creds
        hostAPI: https://infisical.example.com # ChangeThis
        secretsScope:
          environmentSlug: staging
          projectSlug: test-for-github-dbn-j # ChangeThis
          recursive: true
          secretsPath: /
  ```
5. Create an External Secret (different Options and their results as comments in Manifest):
  ```
  apiVersion: external-secrets.io/v1beta1
  kind: ExternalSecret
  metadata:
    name: es-test
  spec:
    refreshInterval: 1m
    secretStoreRef:
      name: test-for-github
      kind: SecretStore
    target:
      name: es-test
      creationPolicy: Owner
    data:
    # ### Option 1: simple access
    # # Result: Works for Secrets in 0. Layer (SECRET_LAYER_0), but when specifying something else, the key is ""
    # - secretKey: key
    #   remoteRef:
    #     key: SECRET_LAYER_0 # SECRET_LAYER_0 or SECRET_LAYER_1 or UNIQUE_SECRET
    # ### Option 2: data with path
    # # Result: doesn't work, because it's using the whole path as "Secret-Key" with `secretPath=/`, therefore its key is ""
    # # See Logline: "Route GET:/api/v3/secrets/raw/folder0/SECRET_LAYER_1?environment=staging&include_imports=true&secretPath=%2F&workspaceSlug=test-for-github-dbn-j not found"
    # - secretKey: key
    #   remoteRef:
    #     key: folder0/SECRET_LAYER_1
    # ### Option 3: dataFrom with name
    # # Result: Works with `SECRET_LAYER_0` and `UNIQUE_SECRET`, but with `SECRET_LAYER_1`, it saves value `SECRET_LAYER_1_1`
    # dataFrom:
    # - find:
    #     name:
    #       regexp: "SECRET_LAYER_1"
    # ### Option4: dataFrom with path and name
    # # Result: doesn't work at all. As soon as I specify `path`, it doesn't create a secret anymore
    # # The Log-Lines also don't show anything, only the requests of the SecretStore to `"/api/v3/secrets/raw?environment=staging&expandSecretReferences=true&include_imports=true&recursive=true&secretPath=%2F&workspaceSlug=test-for-github-dbn-j"`
    # dataFrom:
    # - find:
    #     path: folder0
    #     name: 
    #       regexp: "SECRET_LAYER_1"
  ```

**Expected behavior**
When creating a SecretStore with `spec.provider.infisical.secretsScope.recursive=true` and creating an External-Secret with
  ```
  dataFrom:
    - find:
        path: folder0
        name: 
          regexp: "SECRET_LAYER_1"
  ```
it should access the Secret `folder0/SECRET_LAYER_1`. Instead it doesn't create the Secret and sets the External-Secret to the state `SecretSynced` with message "secret retained due to DeletionPolicy=Retain"

**Additional context**
I've checked the Request that is done by the SecretStore
```
curl --request GET \
  --url 'https://infisical.example.com/api/v3/secrets/raw?environment=staging&expandSecretReferences=true&include_imports=true&recursive=true&secretPath=%252F&workspaceSlug=test-for-github-dbn-j' \
  --header 'authorization: Bearer SECRETBEARERTOKEN'
```
and it returns all the secrets, even both SECRET_LAYER_1 with their different values, but also different `secretPath`'s.

Pull request:
infisical: support secrets within paths for `data` references
(cc @akhilmhdh)

## Problem Statement

When attempting to access a secret in a directory, provided `secretsPath: /`, like so:
```yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: infisical-managed-secrets
spec:
  secretStoreRef:
    kind: SecretStore
    name: infisical
  target:
    name: auth-api
  data:
    - secretKey: API_KEY
      remoteRef:
        key: "foo/bar"
``` 

This will always result in a 404 API error from Infisical, because the API request is malformed (`/api/v3/secrets/raw/foo/bar`). The correct API request would instead be `/api/v3/secrets/raw/bar?secretPath=/foo`. This either:
- Made it so you simply cannot use secrets in folders with direct references via `data`
- Made it so you have to use a separate secret provider with a distinct `path`, which isn't very usable or very obvious

In combination with #4059, this would result in empty secrets but after that issue is fixed these would fail to sync with an error.

Overall this was also quite confusing with the `SecretStore`'s `secretsPath` property - would a `remoteRef` be an additive path? Would it supercede the `SecretStore`?


## Related Issue

Fixes #4298

## Proposed Changes

As suggested on the issue (https://github.com/external-secrets/external-secrets/issues/4298#issuecomment-2600966462) this makes a particular changes:
- `remoteRef`'s with paths take precedence over any `secretsPath` on the `SecretStore`
- If a `remoteRef` is detected to be a path (i.e. it contains `/`), an error is raised if it does not start with `/` with an actionable error indicating to use `/` and that it is an absolute reference. This should hopefuly further clarify the interaction between this, and `secretsPath`
- Documentation is updated with more specific examples of data in Infisical and how you would retrieve it, now that these features work

I've tested this against a self-hosted Infisical instance. I used a branch rebased off of #4304 to test this ([lgo:joey-infisical-folder-handling-testing](https://github.com/lgo/external-secrets/tree/joey-infisical-folder-handling-testing))
- With a `remoteRef: {key: "foo/bar"}`
- :x: `ExternalSecret` expectedly fails with `a secret key referencing a folder must start with a '/' as it is an absolute path, key: foo/bar`
- With a `remoteKey: {key: "/foo/bar"}`
- ✅ the secret successfully syncs.

**TODO**:
- [x] This needs to be rebased after merging #4304. I had a few changes layered on that I'll include after the rebase.

## Checklist

- [x] I have read the [contribution guidelines](https://external-secrets.io/latest/contributing/process/#submitting-a-pull-request)
- [x] All commits are signed with `git commit --signoff`
- [ ] My changes have reasonable test coverage
- [x] All tests pass with `make test`
- [x] I ensured my PR is ready for review with `make reviewable`


Patch:
diff --git a/docs/provider/infisical.md b/docs/provider/infisical.md
index 617730bf9..e376ed84d 100644
--- a/docs/provider/infisical.md
+++ b/docs/provider/infisical.md
@@ -3,6 +3,7 @@
 Sync secrets from [Infisical](https://www.infisical.com) to your Kubernetes cluster using External Secrets Operator.
 
 ## Authentication
+
 In order for the operator to fetch secrets from Infisical, it needs to first authenticate with Infisical.
 
 To authenticate, you can use [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) from [Machine identities](https://infisical.com/docs/documentation/platform/identities/machine-identities).
@@ -42,7 +43,22 @@ You will then need to create a generic `SecretStore`. An sample `SecretStore` ha
 !!! Note
     For `ClusterSecretStore`, be sure to set `namespace` in `universalAuthCredentials.clientId` and `universalAuthCredentials.clientSecret`.
 
-## Fetch Individual Secret(s)
+## Fetching secrets
+
+For the following examples, it assumes we have a secret structure in an Infisical project with the following structure:
+
+```plaintext
+/API_KEY
+/DB_PASSWORD
+/JSON_BLOB
+/my-app
+  /SERVICE_PASSWORD
+  /ADMIN_PASSWORD
+```
+
+Where `JSON_BLOB` is a JSON string like `{"key": "value"}`.
+
+### Fetch Individual Secret(s)
 
 To sync one or more secrets individually, use the following YAML:
 
@@ -50,7 +66,7 @@ To sync one or more secrets individually, use the following YAML:
 {% include 'infisical-fetch-secret.yaml' %}
 ```
 
-## Fetch All Secrets
+### Fetch All Secrets
 
 To sync all secrets from an Infisical , use the following YAML:
 
@@ -58,11 +74,10 @@ To sync all secrets from an Infisical , use the following YAML:
 {% include 'infisical-fetch-all-secrets.yaml' %}
 ```
 
-## Filter By Prefix/Name
+### Filtering secrets
 
 To filter secrets by `path` (path prefix) and `name` (regular expression).
 
 ``` yaml
 {% include 'infisical-filtered-secrets.yaml' %}
 ```
-
diff --git a/docs/snippets/infisical-fetch-all-secrets.yaml b/docs/snippets/infisical-fetch-all-secrets.yaml
index e0ff9a5c9..dad0e0693 100644
--- a/docs/snippets/infisical-fetch-all-secrets.yaml
+++ b/docs/snippets/infisical-fetch-all-secrets.yaml
@@ -10,6 +10,8 @@ spec:
   target:
     name: auth-api
 
+  # dataFrom will fetch all secrets that are inside the `secretsPath`. When `recursive` is
+  # enabled, it will also fetch all secrets recursively in sub-directories.
   dataFrom:
     - find:
         name:
diff --git a/docs/snippets/infisical-fetch-secret.yaml b/docs/snippets/infisical-fetch-secret.yaml
index eeb8153e3..314e0d1ec 100644
--- a/docs/snippets/infisical-fetch-secret.yaml
+++ b/docs/snippets/infisical-fetch-secret.yaml
@@ -11,6 +11,18 @@ spec:
     name: auth-api
 
   data:
+    # When referencing a secret within the `secretsPath`, the `key` can just be a secret
+    # name.
     - secretKey: API_KEY
       remoteRef:
         key: API_KEY
+    # Properties can be extracted from secrets that are JSON strings.
+    - secretKey: JSON_KEY
+      remoteRef:
+        key: JSON_BLOB
+        property: key
+    # When referencing secrets in paths (other than `secretsPath`), the `key` must be an
+    # absolute path to the secret.
+    - secretKey: PASSWORD
+      remoteRef:
+        key: /my-app/SERVICE_PASSWORD
diff --git a/docs/snippets/infisical-generic-secret-store.yaml b/docs/snippets/infisical-generic-secret-store.yaml
index 6092703c1..93215fcab 100644
--- a/docs/snippets/infisical-generic-secret-store.yaml
+++ b/docs/snippets/infisical-generic-secret-store.yaml
@@ -5,6 +5,10 @@ metadata:
 spec:
   provider:
     infisical:
+      # Optional (default: https://app.infisical.com).
+      #
+      # Override this if you are using a different Infisical instance.
+      hostAPI: https://app.infisical.com
       auth:
         universalAuthCredentials:
           clientId:
@@ -15,15 +19,22 @@ spec:
             key: clientSecret
             namespace: default
             name: universal-auth-credentials
-      # Details to pull secrets from
       secretsScope:
         projectSlug: first-project-fujo
-        environmentSlug: dev # "dev", "staging", "prod", etc..
-        # optional
-        secretsPath: / # Root is "/"
-        # optional
-        recursive: true # Default is false
+        # "dev", "staging", "prod", etc.
+        environmentSlug: dev
+        # Optional (default: `/`).
+        #
+        # Secrets will only be retrieved from this path for `data` and `dataFrom` rules. When a
+        # `data` `remoteRef` uses a path (e.g. `/foo/bar`), that reference will use an absolute
+        # reference and disregard this default.
+        #
+        # If you need to prevent access to secrets outside of this path, rely on instead setting
+        # Access Controls in Infisical.
+        secretsPath: /
+        # Optional (default: false).
+        #
+        # When recursive is enabled, secrets retrieved using `dataFrom` patterns will fetch all secrets recursive.
+        recursive: false
         # optional
         expandSecretReferences: false # Default is true
-      # optional
-      hostAPI: https://app.infisical.com
diff --git a/pkg/provider/infisical/client.go b/pkg/provider/infisical/client.go
index 6044ad688..ec59ffe33 100644
--- a/pkg/provider/infisical/client.go
+++ b/pkg/provider/infisical/client.go
@@ -43,14 +43,42 @@ func getPropertyValue(jsonData, propertyName, keyName string) ([]byte, error) {
 	return []byte(result.Str), nil
 }
 
-// if GetSecret returns an error with type NoSecretError.
-// then the secret entry will be deleted depending on the deletionPolicy.
+// getSecretAddress returns the path and key from the given key.
+//
+// Users can configure a root path, and when a SecretKey is provided with a slash we assume that it is
+// within a path appended to the root path.
+//
+// If the key is not addressing a path at all (i.e. has no `/`), simply return the original
+// path and key.
+func getSecretAddress(defaultPath, key string) (string, string, error) {
+	if !strings.Contains(key, "/") {
+		return defaultPath, key, nil
+	}
+
+	// Check if `key` starts with a `/`, and throw and error if it does not.
+	if !strings.HasPrefix(key, "/") {
+		return "", "", fmt.Errorf("a secret key referencing a folder must start with a '/' as it is an absolute path, key: %s", key)
+	}
+
+	// Otherwise, take the prefix from `key` and use that as the path. We intentionally discard
+	// `defaultPath`.
+	lastIndex := strings.LastIndex(key, "/")
+	return key[:lastIndex], key[lastIndex+1:], nil
+}
+
+// GetSecret if this returns an error with type NoSecretError then the secret entry will be deleted depending on the
+// deletionPolicy.
 func (p *Provider) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	path, key, err := getSecretAddress(p.apiScope.SecretPath, ref.Key)
+	if err != nil {
+		return nil, err
+	}
+
 	secret, err := p.apiClient.GetSecretByKeyV3(api.GetSecretByKeyV3Request{
 		EnvironmentSlug:        p.apiScope.EnvironmentSlug,
 		ProjectSlug:            p.apiScope.ProjectSlug,
-		SecretKey:              ref.Key,
-		SecretPath:             p.apiScope.SecretPath,
+		SecretKey:              key,
+		SecretPath:             path,
 		ExpandSecretReferences: p.apiScope.ExpandSecretReferences,
 	})
 
diff --git a/pkg/provider/infisical/provider.go b/pkg/provider/infisical/provider.go
index df2937c37..627af9e35 100644
--- a/pkg/provider/infisical/provider.go
+++ b/pkg/provider/infisical/provider.go
@@ -21,22 +21,16 @@ import (
 	"net/http"
 	"time"
 
-	ctrl "sigs.k8s.io/controller-runtime"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/pkg/provider/infisical/api"
-	"github.com/external-secrets/external-secrets/pkg/provider/infisical/constants"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
 )
 
-var (
-	Logger = ctrl.Log.WithName("provider").WithName(constants.ProviderName)
-)
-
 type Provider struct {
 	apiClient *api.InfisicalClient
 	apiScope  *InfisicalClientScope
@@ -96,13 +90,18 @@ func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube
 			return nil, fmt.Errorf("failed to authenticate via universal auth %w", err)
 		}
 
+		secretPath := infisicalSpec.SecretsScope.SecretsPath
+		if secretPath == "" {
+			secretPath = "/"
+		}
+
 		return &Provider{
 			apiClient: apiClient,
 			apiScope: &InfisicalClientScope{
 				EnvironmentSlug:        infisicalSpec.SecretsScope.EnvironmentSlug,
 				ProjectSlug:            infisicalSpec.SecretsScope.ProjectSlug,
 				Recursive:              infisicalSpec.SecretsScope.Recursive,
-				SecretPath:             infisicalSpec.SecretsScope.SecretsPath,
+				SecretPath:             secretPath,
 				ExpandSecretReferences: infisicalSpec.SecretsScope.ExpandSecretReferences,
 			},
 		}, nil


Test patch:
diff --git a/pkg/provider/infisical/client_test.go b/pkg/provider/infisical/client_test.go
new file mode 100644
index 000000000..a28e65a43
--- /dev/null
+++ b/pkg/provider/infisical/client_test.go
@@ -0,0 +1,62 @@
+/*
+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 infisical
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetSecretAddress(t *testing.T) {
+	t.Run("when the key is not addressing a path and uses the default path", func(t *testing.T) {
+		path, key, err := getSecretAddress("/", "foo")
+		assert.NoError(t, err)
+		assert.Equal(t, "/", path)
+		assert.Equal(t, "foo", key)
+
+		path, key, err = getSecretAddress("/foo", "bar")
+		assert.NoError(t, err)
+		assert.Equal(t, "/foo", path)
+		assert.Equal(t, "bar", key)
+	})
+
+	t.Run("when the key is addressing a path", func(t *testing.T) {
+		path, key, err := getSecretAddress("/", "/foo/bar")
+		assert.NoError(t, err)
+		assert.Equal(t, path, "/foo")
+		assert.Equal(t, key, "bar")
+	})
+
+	t.Run("when the key is addressing a path and ignores the default path", func(t *testing.T) {
+		path, key, err := getSecretAddress("/foo", "/bar/baz")
+		assert.NoError(t, err)
+		assert.Equal(t, "/bar", path)
+		assert.Equal(t, "baz", key)
+	})
+
+	t.Run("works with a nested directory", func(t *testing.T) {
+		path, key, err := getSecretAddress("/", "/foo/bar/baz")
+		assert.NoError(t, err)
+		assert.Equal(t, "/foo/bar", path)
+		assert.Equal(t, "baz", key, "baz")
+	})
+
+	t.Run("fails when the key is a folder but does not begin with a slash", func(t *testing.T) {
+		_, _, err := getSecretAddress("/", "bar/baz")
+		assert.Error(t, err)
+		assert.Equal(t, err.Error(), "a secret key referencing a folder must start with a '/' as it is an absolute path, key: bar/baz")
+	})
+}
diff --git a/pkg/provider/infisical/provider_test.go b/pkg/provider/infisical/provider_test.go
index abd5cd9a0..02d507bac 100644
--- a/pkg/provider/infisical/provider_test.go
+++ b/pkg/provider/infisical/provider_test.go
@@ -7,7 +7,7 @@ You may obtain a copy of the License at
 
 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 impliec.
+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.
 */
@@ -16,7 +16,11 @@ package infisical
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -39,6 +43,7 @@ type TestCases struct {
 	Name           string
 	MockStatusCode int
 	MockResponse   any
+	Key            string
 	Property       string
 	Error          error
 	Output         any
@@ -57,6 +62,7 @@ func TestGetSecret(t *testing.T) {
 					SecretValue: "bar",
 				},
 			},
+			Key:    key,
 			Output: []byte("bar"),
 		},
 		{
@@ -68,6 +74,7 @@ func TestGetSecret(t *testing.T) {
 					SecretValue: `{"bar": "value"}`,
 				},
 			},
+			Key:      key,
 			Property: "bar",
 			Output:   []byte("value"),
 		},
@@ -79,9 +86,22 @@ func TestGetSecret(t *testing.T) {
 				Err:        "Not Found",
 				Message:    "Secret not found",
 			},
-			Error:  esv1.NoSecretError{},
+			Key:    "key",
+			Error:  esv1.NoSecretErr,
 			Output: "",
 		},
+		{
+			Name:           "Key_with_slash",
+			MockStatusCode: 200,
+			MockResponse: api.GetSecretByKeyV3Response{
+				Secret: api.SecretsV3{
+					SecretKey:   "bar",
+					SecretValue: "value",
+				},
+			},
+			Key:    "/foo/bar",
+			Output: []byte("value"),
+		},
 	}
 
 	for _, tc := range testCases {
@@ -94,7 +114,7 @@ func TestGetSecret(t *testing.T) {
 			}
 
 			output, err := p.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{
-				Key:      key,
+				Key:      tc.Key,
 				Property: tc.Property,
 			})
 
@@ -108,6 +128,54 @@ func TestGetSecret(t *testing.T) {
 	}
 }
 
+// TestGetSecretWithPath verifies that request is translated from a key
+// `/foo/bar` to a secret `bar` with `secretPath` of `/foo`.
+func TestGetSecretWithPath(t *testing.T) {
+	requestedKey := "/foo/bar"
+	expectedSecretPath := "/foo"
+	expectedSecretKey := "bar"
+
+	// Prepare the mock response.
+	data := api.GetSecretByKeyV3Response{
+		Secret: api.SecretsV3{
+			SecretKey:   expectedSecretKey,
+			SecretValue: `value`,
+		},
+	}
+	body, err := json.Marshal(data)
+	if err != nil {
+		panic(err)
+	}
+
+	// Prepare the mock server, which asserts the request translation is correct.
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, fmt.Sprintf("/api/v3/secrets/raw/%s", expectedSecretKey), r.URL.Path)
+		assert.Equal(t, expectedSecretPath, r.URL.Query().Get("secretPath"))
+		w.WriteHeader(200)
+		_, err := w.Write(body)
+		if err != nil {
+			panic(err)
+		}
+	}))
+	defer server.Close()
+
+	client, err := api.NewAPIClient(server.URL, server.Client())
+	require.NoError(t, err)
+	p := &Provider{
+		apiClient: client,
+		apiScope:  &apiScope,
+	}
+
+	// Retrieve the secret.
+	output, err := p.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{
+		Key:      requestedKey,
+		Property: "",
+	})
+	// And, we should get back the expected secret value.
+	require.NoError(t, err)
+	assert.Equal(t, []byte("value"), output)
+}
+
 func TestGetSecretMap(t *testing.T) {
 	key := "foo"
 	testCases := []TestCases{
@@ -120,6 +188,7 @@ func TestGetSecretMap(t *testing.T) {
 					SecretValue: `{"bar": "value"}`,
 				},
 			},
+			Key: key,
 			Output: map[string][]byte{
 				"bar": []byte("value"),
 			},
@@ -128,6 +197,7 @@ func TestGetSecretMap(t *testing.T) {
 			Name:           "Get_invalid_map",
 			MockStatusCode: 200,
 			MockResponse:   []byte(``),
+			Key:            key,
 			Error:          errors.New("unable to unmarshal secret foo"),
 		},
 	}
@@ -141,7 +211,8 @@ func TestGetSecretMap(t *testing.T) {
 				apiScope:  &apiScope,
 			}
 			output, err := p.GetSecretMap(context.Background(), esv1.ExternalSecretDataRemoteRef{
-				Key: key,
+				Key:      tc.Key,
+				Property: tc.Property,
 			})
 			if tc.Error == nil {
 				assert.NoError(t, err)
@@ -153,7 +224,7 @@ func TestGetSecretMap(t *testing.T) {
 	}
 }
 
-func makeSecretStore(projectSlug, environment, secretPath string, fn ...storeModifier) *esv1.SecretStore {
+func makeSecretStore(projectSlug, environment, secretsPath string, fn ...storeModifier) *esv1.SecretStore {
 	store := &esv1.SecretStore{
 		Spec: esv1.SecretStoreSpec{
 			Provider: &esv1.SecretStoreProvider{
@@ -162,7 +233,7 @@ func makeSecretStore(projectSlug, environment, secretPath string, fn ...storeMod
 						UniversalAuthCredentials: &esv1.UniversalAuthCredentials{},
 					},
 					SecretsScope: esv1.MachineIdentityScopeInWorkspace{
-						SecretsPath:     secretPath,
+						SecretsPath:     secretsPath,
 						EnvironmentSlug: environment,
 						ProjectSlug:     projectSlug,
 					},
@@ -199,6 +270,7 @@ func withClientSecret(name, key string, namespace *string) storeModifier {
 }
 
 type ValidateStoreTestCase struct {
+	name        string
 	store       *esv1.SecretStore
 	assertError func(t *testing.T, err error)
 }
@@ -211,31 +283,37 @@ func TestValidateStore(t *testing.T) {
 
 	testCases := []ValidateStoreTestCase{
 		{
+			name:  "Missing projectSlug",
 			store: makeSecretStore("", "", ""),
 			assertError: func(t *testing.T, err error) {
 				require.ErrorAs(t, err, &authScopeMissingErr)
 			},
 		},
 		{
+			name:  "Missing clientID",
 			store: makeSecretStore(apiScope.ProjectSlug, apiScope.EnvironmentSlug, apiScope.SecretPath, withClientID(authType, randomID, nil)),
 			assertError: func(t *testing.T, err error) {
 				require.ErrorAs(t, err, &authCredMissingErr)
 			},
 		},
 		{
+			name:  "Missing clientSecret",
 			store: makeSecretStore(apiScope.ProjectSlug, apiScope.EnvironmentSlug, apiScope.SecretPath, withClientSecret(authType, randomID, nil)),
 			assertError: func(t *testing.T, err error) {
 				require.ErrorAs(t, err, &authCredMissingErr)
 			},
 		},
 		{
+			name:        "Success",
 			store:       makeSecretStore(apiScope.ProjectSlug, apiScope.EnvironmentSlug, apiScope.SecretPath, withClientID(authType, randomID, nil), withClientSecret(authType, randomID, nil)),
 			assertError: func(t *testing.T, err error) { require.NoError(t, err) },
 		},
 	}
 	p := Provider{}
 	for _, tc := range testCases {
-		_, err := p.ValidateStore(tc.store)
-		tc.assertError(t, err)
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := p.ValidateStore(tc.store)
+			tc.assertError(t, err)
+		})
 	}
 }