Browse Source

Feature/asm 11630 akeyless push secret (#3907)

* feat[ASM-11630]- Akeyless PushSecret: implement push, delete, exists

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: contextualise token, add metrics, make new function interface friendly

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: add test on SecretExists, PushSecret, DeleteSecret

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: update documentations

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: refactor metrics func names

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: linting

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: simplify push

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: decrease code complexity and deduplicate

Signed-off-by: Dan Barak <dan.b@akeyless.io>

* feat[ASM-11630]- Akeyless PushSecret: check for token type assertion and decrease PushSecret complexity

Signed-off-by: Dan Barak <dan.b@akeyless.io>

---------

Signed-off-by: Dan Barak <dan.b@akeyless.io>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
dan-akeyless 1 year ago
parent
commit
680a3a4b8d

+ 27 - 5
docs/provider/akeyless.md

@@ -1,7 +1,9 @@
 ## Akeyless Secrets Management Platform
 ## Akeyless Secrets Management Platform
 
 
 External Secrets Operator integrates with the [Akeyless Secrets Management Platform](https://www.akeyless.io/).
 External Secrets Operator integrates with the [Akeyless Secrets Management Platform](https://www.akeyless.io/).
-### Create Secret Store:
+
+### Create Secret Store
+
 SecretStore resource specifies how to access Akeyless. This resource is namespaced.
 SecretStore resource specifies how to access Akeyless. This resource is namespaced.
 
 
 **NOTE:** Make sure the Akeyless provider is listed in the Kind=SecretStore.
 **NOTE:** Make sure the Akeyless provider is listed in the Kind=SecretStore.
@@ -9,7 +11,7 @@ If you use a customer fragment, define the value of akeylessGWApiURL as the URL
 
 
 Akeyelss provide several Authentication Methods:
 Akeyelss provide several Authentication Methods:
 
 
-### Authentication with Kubernetes:
+### Authentication with Kubernetes
 
 
 Options for obtaining Kubernetes credentials include:
 Options for obtaining Kubernetes credentials include:
 
 
@@ -18,11 +20,12 @@ Options for obtaining Kubernetes credentials include:
 3. Using transient credentials from the mounted service account token within the external-secrets operator
 3. Using transient credentials from the mounted service account token within the external-secrets operator
 
 
 #### Create the Akeyless Secret Store Provider with Kubernetes Auth-Method
 #### Create the Akeyless Secret Store Provider with Kubernetes Auth-Method
+
 ```yaml
 ```yaml
 {% include 'akeyless-secret-store-k8s-auth.yaml' %}
 {% include 'akeyless-secret-store-k8s-auth.yaml' %}
 ```
 ```
-**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` for `serviceAccountRef` and `secretRef` according to  the namespaces where the secrets reside.
 
 
+**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` for `serviceAccountRef` and `secretRef` according to  the namespaces where the secrets reside.
 
 
 ### Authentication With Cloud-Identity or Api-Access-Key
 ### Authentication With Cloud-Identity or Api-Access-Key
 
 
@@ -38,6 +41,7 @@ The supported auth-methods and their parameters are:
 | `azure_ad` |  azure object id  (optional)                                                          |
 | `azure_ad` |  azure object id  (optional)                                                          |
 | `api_key`      | The access key.                                                                                                                                     |
 | `api_key`      | The access key.                                                                                                                                     |
 | `k8s`         | The k8s configuration name |
 | `k8s`         | The k8s configuration name |
+
 For more information see [Akeyless Authentication Methods](https://docs.akeyless.io/docs/access-and-authentication-methods)
 For more information see [Akeyless Authentication Methods](https://docs.akeyless.io/docs/access-and-authentication-methods)
 
 
 #### Creating an Akeyless Credentials Secret
 #### Creating an Akeyless Credentials Secret
@@ -61,9 +65,11 @@ stringData:
 ```yaml
 ```yaml
 {% include 'akeyless-secret-store.yaml' %}
 {% include 'akeyless-secret-store.yaml' %}
 ```
 ```
+
 **NOTE:** In case of a `ClusterSecretStore`, be sure to provide `namespace` for `accessID`, `accessType` and `accessTypeParam`  according to the namespaces where the secrets reside.
 **NOTE:** In case of a `ClusterSecretStore`, be sure to provide `namespace` for `accessID`, `accessType` and `accessTypeParam`  according to the namespaces where the secrets reside.
 
 
 #### Create the Akeyless Secret Store With CAs for TLS handshake
 #### Create the Akeyless Secret Store With CAs for TLS handshake
+
 ```yaml
 ```yaml
 ....
 ....
 spec:
 spec:
@@ -103,11 +109,27 @@ DataFrom can be used to get a secret as a JSON string and attempt to parse it.
 ```
 ```
 
 
 ### Getting the Kubernetes Secret
 ### Getting the Kubernetes Secret
+
 The operator will fetch the secret and inject it as a `Kind=Secret`.
 The operator will fetch the secret and inject it as a `Kind=Secret`.
-```
+
+```bash
 kubectl get secret database-credentials -o jsonpath='{.data.db-password}' | base64 -d
 kubectl get secret database-credentials -o jsonpath='{.data.db-password}' | base64 -d
 ```
 ```
 
 
-```
+```bash
 kubectl get secret database-credentials-json -o jsonpath='{.data}'
 kubectl get secret database-credentials-json -o jsonpath='{.data}'
 ```
 ```
+
+### Pushing a secret
+
+To push a secret from Kubernetes cluster and create it as a secret to Akeyless, a `Kind=PushSecret` resource is needed.
+
+{% include 'akeyless-push-secret.yaml' %}
+
+Then when you create a matching secret as follows:
+
+```bash
+kubectl create secret generic --from-literal=cache-pass=mypassword k8s-created-secret
+```
+
+Then it will create a secret in akeyless `eso-created/my-secret` with value `{"cache-pass":"mypassword"}`

+ 18 - 0
docs/snippets/akeyless-push-secret.yaml

@@ -0,0 +1,18 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: PushSecret
+metadata:
+ name: push-secret
+spec:
+ refreshInterval: 5s
+ updatePolicy: Replace
+ deletionPolicy: Delete
+ secretStoreRefs:
+   - name: akeyless-secret-store
+     kind: SecretStore
+ selector:
+   secret:
+     name: k8s-created-secret
+ data:
+   - match:
+      remoteRef:
+        remoteKey: eso-created/my-secret

+ 3 - 3
e2e/suites/provider/cases/akeyless/provider.go

@@ -187,10 +187,10 @@ func (a *akeylessProvider) GetToken() (string, error) {
 	}
 	}
 
 
 	authOut, _, err := a.restAPIClient.Auth(ctx).Body(*authBody).Execute()
 	authOut, _, err := a.restAPIClient.Auth(ctx).Body(*authBody).Execute()
+	if errors.As(err, &apiErr) {
+		return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
-		}
 		return "", fmt.Errorf("authentication failed: %w", err)
 		return "", fmt.Errorf("authentication failed: %w", err)
 	}
 	}
 
 

+ 3 - 0
pkg/constants/constants.go

@@ -94,6 +94,9 @@ const (
 	CallAKEYLESSSMGetRotatedSecretValue = "GetRotatedSecretValue"
 	CallAKEYLESSSMGetRotatedSecretValue = "GetRotatedSecretValue"
 	CallAKEYLESSSMGetCertificateValue   = "GetCertificateValue"
 	CallAKEYLESSSMGetCertificateValue   = "GetCertificateValue"
 	CallAKEYLESSSMGetDynamicSecretValue = "GetDynamicSecretsValue"
 	CallAKEYLESSSMGetDynamicSecretValue = "GetDynamicSecretsValue"
+	CallAKEYLESSSMCreateSecret          = "CreateSecret"
+	CallAKEYLESSSMUpdateSecretVal       = "UpdateSecretVal"
+	CallAKEYLESSSMDeleteItem            = "DeleteItem"
 
 
 	StatusError   = "error"
 	StatusError   = "error"
 	StatusSuccess = "success"
 	StatusSuccess = "success"

+ 154 - 34
pkg/provider/akeyless/akeyless.go

@@ -15,6 +15,7 @@ limitations under the License.
 package akeyless
 package akeyless
 
 
 import (
 import (
+	"bytes"
 	"context"
 	"context"
 	"crypto/tls"
 	"crypto/tls"
 	"crypto/x509"
 	"crypto/x509"
@@ -23,6 +24,7 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"slices"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -41,9 +43,13 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/utils"
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 )
 
 
+type AkeylessCtx string
+
 const (
 const (
-	defaultAPIUrl     = "https://api.akeyless.io"
-	errNotImplemented = "not implemented"
+	defaultAPIUrl                   = "https://api.akeyless.io"
+	errNotImplemented               = "not implemented"
+	ExtSecretManagedTag             = "k8s-external-secrets"
+	AkeylessToken       AkeylessCtx = "AKEYLESS_TOKEN"
 )
 )
 
 
 // https://github.com/external-secrets/external-secrets/issues/644
 // https://github.com/external-secrets/external-secrets/issues/644
@@ -77,9 +83,13 @@ type Item struct {
 }
 }
 
 
 type akeylessVaultInterface interface {
 type akeylessVaultInterface interface {
-	GetSecretByType(ctx context.Context, secretName, token string, version int32) (string, error)
+	GetSecretByType(ctx context.Context, secretName string, version int32) (string, error)
 	TokenFromSecretRef(ctx context.Context) (string, error)
 	TokenFromSecretRef(ctx context.Context) (string, error)
-	ListSecrets(ctx context.Context, path, tag, token string) ([]string, error)
+	ListSecrets(ctx context.Context, path, tag string) ([]string, error)
+	DescribeItem(ctx context.Context, itemName string) (*akeyless.Item, error)
+	CreateSecret(ctx context.Context, remoteKey, data string) error
+	UpdateSecret(ctx context.Context, remoteKey, data string) error
+	DeleteSecret(ctx context.Context, remoteKey string) error
 }
 }
 
 
 func init() {
 func init() {
@@ -219,6 +229,17 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
 	return &Akeyless{Client: akl, url: akeylessGwAPIURL}, nil
 	return &Akeyless{Client: akl, url: akeylessGwAPIURL}, nil
 }
 }
 
 
+func (a *Akeyless) contextWithToken(ctx context.Context) (context.Context, error) {
+	if v := ctx.Value(AkeylessToken); v != nil {
+		return ctx, nil
+	}
+	token, err := a.Client.TokenFromSecretRef(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return context.WithValue(ctx, AkeylessToken, token), nil
+}
+
 func (a *Akeyless) Close(_ context.Context) error {
 func (a *Akeyless) Close(_ context.Context) error {
 	return nil
 	return nil
 }
 }
@@ -234,26 +255,13 @@ func (a *Akeyless) Validate() (esv1beta1.ValidationResult, error) {
 	return esv1beta1.ValidationResultReady, nil
 	return esv1beta1.ValidationResultReady, nil
 }
 }
 
 
-func (a *Akeyless) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
-	return errors.New(errNotImplemented)
-}
-
-func (a *Akeyless) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
-	return errors.New(errNotImplemented)
-}
-
-func (a *Akeyless) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
-	return false, errors.New(errNotImplemented)
-}
-
 // Implements store.Client.GetSecret Interface.
 // Implements store.Client.GetSecret Interface.
 // Retrieves a secret with the secret name defined in ref.Name.
 // Retrieves a secret with the secret name defined in ref.Name.
 func (a *Akeyless) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 func (a *Akeyless) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	if utils.IsNil(a.Client) {
 	if utils.IsNil(a.Client) {
 		return nil, errors.New(errUninitalizedAkeylessProvider)
 		return nil, errors.New(errUninitalizedAkeylessProvider)
 	}
 	}
