Browse Source

feat(secretserver): support authentication via access token (#6597)

* feat(secretserver): support authentication via access token

Add an optional `token` field to the Delinea Secret Server provider so
users can authenticate with an existing access token instead of a
username/password pair. When `token` is set, username and password are
no longer required and are ignored. The token can be supplied inline via
`value` or referenced from a Kubernetes Secret via `secretRef`.

Applies to both v1 and v1beta1 APIs, updates validation and the client
credential loading, adds unit tests for the token paths, and refreshes
the generated CRDs/deepcopy/snapshots and provider docs.

Closes #3977

Signed-off-by: sumanpal <sumanpal198@gmail.com>

* fix(secretserver): address token-auth review feedback

- restrict token field to v1 only (revert all v1beta1 changes)
- short-circuit doesConfigDependOnNamespace when Token is set
- route credential loading through esutils referent validation

Signed-off-by: sumanpal <sumanpal198@gmail.com>

* test(secretserver): assert referent guard blocks cross-namespace secret access

Add a unit test proving loadConfigSecret refuses to resolve a secret from a
different namespace than the namespaced SecretStore, even when that secret
exists, exercising the esutils referent-validation guard in the
credential-loading path.

Signed-off-by: sumanpal <sumanpal198@gmail.com>

* fix(secretserver): enforce token-or-username/password via CEL validation

Add a kubebuilder XValidation rule on SecretServerProvider requiring either
token, or both username and password, so admission rejects no-auth and
half-configured stores. Clarify in the type comment that Token takes precedence
when set. Regenerate the affected CRDs.

Signed-off-by: Suman Pal <46846048+sumanpal97@users.noreply.github.com>

* fix(secretserver): validate SecretServerProviderRef value/secretRef

Add kubebuilder validation on SecretServerProviderRef so exactly one of value
or secretRef must be set (XValidation XOR), and value must be non-empty when
provided (MinLength=1). Applies to username, password, and token via the shared
type. Regenerate the affected CRDs.

Signed-off-by: Suman Pal <46846048+sumanpal97@users.noreply.github.com>

* docs(secretserver): regenerate API docs for token auth field

Signed-off-by: Suman Pal <46846048+sumanpal97@users.noreply.github.com>

---------

Signed-off-by: sumanpal <sumanpal198@gmail.com>
Signed-off-by: Suman Pal <46846048+sumanpal97@users.noreply.github.com>
Co-authored-by: Jean-Philippe Evrard <jean-philippe.evrard+rochepub@external.roche.com>
Suman Pal 6 days ago
parent
commit
69d1f6d7bb

+ 18 - 5
apis/externalsecrets/v1/secretsstore_secretserver_types.go

@@ -19,11 +19,13 @@ package v1
 import esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 
 // SecretServerProviderRef references a value that can be specified directly or via a secret
-// for a SecretServerProvider.
+// for a SecretServerProvider. Exactly one of Value or SecretRef must be set.
+// +kubebuilder:validation:XValidation:rule="has(self.value) != has(self.secretRef)",message="exactly one of value or secretRef must be set"
 type SecretServerProviderRef struct {
 
 	// Value can be specified directly to set a value without using a secret.
 	// +optional
+	// +kubebuilder:validation:MinLength=1
 	Value string `json:"value,omitempty"`
 
 	// SecretRef references a key in a secret that will be used as value.
@@ -33,14 +35,25 @@ type SecretServerProviderRef struct {
 
 // SecretServerProvider provides access to authenticate to a secrets provider server.
 // See: https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go.
+// Authentication requires either Token, or both Username and Password. If Token is
+// set it takes precedence and Username/Password are ignored.
+// +kubebuilder:validation:XValidation:rule="has(self.token) || (has(self.username) && has(self.password))",message="either token, or both username and password, must be set"
 type SecretServerProvider struct {
 	// Username is the secret server account username.
-	// +required
-	Username *SecretServerProviderRef `json:"username"`
+	// Required unless Token is set.
+	// +optional
+	Username *SecretServerProviderRef `json:"username,omitempty"`
 
 	// Password is the secret server account password.
-	// +required
-	Password *SecretServerProviderRef `json:"password"`
+	// Required unless Token is set.
+	// +optional
+	Password *SecretServerProviderRef `json:"password,omitempty"`
+
+	// Token is an access token used to authenticate to the secret server,
+	// as an alternative to Username and Password. When set, Username and
+	// Password are not required and are ignored.
+	// +optional
+	Token *SecretServerProviderRef `json:"token,omitempty"`
 
 	// Domain is the secret server domain.
 	// +optional

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

@@ -3690,6 +3690,11 @@ func (in *SecretServerProvider) DeepCopyInto(out *SecretServerProvider) {
 		*out = new(SecretServerProviderRef)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Token != nil {
+		in, out := &in.Token, &out.Token
+		*out = new(SecretServerProviderRef)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.CABundle != nil {
 		in, out := &in.CABundle, &out.CABundle
 		*out = make([]byte, len(*in))

+ 61 - 4
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -5424,7 +5424,9 @@ spec:
                         description: Domain is the secret server domain.
                         type: string
                       password:
-                        description: Password is the secret server account password.
+                        description: |-
+                          Password is the secret server account password.
+                          Required unless Token is set.
                         properties:
                           secretRef:
                             description: SecretRef references a key in a secret that
@@ -5457,15 +5459,64 @@ spec:
                           value:
                             description: Value can be specified directly to set a
                               value without using a secret.
+                            minLength: 1
                             type: string
                         type: object
+                        x-kubernetes-validations:
+                        - message: exactly one of value or secretRef must be set
+                          rule: has(self.value) != has(self.secretRef)
                       serverURL:
                         description: |-
                           ServerURL
                           URL to your secret server installation
                         type: string
+                      token:
+                        description: |-
+                          Token is an access token used to authenticate to the secret server,
+                          as an alternative to Username and Password. When set, Username and
+                          Password are not required and are ignored.
+                        properties:
+                          secretRef:
+                            description: SecretRef references a key in a secret that
+                              will be used as value.
+                            properties:
+                              key:
+                                description: |-
+                                  A key in the referenced Secret.
+                                  Some instances of this field may be defaulted, in others it may be required.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[-._a-zA-Z0-9]+$
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                type: string
+                              namespace:
+                                description: |-
+                                  The namespace of the Secret resource being referred to.
+                                  Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                maxLength: 63
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                type: string
+                            type: object
+                          value:
+                            description: Value can be specified directly to set a
+                              value without using a secret.
+                            minLength: 1
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: exactly one of value or secretRef must be set
+                          rule: has(self.value) != has(self.secretRef)
                       username:
-                        description: Username is the secret server account username.
+                        description: |-
+                          Username is the secret server account username.
+                          Required unless Token is set.
                         properties:
                           secretRef:
                             description: SecretRef references a key in a secret that
@@ -5498,13 +5549,19 @@ spec:
                           value:
                             description: Value can be specified directly to set a
                               value without using a secret.
+                            minLength: 1
                             type: string
                         type: object
+                        x-kubernetes-validations:
+                        - message: exactly one of value or secretRef must be set
+                          rule: has(self.value) != has(self.secretRef)
                     required:
-                    - password
                     - serverURL
-                    - username
                     type: object
+                    x-kubernetes-validations:
+                    - message: either token, or both username and password, must be
+                        set
+                      rule: has(self.token) || (has(self.username) && has(self.password))
                   senhasegura:
                     description: Senhasegura configures this store to sync secrets
                       using senhasegura provider

+ 61 - 4
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -5424,7 +5424,9 @@ spec:
                         description: Domain is the secret server domain.
                         type: string
                       password:
-                        description: Password is the secret server account password.
+                        description: |-
+                          Password is the secret server account password.
+                          Required unless Token is set.
                         properties:
                           secretRef:
                             description: SecretRef references a key in a secret that
@@ -5457,15 +5459,64 @@ spec:
                           value:
                             description: Value can be specified directly to set a
                               value without using a secret.
+                            minLength: 1
                             type: string
                         type: object
+                        x-kubernetes-validations:
+                        - message: exactly one of value or secretRef must be set
+                          rule: has(self.value) != has(self.secretRef)
                       serverURL:
                         description: |-
                           ServerURL
                           URL to your secret server installation
                         type: string
+                      token:
+                        description: |-
+                          Token is an access token used to authenticate to the secret server,
+                          as an alternative to Username and Password. When set, Username and
+                          Password are not required and are ignored.
+                        properties:
+                          secretRef:
+                            description: SecretRef references a key in a secret that
+                              will be used as value.
+                            properties:
+                              key:
+                                description: |-
+                                  A key in the referenced Secret.
+                                  Some instances of this field may be defaulted, in others it may be required.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[-._a-zA-Z0-9]+$
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                maxLength: 253
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                type: string
+                              namespace:
+                                description: |-
+                                  The namespace of the Secret resource being referred to.
+                                  Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                maxLength: 63
+                                minLength: 1
+                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                type: string
+                            type: object
+                          value:
+                            description: Value can be specified directly to set a
+                              value without using a secret.
+                            minLength: 1
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: exactly one of value or secretRef must be set
+                          rule: has(self.value) != has(self.secretRef)
                       username:
-                        description: Username is the secret server account username.
+                        description: |-
+                          Username is the secret server account username.
+                          Required unless Token is set.
                         properties:
                           secretRef:
                             description: SecretRef references a key in a secret that
@@ -5498,13 +5549,19 @@ spec:
                           value:
                             description: Value can be specified directly to set a
                               value without using a secret.
+                            minLength: 1
                             type: string
                         type: object
+                        x-kubernetes-validations:
+                        - message: exactly one of value or secretRef must be set
+                          rule: has(self.value) != has(self.secretRef)
                     required:
-                    - password
                     - serverURL
-                    - username
                     type: object
+                    x-kubernetes-validations:
+                    - message: either token, or both username and password, must be
+                        set
+                      rule: has(self.token) || (has(self.username) && has(self.password))
                   senhasegura:
                     description: Senhasegura configures this store to sync secrets
                       using senhasegura provider

+ 114 - 8
deploy/crds/bundle.yaml

@@ -7373,7 +7373,9 @@ spec:
                           description: Domain is the secret server domain.
                           type: string
                         password:
-                          description: Password is the secret server account password.
+                          description: |-
+                            Password is the secret server account password.
+                            Required unless Token is set.
                           properties:
                             secretRef:
                               description: SecretRef references a key in a secret that will be used as value.
@@ -7403,15 +7405,61 @@ spec:
                               type: object
                             value:
                               description: Value can be specified directly to set a value without using a secret.
+                              minLength: 1
                               type: string
                           type: object
+                          x-kubernetes-validations:
+                            - message: exactly one of value or secretRef must be set
+                              rule: has(self.value) != has(self.secretRef)
                         serverURL:
                           description: |-
                             ServerURL
                             URL to your secret server installation
                           type: string
+                        token:
+                          description: |-
+                            Token is an access token used to authenticate to the secret server,
+                            as an alternative to Username and Password. When set, Username and
+                            Password are not required and are ignored.
+                          properties:
+                            secretRef:
+                              description: SecretRef references a key in a secret that will be used as value.
+                              properties:
+                                key:
+                                  description: |-
+                                    A key in the referenced Secret.
+                                    Some instances of this field may be defaulted, in others it may be required.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[-._a-zA-Z0-9]+$
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    The namespace of the Secret resource being referred to.
+                                    Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                  maxLength: 63
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                  type: string
+                              type: object
+                            value:
+                              description: Value can be specified directly to set a value without using a secret.
+                              minLength: 1
+                              type: string
+                          type: object
+                          x-kubernetes-validations:
+                            - message: exactly one of value or secretRef must be set
+                              rule: has(self.value) != has(self.secretRef)
                         username:
-                          description: Username is the secret server account username.
+                          description: |-
+                            Username is the secret server account username.
+                            Required unless Token is set.
                           properties:
                             secretRef:
                               description: SecretRef references a key in a secret that will be used as value.
@@ -7441,13 +7489,18 @@ spec:
                               type: object
                             value:
                               description: Value can be specified directly to set a value without using a secret.
+                              minLength: 1
                               type: string
                           type: object
+                          x-kubernetes-validations:
+                            - message: exactly one of value or secretRef must be set
+                              rule: has(self.value) != has(self.secretRef)
                       required:
-                        - password
                         - serverURL
-                        - username
                       type: object
+                      x-kubernetes-validations:
+                        - message: either token, or both username and password, must be set
+                          rule: has(self.token) || (has(self.username) && has(self.password))
                     senhasegura:
                       description: Senhasegura configures this store to sync secrets using senhasegura provider
                       properties:
@@ -20226,7 +20279,9 @@ spec:
                           description: Domain is the secret server domain.
                           type: string
                         password:
-                          description: Password is the secret server account password.
+                          description: |-
+                            Password is the secret server account password.
+                            Required unless Token is set.
                           properties:
                             secretRef:
                               description: SecretRef references a key in a secret that will be used as value.
@@ -20256,15 +20311,61 @@ spec:
                               type: object
                             value:
                               description: Value can be specified directly to set a value without using a secret.
+                              minLength: 1
                               type: string
                           type: object
+                          x-kubernetes-validations:
+                            - message: exactly one of value or secretRef must be set
+                              rule: has(self.value) != has(self.secretRef)
                         serverURL:
                           description: |-
                             ServerURL
                             URL to your secret server installation
                           type: string
+                        token:
+                          description: |-
+                            Token is an access token used to authenticate to the secret server,
+                            as an alternative to Username and Password. When set, Username and
+                            Password are not required and are ignored.
+                          properties:
+                            secretRef:
+                              description: SecretRef references a key in a secret that will be used as value.
+                              properties:
+                                key:
+                                  description: |-
+                                    A key in the referenced Secret.
+                                    Some instances of this field may be defaulted, in others it may be required.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[-._a-zA-Z0-9]+$
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  maxLength: 253
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                  type: string
+                                namespace:
+                                  description: |-
+                                    The namespace of the Secret resource being referred to.
+                                    Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                  maxLength: 63
+                                  minLength: 1
+                                  pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                  type: string
+                              type: object
+                            value:
+                              description: Value can be specified directly to set a value without using a secret.
+                              minLength: 1
+                              type: string
+                          type: object
+                          x-kubernetes-validations:
+                            - message: exactly one of value or secretRef must be set
+                              rule: has(self.value) != has(self.secretRef)
                         username:
-                          description: Username is the secret server account username.
+                          description: |-
+                            Username is the secret server account username.
+                            Required unless Token is set.
                           properties:
                             secretRef:
                               description: SecretRef references a key in a secret that will be used as value.
@@ -20294,13 +20395,18 @@ spec:
                               type: object
                             value:
                               description: Value can be specified directly to set a value without using a secret.
+                              minLength: 1
                               type: string
                           type: object
+                          x-kubernetes-validations:
+                            - message: exactly one of value or secretRef must be set
+                              rule: has(self.value) != has(self.secretRef)
                       required:
-                        - password
                         - serverURL
-                        - username
                       type: object
+                      x-kubernetes-validations:
+                        - message: either token, or both username and password, must be set
+                          rule: has(self.token) || (has(self.username) && has(self.password))
                     senhasegura:
                       description: Senhasegura configures this store to sync secrets using senhasegura provider
                       properties:

+ 26 - 4
docs/api/spec.md

@@ -10179,7 +10179,9 @@ string
 </p>
 <p>
 <p>SecretServerProvider provides access to authenticate to a secrets provider server.
-See: <a href="https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go">https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go</a>.</p>
+See: <a href="https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go">https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go</a>.
+Authentication requires either Token, or both Username and Password. If Token is
+set it takes precedence and Username/Password are ignored.</p>
 </p>
 <table>
 <thead>
@@ -10199,7 +10201,9 @@ SecretServerProviderRef
 </em>
 </td>
 <td>
-<p>Username is the secret server account username.</p>
+<em>(Optional)</em>
+<p>Username is the secret server account username.
+Required unless Token is set.</p>
 </td>
 </tr>
 <tr>
@@ -10212,7 +10216,25 @@ SecretServerProviderRef
 </em>
 </td>
 <td>
-<p>Password is the secret server account password.</p>
+<em>(Optional)</em>
+<p>Password is the secret server account password.
+Required unless Token is set.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>token</code></br>
+<em>
+<a href="#external-secrets.io/v1.SecretServerProviderRef">
+SecretServerProviderRef
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Token is an access token used to authenticate to the secret server,
+as an alternative to Username and Password. When set, Username and
+Password are not required and are ignored.</p>
 </td>
 </tr>
 <tr>
@@ -10277,7 +10299,7 @@ CAProvider
 </p>
 <p>
 <p>SecretServerProviderRef references a value that can be specified directly or via a secret
-for a SecretServerProvider.</p>
+for a SecretServerProvider. Exactly one of Value or SecretRef must be set.</p>
 </p>
 <table>
 <thead>

+ 25 - 0
docs/provider/secretserver.md

@@ -33,6 +33,31 @@ spec:
           key: <KEY_IN_K8S_SECRET>
 ```
 
+## Authenticating with an Access Token
+
+As an alternative to `username` and `password`, you can authenticate using a pre-issued
+access token via the `token` field. This is useful when username/password login is not
+available (for example, on-prem installations with domain accounts). When `token` is set,
+`username` and `password` are not required and are ignored.
+
+Like the other credentials, the token can be provided directly via the `value` field or by
+referencing a Kubernetes secret via `secretRef`.
+
+```yaml
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: secret-server-store
+spec:
+  provider:
+    secretserver:
+      serverURL: "https://yourtenantname.secretservercloud.com"  # or "https://yourtenantname.delinea.app" for Platform
+      token:
+        secretRef:
+          name: <NAME_OF_K8S_SECRET>
+          key: <KEY_IN_K8S_SECRET>
+```
+
 ## Referencing Secrets
 
 Secrets can be referenced using four different key formats in the `remoteRef.key` field:

+ 50 - 19
providers/v1/secretserver/provider.go

@@ -65,22 +65,15 @@ func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube
 		// we are not attached to a specific namespace, but some config values are dependent on it
 		return nil, errClusterStoreRequiresNamespace
 	}
-	username, err := loadConfigSecret(ctx, store.GetKind(), cfg.Username, kube, namespace)
-	if err != nil {
-		return nil, err
-	}
-	password, err := loadConfigSecret(ctx, store.GetKind(), cfg.Password, kube, namespace)
+
+	credentials, err := loadCredentials(ctx, store, cfg, kube, namespace)
 	if err != nil {
 		return nil, err
 	}
 
 	ssConfig := server.Configuration{
-		Credentials: server.UserCredential{
-			Username: username,
-			Password: password,
-			Domain:   cfg.Domain,
-		},
-		ServerURL: cfg.ServerURL,
+		Credentials: credentials,
+		ServerURL:   cfg.ServerURL,
 	}
 
 	if len(cfg.CABundle) > 0 || cfg.CAProvider != nil {
@@ -118,17 +111,41 @@ func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube
 
 func loadConfigSecret(
 	ctx context.Context,
-	storeKind string,
+	store esv1.GenericStore,
 	ref *esv1.SecretServerProviderRef,
 	kube kubeClient.Client,
 	namespace string) (string, error) {
 	if ref.SecretRef == nil {
 		return ref.Value, nil
 	}
-	if err := validateSecretRef(ref); err != nil {
+	if err := validateStoreSecretRef(store, ref); err != nil {
 		return "", err
 	}
-	return resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, ref.SecretRef)
+	return resolvers.SecretKeyRef(ctx, kube, store.GetKind(), namespace, ref.SecretRef)
+}
+
+func loadCredentials(ctx context.Context, store esv1.GenericStore, cfg *esv1.SecretServerProvider, kube kubeClient.Client, namespace string) (server.UserCredential, error) {
+	if cfg.Token != nil {
+		token, err := loadConfigSecret(ctx, store, cfg.Token, kube, namespace)
+		if err != nil {
+			return server.UserCredential{}, err
+		}
+		return server.UserCredential{Token: token}, nil
+	}
+
+	username, err := loadConfigSecret(ctx, store, cfg.Username, kube, namespace)
+	if err != nil {
+		return server.UserCredential{}, err
+	}
+	password, err := loadConfigSecret(ctx, store, cfg.Password, kube, namespace)
+	if err != nil {
+		return server.UserCredential{}, err
+	}
+	return server.UserCredential{
+		Username: username,
+		Password: password,
+		Domain:   cfg.Domain,
+	}, nil
 }
 
 func validateStoreSecretRef(store esv1.GenericStore, ref *esv1.SecretServerProviderRef) error {
@@ -158,10 +175,15 @@ func validateSecretRef(ref *esv1.SecretServerProviderRef) error {
 }
 
 func doesConfigDependOnNamespace(cfg *esv1.SecretServerProvider) bool {
-	if cfg.Username.SecretRef != nil && cfg.Username.SecretRef.Namespace == nil {
+	// Mirror getConfig's precedence: when Token is set, username/password are
+	// ignored, so only the token ref can introduce a namespace dependency.
+	if cfg.Token != nil {
+		return cfg.Token.SecretRef != nil && cfg.Token.SecretRef.Namespace == nil
+	}
+	if cfg.Username != nil && cfg.Username.SecretRef != nil && cfg.Username.SecretRef.Namespace == nil {
 		return true
 	}
-	if cfg.Password.SecretRef != nil && cfg.Password.SecretRef.Namespace == nil {
+	if cfg.Password != nil && cfg.Password.SecretRef != nil && cfg.Password.SecretRef.Namespace == nil {
 		return true
 	}
 	return false
@@ -178,15 +200,24 @@ func getConfig(store esv1.GenericStore) (*esv1.SecretServerProvider, error) {
 	}
 	cfg := storeSpec.Provider.SecretServer
 
+	if cfg.ServerURL == "" {
+		return nil, errEmptyServerURL
+	}
+
+	// Token authentication takes precedence over username/password.
+	if cfg.Token != nil {
+		if err := validateStoreSecretRef(store, cfg.Token); err != nil {
+			return nil, err
+		}
+		return cfg, nil
+	}
+
 	if cfg.Username == nil {
 		return nil, errEmptyUserName
 	}
 	if cfg.Password == nil {
 		return nil, errEmptyPassword
 	}
-	if cfg.ServerURL == "" {
-		return nil, errEmptyServerURL
-	}
 
 	err := validateStoreSecretRef(store, cfg.Username)
 	if err != nil {

+ 134 - 1
providers/v1/secretserver/provider_test.go

@@ -63,6 +63,44 @@ func TestDoesConfigDependOnNamespace(t *testing.T) {
 			},
 			want: false,
 		},
+		"true when Token references a secret without explicit namespace": {
+			cfg: esv1.SecretServerProvider{
+				Token: &esv1.SecretServerProviderRef{
+					SecretRef: &v1.SecretKeySelector{Name: "foo"},
+				},
+			},
+			want: true,
+		},
+		"false when Token uses a direct value": {
+			cfg: esv1.SecretServerProvider{
+				Token: &esv1.SecretServerProviderRef{Value: "foo"},
+			},
+			want: false,
+		},
+		"false when Token has explicit namespace even if Username ref lacks one": {
+			// Token takes precedence, so the ignored Username ref must not
+			// introduce a namespace dependency.
+			cfg: esv1.SecretServerProvider{
+				Token: &esv1.SecretServerProviderRef{
+					SecretRef: &v1.SecretKeySelector{Name: "foo", Namespace: new("ns")},
+				},
+				Username: &esv1.SecretServerProviderRef{
+					SecretRef: &v1.SecretKeySelector{Name: "bar"},
+				},
+			},
+			want: false,
+		},
+		"true when Token ref lacks a namespace even if Username has one": {
+			cfg: esv1.SecretServerProvider{
+				Token: &esv1.SecretServerProviderRef{
+					SecretRef: &v1.SecretKeySelector{Name: "foo"},
+				},
+				Username: &esv1.SecretServerProviderRef{
+					SecretRef: &v1.SecretKeySelector{Name: "bar", Namespace: new("ns")},
+				},
+			},
+			want: true,
+		},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
@@ -147,6 +185,34 @@ func TestValidateStore(t *testing.T) {
 			},
 			want: nil,
 		},
+		"valid with token and no username/password": {
+			cfg: esv1.SecretServerProvider{
+				Token:     validSecretRefUsingValue,
+				ServerURL: testURL,
+			},
+			want: nil,
+		},
+		"invalid without serverURL when using token": {
+			cfg: esv1.SecretServerProvider{
+				Token: validSecretRefUsingValue,
+				/*ServerURL: testURL,*/
+			},
+			want: errEmptyServerURL,
+		},
+		"invalid with ambiguous token": {
+			cfg: esv1.SecretServerProvider{
+				Token:     ambiguousSecretRef,
+				ServerURL: testURL,
+			},
+			want: errSecretRefAndValueConflict,
+		},
+		"invalid with invalid token": {
+			cfg: esv1.SecretServerProvider{
+				Token:     makeSecretRefUsingValue(""),
+				ServerURL: testURL,
+			},
+			want: errSecretRefAndValueMissing,
+		},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
@@ -170,6 +236,8 @@ func TestNewClient(t *testing.T) {
 	passwordKey := passwordSlug
 	passwordValue := generateRandomString()
 	domain := "domain1"
+	tokenKey := "token"
+	tokenValue := generateRandomString()
 
 	clientSecret := &corev1.Secret{
 		ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
@@ -179,6 +247,13 @@ func TestNewClient(t *testing.T) {
 		},
 	}
 
+	tokenSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{Name: "token-secret", Namespace: "default"},
+		Data: map[string][]byte{
+			tokenKey: []byte(tokenValue),
+		},
+	}
+
 	validProvider := &esv1.SecretServerProvider{
 		Username:  makeSecretRefUsingRef(clientSecret.Name, userNameKey),
 		Password:  makeSecretRefUsingRef(clientSecret.Name, passwordKey),
@@ -332,6 +407,30 @@ QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
 			},
 			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
 		},
+		"valid token via value": {
+			provider: &esv1.SecretServerProvider{
+				Token:     makeSecretRefUsingValue(tokenValue),
+				ServerURL: validProvider.ServerURL,
+			},
+			kube: clientfake.NewClientBuilder().Build(),
+		},
+		"valid token via secret ref": {
+			provider: &esv1.SecretServerProvider{
+				Token:     makeSecretRefUsingRef(tokenSecret.Name, tokenKey),
+				ServerURL: validProvider.ServerURL,
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(tokenSecret).Build(),
+		},
+		"dangling token ref": {
+			provider: &esv1.SecretServerProvider{
+				Token:     makeSecretRefUsingRef("typo", tokenKey),
+				ServerURL: validProvider.ServerURL,
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(tokenSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.True(t, kubeErrors.IsNotFound(err))
+			},
+		},
 		"cluster secret store": {
 			store: &esv1.ClusterSecretStore{
 				TypeMeta: metav1.TypeMeta{Kind: esv1.ClusterSecretStoreKind},
@@ -508,8 +607,11 @@ QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
 					Username: userNameValue,
 					Password: passwordValue,
 				}
-				if name == "cluster secret store with domain" {
+				switch name {
+				case "cluster secret store with domain":
 					expectedCredentials.Domain = domain
+				case "valid token via value", "valid token via secret ref":
+					expectedCredentials = server.UserCredential{Token: tokenValue}
 				}
 				assert.Equal(t, expectedCredentials, secretServerClient.Configuration.Credentials)
 			} else {
@@ -628,6 +730,37 @@ func TestValidateStoreSecretRef(t *testing.T) {
 	}
 }
 
+// TestLoadConfigSecretReferentValidation ensures the credential-loading path
+// enforces esutils referent validation. A namespaced SecretStore must not be
+// able to resolve a secret that lives in a different namespace, even if that
+// secret exists — this is the exfiltration guard.
+func TestLoadConfigSecretReferentValidation(t *testing.T) {
+	const storeNamespace = "default"
+	const otherNamespace = "other-ns"
+
+	// A secret in a namespace the store must NOT be allowed to read.
+	stolenSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{Name: "stolen", Namespace: otherNamespace},
+		Data:       map[string][]byte{"token": []byte("super-secret")},
+	}
+	kube := clientfake.NewClientBuilder().WithObjects(stolenSecret).Build()
+
+	// Namespaced SecretStore living in storeNamespace.
+	store := &esv1.SecretStore{
+		TypeMeta:   metav1.TypeMeta{Kind: esv1.SecretStoreKind},
+		ObjectMeta: metav1.ObjectMeta{Namespace: storeNamespace},
+	}
+
+	// Token ref that tries to reach into the other namespace.
+	ref := makeSecretRefUsingNamespacedRef(otherNamespace, stolenSecret.Name, "token")
+
+	val, err := loadConfigSecret(context.Background(), store, ref, kube, storeNamespace)
+
+	assert.Error(t, err)
+	assert.Empty(t, val, "guard must block resolution before the value is read")
+	assert.ErrorContains(t, err, "namespace should either be empty or match")
+}
+
 // TestCapabilities tests the Capabilities function.
 func TestCapabilities(t *testing.T) {
 	tests := map[string]struct {

+ 6 - 0
tests/__snapshot__/clustersecretstore-v1.yaml

@@ -806,6 +806,12 @@ spec:
           namespace: string
         value: string
       serverURL: string
+      token:
+        secretRef:
+          key: string
+          name: string
+          namespace: string
+        value: string
       username:
         secretRef:
           key: string

+ 6 - 0
tests/__snapshot__/secretstore-v1.yaml

@@ -806,6 +806,12 @@ spec:
           namespace: string
         value: string
       serverURL: string
+      token:
+        secretRef:
+          key: string
+          name: string
+          namespace: string
+        value: string
       username:
         secretRef:
           key: string