Browse Source

feat(kubernetes): allow service account auth (#1201)

* feat(kubernetes): allow service account auth

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 3 years ago
parent
commit
cff9be1664

+ 1 - 14
apis/externalsecrets/v1beta1/secretstore_kubernetes_types.go

@@ -32,13 +32,6 @@ type KubernetesServer struct {
 	// see: https://external-secrets.io/v0.4.1/spec/#external-secrets.io/v1alpha1.CAProvider
 	// +optional
 	CAProvider *CAProvider `json:"caProvider,omitempty"`
-
-	// there's still room for impersonation or proxy settings:
-	// Impersonate-User
-	// Impersonate-Group
-	// Impersonate-Extra-( extra name )
-	// Impersonate-Uid
-	// Proxy Settings
 }
 
 // Configures a store to sync secrets with a Kubernetes instance.
@@ -68,9 +61,7 @@ type KubernetesAuth struct {
 
 	// points to a service account that should be used for authentication
 	// +optional
-	ServiceAccount *ServiceAccountAuth `json:"serviceAccount,omitempty"`
-
-	// possibly exec or webhook
+	ServiceAccount *esmeta.ServiceAccountSelector `json:"serviceAccount,omitempty"`
 }
 
 type CertAuth struct {
@@ -81,7 +72,3 @@ type CertAuth struct {
 type TokenAuth struct {
 	BearerToken esmeta.SecretKeySelector `json:"bearerToken,omitempty"`
 }
-
-type ServiceAccountAuth struct {
-	ServiceAccountRef esmeta.ServiceAccountSelector `json:"serviceAccount,omitempty"`
-}

+ 1 - 17
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -1099,7 +1099,7 @@ func (in *KubernetesAuth) DeepCopyInto(out *KubernetesAuth) {
 	}
 	if in.ServiceAccount != nil {
 		in, out := &in.ServiceAccount, &out.ServiceAccount
-		*out = new(ServiceAccountAuth)
+		*out = new(metav1.ServiceAccountSelector)
 		(*in).DeepCopyInto(*out)
 	}
 }
@@ -1577,22 +1577,6 @@ func (in *SenhaseguraProvider) DeepCopy() *SenhaseguraProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ServiceAccountAuth) DeepCopyInto(out *ServiceAccountAuth) {
-	*out = *in
-	in.ServiceAccountRef.DeepCopyInto(&out.ServiceAccountRef)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountAuth.
-func (in *ServiceAccountAuth) DeepCopy() *ServiceAccountAuth {
-	if in == nil {
-		return nil
-	}
-	out := new(ServiceAccountAuth)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TemplateFrom) DeepCopyInto(out *TemplateFrom) {
 	*out = *in
 	if in.ConfigMap != nil {

+ 11 - 16
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -1995,22 +1995,17 @@ spec:
                             description: points to a service account that should be
                               used for authentication
                             properties:
-                              serviceAccount:
-                                description: A reference to a ServiceAccount resource.
-                                properties:
-                                  name:
-                                    description: The name of the ServiceAccount 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
+                              name:
+                                description: The name of the ServiceAccount 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
                           token:
                             description: use static token to authenticate with

+ 11 - 16
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -1998,22 +1998,17 @@ spec:
                             description: points to a service account that should be
                               used for authentication
                             properties:
-                              serviceAccount:
-                                description: A reference to a ServiceAccount resource.
-                                properties:
-                                  name:
-                                    description: The name of the ServiceAccount 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
+                              name:
+                                description: The name of the ServiceAccount 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
                           token:
                             description: use static token to authenticate with

+ 16 - 24
deploy/crds/bundle.yaml

@@ -1807,18 +1807,14 @@ spec:
                             serviceAccount:
                               description: points to a service account that should be used for authentication
                               properties:
-                                serviceAccount:
-                                  description: A reference to a ServiceAccount resource.
-                                  properties:
-                                    name:
-                                      description: The name of the ServiceAccount 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
+                                name:
+                                  description: The name of the ServiceAccount 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
                             token:
                               description: use static token to authenticate with
@@ -4479,18 +4475,14 @@ spec:
                             serviceAccount:
                               description: points to a service account that should be used for authentication
                               properties:
-                                serviceAccount:
-                                  description: A reference to a ServiceAccount resource.
-                                  properties:
-                                    name:
-                                      description: The name of the ServiceAccount 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
+                                name:
+                                  description: The name of the ServiceAccount 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
                             token:
                               description: use static token to authenticate with

+ 144 - 82
docs/provider-kubernetes.md

@@ -1,31 +1,66 @@
-External Secrets Operator allows to retrieve in-cluster secrets or from a remote Kubernetes Cluster.
+External Secrets Operator allows to retrieve secrets from a Kubernetes Cluster - this can be either a remote cluster or the local where the operator runs in.
 
-### Authentication
+A `SecretStore` points to a **specific namespace** in the target Kubernetes Cluster. You are able to retrieve all secrets from that particular namespace given you have the correct set of RBAC permissions.
 
-It's possible to authenticate against the Kubernetes API using client certificates or a bearer token. Authentication using a service account has not yet been implemented. The operator enforces that exactly one authentication method is used.
+The `SecretStore` reconciler checks if you have read access for secrets in that namespace using `SelfSubjectRulesReview`. See below on how to set that up properly.
 
-**NOTE:** `SelfSubjectAccessReview` permission is required for the service account in order to validation work properly.
+### External Secret Spec
 
-## Example
+This provider supports the use of the `Property` field. With it you point to the key of the remote secret. If you leave it empty it will json encode all key/value pairs.
 
-### In-cluster secrets using a Token
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h
+  secretStoreRef:
+    kind: SecretStore
+    name: example               # name of the SecretStore (or kind specified)
+  target:
+    name: secret-to-be-created  # name of the k8s Secret to be created
+  data:
+  - secretKey: extra
+    remoteRef:
+      key: secret-example
+      property: extra
+```
+
+#### find by tag & name
 
-1. Create a K8s Secret with a client token for the default service account
+You can fetch secrets based on labels or names matching a regexp:
 
 ```yaml
-apiVersion: v1
-kind: Secret
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
 metadata:
-  name: mydefaulttoken
-  annotations:
-    kubernetes.io/service-account.name: default
-type: kubernetes.io/service-account-token
+  name: example
+spec:
+  refreshInterval: 1h
+  secretStoreRef:
+    kind: SecretStore
+    name: example
+  target:
+    name: secret-to-be-created
+  dataFrom:
+  - find:
+      name:
+        # match secret name with regexp
+        regexp: "key-.*"
+  - find:
+      tags:
+        # fetch secrets based on label combination
+        app: "nginx"
 ```
-2. Create a SecretStore
 
-The Servers `url` won't be present as it will default to `kubernetes.default`, add a proper value if needed. In this example the Certificate Authority is fetched using the referenced `caProvider`.
+### Target API-Server Configuration
 
-The `auth` section indicates that the type `token` will be used for authentication, it includes the path to fetch the token. Set `remoteNamespace` to the name of the namespace where your target secrets reside.
+The servers `url` can be omitted and defaults to `kubernetes.default`. You **have to** provide a CA certificate in order to connect to the API Server securely.
+For your convenience, each namespace has a ConfigMap `kube-root-ca.crt` that contains the CA certificate of the internal API Server (see `RootCAConfigMap` [feature gate](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/)).
+Use that if you want to connect to the same API server.
+If you want to connect to a remote API Server you need to fetch it and store it inside the cluster as ConfigMap or Secret.
+You may also define it inline as base64 encoded value using the `caBundle` property.
 
 ```yaml
 apiVersion: external-secrets.io/v1beta1
@@ -35,71 +70,91 @@ metadata:
 spec:
   provider:
     kubernetes:
+      remoteNamespace: default
       server:
+        url: "https://myapiserver.tld"
         caProvider:
-          type: Secret
-          name: mydefaulttoken
+          type: ConfigMap
+          name: kube-root-ca.crt
           key: ca.crt
-      auth:
-        token:
-          bearerToken:
-            name: mydefaulttoken
-            key: token
-      remoteNamespace: default
 ```
-3. Create the local secret that will be synced
+
+### Authentication
+
+It's possible to authenticate against the Kubernetes API using client certificates, a bearer token or service account. The operator enforces that exactly one authentication method is used. You can not use the service account that is mounted inside the operator, this is by design to avoid reading secrets across namespaces.
+
+**NOTE:** `SelfSubjectRulesReview` permission is required in order to validation work properly. Please use the following role as reference:
+
+```yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  namespace: default
+  name: eso-store-role
+rules:
+- apiGroups: [""]
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - authorization.k8s.io
+  resources:
+  - selfsubjectrulesreviews
+  verbs:
+  - create
+```
+
+#### Authenticating with BearerToken
+
+Create a Kubernetes secret with a client token. There are many ways to acquire such a token, please refer to the [Kubernetes Authentication docs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authentication-strategies).
 
 ```yaml
 apiVersion: v1
 kind: Secret
 metadata:
-  name: secret-example
+  name: mydefaulttoken
 data:
-  extra: YmFyCg==
+  token: "...."
 ```
-4. Finally create the ExternalSecret resource
+
+Create a SecretStore: The `auth` section indicates that the type `token` will be used for authentication, it includes the path to fetch the token. Set `remoteNamespace` to the name of the namespace where your target secrets reside.
 
 ```yaml
 apiVersion: external-secrets.io/v1beta1
-kind: ExternalSecret
+kind: SecretStore
 metadata:
   name: example
 spec:
-  refreshInterval: 1h
-  secretStoreRef:
-    kind: SecretStore
-    name: example               # name of the SecretStore (or kind specified)
-  target:
-    name: secret-to-be-created  # name of the k8s Secret to be created
-    creationPolicy: Owner
-  data:
-  - secretKey: extra
-    remoteRef:
-      key: secret-example
-      property: extra
+  provider:
+    kubernetes:
+      server:
+        # ...
+      auth:
+        token:
+          bearerToken:
+            name: mydefaulttoken
+            key: token
+      remoteNamespace: default
 ```
 
-### Remote Secret using a Token
+#### Authenticating with ServiceAccount
 
-1. Create a K8s Secret with the encoded base64 ca and client token.
+Create a Kubernetes Service Account, please refer to the [Service Account Tokens Documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#service-account-tokens) on how they work and how to create them.
 
-```yaml
-apiVersion: v1
-kind: Secret
-metadata:
-  name: cluster-secrets
-data:
-  # Fill with your encoded base64 CA
-  certificate-authority-data: Cg==
-stringData:
-  # Fill with your string Token
-  bearerToken: "my-token"
 ```
-2. Create a SecretStore
+$ kubectl create serviceaccount my-store
+```
 
-The Server section specifies the `url` of the remote Kubernetes API. In this example the Certificate Authority is fetch using the encoded base64 `caBundle`.
+This Service Account needs permissions to read `Secret` and create `SelfSubjectRulesReview` resources. Please see the above role.
+
+```
+$ kubectl create rolebinding my-store --role=eso-store-role --serviceaccount=default:my-store
+```
 
-The `auth` section indicates that the  `token` type will be used for authentication, it includes the path to fetch the token.
+Create a SecretStore: the `auth` section indicates that the type `serviceAccount` will be used for authentication.
 
 ```yaml
 apiVersion: external-secrets.io/v1beta1
@@ -109,37 +164,44 @@ metadata:
 spec:
   provider:
     kubernetes:
-      # If not remoteNamesapce is provided, default     namespace is used
-      remoteNamespace: remote-namespace
       server:
-        url: https://remote.kubernetes.api-server.address
-        # Add your encoded base64 to caBundle
-        caBundle: Cg==
+        # ...
       auth:
-        # Adds referenced bearerToken
-        token:
-          bearerToken:
-            name: cluster-secrets
-            key: bearerToken
+        serviceAccount:
+          name: "my-store"
+          namespace: "" # only ClusterSecretStore
+      remoteNamespace: default
+```
+
+#### Authenticating with Client Certificates
+
+Create a Kubernetes secret which contains the client key and certificate. See [Generate Certificates Documentations](https://kubernetes.io/docs/tasks/administer-cluster/certificates/) on how to create them.
+
 ```
-4. Finally create the ExternalSecret resource
+$ kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key
+```
+
+Reference the `tls-secret` in the SecretStore
 
 ```yaml
 apiVersion: external-secrets.io/v1beta1
-kind: ExternalSecret
+kind: SecretStore
 metadata:
   name: example
 spec:
-  refreshInterval: 1h
-  secretStoreRef:
-    kind: SecretStore
-    name: example               # name of the SecretStore (or kind specified)
-  target:
-    name: secret-to-be-created  # name of the k8s Secret to be created
-    creationPolicy: Owner
-  data:
-  - secretKey: extra
-    remoteRef:
-      key: secret-remote-example
-      property: extra
-```
+  provider:
+    kubernetes:
+      server:
+        # ...
+      auth:
+        cert:
+          clientCert:
+            name: "tls-secret"
+            key: "tls.crt"
+            namespace: "foobar" # only ClusterSecretStore
+          clientKey:
+            name: "tls-secret"
+            key: "tls.key"
+            namespace: "foobar" # only ClusterSecretStore
+      remoteNamespace: default
+```

+ 1 - 31
docs/spec.md

@@ -2739,9 +2739,7 @@ TokenAuth
 <td>
 <code>serviceAccount</code></br>
 <em>
-<a href="#external-secrets.io/v1beta1.ServiceAccountAuth">
-ServiceAccountAuth
-</a>
+github.com/external-secrets/external-secrets/apis/meta/v1.ServiceAccountSelector
 </em>
 </td>
 <td>
@@ -3911,34 +3909,6 @@ bool
 </tr>
 </tbody>
 </table>
-<h3 id="external-secrets.io/v1beta1.ServiceAccountAuth">ServiceAccountAuth
-</h3>
-<p>
-(<em>Appears on:</em>
-<a href="#external-secrets.io/v1beta1.KubernetesAuth">KubernetesAuth</a>)
-</p>
-<p>
-</p>
-<table>
-<thead>
-<tr>
-<th>Field</th>
-<th>Description</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td>
-<code>serviceAccount</code></br>
-<em>
-github.com/external-secrets/external-secrets/apis/meta/v1.ServiceAccountSelector
-</em>
-</td>
-<td>
-</td>
-</tr>
-</tbody>
-</table>
 <h3 id="external-secrets.io/v1beta1.TemplateEngineVersion">TemplateEngineVersion
 (<code>string</code> alias)</p></h3>
 <p>

+ 11 - 7
e2e/suites/provider/cases/azure/provider.go

@@ -15,6 +15,7 @@ package azure
 import (
 	"context"
 	"os"
+	"sync"
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
@@ -47,23 +48,26 @@ type azureProvider struct {
 func newazureProvider(f *framework.Framework, clientID, clientSecret, tenantID, vaultURL string) *azureProvider {
 	clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
 	clientCredentialsConfig.Resource = "https://vault.azure.net"
-	authorizer, err := clientCredentialsConfig.Authorizer()
-	if err != nil {
-		Fail(err.Error())
-	}
 	basicClient := keyvault.New()
-	basicClient.Authorizer = authorizer
-
 	prov := &azureProvider{
 		framework:    f,
+		client:       &basicClient,
 		clientID:     clientID,
 		clientSecret: clientSecret,
 		tenantID:     tenantID,
 		vaultURL:     vaultURL,
-		client:       &basicClient,
 	}
 
+	o := &sync.Once{}
 	BeforeEach(func() {
+		// run authorizor only if this spec is called
+		o.Do(func() {
+			authorizer, err := clientCredentialsConfig.Authorizer()
+			if err != nil {
+				Fail(err.Error())
+			}
+			prov.client.Authorizer = authorizer
+		})
 		prov.CreateSecretStore()
 	})
 

+ 1 - 0
e2e/suites/provider/cases/import.go

@@ -20,6 +20,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/aws/secretsmanager"
 	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/azure"
 	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/gcp"
+	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/kubernetes"
 	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/template"
 	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/vault"
 )

+ 135 - 0
e2e/suites/provider/cases/kubernetes/kubernetes.go

@@ -0,0 +1,135 @@
+/*
+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 kubernetes
+
+import (
+	"fmt"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+	v1 "k8s.io/api/core/v1"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
+)
+
+const referentAuth = "with referent auth"
+
+var _ = Describe("[kubernetes] ", Label("kubernetes"), func() {
+	f := framework.New("eso-kubernetes")
+	prov := NewProvider(f)
+
+	DescribeTable("sync secrets",
+		framework.TableFunc(f,
+			prov),
+		Entry(common.JSONDataWithProperty(f)),
+		Entry(common.JSONDataWithoutTargetName(f)),
+		Entry(common.JSONDataWithTemplate(f)),
+		Entry(common.DataPropertyDockerconfigJSON(f)),
+		Entry(common.SSHKeySyncDataProperty(f)),
+		Entry(common.JSONDataFromSync(f)),
+		Entry(FindByTag(f)),
+		Entry(FindByName(f)),
+
+		framework.Compose(referentAuth, f, common.JSONDataWithProperty, withReferentStore),
+		framework.Compose(referentAuth, f, common.JSONDataWithoutTargetName, withReferentStore),
+	)
+})
+
+func withReferentStore(tc *framework.TestCase) {
+	tc.ExternalSecret.Spec.SecretStoreRef.Name = referentStoreName(tc.Framework)
+	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
+}
+
+const (
+	secretValue1 = "{\"foo1\":\"foo1-val\"}"
+)
+
+// This case creates multiple secrets with simple key/value pairs and syncs them using multiple .Spec.Data blocks.
+func FindByName(f *framework.Framework) (string, func(*framework.TestCase)) {
+	return "[common] should find secrets by name using .DataFrom[]", func(tc *framework.TestCase) {
+		const namePrefix = "e2e-find-name-%s-%s"
+		secretKeyOne := fmt.Sprintf(namePrefix, f.Namespace.Name, "one")
+		secretKeyTwo := fmt.Sprintf(namePrefix, f.Namespace.Name, "two")
+		secretKeyThree := fmt.Sprintf(namePrefix, f.Namespace.Name, "three")
+		secretValue := "{\"foo1\":\"foo1-val\"}"
+		tc.Secrets = map[string]framework.SecretEntry{
+			secretKeyOne:   {Value: secretValue},
+			secretKeyTwo:   {Value: secretValue},
+			secretKeyThree: {Value: secretValue},
+		}
+		tc.ExpectedSecret = &v1.Secret{
+			Type: v1.SecretTypeOpaque,
+			Data: map[string][]byte{
+				secretKeyOne:   []byte(secretValue),
+				secretKeyTwo:   []byte(secretValue),
+				secretKeyThree: []byte(secretValue),
+			},
+		}
+		tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
+			{
+				Find: &esapi.ExternalSecretFind{
+					Name: &esapi.FindName{
+						RegExp: fmt.Sprintf("e2e-find-name-%s.+", f.Namespace.Name),
+					},
+				},
+			},
+		}
+	}
+}
+
+// This case creates multiple secrets with simple key/value pairs and syncs them using multiple .Spec.Data blocks.
+func FindByTag(f *framework.Framework) (string, func(*framework.TestCase)) {
+	return "[common] should find secrets by tags using .DataFrom[]", func(tc *framework.TestCase) {
+		const namePrefix = "e2e-find-name-%s-%s"
+		secretKeyOne := fmt.Sprintf(namePrefix, f.Namespace.Name, "one")
+		secretKeyTwo := fmt.Sprintf(namePrefix, f.Namespace.Name, "two")
+		secretKeyThree := fmt.Sprintf(namePrefix, f.Namespace.Name, "three")
+		tc.Secrets = map[string]framework.SecretEntry{
+			secretKeyOne: {
+				Value: secretValue1,
+				Tags: map[string]string{
+					"test": f.Namespace.Name,
+				}},
+			secretKeyTwo: {
+				Value: secretValue1,
+				Tags: map[string]string{
+					"test": f.Namespace.Name,
+				}},
+			secretKeyThree: {
+				Value: secretValue1,
+				Tags: map[string]string{
+					"noop": f.Namespace.Name,
+				}},
+		}
+		tc.ExpectedSecret = &v1.Secret{
+			Type: v1.SecretTypeOpaque,
+			Data: map[string][]byte{
+				fmt.Sprintf("e2e-find-name-%s-one", f.Namespace.Name): []byte(secretValue1),
+				fmt.Sprintf("e2e-find-name-%s-two", f.Namespace.Name): []byte(secretValue1),
+			},
+		}
+		tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
+			{
+				Find: &esapi.ExternalSecretFind{
+					Tags: map[string]string{
+						"test": f.Namespace.Name,
+					},
+				},
+			},
+		}
+	}
+}

+ 190 - 0
e2e/suites/provider/cases/kubernetes/provider.go

@@ -0,0 +1,190 @@
+/*
+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 kubernetes
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	. "github.com/onsi/gomega"
+	v1 "k8s.io/api/core/v1"
+	rbac "k8s.io/api/rbac/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+)
+
+type Provider struct {
+	framework *framework.Framework
+}
+
+func NewProvider(f *framework.Framework) *Provider {
+	prov := &Provider{
+		framework: f,
+	}
+	BeforeEach(prov.BeforeEach)
+	return prov
+}
+
+func (s *Provider) CreateSecret(key string, val framework.SecretEntry) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      key,
+			Namespace: s.framework.Namespace.Name,
+			Labels:    val.Tags,
+		},
+		Data: make(map[string][]byte),
+	}
+	stringMap := make(map[string]string)
+	err := json.Unmarshal([]byte(val.Value), &stringMap)
+	Expect(err).ToNot(HaveOccurred())
+
+	for k, v := range stringMap {
+		secret.Data[k] = []byte(v)
+	}
+	err = s.framework.CRClient.Create(context.Background(), secret)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *Provider) BeforeEach() {
+	s.CreateStore()
+	s.CreateReferentStore()
+}
+
+func (s *Provider) DeleteSecret(key string) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      key,
+			Namespace: s.framework.Namespace.Name,
+		},
+	}
+	err := s.framework.CRClient.Delete(context.Background(), secret, &client.DeleteOptions{})
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func makeDefaultStore(suffix, namespace string) (*rbac.Role, *rbac.RoleBinding, *esv1beta1.SecretStore) {
+	roleName := fmt.Sprintf("%s-%s", "allow-eso-secret-read", suffix)
+	role := &rbac.Role{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      roleName,
+			Namespace: namespace,
+		},
+		Rules: []rbac.PolicyRule{
+			{
+				APIGroups: []string{""},
+				Resources: []string{"secrets"},
+				Verbs:     []string{"get", "list", "watch", "create", "update", "delete", "patch"},
+			},
+			{
+				Verbs:     []string{"create"},
+				APIGroups: []string{"authorization.k8s.io"},
+				Resources: []string{"selfsubjectrulesreviews"},
+			},
+		},
+	}
+
+	rb := &rbac.RoleBinding{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      fmt.Sprintf("%s-%s", "eso-rb", suffix),
+			Namespace: namespace,
+		},
+		Subjects: []rbac.Subject{
+			{
+				Kind:      "ServiceAccount",
+				Name:      "default",
+				Namespace: namespace,
+			},
+		},
+		RoleRef: rbac.RoleRef{
+			Kind:     "Role",
+			Name:     roleName,
+			APIGroup: "rbac.authorization.k8s.io",
+		},
+	}
+
+	store := &esv1beta1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      namespace,
+			Namespace: namespace,
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				Kubernetes: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CAProvider: &esv1beta1.CAProvider{
+							Type: esv1beta1.CAProviderTypeConfigMap,
+							Name: "kube-root-ca.crt",
+							Key:  "ca.crt",
+						},
+					},
+					Auth: esv1beta1.KubernetesAuth{
+						ServiceAccount: &esmeta.ServiceAccountSelector{
+							Name: "default",
+						},
+					},
+					RemoteNamespace: namespace,
+				},
+			},
+		},
+	}
+
+	return role, rb, store
+}
+
+func (s *Provider) CreateStore() {
+	rb, role, store := makeDefaultStore("", s.framework.Namespace.Name)
+
+	err := s.framework.CRClient.Create(context.Background(), role)
+	Expect(err).ToNot(HaveOccurred())
+
+	err = s.framework.CRClient.Create(context.Background(), rb)
+	Expect(err).ToNot(HaveOccurred())
+
+	err = s.framework.CRClient.Create(context.Background(), store)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *Provider) CreateReferentStore() {
+	rb, role, store := makeDefaultStore("referent", s.framework.Namespace.Name)
+	// ServiceAccount Namespace is not set, this will be inferred
+	// from the ExternalSecret
+	css := &esv1beta1.ClusterSecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: referentStoreName(s.framework),
+		},
+		Spec: store.Spec,
+	}
+	css.Spec.Provider.Kubernetes.Server.CAProvider.Namespace = &s.framework.Namespace.Name
+
+	err := s.framework.CRClient.Create(context.Background(), role)
+	Expect(err).ToNot(HaveOccurred())
+
+	err = s.framework.CRClient.Create(context.Background(), rb)
+	Expect(err).ToNot(HaveOccurred())
+
+	err = s.framework.CRClient.Create(context.Background(), css)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func referentStoreName(f *framework.Framework) string {
+	return fmt.Sprintf("%s-referent", f.Namespace.Name)
+}

+ 195 - 0
pkg/provider/kubernetes/auth.go

@@ -0,0 +1,195 @@
+/*
+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 kubernetes
+
+import (
+	"context"
+	"fmt"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+const (
+	errInvalidClusterStoreMissingNamespace = "missing namespace"
+	errFetchCredentials                    = "could not fetch credentials: %w"
+	errMissingCredentials                  = "missing credentials: \"%s\""
+	errEmptyKey                            = "key %s found but empty"
+	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"
+)
+
+func (k *BaseClient) setAuth(ctx context.Context) error {
+	err := k.setCA(ctx)
+	if err != nil {
+		return err
+	}
+	if k.store.Auth.Token != nil {
+		k.BearerToken, err = k.fetchSecretKey(ctx, k.store.Auth.Token.BearerToken)
+		if err != nil {
+			return fmt.Errorf("could not fetch Auth.Token.BearerToken: %w", err)
+		}
+		return nil
+	}
+	if k.store.Auth.ServiceAccount != nil {
+		k.BearerToken, err = k.secretKeyRefForServiceAccount(ctx, k.store.Auth.ServiceAccount)
+		if err != nil {
+			return fmt.Errorf("could not fetch Auth.ServiceAccount: %w", err)
+		}
+		return nil
+	}
+	if k.store.Auth.Cert != nil {
+		return k.setClientCert(ctx)
+	}
+	return fmt.Errorf("no credentials provided")
+}
+
+func (k *BaseClient) setCA(ctx context.Context) error {
+	if k.store.Server.CABundle != nil {
+		k.CA = k.store.Server.CABundle
+		return nil
+	}
+	if k.store.Server.CAProvider != nil {
+		var ca []byte
+		var err error
+		switch k.store.Server.CAProvider.Type {
+		case esv1beta1.CAProviderTypeConfigMap:
+			keySelector := esmeta.SecretKeySelector{
+				Name:      k.store.Server.CAProvider.Name,
+				Namespace: k.store.Server.CAProvider.Namespace,
+				Key:       k.store.Server.CAProvider.Key,
+			}
+			ca, err = k.fetchConfigMapKey(ctx, keySelector)
+			if err != nil {
+				return fmt.Errorf("unable to fetch Server.CAProvider ConfigMap: %w", err)
+			}
+		case esv1beta1.CAProviderTypeSecret:
+			keySelector := esmeta.SecretKeySelector{
+				Name:      k.store.Server.CAProvider.Name,
+				Namespace: k.store.Server.CAProvider.Namespace,
+				Key:       k.store.Server.CAProvider.Key,
+			}
+			ca, err = k.fetchSecretKey(ctx, keySelector)
+			if err != nil {
+				return fmt.Errorf("unable to fetch Server.CAProvider Secret: %w", err)
+			}
+		}
+		k.CA = ca
+		return nil
+	}
+	return fmt.Errorf("no Certificate Authority provided")
+}
+
+func (k *BaseClient) setClientCert(ctx context.Context) error {
+	var err error
+	k.Certificate, err = k.fetchSecretKey(ctx, k.store.Auth.Cert.ClientCert)
+	if err != nil {
+		return fmt.Errorf("unable to fetch client certificate: %w", err)
+	}
+	k.Key, err = k.fetchSecretKey(ctx, k.store.Auth.Cert.ClientKey)
+	if err != nil {
+		return fmt.Errorf("unable to fetch client key: %w", err)
+	}
+	return nil
+}
+
+func (k *BaseClient) secretKeyRefForServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) ([]byte, error) {
+	serviceAccount := &corev1.ServiceAccount{}
+	ref := types.NamespacedName{
+		Namespace: k.namespace,
+		Name:      serviceAccountRef.Name,
+	}
+	if (k.storeKind == esv1beta1.ClusterSecretStoreKind) &&
+		(serviceAccountRef.Namespace != nil) {
+		ref.Namespace = *serviceAccountRef.Namespace
+	}
+	err := k.kube.Get(ctx, ref, serviceAccount)
+	if err != nil {
+		return nil, fmt.Errorf(errGetKubeSA, ref.Name, err)
+	}
+	if len(serviceAccount.Secrets) == 0 {
+		return nil, fmt.Errorf(errGetKubeSASecrets, ref.Name)
+	}
+	for _, tokenRef := range serviceAccount.Secrets {
+		retval, err := k.fetchSecretKey(ctx, esmeta.SecretKeySelector{
+			Name:      tokenRef.Name,
+			Namespace: &ref.Namespace,
+			Key:       "token",
+		})
+		if err != nil {
+			continue
+		}
+		return retval, nil
+	}
+	return nil, fmt.Errorf(errGetKubeSANoToken, ref.Name)
+}
+
+func (k *BaseClient) fetchSecretKey(ctx context.Context, key esmeta.SecretKeySelector) ([]byte, error) {
+	keySecret := &corev1.Secret{}
+	objectKey := types.NamespacedName{
+		Name:      key.Name,
+		Namespace: k.namespace,
+	}
+	// only ClusterStore is allowed to set namespace (and then it's required)
+	if k.storeKind == esv1beta1.ClusterSecretStoreKind {
+		if key.Namespace == nil {
+			return nil, fmt.Errorf(errInvalidClusterStoreMissingNamespace)
+		}
+		objectKey.Namespace = *key.Namespace
+	}
+	err := k.kube.Get(ctx, objectKey, keySecret)
+	if err != nil {
+		return nil, fmt.Errorf(errFetchCredentials, err)
+	}
+	val, ok := keySecret.Data[key.Key]
+	if !ok {
+		return nil, fmt.Errorf(errMissingCredentials, key.Key)
+	}
+	if len(val) == 0 {
+		return nil, fmt.Errorf(errEmptyKey, key.Key)
+	}
+	return val, nil
+}
+
+func (k *BaseClient) fetchConfigMapKey(ctx context.Context, key esmeta.SecretKeySelector) ([]byte, error) {
+	configMap := &corev1.ConfigMap{}
+	objectKey := types.NamespacedName{
+		Name:      key.Name,
+		Namespace: k.namespace,
+	}
+	// only ClusterStore is allowed to set namespace (and then it's required)
+	if k.storeKind == esv1beta1.ClusterSecretStoreKind {
+		if key.Namespace == nil {
+			return nil, fmt.Errorf(errInvalidClusterStoreMissingNamespace)
+		}
+		objectKey.Namespace = *key.Namespace
+	}
+	err := k.kube.Get(ctx, objectKey, configMap)
+	if err != nil {
+		return nil, fmt.Errorf(errFetchCredentials, err)
+	}
+	val, ok := configMap.Data[key.Key]
+	if !ok {
+		return nil, fmt.Errorf(errMissingCredentials, key.Key)
+	}
+	if val == "" {
+		return nil, fmt.Errorf(errEmptyKey, key.Key)
+	}
+	return []byte(val), nil
+}

+ 265 - 0
pkg/provider/kubernetes/auth_test.go

@@ -0,0 +1,265 @@
+/*
+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 kubernetes
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+func TestSetAuth(t *testing.T) {
+	type fields struct {
+		kube      kclient.Client
+		store     *esv1beta1.KubernetesProvider
+		namespace string
+		storeKind string
+	}
+	type want struct {
+		Certificate []byte
+		Key         []byte
+		CA          []byte
+		BearerToken []byte
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		want    want
+		wantErr bool
+	}{
+		{
+			name: "should return err if no ca provided",
+			fields: fields{
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{},
+				},
+			},
+			want:    want{},
+			wantErr: true,
+		},
+		{
+			name: "should return err if no auth provided",
+			fields: fields{
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CABundle: []byte("1234"),
+					},
+				},
+			},
+			want: want{
+				CA: []byte("1234"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "should fetch ca from Secret",
+			fields: fields{
+				namespace: "default",
+				kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "foobar",
+						Namespace: "default",
+					},
+					Data: map[string][]byte{
+						"cert": []byte("1234"),
+					},
+				}).Build(),
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CAProvider: &esv1beta1.CAProvider{
+							Type: esv1beta1.CAProviderTypeSecret,
+							Name: "foobar",
+							Key:  "cert",
+						},
+					},
+				},
+			},
+			want: want{
+				CA: []byte("1234"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "should fetch ca from ConfigMap",
+			fields: fields{
+				namespace: "default",
+				kube: fclient.NewClientBuilder().WithObjects(&corev1.ConfigMap{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "foobar",
+						Namespace: "default",
+					},
+					Data: map[string]string{
+						"cert": "1234",
+					},
+				}).Build(),
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CAProvider: &esv1beta1.CAProvider{
+							Type: esv1beta1.CAProviderTypeConfigMap,
+							Name: "foobar",
+							Key:  "cert",
+						},
+					},
+				},
+			},
+			want: want{
+				CA: []byte("1234"),
+			},
+			wantErr: true,
+		},
+		{
+			name: "should set token from secret",
+			fields: fields{
+				namespace: "default",
+				kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "foobar",
+						Namespace: "default",
+					},
+					Data: map[string][]byte{
+						"token": []byte("mytoken"),
+					},
+				}).Build(),
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CABundle: []byte("1234"),
+					},
+					Auth: esv1beta1.KubernetesAuth{
+						Token: &esv1beta1.TokenAuth{
+							BearerToken: v1.SecretKeySelector{
+								Name:      "foobar",
+								Namespace: pointer.String("shouldnotberelevant"),
+								Key:       "token",
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				CA:          []byte("1234"),
+				BearerToken: []byte("mytoken"),
+			},
+			wantErr: false,
+		},
+		{
+			name: "should set client cert from secret",
+			fields: fields{
+				namespace: "default",
+				kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "mycert",
+						Namespace: "default",
+					},
+					Data: map[string][]byte{
+						"cert": []byte("my-cert"),
+						"key":  []byte("my-key"),
+					},
+				}).Build(),
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CABundle: []byte("1234"),
+					},
+					Auth: esv1beta1.KubernetesAuth{
+						Cert: &esv1beta1.CertAuth{
+							ClientCert: v1.SecretKeySelector{
+								Name: "mycert",
+								Key:  "cert",
+							},
+							ClientKey: v1.SecretKeySelector{
+								Name: "mycert",
+								Key:  "key",
+							},
+						},
+					},
+				},
+			},
+			want: want{
+				CA:          []byte("1234"),
+				Certificate: []byte("my-cert"),
+				Key:         []byte("my-key"),
+			},
+			wantErr: false,
+		},
+		{
+			name: "should set token from service account",
+			fields: fields{
+				namespace: "default",
+				kube: fclient.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "my-sa",
+						Namespace: "default",
+					},
+					Secrets: []corev1.ObjectReference{
+						{Name: "sa-token", Namespace: "default"},
+					},
+				}, &corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "sa-token",
+						Namespace: "default",
+					},
+					Data: map[string][]byte{
+						"token": []byte("my-sa-token"),
+					},
+				}).Build(),
+				store: &esv1beta1.KubernetesProvider{
+					Server: esv1beta1.KubernetesServer{
+						CABundle: []byte("1234"),
+					},
+					Auth: esv1beta1.KubernetesAuth{
+						ServiceAccount: &v1.ServiceAccountSelector{
+							Name:      "my-sa",
+							Namespace: pointer.String("shouldnotberelevant"),
+						},
+					},
+				},
+			},
+			want: want{
+				CA:          []byte("1234"),
+				BearerToken: []byte("my-sa-token"),
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			k := &BaseClient{
+				kube:      tt.fields.kube,
+				store:     tt.fields.store,
+				namespace: tt.fields.namespace,
+				storeKind: tt.fields.storeKind,
+			}
+			if err := k.setAuth(context.Background()); (err != nil) != tt.wantErr {
+				t.Errorf("BaseClient.setAuth() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			w := want{
+				Certificate: k.Certificate,
+				Key:         k.Key,
+				CA:          k.CA,
+				BearerToken: k.BearerToken,
+			}
+			if !cmp.Equal(w, tt.want) {
+				t.Errorf("unexpected value: expected %#v, got %#v", tt.want, w)
+			}
+		})
+	}
+}

+ 112 - 175
pkg/provider/kubernetes/kubernetes.go

@@ -16,41 +16,33 @@ package kubernetes
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 
 	authv1 "k8s.io/api/authorization/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
+	labels "k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/find"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 
-const (
-	errPropertyNotFound                    = "property field not found on extrenal secrets"
-	errKubernetesCredSecretName            = "kubernetes credentials are empty"
-	errInvalidClusterStoreMissingNamespace = "invalid clusterStore missing Cert namespace"
-	errFetchCredentialsSecret              = "could not fetch Credentials secret: %w"
-	errMissingCredentials                  = "missing Credentials: %v"
-	errUninitalizedKubernetesProvider      = "provider kubernetes is not initialized"
-	errEmptyKey                            = "key %s found but empty"
-)
-
 // https://github.com/external-secrets/external-secrets/issues/644
 var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
 var _ esv1beta1.Provider = &ProviderKubernetes{}
 
 type KClient interface {
 	Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error)
+	List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error)
 }
 
 type RClient interface {
-	Create(ctx context.Context, SelfSubjectAccessReview *authv1.SelfSubjectAccessReview, opts metav1.CreateOptions) (*authv1.SelfSubjectAccessReview, error)
+	Create(ctx context.Context, selfSubjectRulesReview *authv1.SelfSubjectRulesReview, opts metav1.CreateOptions) (*authv1.SelfSubjectRulesReview, error)
 }
 
 // ProviderKubernetes is a provider for Kubernetes.
@@ -58,6 +50,8 @@ type ProviderKubernetes struct {
 	Client       KClient
 	ReviewClient RClient
 	Namespace    string
+	store        *esv1beta1.KubernetesProvider
+	storeKind    string
 }
 
 var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
@@ -65,8 +59,8 @@ var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
 type BaseClient struct {
 	kube        kclient.Client
 	store       *esv1beta1.KubernetesProvider
-	namespace   string
 	storeKind   string
+	namespace   string
 	Certificate []byte
 	Key         []byte
 	CA          []byte
@@ -80,32 +74,41 @@ func init() {
 }
 
 // NewClient constructs a Kubernetes Provider.
-func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
+func (p *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Kubernetes == nil {
 		return nil, fmt.Errorf("no store type or wrong store type")
 	}
 	storeSpecKubernetes := storeSpec.Provider.Kubernetes
 
-	bStore := BaseClient{
+	client := BaseClient{
 		kube:      kube,
 		store:     storeSpecKubernetes,
 		namespace: namespace,
 		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
 	}
+	p.Namespace = client.store.RemoteNamespace
+	p.store = storeSpecKubernetes
+	p.storeKind = store.GetObjectKind().GroupVersionKind().Kind
 
-	if err := bStore.setAuth(ctx); err != nil {
+	// allow SecretStore controller validation to pass
+	// when using referent namespace.
+	if client.storeKind == esv1beta1.ClusterSecretStoreKind && client.namespace == "" && isReferentSpec(storeSpecKubernetes) {
+		return p, nil
+	}
+
+	if err := client.setAuth(ctx); err != nil {
 		return nil, err
 	}
 
 	config := &rest.Config{
-		Host:        bStore.store.Server.URL,
-		BearerToken: string(bStore.BearerToken),
+		Host:        client.store.Server.URL,
+		BearerToken: string(client.BearerToken),
 		TLSClientConfig: rest.TLSClientConfig{
 			Insecure: false,
-			CertData: bStore.Certificate,
-			KeyData:  bStore.Key,
-			CAData:   bStore.CA,
+			CertData: client.Certificate,
+			KeyData:  client.Key,
+			CAData:   client.CA,
 		},
 	}
 
@@ -113,192 +116,126 @@ func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.Gene
 	if err != nil {
 		return nil, fmt.Errorf("error configuring clientset: %w", err)
 	}
+	p.Client = kubeClientSet.CoreV1().Secrets(client.store.RemoteNamespace)
+	p.ReviewClient = kubeClientSet.AuthorizationV1().SelfSubjectRulesReviews()
+	return p, nil
+}
 
-	k.Client = kubeClientSet.CoreV1().Secrets(bStore.store.RemoteNamespace)
-	k.Namespace = bStore.store.RemoteNamespace
-	k.ReviewClient = kubeClientSet.AuthorizationV1().SelfSubjectAccessReviews()
-
-	return k, nil
+func isReferentSpec(prov *esv1beta1.KubernetesProvider) bool {
+	if prov.Auth.Cert != nil {
+		if prov.Auth.Cert.ClientCert.Namespace == nil {
+			return true
+		}
+		if prov.Auth.Cert.ClientKey.Namespace == nil {
+			return true
+		}
+	}
+	if prov.Auth.ServiceAccount != nil {
+		if prov.Auth.ServiceAccount.Namespace == nil {
+			return true
+		}
+	}
+	if prov.Auth.Token != nil {
+		if prov.Auth.Token.BearerToken.Namespace == nil {
+			return true
+		}
+	}
+	return false
 }
 
-func (k *ProviderKubernetes) Close(ctx context.Context) error {
+func (p *ProviderKubernetes) Close(ctx context.Context) error {
 	return nil
 }
 
-func (k *ProviderKubernetes) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	if ref.Property == "" {
-		return nil, fmt.Errorf(errPropertyNotFound)
-	}
-
-	payload, err := k.GetSecretMap(ctx, ref)
-
+func (p *ProviderKubernetes) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	secretMap, err := p.GetSecretMap(ctx, ref)
 	if err != nil {
 		return nil, err
 	}
-
-	val, ok := payload[ref.Property]
-	if !ok {
-		return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
+	if ref.Property != "" {
+		val, ok := secretMap[ref.Property]
+		if !ok {
+			return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
+		}
+		return val, nil
 	}
-	return val, nil
-}
-
-func (k *ProviderKubernetes) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	if utils.IsNil(k.Client) {
-		return nil, fmt.Errorf(errUninitalizedKubernetesProvider)
+	strMap := make(map[string]string)
+	for k, v := range secretMap {
+		strMap[k] = string(v)
 	}
-	opts := metav1.GetOptions{}
-	secretOut, err := k.Client.Get(ctx, ref.Key, opts)
-
+	jsonStr, err := json.Marshal(strMap)
 	if err != nil {
-		return nil, err
-	}
-
-	var payload map[string][]byte
-	if len(secretOut.Data) != 0 {
-		payload = secretOut.Data
+		return nil, fmt.Errorf("unabled to marshal json: %w", err)
 	}
-
-	return payload, nil
+	return jsonStr, nil
 }
 
-func (k *ProviderKubernetes) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
-	return nil, fmt.Errorf("not implemented")
-}
-
-func (k *BaseClient) setAuth(ctx context.Context) error {
-	var err error
-	if len(k.store.Server.CABundle) > 0 {
-		k.CA = k.store.Server.CABundle
-	} else if k.store.Server.CAProvider != nil {
-		keySelector := esmeta.SecretKeySelector{
-			Name:      k.store.Server.CAProvider.Name,
-			Namespace: k.store.Server.CAProvider.Namespace,
-			Key:       k.store.Server.CAProvider.Key,
-		}
-		k.CA, err = k.fetchSecretKey(ctx, keySelector, "CA")
-		if err != nil {
-			return err
-		}
-	} else {
-		return fmt.Errorf("no Certificate Authority provided")
-	}
-
-	if k.store.Auth.Token != nil {
-		k.BearerToken, err = k.fetchSecretKey(ctx, k.store.Auth.Token.BearerToken, "bearerToken")
-		if err != nil {
-			return err
-		}
-	} else if k.store.Auth.ServiceAccount != nil {
-		return fmt.Errorf("not implemented yet")
-	} else if k.store.Auth.Cert != nil {
-		k.Certificate, err = k.fetchSecretKey(ctx, k.store.Auth.Cert.ClientCert, "cert")
-		if err != nil {
-			return err
-		}
-		k.Key, err = k.fetchSecretKey(ctx, k.store.Auth.Cert.ClientKey, "key")
-		if err != nil {
-			return err
-		}
-	} else {
-		return fmt.Errorf("no credentials provided")
+func (p *ProviderKubernetes) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	secret, err := p.Client.Get(ctx, ref.Key, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
 	}
-
-	return nil
+	return secret.Data, nil
 }
 
-func (k *BaseClient) fetchSecretKey(ctx context.Context, key esmeta.SecretKeySelector, component string) ([]byte, error) {
-	keySecret := &corev1.Secret{}
-	keySecretName := key.Name
-	if keySecretName == "" {
-		return nil, fmt.Errorf(errKubernetesCredSecretName)
-	}
-	objectKey := types.NamespacedName{
-		Name:      keySecretName,
-		Namespace: k.namespace,
+func (p *ProviderKubernetes) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	if ref.Tags != nil {
+		return p.findByTags(ctx, ref)
 	}
-	// only ClusterStore is allowed to set namespace (and then it's required)
-	if k.storeKind == esv1beta1.ClusterSecretStoreKind {
-		if key.Namespace == nil {
-			return nil, fmt.Errorf(errInvalidClusterStoreMissingNamespace)
-		}
-		objectKey.Namespace = *key.Namespace
+	if ref.Name != nil {
+		return p.findByName(ctx, ref)
 	}
+	return nil, fmt.Errorf("unexpected find operator: %#v", ref)
+}
 
-	err := k.kube.Get(ctx, objectKey, keySecret)
+func (p *ProviderKubernetes) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	// empty/nil tags = everything
+	sel, err := labels.ValidatedSelectorFromSet(ref.Tags)
 	if err != nil {
-		return nil, fmt.Errorf(errFetchCredentialsSecret, err)
+		return nil, fmt.Errorf("unable to validate selector tags: %w", err)
 	}
-
-	val, ok := keySecret.Data[key.Key]
-	if !ok {
-		return nil, fmt.Errorf(errMissingCredentials, component)
+	secrets, err := p.Client.List(ctx, metav1.ListOptions{LabelSelector: sel.String()})
+	if err != nil {
+		return nil, fmt.Errorf("unable to list secrets: %w", err)
 	}
-
-	if len(val) == 0 {
-		return nil, fmt.Errorf(errEmptyKey, component)
+	data := make(map[string][]byte)
+	for _, secret := range secrets.Items {
+		jsonStr, err := json.Marshal(convertMap(secret.Data))
+		if err != nil {
+			return nil, err
+		}
+		data[secret.Name] = jsonStr
 	}
-	return val, nil
+	return utils.ConvertKeys(ref.ConversionStrategy, data)
 }
 
-func (k *ProviderKubernetes) Validate() (esv1beta1.ValidationResult, error) {
-	ctx := context.Background()
-
-	authReview, err := k.ReviewClient.Create(ctx, &authv1.SelfSubjectAccessReview{
-		Spec: authv1.SelfSubjectAccessReviewSpec{
-			ResourceAttributes: &authv1.ResourceAttributes{
-				Resource:  "secrets",
-				Namespace: k.Namespace,
-				Verb:      "get",
-			},
-		},
-	}, metav1.CreateOptions{})
-
+func (p *ProviderKubernetes) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	secrets, err := p.Client.List(ctx, metav1.ListOptions{})
 	if err != nil {
-		return esv1beta1.ValidationResultUnknown, fmt.Errorf("could not verify if client is valid: %w", err)
-	}
-
-	if !authReview.Status.Allowed {
-		return esv1beta1.ValidationResultError, fmt.Errorf("client is not allowed to get secrets")
+		return nil, fmt.Errorf("unable to list secrets: %w", err)
 	}
-
-	return esv1beta1.ValidationResultReady, nil
-}
-
-func (k *ProviderKubernetes) ValidateStore(store esv1beta1.GenericStore) error {
-	storeSpec := store.GetSpec()
-	k8sSpec := storeSpec.Provider.Kubernetes
-	if k8sSpec.Server.CABundle == nil && k8sSpec.Server.CAProvider == nil {
-		return fmt.Errorf("a CABundle or CAProvider is required")
+	matcher, err := find.New(*ref.Name)
+	if err != nil {
+		return nil, err
 	}
-
-	if k8sSpec.Auth.Cert != nil {
-		if k8sSpec.Auth.Cert.ClientCert.Name == "" {
-			return fmt.Errorf("ClientCert.Name cannot be empty")
-		}
-		if k8sSpec.Auth.Cert.ClientCert.Key == "" {
-			return fmt.Errorf("ClientCert.Key cannot be empty")
-		}
-		if err := utils.ValidateSecretSelector(store, k8sSpec.Auth.Cert.ClientCert); err != nil {
-			return err
-		}
-	} else if k8sSpec.Auth.Token != nil {
-		if k8sSpec.Auth.Token.BearerToken.Name == "" {
-			return fmt.Errorf("BearerToken.Name cannot be empty")
-		}
-		if k8sSpec.Auth.Token.BearerToken.Key == "" {
-			return fmt.Errorf("BearerToken.Key cannot be empty")
+	data := make(map[string][]byte)
+	for _, secret := range secrets.Items {
+		if !matcher.MatchName(secret.Name) {
+			continue
 		}
-		if err := utils.ValidateSecretSelector(store, k8sSpec.Auth.Token.BearerToken); err != nil {
-			return err
+		jsonStr, err := json.Marshal(convertMap(secret.Data))
+		if err != nil {
+			return nil, err
 		}
-	} else {
-		return fmt.Errorf("an Auth type must be specified")
+		data[secret.Name] = jsonStr
 	}
+	return utils.ConvertKeys(ref.ConversionStrategy, data)
+}
 
-	if k8sSpec.Auth.Cert != nil && k8sSpec.Auth.Token != nil {
-		return fmt.Errorf("only one authentication method is allowed")
+func convertMap(in map[string][]byte) map[string]string {
+	out := make(map[string]string)
+	for k, v := range in {
+		out[k] = string(v)
 	}
-
-	return nil
+	return out
 }

+ 394 - 339
pkg/provider/kubernetes/kubernetes_test.go

@@ -16,14 +16,14 @@ package kubernetes
 import (
 	"context"
 	"errors"
-	"fmt"
 	"reflect"
-	"strings"
 	"testing"
 
-	authv1 "k8s.io/api/authorization/v1"
+	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -31,14 +31,32 @@ import (
 )
 
 const (
-	errTestFetchCredentialsSecret = "test could not fetch Credentials secret failed"
-	errTestAuthValue              = "test failed key didn't match expected value"
-	errSomethingWentWrong         = "Something went wrong"
-	errExpectedErr                = "wanted error got nil"
+	errSomethingWentWrong = "Something went wrong"
+	testCertificate       = `-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw
+EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4
+MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj
+aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW
+Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE
+1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e
+ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa
+YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC
+pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G
+CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN
+ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa
+lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0
+mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
+9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
+QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
+-----END CERTIFICATE-----`
 )
 
 type fakeClient struct {
-	secretMap map[string]corev1.Secret
+	t                   *testing.T
+	secretMap           map[string]corev1.Secret
+	expectedListOptions metav1.ListOptions
 }
 
 func (fk fakeClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error) {
@@ -50,359 +68,396 @@ func (fk fakeClient) Get(ctx context.Context, name string, opts metav1.GetOption
 	return &secret, nil
 }
 
-type fakeReviewClient struct {
-	authReview *authv1.SelfSubjectAccessReview
-}
-
-func (fk fakeReviewClient) Create(ctx context.Context, selfSubjectAccessReview *authv1.SelfSubjectAccessReview, opts metav1.CreateOptions) (*authv1.SelfSubjectAccessReview, error) {
-	if fk.authReview == nil {
-		return nil, errors.New(errSomethingWentWrong)
+func (fk fakeClient) List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error) {
+	assert.Equal(fk.t, fk.expectedListOptions, opts)
+	list := &corev1.SecretList{}
+	for _, v := range fk.secretMap {
+		list.Items = append(list.Items, v)
 	}
-	return fk.authReview, nil
+	return list, nil
 }
 
-func TestKubernetesSecretManagerGetSecret(t *testing.T) {
-	expected := make(map[string][]byte)
-	value := "bar"
-	expected["foo"] = []byte(value)
-	mysecret := corev1.Secret{Data: expected}
-	mysecretmap := make(map[string]corev1.Secret)
-	mysecretmap["Key"] = mysecret
-
-	fk := fakeClient{secretMap: mysecretmap}
-	kp := ProviderKubernetes{Client: fk}
-
-	ref := esv1beta1.ExternalSecretDataRemoteRef{Key: "Key", Property: "foo"}
-	ctx := context.Background()
-
-	output, _ := kp.GetSecret(ctx, ref)
-
-	if string(output) != value {
-		t.Error("missing match value of the secret")
-	}
-
-	ref = esv1beta1.ExternalSecretDataRemoteRef{Key: "Key2", Property: "foo"}
-	_, err := kp.GetSecret(ctx, ref)
-
-	if err.Error() != errSomethingWentWrong {
-		t.Error("test failed")
-	}
-
-	ref = esv1beta1.ExternalSecretDataRemoteRef{Key: "Key", Property: "foo2"}
-	_, err = kp.GetSecret(ctx, ref)
-	expectedError := fmt.Sprintf("property %s does not exist in key %s", ref.Property, ref.Key)
-	if err.Error() != expectedError {
-		t.Error("test not existing property failed")
-	}
-
-	kp = ProviderKubernetes{Client: nil}
-	_, err = kp.GetSecret(ctx, ref)
-
-	if err.Error() != errUninitalizedKubernetesProvider {
-		t.Error("test nil Client failed")
-	}
-
-	ref = esv1beta1.ExternalSecretDataRemoteRef{Key: "Key", Property: ""}
-	_, err = kp.GetSecret(ctx, ref)
-
-	if err.Error() != "property field not found on extrenal secrets" {
-		t.Error("test nil Property failed")
-	}
-}
-
-func TestKubernetesSecretManagerGetSecretMap(t *testing.T) {
-	expected := make(map[string][]byte)
-	value := "bar"
-	expected["foo"] = []byte(value)
-	expected["foo2"] = []byte(value)
-	mysecret := corev1.Secret{Data: expected}
-	mysecretmap := make(map[string]corev1.Secret)
-	mysecretmap["Key"] = mysecret
-
-	fk := fakeClient{secretMap: mysecretmap}
-	kp := ProviderKubernetes{Client: fk}
-
-	ref := esv1beta1.ExternalSecretDataRemoteRef{Key: "Key", Property: ""}
-	ctx := context.Background()
-
-	output, err := kp.GetSecretMap(ctx, ref)
-
-	if err != nil {
-		t.Error("test failed")
-	}
-	if !reflect.DeepEqual(output, expected) {
-		t.Error("Objects are not equal")
-	}
-}
-
-func TestKubernetesSecretManagerSetAuth(t *testing.T) {
-	secretName := "good-name"
-	CABundle := "CABundle"
-	kp := esv1beta1.KubernetesProvider{Server: esv1beta1.KubernetesServer{}}
-
-	fs := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{Name: secretName},
-		Data:       make(map[string][]byte),
-	}
-	fs.Data["cert"] = []byte("secret-cert")
-	fs.Data["ca"] = []byte("secret-ca")
-	fs.Data["bearerToken"] = []byte("bearerToken")
-
-	fs2 := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{Name: "secret-for-the-key"},
-		Data:       make(map[string][]byte),
-	}
-	fs2.Data["key"] = []byte("secret-key")
-
-	fk := fclient.NewClientBuilder().WithObjects(fs, fs2).Build()
-	bc := BaseClient{fk, &kp, "", "", nil, nil, nil, nil}
-
-	ctx := context.Background()
-
-	err := bc.setAuth(ctx)
-
-	if err.Error() != "no Certificate Authority provided" {
-		fmt.Println(err.Error())
-		t.Error("test no Certificate Authority provided failed")
-	}
-
-	kp.Server.CAProvider = &esv1beta1.CAProvider{
-		Type:      esv1beta1.CAProviderTypeConfigMap,
-		Name:      fs.ObjectMeta.Name,
-		Namespace: &fs.ObjectMeta.Namespace,
-		Key:       "ca",
-	}
-
-	bc.setAuth(ctx)
-
-	if string(bc.CA) != "secret-ca" {
-		t.Error("failed to set CA provider")
-	}
-
-	kp.Server.CABundle = []byte(CABundle)
-
-	err = bc.setAuth(ctx)
-
-	if err.Error() != "no credentials provided" {
-		fmt.Println(err.Error())
-		t.Error("test kubernetes credentials not empty failed")
-	}
-
-	if string(bc.CA) != CABundle {
-		t.Error("failed to set CA provider")
-	}
-
-	kp = esv1beta1.KubernetesProvider{
-		Auth: esv1beta1.KubernetesAuth{
-			Cert: &esv1beta1.CertAuth{
-				ClientCert: v1.SecretKeySelector{
-					Name: "fake-name",
+func TestGetSecret(t *testing.T) {
+	type fields struct {
+		Client       KClient
+		ReviewClient RClient
+		Namespace    string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		ref    esv1beta1.ExternalSecretDataRemoteRef
+
+		want    []byte
+		wantErr bool
+	}{
+		{
+			name: "err GetSecretMap",
+			fields: fields{
+				Client: fakeClient{
+					t:         t,
+					secretMap: map[string]corev1.Secret{},
 				},
+				Namespace: "default",
+			},
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "mysec",
+				Property: "token",
 			},
+			wantErr: true,
 		},
-	}
-	kp.Server.CABundle = []byte(CABundle)
-
-	err = bc.setAuth(ctx)
-
-	if err.Error() != "could not fetch Credentials secret: secrets \"fake-name\" not found" {
-		fmt.Println(err.Error())
-		t.Error(errTestFetchCredentialsSecret)
-	}
-
-	kp.Auth.Cert.ClientCert.Name = fs.ObjectMeta.Name
-
-	err = bc.setAuth(ctx)
-
-	if err.Error() != fmt.Errorf(errMissingCredentials, "cert").Error() {
-		fmt.Println(err.Error())
-		t.Error(errTestFetchCredentialsSecret)
-	}
-
-	kp.Auth.Cert.ClientCert.Key = "cert"
-	kp.Auth.Cert.ClientKey.Name = "secret-for-the-key"
-
-	err = bc.setAuth(ctx)
-
-	if err.Error() != fmt.Errorf(errMissingCredentials, "key").Error() {
-		fmt.Println(err.Error())
-		t.Error(errTestFetchCredentialsSecret)
-	}
-	kp.Auth.Cert.ClientKey.Key = "key"
-
-	bc.setAuth(ctx)
-
-	kp.Auth.Token = &esv1beta1.TokenAuth{BearerToken: v1.SecretKeySelector{Name: secretName}}
-
-	err = bc.setAuth(ctx)
-
-	if err.Error() != fmt.Errorf(errMissingCredentials, "bearerToken").Error() {
-		fmt.Println(err.Error())
-		t.Error(errTestFetchCredentialsSecret)
-	}
-
-	kp.Auth.Token = &esv1beta1.TokenAuth{BearerToken: v1.SecretKeySelector{Name: secretName, Key: "bearerToken"}}
-
-	err = bc.setAuth(ctx)
-
-	if err != nil {
-		fmt.Println(err.Error())
-		t.Error(errTestFetchCredentialsSecret)
-	}
-	if string(bc.CA) != CABundle {
-		t.Error(errTestAuthValue)
-	}
-	if string(bc.Certificate) != "secret-cert" {
-		t.Error(errTestAuthValue)
-	}
-	if string(bc.Key) != "secret-key" {
-		t.Errorf(errTestAuthValue)
-	}
-	if string(bc.BearerToken) != "bearerToken" {
-		t.Error(errTestAuthValue)
-	}
-}
-func TestValidateStore(t *testing.T) {
-	p := ProviderKubernetes{}
-	store := &esv1beta1.SecretStore{
-		Spec: esv1beta1.SecretStoreSpec{
-			Provider: &esv1beta1.SecretStoreProvider{
-				Kubernetes: &esv1beta1.KubernetesProvider{},
+		{
+			name: "wrong property",
+			fields: fields{
+				Client: fakeClient{
+					t: t,
+					secretMap: map[string]corev1.Secret{
+						"mysec": {
+							Data: map[string][]byte{
+								"token": []byte(`foobar`),
+							},
+						},
+					},
+				},
+				Namespace: "default",
 			},
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "mysec",
+				Property: "not-the-token",
+			},
+			wantErr: true,
 		},
-	}
-	secretName := "my-secret-name"
-	secretKey := "my-secert-key"
-	err := p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "a CABundle or CAProvider is required" {
-		t.Errorf("service CA test failed, got %v", err.Error())
-	}
-
-	bundle := []byte("ca-bundle")
-	store.Spec.Provider.Kubernetes.Server.CABundle = bundle
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "an Auth type must be specified" {
-		t.Errorf("empty Auth test failed")
-	}
-	store.Spec.Provider.Kubernetes.Auth = esv1beta1.KubernetesAuth{Cert: &esv1beta1.CertAuth{}}
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "ClientCert.Name cannot be empty" {
-		t.Errorf("KeySelector test failed: expected clientCert name is required, got %v", err)
-	}
-	store.Spec.Provider.Kubernetes.Auth.Cert.ClientCert.Name = secretName
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "ClientCert.Key cannot be empty" {
-		t.Errorf("KeySelector test failed: expected clientCert Key is required, got %v", err)
-	}
-	store.Spec.Provider.Kubernetes.Auth.Cert.ClientCert.Key = secretKey
-	ns := "ns-one"
-	store.Spec.Provider.Kubernetes.Auth.Cert.ClientCert.Namespace = &ns
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "namespace not allowed with namespaced SecretStore" {
-		t.Errorf("KeySelector test failed: expected namespace not allowed, got %v", err)
-	}
-	store.Spec.Provider.Kubernetes.Auth = esv1beta1.KubernetesAuth{Token: &esv1beta1.TokenAuth{}}
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "BearerToken.Name cannot be empty" {
-		t.Errorf("KeySelector test failed: expected bearer token name is required, got %v", err)
-	}
-	store.Spec.Provider.Kubernetes.Auth.Token.BearerToken.Name = secretName
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "BearerToken.Key cannot be empty" {
-		t.Errorf("KeySelector test failed: expected bearer token key is required, got %v", err)
-	}
-	store.Spec.Provider.Kubernetes.Auth.Token.BearerToken.Key = secretKey
-	store.Spec.Provider.Kubernetes.Auth.Token.BearerToken.Namespace = &ns
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "namespace not allowed with namespaced SecretStore" {
-		t.Errorf("KeySelector test failed: expected namespace not allowed, got %v", err)
-	}
-	store.Spec.Provider.Kubernetes.Auth = esv1beta1.KubernetesAuth{
-		Cert: &esv1beta1.CertAuth{
-			ClientCert: v1.SecretKeySelector{
-				Name: secretName,
-				Key:  secretKey,
+		{
+			name: "successful case",
+			fields: fields{
+				Client: fakeClient{
+					t: t,
+					secretMap: map[string]corev1.Secret{
+						"mysec": {
+							Data: map[string][]byte{
+								"token": []byte(`foobar`),
+							},
+						},
+					},
+				},
+				Namespace: "default",
 			},
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "mysec",
+				Property: "token",
+			},
+			want: []byte(`foobar`),
 		},
-		Token: &esv1beta1.TokenAuth{
-			BearerToken: v1.SecretKeySelector{
-				Name: secretName,
-				Key:  secretKey,
+		{
+			name: "successful case without property",
+			fields: fields{
+				Client: fakeClient{
+					t: t,
+					secretMap: map[string]corev1.Secret{
+						"mysec": {
+							Data: map[string][]byte{
+								"token": []byte(`foobar`),
+							},
+						},
+					},
+				},
+				Namespace: "default",
+			},
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key: "mysec",
 			},
+			want: []byte(`{"token":"foobar"}`),
 		},
 	}
-	err = p.ValidateStore(store)
-	if err == nil {
-		t.Errorf(errExpectedErr)
-	} else if err.Error() != "only one authentication method is allowed" {
-		t.Errorf("KeySelector test failed: expected only one auth method allowed, got %v", err)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &ProviderKubernetes{
+				Client:       tt.fields.Client,
+				ReviewClient: tt.fields.ReviewClient,
+				Namespace:    tt.fields.Namespace,
+			}
+			got, err := p.GetSecret(context.Background(), tt.ref)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ProviderKubernetes.GetSecret() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ProviderKubernetes.GetSecret() = %v, want %v", got, tt.want)
+			}
+		})
 	}
 }
 
-func ErrorContains(out error, want string) bool {
-	if out == nil {
-		return want == ""
+func TestNewClient(t *testing.T) {
+	type fields struct {
+		Client       KClient
+		ReviewClient RClient
+		Namespace    string
+	}
+	type args struct {
+		store     esv1beta1.GenericStore
+		kube      kclient.Client
+		namespace string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    bool
+		wantErr bool
+	}{
+		{
+			name:   "invalid store",
+			fields: fields{},
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: metav1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{},
+					},
+				},
+				kube: fclient.NewClientBuilder().Build(),
+			},
+			wantErr: true,
+		},
+		{
+			name:   "test referent auth return",
+			fields: fields{},
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: metav1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Kubernetes: &esv1beta1.KubernetesProvider{
+								Server: esv1beta1.KubernetesServer{
+									CABundle: []byte(testCertificate),
+								},
+								Auth: esv1beta1.KubernetesAuth{
+									Token: &esv1beta1.TokenAuth{
+										BearerToken: v1.SecretKeySelector{
+											Name: "foo",
+											Key:  "token",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				namespace: "",
+				kube:      fclient.NewClientBuilder().Build(),
+			},
+			want: true,
+		},
+		{
+			name:   "auth fail results in error",
+			fields: fields{},
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: metav1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Kubernetes: &esv1beta1.KubernetesProvider{
+								Server: esv1beta1.KubernetesServer{
+									CABundle: []byte(testCertificate),
+								},
+								RemoteNamespace: "remote",
+								Auth: esv1beta1.KubernetesAuth{
+									Token: &esv1beta1.TokenAuth{
+										BearerToken: v1.SecretKeySelector{
+											Name:      "foo",
+											Namespace: pointer.String("default"),
+											Key:       "token",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				namespace: "foobarothernamespace",
+				kube:      fclient.NewClientBuilder().Build(),
+			},
+			wantErr: true,
+		},
+		{
+			name:   "test auth",
+			fields: fields{},
+			args: args{
+				store: &esv1beta1.ClusterSecretStore{
+					TypeMeta: metav1.TypeMeta{
+						Kind: esv1beta1.ClusterSecretStoreKind,
+					},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Kubernetes: &esv1beta1.KubernetesProvider{
+								Server: esv1beta1.KubernetesServer{
+									CABundle: []byte(testCertificate),
+								},
+								RemoteNamespace: "remote",
+								Auth: esv1beta1.KubernetesAuth{
+									Token: &esv1beta1.TokenAuth{
+										BearerToken: v1.SecretKeySelector{
+											Name:      "foo",
+											Namespace: pointer.String("default"),
+											Key:       "token",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				namespace: "foobarothernamespace",
+				kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "foo",
+						Namespace: "default",
+					},
+					Data: map[string][]byte{
+						"token": []byte("1234"),
+					},
+				}).Build(),
+			},
+			want: true,
+		},
 	}
-	if want == "" {
-		return false
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &ProviderKubernetes{
+				Client:       tt.fields.Client,
+				ReviewClient: tt.fields.ReviewClient,
+				Namespace:    tt.fields.Namespace,
+			}
+			got, err := p.NewClient(context.Background(), tt.args.store, tt.args.kube, tt.args.namespace)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ProviderKubernetes.NewClient() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if tt.want {
+				assert.NotNil(t, got)
+			} else {
+				assert.Nil(t, got)
+			}
+		})
 	}
-	return strings.Contains(out.Error(), want)
 }
 
-func TestValidate(t *testing.T) {
-	authReview := authv1.SelfSubjectAccessReview{
-		Status: authv1.SubjectAccessReviewStatus{
-			Allowed: true,
+func TestGetAllSecrets(t *testing.T) {
+	type fields struct {
+		Client       KClient
+		ReviewClient RClient
+		Namespace    string
+	}
+	type args struct {
+		ctx context.Context
+		ref esv1beta1.ExternalSecretFind
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    map[string][]byte
+		wantErr bool
+	}{
+		{
+			name: "use regex",
+			fields: fields{
+				Client: fakeClient{
+					t: t,
+					secretMap: map[string]corev1.Secret{
+						"mysec": {
+							ObjectMeta: metav1.ObjectMeta{
+								Name: "mysec",
+							},
+							Data: map[string][]byte{
+								"token": []byte(`foo`),
+							},
+						},
+						"other": {
+							ObjectMeta: metav1.ObjectMeta{
+								Name: "other",
+							},
+							Data: map[string][]byte{
+								"token": []byte(`bar`),
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				ref: esv1beta1.ExternalSecretFind{
+					Name: &esv1beta1.FindName{
+						RegExp: "other",
+					},
+				},
+			},
+			want: map[string][]byte{
+				"other": []byte(`{"token":"bar"}`),
+			},
 		},
-	}
-	fakeClient := fakeReviewClient{authReview: &authReview}
-	k := ProviderKubernetes{ReviewClient: fakeClient}
-	validationResult, err := k.Validate()
-	if err != nil {
-		t.Errorf("Test Failed! %v", err)
-	}
-	if validationResult != esv1beta1.ValidationResultReady {
-		t.Errorf("Test Failed! Wanted could not indicate validationResult is %s, got: %s", esv1beta1.ValidationResultReady, validationResult)
-	}
-
-	authReview = authv1.SelfSubjectAccessReview{
-		Status: authv1.SubjectAccessReviewStatus{
-			Allowed: false,
+		{
+			name: "use tags/labels",
+			fields: fields{
+				Client: fakeClient{
+					t: t,
+					expectedListOptions: metav1.ListOptions{
+						LabelSelector: "app=foobar",
+					},
+					secretMap: map[string]corev1.Secret{
+						"mysec": {
+							ObjectMeta: metav1.ObjectMeta{
+								Name: "mysec",
+							},
+							Data: map[string][]byte{
+								"token": []byte(`foo`),
+							},
+						},
+						"other": {
+							ObjectMeta: metav1.ObjectMeta{
+								Name: "other",
+							},
+							Data: map[string][]byte{
+								"token": []byte(`bar`),
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				ref: esv1beta1.ExternalSecretFind{
+					Tags: map[string]string{
+						"app": "foobar",
+					},
+				},
+			},
+			want: map[string][]byte{
+				"mysec": []byte(`{"token":"foo"}`),
+				"other": []byte(`{"token":"bar"}`),
+			},
 		},
 	}
-	fakeClient = fakeReviewClient{authReview: &authReview}
-	k = ProviderKubernetes{ReviewClient: fakeClient}
-	validationResult, err = k.Validate()
-	if err.Error() != "client is not allowed to get secrets" {
-		t.Errorf("Test Failed! Wanted client is not allowed to get secrets got: %v", err)
-	}
-	if validationResult != esv1beta1.ValidationResultError {
-		t.Errorf("Test Failed! Wanted could not indicate validationResult is %s, got: %s", esv1beta1.ValidationResultError, validationResult)
-	}
-
-	fakeClient = fakeReviewClient{}
-	k = ProviderKubernetes{ReviewClient: fakeClient}
-	validationResult, err = k.Validate()
-	if err.Error() != "could not verify if client is valid: Something went wrong" {
-		t.Errorf("Test Failed! Wanted could not verify if client is valid: Something went wrong got: %v", err)
-	}
-	if validationResult != esv1beta1.ValidationResultUnknown {
-		t.Errorf("Test Failed! Wanted could not indicate validationResult is %s, got: %s", esv1beta1.ValidationResultUnknown, validationResult)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &ProviderKubernetes{
+				Client:       tt.fields.Client,
+				ReviewClient: tt.fields.ReviewClient,
+				Namespace:    tt.fields.Namespace,
+			}
+			got, err := p.GetAllSecrets(tt.args.ctx, tt.args.ref)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ProviderKubernetes.GetAllSecrets() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ProviderKubernetes.GetAllSecrets() = %v, want %v", got, tt.want)
+			}
+		})
 	}
 }

+ 100 - 0
pkg/provider/kubernetes/validate.go

@@ -0,0 +1,100 @@
+/*
+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 kubernetes
+
+import (
+	"context"
+	"fmt"
+
+	authv1 "k8s.io/api/authorization/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+func (p *ProviderKubernetes) ValidateStore(store esv1beta1.GenericStore) error {
+	storeSpec := store.GetSpec()
+	k8sSpec := storeSpec.Provider.Kubernetes
+	if k8sSpec.Server.CABundle == nil && k8sSpec.Server.CAProvider == nil {
+		return fmt.Errorf("a CABundle or CAProvider is required")
+	}
+	if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind &&
+		k8sSpec.Server.CAProvider != nil &&
+		k8sSpec.Server.CAProvider.Namespace == nil {
+		return fmt.Errorf("CAProvider.namespace must not be empty with ClusterSecretStore")
+	}
+	if k8sSpec.Auth.Cert != nil {
+		if k8sSpec.Auth.Cert.ClientCert.Name == "" {
+			return fmt.Errorf("ClientCert.Name cannot be empty")
+		}
+		if k8sSpec.Auth.Cert.ClientCert.Key == "" {
+			return fmt.Errorf("ClientCert.Key cannot be empty")
+		}
+		if err := utils.ValidateSecretSelector(store, k8sSpec.Auth.Cert.ClientCert); err != nil {
+			return err
+		}
+	}
+	if k8sSpec.Auth.Token != nil {
+		if k8sSpec.Auth.Token.BearerToken.Name == "" {
+			return fmt.Errorf("BearerToken.Name cannot be empty")
+		}
+		if k8sSpec.Auth.Token.BearerToken.Key == "" {
+			return fmt.Errorf("BearerToken.Key cannot be empty")
+		}
+		if err := utils.ValidateSecretSelector(store, k8sSpec.Auth.Token.BearerToken); err != nil {
+			return err
+		}
+	}
+	if k8sSpec.Auth.ServiceAccount != nil {
+		if err := utils.ValidateReferentServiceAccountSelector(store, *k8sSpec.Auth.ServiceAccount); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (p *ProviderKubernetes) Validate() (esv1beta1.ValidationResult, error) {
+	// when using referent namespace we can not validate the token
+	// because the namespace is not known yet when Validate() is called
+	// from the SecretStore controller.
+	if p.storeKind == esv1beta1.ClusterSecretStoreKind && isReferentSpec(p.store) {
+		return esv1beta1.ValidationResultUnknown, nil
+	}
+	ctx := context.Background()
+	t := authv1.SelfSubjectRulesReview{
+		Spec: authv1.SelfSubjectRulesReviewSpec{
+			Namespace: p.Namespace,
+		},
+	}
+	authReview, err := p.ReviewClient.Create(ctx, &t, metav1.CreateOptions{})
+	if err != nil {
+		return esv1beta1.ValidationResultUnknown, fmt.Errorf("could not verify if client is valid: %w", err)
+	}
+	for _, rev := range authReview.Status.ResourceRules {
+		if contains("secrets", rev.Resources) && contains("get", rev.Verbs) {
+			return esv1beta1.ValidationResultReady, nil
+		}
+	}
+	return esv1beta1.ValidationResultError, fmt.Errorf("client is not allowed to get secrets")
+}
+
+func contains(sub string, args []string) bool {
+	for _, k := range args {
+		if k == sub {
+			return true
+		}
+	}
+	return false
+}

+ 357 - 0
pkg/provider/kubernetes/validate_test.go

@@ -0,0 +1,357 @@
+/*
+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 kubernetes
+
+import (
+	"context"
+	"errors"
+	"reflect"
+	"testing"
+
+	authv1 "k8s.io/api/authorization/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+type fakeReviewClient struct {
+	authReview *authv1.SelfSubjectRulesReview
+}
+
+func (fk fakeReviewClient) Create(ctx context.Context, selfSubjectAccessReview *authv1.SelfSubjectRulesReview, opts metav1.CreateOptions) (*authv1.SelfSubjectRulesReview, error) {
+	if fk.authReview == nil {
+		return nil, errors.New(errSomethingWentWrong)
+	}
+	return fk.authReview, nil
+}
+
+func TestValidateStore(t *testing.T) {
+	type fields struct {
+		Client       KClient
+		ReviewClient RClient
+		Namespace    string
+	}
+
+	tests := []struct {
+		name    string
+		fields  fields
+		store   esv1beta1.GenericStore
+		wantErr bool
+	}{
+		{
+			name: "empty ca",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid client cert name",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								Cert: &esv1beta1.CertAuth{
+									ClientCert: v1.SecretKeySelector{
+										Name: "",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid client cert key",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								Cert: &esv1beta1.CertAuth{
+									ClientCert: v1.SecretKeySelector{
+										Name: "foobar",
+										Key:  "",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid client cert secretRef",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								Cert: &esv1beta1.CertAuth{
+									ClientCert: v1.SecretKeySelector{
+										Name:      "foobar",
+										Key:       "foobar",
+										Namespace: pointer.String("noop"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid client token auth name",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								Token: &esv1beta1.TokenAuth{
+									BearerToken: v1.SecretKeySelector{
+										Name: "",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid client token auth key",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								Token: &esv1beta1.TokenAuth{
+									BearerToken: v1.SecretKeySelector{
+										Name: "foobar",
+										Key:  "",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid client token auth namespace",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								Token: &esv1beta1.TokenAuth{
+									BearerToken: v1.SecretKeySelector{
+										Name:      "foobar",
+										Key:       "foobar",
+										Namespace: pointer.String("nop"),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid service account auth name",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								ServiceAccount: &v1.ServiceAccountSelector{
+									Name:      "foobar",
+									Namespace: pointer.String("foobar"),
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "valid auth",
+			store: &esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Kubernetes: &esv1beta1.KubernetesProvider{
+							Server: esv1beta1.KubernetesServer{
+								CABundle: []byte("1234"),
+							},
+							Auth: esv1beta1.KubernetesAuth{
+								ServiceAccount: &v1.ServiceAccountSelector{
+									Name: "foobar",
+								},
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			k := &ProviderKubernetes{
+				Client:       tt.fields.Client,
+				ReviewClient: tt.fields.ReviewClient,
+				Namespace:    tt.fields.Namespace,
+			}
+			if err := k.ValidateStore(tt.store); (err != nil) != tt.wantErr {
+				t.Errorf("ProviderKubernetes.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestValidate(t *testing.T) {
+	successReview := authv1.SelfSubjectRulesReview{
+		Status: authv1.SubjectRulesReviewStatus{
+			ResourceRules: []authv1.ResourceRule{
+				{
+					Verbs:     []string{"get"},
+					Resources: []string{"secrets"},
+				},
+			},
+		},
+	}
+	failReview := authv1.SelfSubjectRulesReview{
+		Status: authv1.SubjectRulesReviewStatus{
+			ResourceRules: []authv1.ResourceRule{
+				{
+					Verbs:     []string{"update"},
+					Resources: []string{"secrets"},
+				},
+			},
+		},
+	}
+
+	type fields struct {
+		Client       KClient
+		ReviewClient RClient
+		Namespace    string
+		store        *esv1beta1.KubernetesProvider
+		storeKind    string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		want    esv1beta1.ValidationResult
+		wantErr bool
+	}{
+		{
+			name: "empty ns should return unknown for referent auth",
+			fields: fields{
+				storeKind: esv1beta1.ClusterSecretStoreKind,
+				store: &esv1beta1.KubernetesProvider{
+					Auth: esv1beta1.KubernetesAuth{
+						ServiceAccount: &v1.ServiceAccountSelector{
+							Name: "foobar",
+						},
+					},
+				},
+				ReviewClient: fakeReviewClient{authReview: &successReview},
+			},
+			want:    esv1beta1.ValidationResultUnknown,
+			wantErr: false,
+		},
+		{
+			name: "review results in unknown",
+			fields: fields{
+				Namespace:    "default",
+				ReviewClient: fakeReviewClient{},
+			},
+			want:    esv1beta1.ValidationResultUnknown,
+			wantErr: true,
+		},
+		{
+			name: "not allowed results in error",
+			fields: fields{
+				Namespace:    "default",
+				ReviewClient: fakeReviewClient{authReview: &failReview},
+			},
+			want:    esv1beta1.ValidationResultError,
+			wantErr: true,
+		},
+		{
+			name: "allowed results in no error",
+			fields: fields{
+				Namespace:    "default",
+				ReviewClient: fakeReviewClient{authReview: &successReview},
+			},
+			want:    esv1beta1.ValidationResultReady,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			k := &ProviderKubernetes{
+				Client:       tt.fields.Client,
+				ReviewClient: tt.fields.ReviewClient,
+				Namespace:    tt.fields.Namespace,
+				store:        tt.fields.store,
+				storeKind:    tt.fields.storeKind,
+			}
+			got, err := k.Validate()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ProviderKubernetes.Validate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ProviderKubernetes.Validate() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}