-
-	token, err := a.Client.TokenFromSecretRef(ctx)
+	ctx, err := a.contextWithToken(ctx)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -264,7 +272,7 @@ func (a *Akeyless) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDa
 			version = int32(i)
 			version = int32(i)
 		}
 		}
 	}
 	}
-	value, err := a.Client.GetSecretByType(ctx, ref.Key, token, version)
+	value, err := a.Client.GetSecretByType(ctx, ref.Key, version)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -297,6 +305,10 @@ func (a *Akeyless) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecr
 	if utils.IsNil(a.Client) {
 	if utils.IsNil(a.Client) {
 		return nil, errors.New(errUninitalizedAkeylessProvider)
 		return nil, errors.New(errUninitalizedAkeylessProvider)
 	}
 	}
+	ctx, err := a.contextWithToken(ctx)
+	if err != nil {
+		return nil, err
+	}
 
 
 	searchPath := ""
 	searchPath := ""
 	if ref.Path != nil {
 	if ref.Path != nil {
@@ -308,25 +320,20 @@ func (a *Akeyless) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecr
 			searchPath += "/"
 			searchPath += "/"
 		}
 		}
 	}
 	}
-	token, err := a.Client.TokenFromSecretRef(ctx)
-	if err != nil {
-		return nil, err
-	}
-
 	if ref.Name != nil {
 	if ref.Name != nil {
-		potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, "", token)
+		potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, "")
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 		if len(potentialSecrets) == 0 {
 		if len(potentialSecrets) == 0 {
 			return nil, nil
 			return nil, nil
 		}
 		}
-		return a.findSecretsFromName(ctx, potentialSecrets, *ref.Name, token)
+		return a.findSecretsFromName(ctx, potentialSecrets, *ref.Name)
 	}
 	}
 	if len(ref.Tags) > 0 {
 	if len(ref.Tags) > 0 {
 		var potentialSecretsName []string
 		var potentialSecretsName []string
 		for _, v := range ref.Tags {
 		for _, v := range ref.Tags {
-			potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, v, token)
+			potentialSecrets, err := a.Client.ListSecrets(ctx, searchPath, v)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -337,16 +344,15 @@ func (a *Akeyless) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecr
 		if len(potentialSecretsName) == 0 {
 		if len(potentialSecretsName) == 0 {
 			return nil, nil
 			return nil, nil
 		}
 		}
-		return a.getSecrets(ctx, potentialSecretsName, token)
+		return a.getSecrets(ctx, potentialSecretsName)
 	}
 	}
-
 	return nil, errors.New("unexpected find operator")
 	return nil, errors.New("unexpected find operator")
 }
 }
 
 
-func (a *Akeyless) getSecrets(ctx context.Context, candidates []string, token string) (map[string][]byte, error) {
+func (a *Akeyless) getSecrets(ctx context.Context, candidates []string) (map[string][]byte, error) {
 	secrets := make(map[string][]byte)
 	secrets := make(map[string][]byte)
 	for _, name := range candidates {
 	for _, name := range candidates {
-		secretValue, err := a.Client.GetSecretByType(ctx, name, token, 0)
+		secretValue, err := a.Client.GetSecretByType(ctx, name, 0)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -357,7 +363,7 @@ func (a *Akeyless) getSecrets(ctx context.Context, candidates []string, token st
 	return secrets, nil
 	return secrets, nil
 }
 }
 
 
-func (a *Akeyless) findSecretsFromName(ctx context.Context, candidates []string, ref esv1beta1.FindName, token string) (map[string][]byte, error) {
+func (a *Akeyless) findSecretsFromName(ctx context.Context, candidates []string, ref esv1beta1.FindName) (map[string][]byte, error) {
 	secrets := make(map[string][]byte)
 	secrets := make(map[string][]byte)
 	matcher, err := find.New(ref)
 	matcher, err := find.New(ref)
 	if err != nil {
 	if err != nil {
@@ -366,7 +372,7 @@ func (a *Akeyless) findSecretsFromName(ctx context.Context, candidates []string,
 	for _, name := range candidates {
 	for _, name := range candidates {
 		ok := matcher.MatchName(name)
 		ok := matcher.MatchName(name)
 		if ok {
 		if ok {
-			secretValue, err := a.Client.GetSecretByType(ctx, name, token, 0)
+			secretValue, err := a.Client.GetSecretByType(ctx, name, 0)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -384,7 +390,6 @@ func (a *Akeyless) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecre
 	if utils.IsNil(a.Client) {
 	if utils.IsNil(a.Client) {
 		return nil, errors.New(errUninitalizedAkeylessProvider)
 		return nil, errors.New(errUninitalizedAkeylessProvider)
 	}
 	}
-
 	val, err := a.GetSecret(ctx, ref)
 	val, err := a.GetSecret(ctx, ref)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -404,6 +409,121 @@ func (a *Akeyless) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecre
 	return secretData, nil
 	return secretData, nil
 }
 }
 
 
+func (a *Akeyless) SecretExists(ctx context.Context, ref esv1beta1.PushSecretRemoteRef) (bool, error) {
+	if utils.IsNil(a.Client) {
+		return false, errors.New(errUninitalizedAkeylessProvider)
+	}
+	secret, err := a.GetSecret(ctx, esv1beta1.ExternalSecretDataRemoteRef{Key: ref.GetRemoteKey()})
+	if errors.Is(err, ErrItemNotExists) {
+		return false, nil
+	}
+	if err != nil {
+		return false, err
+	}
+	if ref.GetProperty() == "" {
+		return true, nil
+	}
+	var secretMap map[string]any
+	err = json.Unmarshal(secret, &secretMap)
+	if err != nil {
+		return false, err
+	}
+	_, ok := secretMap[ref.GetProperty()]
+	return ok, nil
+}
+
+func initMapIfNotExist(psd esv1beta1.PushSecretData, secretMapSize int) map[string]any {
+	mapSize := 1
+	if psd.GetProperty() == "" {
+		mapSize = secretMapSize
+	}
+	return make(map[string]any, mapSize)
+}
+
+func (a *Akeyless) PushSecret(ctx context.Context, secret *corev1.Secret, psd esv1beta1.PushSecretData) error {
+	if utils.IsNil(a.Client) {
+		return errors.New(errUninitalizedAkeylessProvider)
+	}
+	ctx, err := a.contextWithToken(ctx)
+	if err != nil {
+		return err
+	}
+	secretRemote, err := a.GetSecret(ctx, esv1beta1.ExternalSecretDataRemoteRef{Key: psd.GetRemoteKey()})
+	isNotExists := errors.Is(err, ErrItemNotExists)
+	if err != nil && !isNotExists {
+		return err
+	}
+	var data map[string]any
+	if isNotExists {
+		data = initMapIfNotExist(psd, len(secret.Data))
+		err = nil
+	} else {
+		err = json.Unmarshal(secretRemote, &data)
+	}
+	if err != nil {
+		return err
+	}
+	if psd.GetProperty() == "" {
+		for k, v := range secret.Data {
+			data[k] = string(v)
+		}
+	} else if v, ok := secret.Data[psd.GetSecretKey()]; ok {
+		data[psd.GetProperty()] = string(v)
+	}
+	dataByte, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+	if bytes.Equal(dataByte, secretRemote) {
+		return nil
+	}
+	if isNotExists {
+		return a.Client.CreateSecret(ctx, psd.GetRemoteKey(), string(dataByte))
+	}
+	return a.Client.UpdateSecret(ctx, psd.GetRemoteKey(), string(dataByte))
+}
+
+func (a *Akeyless) DeleteSecret(ctx context.Context, psr esv1beta1.PushSecretRemoteRef) error {
+	if utils.IsNil(a.Client) {
+		return errors.New(errUninitalizedAkeylessProvider)
+	}
+	ctx, err := a.contextWithToken(ctx)
+	if err != nil {
+		return err
+	}
+	item, err := a.Client.DescribeItem(ctx, psr.GetRemoteKey())
+	if err != nil {
+		return err
+	}
+	if item == nil || item.ItemTags == nil || !slices.Contains(*item.ItemTags, ExtSecretManagedTag) {
+		return nil
+	}
+	if psr.GetProperty() == "" {
+		err = a.Client.DeleteSecret(ctx, psr.GetRemoteKey())
+		return err
+	}
+	secret, err := a.GetSecret(ctx, esv1beta1.ExternalSecretDataRemoteRef{Key: psr.GetRemoteKey()})
+	if err != nil {
+		return err
+	}
+	var secretMap map[string]any
+	err = json.Unmarshal(secret, &secretMap)
+	if err != nil {
+		return err
+	}
+	delete(secretMap, psr.GetProperty())
+	if len(secretMap) == 0 {
+		err = a.Client.DeleteSecret(ctx, psr.GetRemoteKey())
+		return err
+	}
+	byteSecretMap, err := json.Marshal(secretMap)
+	if err != nil {
+		return err
+	}
+	err = a.Client.UpdateSecret(ctx, psr.GetRemoteKey(), string(byteSecretMap))
+	return err
+}
+
 func (a *akeylessBase) getAkeylessHTTPClient(ctx context.Context, provider *esv1beta1.AkeylessProvider) (*http.Client, error) {
 func (a *akeylessBase) getAkeylessHTTPClient(ctx context.Context, provider *esv1beta1.AkeylessProvider) (*http.Client, error) {
 	client := &http.Client{Timeout: 30 * time.Second}
 	client := &http.Client{Timeout: 30 * time.Second}
 	if len(provider.CABundle) == 0 && provider.CAProvider == nil {
 	if len(provider.CABundle) == 0 && provider.CAProvider == nil {

+ 128 - 94
pkg/provider/akeyless/akeyless_api.go

@@ -40,12 +40,20 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
 	"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
 )
 )
 
 
-var apiErr akeyless.GenericOpenAPIError
+var (
+	apiErr            akeyless.GenericOpenAPIError
+	ErrItemNotExists  = errors.New("item does not exist")
+	ErrTokenNotExists = errors.New("token does not exist")
+)
 
 
 const DefServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
 const DefServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
 
 
-func (a *akeylessBase) GetToken(accessID, accType, accTypeParam string, k8sAuth *esv1beta1.AkeylessKubernetesAuth) (string, error) {
-	ctx := context.Background()
+type Tokener interface {
+	SetToken(v string)
+	SetUidToken(v string)
+}
+
+func (a *akeylessBase) GetToken(ctx context.Context, accessID, accType, accTypeParam string, k8sAuth *esv1beta1.AkeylessKubernetesAuth) (string, error) {
 	authBody := akeyless.NewAuthWithDefaults()
 	authBody := akeyless.NewAuthWithDefaults()
 	authBody.AccessId = akeyless.PtrString(accessID)
 	authBody.AccessId = akeyless.PtrString(accessID)
 	if accType == "api_key" || accType == "access_key" {
 	if accType == "api_key" || accType == "access_key" {
@@ -71,83 +79,91 @@ func (a *akeylessBase) GetToken(accessID, accType, accTypeParam string, k8sAuth
 
 
 	authOut, res, err := a.RestAPI.Auth(ctx).Body(*authBody).Execute()
 	authOut, res, err := a.RestAPI.Auth(ctx).Body(*authBody).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMAuth, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMAuth, err)
+	if errors.As(err, &apiErr) {
+		return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
-		}
 		return "", fmt.Errorf("authentication failed: %w", err)
 		return "", fmt.Errorf("authentication failed: %w", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
-
 	token := authOut.GetToken()
 	token := authOut.GetToken()
 	return token, nil
 	return token, nil
 }
 }
 
 
-func (a *akeylessBase) GetSecretByType(ctx context.Context, secretName, token string, version int32) (string, error) {
-	item, err := a.DescribeItem(ctx, secretName, token)
+func (a *akeylessBase) GetSecretByType(ctx context.Context, secretName string, version int32) (string, error) {
+	item, err := a.DescribeItem(ctx, secretName)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
+	if _, ok := item.GetItemNameOk(); !ok {
+		return "", ErrItemNotExists
+	}
 	secretType := item.GetItemType()
 	secretType := item.GetItemType()
-
 	switch secretType {
 	switch secretType {
 	case "STATIC_SECRET":
 	case "STATIC_SECRET":
-		return a.GetStaticSecret(ctx, secretName, token, version)
+		return a.GetStaticSecret(ctx, secretName, version)
 	case "DYNAMIC_SECRET":
 	case "DYNAMIC_SECRET":
-		return a.GetDynamicSecrets(ctx, secretName, token)
+		return a.GetDynamicSecrets(ctx, secretName)
 	case "ROTATED_SECRET":
 	case "ROTATED_SECRET":
-		return a.GetRotatedSecrets(ctx, secretName, token, version)
+		return a.GetRotatedSecrets(ctx, secretName, version)
 	case "CERTIFICATE":
 	case "CERTIFICATE":
-		return a.GetCertificate(ctx, secretName, token, version)
+		return a.GetCertificate(ctx, secretName, version)
 	default:
 	default:
 		return "", fmt.Errorf("invalid item type: %v", secretType)
 		return "", fmt.Errorf("invalid item type: %v", secretType)
 	}
 	}
 }
 }
 
 
-func (a *akeylessBase) DescribeItem(ctx context.Context, itemName, token string) (*akeyless.Item, error) {
-	body := akeyless.DescribeItem{
-		Name: itemName,
+func SetBodyToken(t Tokener, ctx context.Context) error {
+	token, ok := ctx.Value(AkeylessToken).(string)
+	if !ok {
+		return ErrTokenNotExists
 	}
 	}
 	if strings.HasPrefix(token, "u-") {
 	if strings.HasPrefix(token, "u-") {
-		body.UidToken = &token
+		t.SetUidToken(token)
 	} else {
 	} else {
-		body.Token = &token
+		t.SetToken(token)
+	}
+	return nil
+}
+
+func (a *akeylessBase) DescribeItem(ctx context.Context, itemName string) (*akeyless.Item, error) {
+	body := akeyless.DescribeItem{
+		Name: itemName,
+	}
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return nil, err
 	}
 	}
 	gsvOut, res, err := a.RestAPI.DescribeItem(ctx).Body(body).Execute()
 	gsvOut, res, err := a.RestAPI.DescribeItem(ctx).Body(body).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMDescribeItem, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMDescribeItem, err)
-	if err != nil {
-		if errors.As(err, &apiErr) {
-			var item *Item
-			err = json.Unmarshal(apiErr.Body(), &item)
-			if err != nil {
-				return nil, fmt.Errorf("can't describe item: %v, error: %v", itemName, string(apiErr.Body()))
-			}
-		} else {
-			return nil, fmt.Errorf("can't describe item: %w", err)
+	if errors.As(err, &apiErr) {
+		var item *Item
+		err = json.Unmarshal(apiErr.Body(), &item)
+		if err != nil {
+			return nil, fmt.Errorf("can't describe item: %v, error: %v", itemName, string(apiErr.Body()))
 		}
 		}
 	}
 	}
+	if err != nil {
+		return nil, fmt.Errorf("can't describe item: %w", err)
+	}
 	defer res.Body.Close()
 	defer res.Body.Close()
 
 
 	return &gsvOut, nil
 	return &gsvOut, nil
 }
 }
 
 
-func (a *akeylessBase) GetCertificate(ctx context.Context, certificateName, token string, version int32) (string, error) {
+func (a *akeylessBase) GetCertificate(ctx context.Context, certificateName string, version int32) (string, error) {
 	body := akeyless.GetCertificateValue{
 	body := akeyless.GetCertificateValue{
 		Name:    certificateName,
 		Name:    certificateName,
 		Version: &version,
 		Version: &version,
 	}
 	}
-	if strings.HasPrefix(token, "u-") {
-		body.UidToken = &token
-	} else {
-		body.Token = &token
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return "", err
 	}
 	}
-
 	gcvOut, res, err := a.RestAPI.GetCertificateValue(ctx).Body(body).Execute()
 	gcvOut, res, err := a.RestAPI.GetCertificateValue(ctx).Body(body).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetCertificateValue, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetCertificateValue, err)
+	if errors.As(err, &apiErr) {
+		return "", fmt.Errorf("can't get certificate value: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return "", fmt.Errorf("can't get certificate value: %v", string(apiErr.Body()))
-		}
 		return "", fmt.Errorf("can't get certificate value: %w", err)
 		return "", fmt.Errorf("can't get certificate value: %w", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
@@ -160,28 +176,25 @@ func (a *akeylessBase) GetCertificate(ctx context.Context, certificateName, toke
 	return string(out), nil
 	return string(out), nil
 }
 }
 
 
-func (a *akeylessBase) GetRotatedSecrets(ctx context.Context, secretName, token string, version int32) (string, error) {
+func (a *akeylessBase) GetRotatedSecrets(ctx context.Context, secretName string, version int32) (string, error) {
 	body := akeyless.GetRotatedSecretValue{
 	body := akeyless.GetRotatedSecretValue{
 		Names:   secretName,
 		Names:   secretName,
 		Version: &version,
 		Version: &version,
 	}
 	}
-	if strings.HasPrefix(token, "u-") {
-		body.UidToken = &token
-	} else {
-		body.Token = &token
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return "", err
 	}
 	}
-
 	gsvOut, res, err := a.RestAPI.GetRotatedSecretValue(ctx).Body(body).Execute()
 	gsvOut, res, err := a.RestAPI.GetRotatedSecretValue(ctx).Body(body).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetRotatedSecretValue, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetRotatedSecretValue, err)
+	if errors.As(err, &apiErr) {
+		return "", fmt.Errorf("can't get rotated secret value: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return "", fmt.Errorf("can't get rotated secret value: %v", string(apiErr.Body()))
-		}
 		return "", fmt.Errorf("can't get rotated secret value: %w", err)
 		return "", fmt.Errorf("can't get rotated secret value: %w", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
-
 	valI, ok := gsvOut["value"]
 	valI, ok := gsvOut["value"]
+	var out []byte
 	if ok {
 	if ok {
 		val, convert := valI.(map[string]any)
 		val, convert := valI.(map[string]any)
 		if !convert {
 		if !convert {
@@ -190,72 +203,56 @@ func (a *akeylessBase) GetRotatedSecrets(ctx context.Context, secretName, token
 		if _, ok := val["payload"]; ok {
 		if _, ok := val["payload"]; ok {
 			return fmt.Sprintf("%v", val["payload"]), nil
 			return fmt.Sprintf("%v", val["payload"]), nil
 		} else if _, ok := val["target_value"]; ok {
 		} else if _, ok := val["target_value"]; ok {
-			out, err := json.Marshal(val["target_value"])
-			if err != nil {
-				return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
-			}
-			return string(out), nil
+			out, err = json.Marshal(val["target_value"])
 		} else {
 		} else {
-			out, err := json.Marshal(val)
-			if err != nil {
-				return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
-			}
-			return string(out), nil
+			out, err = json.Marshal(val)
 		}
 		}
+	} else {
+		out, err = json.Marshal(gsvOut)
 	}
 	}
-	out, err := json.Marshal(gsvOut)
 	if err != nil {
 	if err != nil {
 		return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
 		return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
 	}
 	}
 	return string(out), nil
 	return string(out), nil
 }
 }
 
 
-func (a *akeylessBase) GetDynamicSecrets(ctx context.Context, secretName, token string) (string, error) {
+func (a *akeylessBase) GetDynamicSecrets(ctx context.Context, secretName string) (string, error) {
 	body := akeyless.GetDynamicSecretValue{
 	body := akeyless.GetDynamicSecretValue{
 		Name: secretName,
 		Name: secretName,
 	}
 	}
-	if strings.HasPrefix(token, "u-") {
-		body.UidToken = &token
-	} else {
-		body.Token = &token
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return "", err
 	}
 	}
-
 	gsvOut, res, err := a.RestAPI.GetDynamicSecretValue(ctx).Body(body).Execute()
 	gsvOut, res, err := a.RestAPI.GetDynamicSecretValue(ctx).Body(body).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetDynamicSecretValue, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetDynamicSecretValue, err)
+	if errors.As(err, &apiErr) {
+		return "", fmt.Errorf("can't get dynamic secret value: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return "", fmt.Errorf("can't get dynamic secret value: %v", string(apiErr.Body()))
-		}
 		return "", fmt.Errorf("can't get dynamic secret value: %w", err)
 		return "", fmt.Errorf("can't get dynamic secret value: %w", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
-
 	out, err := json.Marshal(gsvOut)
 	out, err := json.Marshal(gsvOut)
 	if err != nil {
 	if err != nil {
 		return "", fmt.Errorf("can't marshal dynamic secret value: %w", err)
 		return "", fmt.Errorf("can't marshal dynamic secret value: %w", err)
 	}
 	}
-
 	return string(out), nil
 	return string(out), nil
 }
 }
 
 
-func (a *akeylessBase) GetStaticSecret(ctx context.Context, secretName, token string, version int32) (string, error) {
-	gsvBody := akeyless.GetSecretValue{
+func (a *akeylessBase) GetStaticSecret(ctx context.Context, secretName string, version int32) (string, error) {
+	body := akeyless.GetSecretValue{
 		Names:   []string{secretName},
 		Names:   []string{secretName},
 		Version: &version,
 		Version: &version,
 	}
 	}
-
-	if strings.HasPrefix(token, "u-") {
-		gsvBody.UidToken = &token
-	} else {
-		gsvBody.Token = &token
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return "", err
 	}
 	}
-
-	gsvOut, res, err := a.RestAPI.GetSecretValue(ctx).Body(gsvBody).Execute()
+	gsvOut, res, err := a.RestAPI.GetSecretValue(ctx).Body(body).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetSecretValue, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetSecretValue, err)
+	if errors.As(err, &apiErr) {
+		return "", fmt.Errorf("can't get secret value: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return "", fmt.Errorf("can't get secret value: %v", string(apiErr.Body()))
-		}
 		return "", fmt.Errorf("can't get secret value: %w", err)
 		return "", fmt.Errorf("can't get secret value: %w", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
@@ -263,7 +260,6 @@ func (a *akeylessBase) GetStaticSecret(ctx context.Context, secretName, token st
 	if !ok {
 	if !ok {
 		return "", fmt.Errorf("can't get secret: %v", secretName)
 		return "", fmt.Errorf("can't get secret: %v", secretName)
 	}
 	}
-
 	return val, nil
 	return val, nil
 }
 }
 
 
@@ -284,31 +280,27 @@ func (a *akeylessBase) getCloudID(provider, accTypeParam string) (string, error)
 	return cloudID, err
 	return cloudID, err
 }
 }
 
 
-func (a *akeylessBase) ListSecrets(ctx context.Context, path, tag, token string) ([]string, error) {
+func (a *akeylessBase) ListSecrets(ctx context.Context, path, tag string) ([]string, error) {
 	secretTypes := &[]string{"static-secret", "dynamic-secret", "rotated-secret"}
 	secretTypes := &[]string{"static-secret", "dynamic-secret", "rotated-secret"}
 	MinimalView := true
 	MinimalView := true
 	if tag != "" {
 	if tag != "" {
 		MinimalView = false
 		MinimalView = false
 	}
 	}
-	gsvBody := akeyless.ListItems{
+	body := akeyless.ListItems{
 		Filter:      &path,
 		Filter:      &path,
 		Type:        secretTypes,
 		Type:        secretTypes,
 		MinimalView: &MinimalView,
 		MinimalView: &MinimalView,
 		Tag:         &tag,
 		Tag:         &tag,
 	}
 	}
-
-	if strings.HasPrefix(token, "u-") {
-		gsvBody.UidToken = &token
-	} else {
-		gsvBody.Token = &token
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return nil, err
 	}
 	}
-
-	lipOut, res, err := a.RestAPI.ListItems(ctx).Body(gsvBody).Execute()
+	lipOut, res, err := a.RestAPI.ListItems(ctx).Body(body).Execute()
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMListItems, err)
 	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMListItems, err)
+	if errors.As(err, &apiErr) {
+		return nil, fmt.Errorf("can't get secrets list: %v", string(apiErr.Body()))
+	}
 	if err != nil {
 	if err != nil {
-		if errors.As(err, &apiErr) {
-			return nil, fmt.Errorf("can't get secrets list: %v", string(apiErr.Body()))
-		}
 		return nil, fmt.Errorf("error on get secrets list: %w", err)
 		return nil, fmt.Errorf("error on get secrets list: %w", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
@@ -325,6 +317,48 @@ func (a *akeylessBase) ListSecrets(ctx context.Context, path, tag, token string)
 	return listNames, nil
 	return listNames, nil
 }
 }
 
 
+func (a *akeylessBase) CreateSecret(ctx context.Context, remoteKey, data string) error {
+	body := akeyless.CreateSecret{
+		Name:  remoteKey,
+		Value: data,
+		Tags:  &[]string{ExtSecretManagedTag},
+	}
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return err
+	}
+	_, res, err := a.RestAPI.CreateSecret(ctx).Body(body).Execute()
+	defer res.Body.Close()
+	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMCreateSecret, err)
+	return err
+}
+
+func (a *akeylessBase) UpdateSecret(ctx context.Context, remoteKey, data string) error {
+	body := akeyless.UpdateSecretVal{
+		Name:  remoteKey,
+		Value: data,
+	}
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return err
+	}
+	_, res, err := a.RestAPI.UpdateSecretVal(ctx).Body(body).Execute()
+	defer res.Body.Close()
+	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMUpdateSecretVal, err)
+	return err
+}
+
+func (a *akeylessBase) DeleteSecret(ctx context.Context, remoteKey string) error {
+	body := akeyless.DeleteItem{
+		Name: remoteKey,
+	}
+	if err := SetBodyToken(&body, ctx); err != nil {
+		return err
+	}
+	_, res, err := a.RestAPI.DeleteItem(ctx).Body(body).Execute()
+	defer res.Body.Close()
+	metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMDeleteItem, err)
+	return err
+}
+
 func (a *akeylessBase) getK8SServiceAccountJWT(ctx context.Context, kubernetesAuth *esv1beta1.AkeylessKubernetesAuth) (string, error) {
 func (a *akeylessBase) getK8SServiceAccountJWT(ctx context.Context, kubernetesAuth *esv1beta1.AkeylessKubernetesAuth) (string, error) {
 	if kubernetesAuth != nil {
 	if kubernetesAuth != nil {
 		if kubernetesAuth.ServiceAccountRef != nil {
 		if kubernetesAuth.ServiceAccountRef != nil {

+ 208 - 36
pkg/provider/akeyless/akeyless_test.go

@@ -18,40 +18,81 @@ import (
 	"context"
 	"context"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"reflect"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
+	"github.com/akeylesslabs/akeyless-go/v3"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	fakeakeyless "github.com/external-secrets/external-secrets/pkg/provider/akeyless/fake"
 	fakeakeyless "github.com/external-secrets/external-secrets/pkg/provider/akeyless/fake"
+	testingfake "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
 )
 )
 
 
 type akeylessTestCase struct {
 type akeylessTestCase struct {
+	testName       string
 	mockClient     *fakeakeyless.AkeylessMockClient
 	mockClient     *fakeakeyless.AkeylessMockClient
 	apiInput       *fakeakeyless.Input
 	apiInput       *fakeakeyless.Input
 	apiOutput      *fakeakeyless.Output
 	apiOutput      *fakeakeyless.Output
 	ref            *esv1beta1.ExternalSecretDataRemoteRef
 	ref            *esv1beta1.ExternalSecretDataRemoteRef
+	input          any
+	input2         any
 	expectError    string
 	expectError    string
+	expectedVal    any
 	expectedSecret string
 	expectedSecret string
-	// for testing secretmap
-	expectedData map[string][]byte
 }
 }
 
 
-func makeValidAkeylessTestCase() *akeylessTestCase {
+const fmtExpectedError = "unexpected error: %s, expected: '%s'"
+
+func (a *akeylessTestCase) SetMockClient(c *fakeakeyless.AkeylessMockClient) *akeylessTestCase {
+	a.mockClient = c
+	return a
+}
+
+func (a *akeylessTestCase) SetExpectErr(err string) *akeylessTestCase {
+	a.expectError = err
+	return a
+}
+
+func (a *akeylessTestCase) SetExpectVal(val any) *akeylessTestCase {
+	a.expectedVal = val
+	return a
+}
+
+func (a *akeylessTestCase) SetExpectInput(input any) *akeylessTestCase {
+	a.input = input
+	return a
+}
+
+func (a *akeylessTestCase) SetExpectInput2(input any) *akeylessTestCase {
+	a.input2 = input
+	return a
+}
+
+func makeValidAkeylessTestCase(testName string) *akeylessTestCase {
 	smtc := akeylessTestCase{
 	smtc := akeylessTestCase{
+		testName:       testName,
 		mockClient:     &fakeakeyless.AkeylessMockClient{},
 		mockClient:     &fakeakeyless.AkeylessMockClient{},
 		apiInput:       makeValidInput(),
 		apiInput:       makeValidInput(),
 		ref:            makeValidRef(),
 		ref:            makeValidRef(),
 		apiOutput:      makeValidOutput(),
 		apiOutput:      makeValidOutput(),
 		expectError:    "",
 		expectError:    "",
 		expectedSecret: "",
 		expectedSecret: "",
-		expectedData:   map[string][]byte{},
 	}
 	}
 	smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
 	smtc.mockClient.WithValue(smtc.apiInput, smtc.apiOutput)
 	return &smtc
 	return &smtc
 }
 }
 
 
+func nilProviderTestCase() *akeylessTestCase {
+	return makeValidAkeylessTestCase("nil provider").SetMockClient(nil).SetExpectErr(errUninitalizedAkeylessProvider)
+}
+func failGetTestCase() *akeylessTestCase {
+	return makeValidAkeylessTestCase("fail GetSecret").SetExpectVal(false).SetExpectErr("fail get").
+		SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return "", errors.New("fail get") }))
+}
+
 func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 	return &esv1beta1.ExternalSecretDataRemoteRef{
 	return &esv1beta1.ExternalSecretDataRemoteRef{
 		Key:     "test-secret",
 		Key:     "test-secret",
@@ -75,7 +116,7 @@ func makeValidOutput() *fakeakeyless.Output {
 }
 }
 
 
 func makeValidAkeylessTestCaseCustom(tweaks ...func(smtc *akeylessTestCase)) *akeylessTestCase {
 func makeValidAkeylessTestCaseCustom(tweaks ...func(smtc *akeylessTestCase)) *akeylessTestCase {
-	smtc := makeValidAkeylessTestCase()
+	smtc := makeValidAkeylessTestCase("")
 	for _, fn := range tweaks {
 	for _, fn := range tweaks {
 		fn(smtc)
 		fn(smtc)
 	}
 	}
@@ -114,16 +155,12 @@ func TestAkeylessGetSecret(t *testing.T) {
 	}
 	}
 
 
 	sm := Akeyless{}
 	sm := Akeyless{}
-	for k, v := range successCases {
+	for _, v := range successCases {
 		sm.Client = v.mockClient
 		sm.Client = v.mockClient
 		fmt.Println(*v.ref)
 		fmt.Println(*v.ref)
 		out, err := sm.GetSecret(context.Background(), *v.ref)
 		out, err := sm.GetSecret(context.Background(), *v.ref)
-		if !ErrorContains(err, v.expectError) {
-			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
-		}
-		if string(out) != v.expectedSecret {
-			t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
-		}
+		require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
+		require.Equal(t, string(out), v.expectedSecret)
 	}
 	}
 }
 }
 
 
@@ -160,9 +197,7 @@ func TestValidateStore(t *testing.T) {
 		}
 		}
 
 
 		_, err := provider.ValidateStore(store)
 		_, err := provider.ValidateStore(store)
-		if err != nil {
-			t.Error(err.Error())
-		}
+		require.NoError(t, err)
 	})
 	})
 
 
 	t.Run("k8s auth", func(t *testing.T) {
 	t.Run("k8s auth", func(t *testing.T) {
@@ -186,9 +221,7 @@ func TestValidateStore(t *testing.T) {
 		}
 		}
 
 
 		_, err := provider.ValidateStore(store)
 		_, err := provider.ValidateStore(store)
-		if err != nil {
-			t.Error(err.Error())
-		}
+		require.NoError(t, err)
 	})
 	})
 
 
 	t.Run("bad conf auth", func(t *testing.T) {
 	t.Run("bad conf auth", func(t *testing.T) {
@@ -204,9 +237,7 @@ func TestValidateStore(t *testing.T) {
 		}
 		}
 
 
 		_, err := provider.ValidateStore(store)
 		_, err := provider.ValidateStore(store)
-		if err == nil {
-			t.Errorf("expected an error")
-		}
+		require.Error(t, err)
 	})
 	})
 
 
 	t.Run("bad k8s conf auth", func(t *testing.T) {
 	t.Run("bad k8s conf auth", func(t *testing.T) {
@@ -229,9 +260,7 @@ func TestValidateStore(t *testing.T) {
 		}
 		}
 
 
 		_, err := provider.ValidateStore(store)
 		_, err := provider.ValidateStore(store)
-		if err == nil {
-			t.Errorf("expected an error")
-		}
+		require.Error(t, err)
 	})
 	})
 }
 }
 
 
@@ -239,7 +268,7 @@ func TestGetSecretMap(t *testing.T) {
 	// good case: default version & deserialization
 	// good case: default version & deserialization
 	setDeserialization := func(smtc *akeylessTestCase) {
 	setDeserialization := func(smtc *akeylessTestCase) {
 		smtc.apiOutput.Value = `{"foo":"bar"}`
 		smtc.apiOutput.Value = `{"foo":"bar"}`
-		smtc.expectedData["foo"] = []byte("bar")
+		smtc.expectedVal = map[string][]byte{"foo": []byte("bar")}
 	}
 	}
 
 
 	// bad case: invalid json
 	// bad case: invalid json
@@ -250,21 +279,17 @@ func TestGetSecretMap(t *testing.T) {
 
 
 	successCases := []*akeylessTestCase{
 	successCases := []*akeylessTestCase{
 		makeValidAkeylessTestCaseCustom(setDeserialization),
 		makeValidAkeylessTestCaseCustom(setDeserialization),
-		makeValidAkeylessTestCaseCustom(setInvalidJSON),
-		makeValidAkeylessTestCaseCustom(setAPIErr),
-		makeValidAkeylessTestCaseCustom(setNilMockClient),
+		makeValidAkeylessTestCaseCustom(setInvalidJSON).SetExpectVal(map[string][]byte(nil)),
+		makeValidAkeylessTestCaseCustom(setAPIErr).SetExpectVal(map[string][]byte(nil)),
+		makeValidAkeylessTestCaseCustom(setNilMockClient).SetExpectVal(map[string][]byte(nil)),
 	}
 	}
 
 
 	sm := Akeyless{}
 	sm := Akeyless{}
-	for k, v := range successCases {
+	for _, v := range successCases {
 		sm.Client = v.mockClient
 		sm.Client = v.mockClient
 		out, err := sm.GetSecretMap(context.Background(), *v.ref)
 		out, err := sm.GetSecretMap(context.Background(), *v.ref)
-		if !ErrorContains(err, v.expectError) {
-			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
-		}
-		if err == nil && !reflect.DeepEqual(out, v.expectedData) {
-			t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
-		}
+		require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
+		require.Equal(t, v.expectedVal.(map[string][]byte), out)
 	}
 	}
 }
 }
 
 
@@ -277,3 +302,150 @@ func ErrorContains(out error, want string) bool {
 	}
 	}
 	return strings.Contains(out.Error(), want)
 	return strings.Contains(out.Error(), want)
 }
 }
+
+func TestSecretExists(t *testing.T) {
+	testCases := []*akeylessTestCase{
+		nilProviderTestCase().SetExpectVal(false),
+		makeValidAkeylessTestCase("no secret").SetExpectVal(false).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return "", ErrItemNotExists })),
+		failGetTestCase(),
+		makeValidAkeylessTestCase("success without property").SetExpectVal(true).SetExpectInput(&testingfake.PushSecretData{Property: ""}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return "my secret", nil })),
+		makeValidAkeylessTestCase("fail unmarshal").SetExpectVal(false).SetExpectErr("invalid character 'd' looking for beginning of value").SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return "daenerys", nil })),
+		makeValidAkeylessTestCase("no property").SetExpectVal(false).SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return `{"propa": "a"}`, nil })),
+		makeValidAkeylessTestCase("success with property").SetExpectVal(true).SetExpectInput(&testingfake.PushSecretData{Property: "prop"}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return `{"prop": "a"}`, nil })),
+	}
+
+	sm := Akeyless{}
+	t.Parallel()
+	for _, v := range testCases {
+		t.Run(v.testName, func(t *testing.T) {
+			sm.Client = v.mockClient
+			if v.input == nil {
+				v.input = &testingfake.PushSecretData{}
+			}
+			out, err := sm.SecretExists(context.Background(), v.input.(esv1beta1.PushSecretRemoteRef))
+			require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
+			require.Equal(t, out, v.expectedVal.(bool))
+		})
+	}
+}
+
+func TestPushSecret(t *testing.T) {
+	testCases := []*akeylessTestCase{
+		nilProviderTestCase(),
+		failGetTestCase(),
+		makeValidAkeylessTestCase("fail unmarshal").SetExpectErr("invalid character 'm' looking for beginning of value").
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return "morgoth", nil })),
+		makeValidAkeylessTestCase("create new secret").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return "", ErrItemNotExists }).
+				SetCreateSecretFn(func(ctx context.Context, remoteKey string, data string) error {
+					if data != `{"test":"test"}` {
+						return errors.New("secret is not good")
+					}
+					return nil
+				})),
+		makeValidAkeylessTestCase("update secret").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test2": []byte("test2")}}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return `{"test2":"untest"}`, nil }).
+				SetUpdateSecretFn(func(ctx context.Context, remoteKey string, data string) error {
+					if data != `{"test2":"test2"}` {
+						return errors.New("secret is not good")
+					}
+					return nil
+				})),
+		makeValidAkeylessTestCase("shouldnt update").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return `{"test":"test"}`, nil })),
+		makeValidAkeylessTestCase("merge secret maps").SetExpectInput(&corev1.Secret{Data: map[string][]byte{"test": []byte("test")}}).
+			SetExpectInput2(&testingfake.PushSecretData{Property: "test", SecretKey: "test"}).
+			SetMockClient(fakeakeyless.New().SetGetSecretFn(func(secretName string, version int32) (string, error) { return `{"test2":"test2"}`, nil }).
+				SetUpdateSecretFn(func(ctx context.Context, remoteKey string, data string) error {
+					expected := `{"test":"test","test2":"test2"}`
+					if data != expected {
+						return fmt.Errorf("secret %s expected %s", data, expected)
+					}
+					return nil
+				})),
+	}
+
+	sm := Akeyless{}
+	t.Parallel()
+	for _, v := range testCases {
+		t.Run(v.testName, func(t *testing.T) {
+			sm.Client = v.mockClient
+			if v.input == nil {
+				v.input = &corev1.Secret{}
+			}
+			if v.input2 == nil {
+				v.input2 = &testingfake.PushSecretData{}
+			}
+			err := sm.PushSecret(context.Background(), v.input.(*corev1.Secret), v.input2.(esv1beta1.PushSecretData))
+			require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
+		})
+	}
+}
+
+func TestDeleteSecret(t *testing.T) {
+	testCases := []*akeylessTestCase{
+		nilProviderTestCase(),
+		makeValidAkeylessTestCase("fail describe").SetExpectErr("err desc").
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) { return nil, errors.New("err desc") })),
+		makeValidAkeylessTestCase("no such item").
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) { return nil, nil })),
+		makeValidAkeylessTestCase("tags nil").
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) { return &akeyless.Item{}, nil })),
+		makeValidAkeylessTestCase("no external secret managed tags").
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) {
+				return &akeyless.Item{ItemTags: &[]string{"some-random-tag"}}, nil
+			})),
+		makeValidAkeylessTestCase("delete whole secret").SetExpectInput(&testingfake.PushSecretData{RemoteKey: "42"}).
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) {
+				return &akeyless.Item{ItemTags: &[]string{ExtSecretManagedTag}}, nil
+			}).SetDeleteSecretFn(func(ctx context.Context, remoteKey string) error {
+				if remoteKey != "42" {
+					return fmt.Errorf("remote key %s expected %s", remoteKey, "42")
+				}
+				return nil
+			})),
+		makeValidAkeylessTestCase("delete property of secret").SetExpectInput(&testingfake.PushSecretData{Property: "Foo"}).
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) {
+				return &akeyless.Item{ItemTags: &[]string{ExtSecretManagedTag}}, nil
+			}).SetGetSecretFn(func(secretName string, version int32) (string, error) {
+				return `{"Dio": "Brando", "Foo": "Fighters"}`, nil
+			}).
+				SetUpdateSecretFn(func(ctx context.Context, remoteKey string, data string) error {
+					expected := `{"Dio":"Brando"}`
+					if data != expected {
+						return fmt.Errorf("secret %s expected %s", data, expected)
+					}
+					return nil
+				})),
+		makeValidAkeylessTestCase("delete secret if one property left").SetExpectInput(&testingfake.PushSecretData{RemoteKey: "Rings", Property: "Annatar"}).
+			SetMockClient(fakeakeyless.New().SetDescribeItemFn(func(ctx context.Context, itemName string) (*akeyless.Item, error) {
+				return &akeyless.Item{ItemTags: &[]string{ExtSecretManagedTag}}, nil
+			}).SetGetSecretFn(func(secretName string, version int32) (string, error) {
+				return `{"Annatar": "The Lord of Gifts"}`, nil
+			}).
+				SetDeleteSecretFn(func(ctx context.Context, remoteKey string) error {
+					if remoteKey != "Rings" {
+						return fmt.Errorf("remote key %s expected %s", remoteKey, "Annatar")
+					}
+					return nil
+				})),
+	}
+
+	sm := Akeyless{}
+	t.Parallel()
+	for _, v := range testCases {
+		t.Run(v.testName, func(t *testing.T) {
+			sm.Client = v.mockClient
+			if v.input == nil {
+				v.input = &testingfake.PushSecretData{}
+			}
+			err := sm.DeleteSecret(context.Background(), v.input.(esv1beta1.PushSecretData))
+			require.Truef(t, ErrorContains(err, v.expectError), fmtExpectedError, err, v.expectError)
+		})
+	}
+}

+ 2 - 2
pkg/provider/akeyless/auth.go

@@ -38,7 +38,7 @@ func (a *akeylessBase) TokenFromSecretRef(ctx context.Context) (string, error) {
 
 
 	if prov.Auth.KubernetesAuth != nil {
 	if prov.Auth.KubernetesAuth != nil {
 		auth := prov.Auth.KubernetesAuth
 		auth := prov.Auth.KubernetesAuth
-		return a.GetToken(auth.AccessID, "k8s", auth.K8sConfName, auth)
+		return a.GetToken(ctx, auth.AccessID, "k8s", auth.K8sConfName, auth)
 	}
 	}
 
 
 	accessID, err := resolvers.SecretKeyRef(
 	accessID, err := resolvers.SecretKeyRef(
@@ -79,5 +79,5 @@ func (a *akeylessBase) TokenFromSecretRef(ctx context.Context) (string, error) {
 		return "", errors.New(errMissingAKID)
 		return "", errors.New(errMissingAKID)
 	}
 	}
 
 
-	return a.GetToken(accessID, accessType, accessTypeParam, prov.Auth.KubernetesAuth)
+	return a.GetToken(ctx, accessID, accessType, accessTypeParam, prov.Auth.KubernetesAuth)
 }
 }

+ 56 - 5
pkg/provider/akeyless/fake/fake.go

@@ -16,27 +16,78 @@ package fake
 
 
 import (
 import (
 	"context"
 	"context"
+
+	akeyless "github.com/akeylesslabs/akeyless-go/v3"
 )
 )
 
 
 type AkeylessMockClient struct {
 type AkeylessMockClient struct {
-	getSecret func(secretName, token string, version int32) (string, error)
+	getSecret    func(secretName string, version int32) (string, error)
+	createSecret func(ctx context.Context, remoteKey, data string) error
+	updateSecret func(ctx context.Context, remoteKey, data string) error
+	deleteSecret func(ctx context.Context, remoteKey string) error
+	describeItem func(ctx context.Context, itemName string) (*akeyless.Item, error)
+}
+
+func New() *AkeylessMockClient {
+	return &AkeylessMockClient{}
+}
+
+func (mc *AkeylessMockClient) SetGetSecretFn(f func(secretName string, version int32) (string, error)) *AkeylessMockClient {
+	mc.getSecret = f
+	return mc
+}
+
+func (mc *AkeylessMockClient) SetCreateSecretFn(f func(ctx context.Context, remoteKey, data string) error) *AkeylessMockClient {
+	mc.createSecret = f
+	return mc
+}
+
+func (mc *AkeylessMockClient) SetUpdateSecretFn(f func(ctx context.Context, remoteKey, data string) error) *AkeylessMockClient {
+	mc.updateSecret = f
+	return mc
+}
+
+func (mc *AkeylessMockClient) SetDeleteSecretFn(f func(ctx context.Context, remoteKey string) error) *AkeylessMockClient {
+	mc.deleteSecret = f
+	return mc
+}
+
+func (mc *AkeylessMockClient) SetDescribeItemFn(f func(ctx context.Context, itemName string) (*akeyless.Item, error)) *AkeylessMockClient {
+	mc.describeItem = f
+	return mc
+}
+
+func (mc *AkeylessMockClient) CreateSecret(ctx context.Context, remoteKey, data string) error {
+	return mc.createSecret(ctx, remoteKey, data)
+}
+
+func (mc *AkeylessMockClient) DeleteSecret(ctx context.Context, remoteKey string) error {
+	return mc.deleteSecret(ctx, remoteKey)
+}
+
+func (mc *AkeylessMockClient) DescribeItem(ctx context.Context, itemName string) (*akeyless.Item, error) {
+	return mc.describeItem(ctx, itemName)
+}
+
+func (mc *AkeylessMockClient) UpdateSecret(ctx context.Context, remoteKey, data string) error {
+	return mc.updateSecret(ctx, remoteKey, data)
 }
 }
 
 
 func (mc *AkeylessMockClient) TokenFromSecretRef(_ context.Context) (string, error) {
 func (mc *AkeylessMockClient) TokenFromSecretRef(_ context.Context) (string, error) {
 	return "newToken", nil
 	return "newToken", nil
 }
 }
 
 
-func (mc *AkeylessMockClient) GetSecretByType(_ context.Context, secretName, token string, version int32) (string, error) {
-	return mc.getSecret(secretName, token, version)
+func (mc *AkeylessMockClient) GetSecretByType(_ context.Context, secretName string, version int32) (string, error) {
+	return mc.getSecret(secretName, version)
 }
 }
 
 
-func (mc *AkeylessMockClient) ListSecrets(_ context.Context, _, _, _ string) ([]string, error) {
+func (mc *AkeylessMockClient) ListSecrets(_ context.Context, _, _ string) ([]string, error) {
 	return nil, nil
 	return nil, nil
 }
 }
 
 
 func (mc *AkeylessMockClient) WithValue(_ *Input, out *Output) {
 func (mc *AkeylessMockClient) WithValue(_ *Input, out *Output) {
 	if mc != nil {
 	if mc != nil {
-		mc.getSecret = func(secretName, token string, version int32) (string, error) {
+		mc.getSecret = func(secretName string, version int32) (string, error) {
 			return out.Value, out.Err
 			return out.Value, out.Err
 		}
 		}
 	}
 	}