Browse Source

:sparkles: Feature/deletion policies (#1914)


Signed-off-by: Gustavo Carvalho <gusfcarvalho@gmail.com>
Gustavo Fernandes de Carvalho 3 years ago
parent
commit
769efdc391

+ 18 - 18
docs/introduction/stability-support.md

@@ -44,24 +44,24 @@ The following table describes the stability level of each provider and who's res
 
 
 The following table show the support for features across different providers.
 The following table show the support for features across different providers.
 
 
-| Provider                  | find by name | find by tags | metadataPolicy Fetch | referent authentication | store validation | push secret |
-|---------------------------|:------------:|:------------:| :------------------: | :---------------------: | :--------------: | :---------: |
-| AWS Secrets Manager       |      x       |      x       |                      |            x            |        x         |             |
-| AWS Parameter Store       |      x       |      x       |                      |            x            |        x         |             |
-| Hashicorp Vault           |      x       |      x       |                      |            x            |        x         |             |
-| GCP Secret Manager        |      x       |      x       |                      |            x            |        x         |             |
-| Azure Keyvault            |      x       |      x       |          x           |            x            |        x         |      x      |
-| Kubernetes                |      x       |      x       |                      |            x            |        x         |             |
-| IBM Cloud Secrets Manager |              |              |                      |                         |        x         |             |
-| Yandex Lockbox            |              |              |                      |                         |        x         |             |
-| Gitlab Variables          |      x       |      x       |                      |                         |        x         |             |
-| Alibaba Cloud KMS         |              |              |                      |                         |        x         |             |
-| Oracle Vault              |              |              |                      |                         |        x         |             |
-| Akeyless                  |              |              |                      |                         |        x         |             |
-| 1Password                 |      x       |              |                      |                         |        x         |             |
-| Generic Webhook           |              |              |                      |                         |                  |             |
-| senhasegura DSM           |              |              |                      |                         |        x         |             |
-| Doppler                   |      x       |              |                      |                         |        x         |             |
+| Provider                  | find by name | find by tags | metadataPolicy Fetch | referent authentication | store validation | push secret | DeletionPolicy Merge/Delete |
+|---------------------------|:------------:|:------------:| :------------------: | :---------------------: | :--------------: | :---------: | :-------------------------: 
+| AWS Secrets Manager       |      x       |      x       |                      |            x            |        x         |     x       |            x                |
+| AWS Parameter Store       |      x       |      x       |                      |            x            |        x         |     x       |            x                |
+| Hashicorp Vault           |      x       |      x       |                      |            x            |        x         |     x       |            x                |
+| GCP Secret Manager        |      x       |      x       |                      |            x            |        x         |     x       |            x                |
+| Azure Keyvault            |      x       |      x       |          x           |            x            |        x         |     x       |            x                |
+| Kubernetes                |      x       |      x       |                      |            x            |        x         |     x       |            x                |
+| IBM Cloud Secrets Manager |              |              |                      |                         |        x         |             |                             |
+| Yandex Lockbox            |              |              |                      |                         |        x         |             |                             |
+| Gitlab Variables          |      x       |      x       |                      |                         |        x         |             |                             |
+| Alibaba Cloud KMS         |              |              |                      |                         |        x         |             |                             |
+| Oracle Vault              |              |              |                      |                         |        x         |             |                             |
+| Akeyless                  |              |              |                      |                         |        x         |             |                             |
+| 1Password                 |      x       |              |                      |                         |        x         |             |                             |
+| Generic Webhook           |              |              |                      |                         |                  |             |                             |
+| senhasegura DSM           |              |              |                      |                         |        x         |             |                             |
+| Doppler                   |      x       |              |                      |                         |        x         |             |                             |
 
 
 
 
 ## Support Policy
 ## Support Policy

+ 14 - 1
pkg/provider/azure/keyvault/keyvault.go

@@ -489,6 +489,7 @@ func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretF
 	checkName := ref.Name != nil && len(ref.Name.RegExp) > 0
 	checkName := ref.Name != nil && len(ref.Name.RegExp) > 0
 
 
 	secretListIter, err := basicClient.GetSecretsComplete(context.Background(), *a.provider.VaultURL, nil)
 	secretListIter, err := basicClient.GetSecretsComplete(context.Background(), *a.provider.VaultURL, nil)
+	err = parseError(err)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -502,6 +503,7 @@ func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretF
 			}
 			}
 
 
 			secretResp, err := basicClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, "")
 			secretResp, err := basicClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, "")
+			err = parseError(err)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -568,6 +570,14 @@ func getProperty(secret, property, key string) ([]byte, error) {
 	return []byte(res.String()), nil
 	return []byte(res.String()), nil
 }
 }
 
 
+func parseError(err error) error {
+	aerr := autorest.DetailedError{}
+	if errors.As(err, &aerr) && aerr.StatusCode == 404 {
+		return esv1beta1.NoSecretError{}
+	}
+	return err
+}
+
 // Implements store.Client.GetSecret Interface.
 // Implements store.Client.GetSecret Interface.
 // Retrieves a secret/Key/Certificate/Tag with the secret name defined in ref.Name
 // Retrieves a secret/Key/Certificate/Tag with the secret name defined in ref.Name
 // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
 // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
@@ -579,6 +589,7 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataR
 		// returns a SecretBundle with the secret value
 		// returns a SecretBundle with the secret value
 		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
 		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
 		secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
+		err = parseError(err)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -590,6 +601,7 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataR
 		// returns a CertBundle. We return CER contents of x509 certificate
 		// returns a CertBundle. We return CER contents of x509 certificate
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
 		certResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		certResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
+		err = parseError(err)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -602,6 +614,7 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataR
 		// azure kv returns only public keys
 		// azure kv returns only public keys
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
 		keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
+		err = parseError(err)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -618,7 +631,7 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataR
 func (a *Azure) getSecretTags(ref esv1beta1.ExternalSecretDataRemoteRef) (map[string]*string, error) {
 func (a *Azure) getSecretTags(ref esv1beta1.ExternalSecretDataRemoteRef) (map[string]*string, error) {
 	_, secretName := getObjType(ref)
 	_, secretName := getObjType(ref)
 	secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 	secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
-
+	err = parseError(err)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 23 - 0
pkg/provider/azure/keyvault/keyvault_test.go

@@ -763,6 +763,26 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 			Value: &secretString,
 			Value: &secretString,
 		}
 		}
 	}
 	}
+	// good case
+	secretNotFound := func(smtc *secretManagerTestCase) {
+		smtc.expectedSecret = ""
+		smtc.apiErr = autorest.DetailedError{StatusCode: 404}
+		smtc.expectError = esv1beta1.NoSecretError{}.Error()
+	}
+
+	certNotFound := func(smtc *secretManagerTestCase) {
+		smtc.expectedSecret = ""
+		smtc.secretName = certName
+		smtc.apiErr = autorest.DetailedError{StatusCode: 404}
+		smtc.expectError = esv1beta1.NoSecretError{}.Error()
+	}
+
+	keyNotFound := func(smtc *secretManagerTestCase) {
+		smtc.expectedSecret = ""
+		smtc.secretName = keyName
+		smtc.apiErr = autorest.DetailedError{StatusCode: 404}
+		smtc.expectError = esv1beta1.NoSecretError{}.Error()
+	}
 
 
 	setSecretStringWithVersion := func(smtc *secretManagerTestCase) {
 	setSecretStringWithVersion := func(smtc *secretManagerTestCase) {
 		smtc.expectedSecret = secretString
 		smtc.expectedSecret = secretString
@@ -1062,6 +1082,9 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(badSecretWithProperty),
 		makeValidSecretManagerTestCaseCustom(badSecretWithProperty),
 		makeValidSecretManagerTestCaseCustom(setPubRSAKey),
 		makeValidSecretManagerTestCaseCustom(setPubRSAKey),
 		makeValidSecretManagerTestCaseCustom(setPubECKey),
 		makeValidSecretManagerTestCaseCustom(setPubECKey),
+		makeValidSecretManagerTestCaseCustom(secretNotFound),
+		makeValidSecretManagerTestCaseCustom(certNotFound),
+		makeValidSecretManagerTestCaseCustom(keyNotFound),
 		makeValidSecretManagerTestCaseCustom(setCertificate),
 		makeValidSecretManagerTestCaseCustom(setCertificate),
 		makeValidSecretManagerTestCaseCustom(badSecretType),
 		makeValidSecretManagerTestCaseCustom(badSecretType),
 		makeValidSecretManagerTestCaseCustom(setSecretWithTag),
 		makeValidSecretManagerTestCaseCustom(setSecretWithTag),

+ 9 - 0
pkg/provider/gcp/secretmanager/client.go

@@ -114,6 +114,14 @@ func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemot
 	return c.smClient.DeleteSecret(ctx, deleteSecretVersionReq)
 	return c.smClient.DeleteSecret(ctx, deleteSecretVersionReq)
 }
 }
 
 
+func parseError(err error) error {
+	var gerr *apierror.APIError
+	if errors.As(err, &gerr) && gerr.GRPCStatus().Code() == codes.NotFound {
+		return esv1beta1.NoSecretError{}
+	}
+	return err
+}
+
 // PushSecret pushes a kubernetes secret key into gcp provider Secret.
 // PushSecret pushes a kubernetes secret key into gcp provider Secret.
 func (c *Client) PushSecret(ctx context.Context, payload []byte, remoteRef esv1beta1.PushRemoteRef) error {
 func (c *Client) PushSecret(ctx context.Context, payload []byte, remoteRef esv1beta1.PushRemoteRef) error {
 	createSecretReq := &secretmanagerpb.CreateSecretRequest{
 	createSecretReq := &secretmanagerpb.CreateSecretRequest{
@@ -325,6 +333,7 @@ func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
 		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", c.store.ProjectID, ref.Key, version),
 		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", c.store.ProjectID, ref.Key, version),
 	}
 	}
 	result, err := c.smClient.AccessSecretVersion(ctx, req)
 	result, err := c.smClient.AccessSecretVersion(ctx, req)
+	err = parseError(err)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf(errClientGetSecretAccess, err)
 		return nil, fmt.Errorf(errClientGetSecretAccess, err)
 	}
 	}

+ 8 - 0
pkg/provider/gcp/secretmanager/client_test.go

@@ -113,6 +113,13 @@ func TestSecretManagerGetSecret(t *testing.T) {
 		smtc.apiOutput.Payload.Data = []byte("testtesttest")
 		smtc.apiOutput.Payload.Data = []byte("testtesttest")
 		smtc.expectedSecret = "testtesttest"
 		smtc.expectedSecret = "testtesttest"
 	}
 	}
+	secretNotFound := func(smtc *secretManagerTestCase) {
+		fErr := status.Error(codes.NotFound, "failed")
+		notFoundError, _ := apierror.FromError(fErr)
+		smtc.apiErr = notFoundError
+		smtc.expectedSecret = ""
+		smtc.expectError = esv1beta1.NoSecretErr.Error()
+	}
 	// good case: with a dot in the key name
 	// good case: with a dot in the key name
 	setDotRef := func(smtc *secretManagerTestCase) {
 	setDotRef := func(smtc *secretManagerTestCase) {
 		smtc.ref = &esv1beta1.ExternalSecretDataRemoteRef{
 		smtc.ref = &esv1beta1.ExternalSecretDataRemoteRef{
@@ -164,6 +171,7 @@ func TestSecretManagerGetSecret(t *testing.T) {
 	successCases := []*secretManagerTestCase{
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCase(),
 		makeValidSecretManagerTestCase(),
 		makeValidSecretManagerTestCaseCustom(setSecretString),
 		makeValidSecretManagerTestCaseCustom(setSecretString),
+		makeValidSecretManagerTestCaseCustom(secretNotFound),
 		makeValidSecretManagerTestCaseCustom(setCustomVersion),
 		makeValidSecretManagerTestCaseCustom(setCustomVersion),
 		makeValidSecretManagerTestCaseCustom(setAPIErr),
 		makeValidSecretManagerTestCaseCustom(setAPIErr),
 		makeValidSecretManagerTestCaseCustom(setCustomRef),
 		makeValidSecretManagerTestCaseCustom(setCustomRef),

+ 4 - 0
pkg/provider/kubernetes/client.go

@@ -18,6 +18,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 
 
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	labels "k8s.io/apimachinery/pkg/labels"
 	labels "k8s.io/apimachinery/pkg/labels"
 
 
@@ -60,6 +61,9 @@ func (c *Client) PushSecret(ctx context.Context, value []byte, remoteRef esv1bet
 
 
 func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
 	secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
+	if apierrors.IsNotFound(err) {
+		return nil, esv1beta1.NoSecretError{}
+	}
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 26 - 1
pkg/provider/kubernetes/client_test.go

@@ -21,7 +21,9 @@ import (
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 )
 )
@@ -34,6 +36,7 @@ type fakeClient struct {
 	t                   *testing.T
 	t                   *testing.T
 	secretMap           map[string]corev1.Secret
 	secretMap           map[string]corev1.Secret
 	expectedListOptions metav1.ListOptions
 	expectedListOptions metav1.ListOptions
+	err                 error
 }
 }
 
 
 func (fk fakeClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error) {
 func (fk fakeClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error) {
@@ -42,7 +45,7 @@ func (fk fakeClient) Get(ctx context.Context, name string, opts metav1.GetOption
 	if !ok {
 	if !ok {
 		return nil, errors.New(errSomethingWentWrong)
 		return nil, errors.New(errSomethingWentWrong)
 	}
 	}
-	return &secret, nil
+	return &secret, fk.err
 }
 }
 
 
 func (fk fakeClient) List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error) {
 func (fk fakeClient) List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error) {
@@ -68,6 +71,28 @@ func TestGetSecret(t *testing.T) {
 		want    []byte
 		want    []byte
 		wantErr bool
 		wantErr bool
 	}{
 	}{
+		{
+			name: "secretNotFound",
+			fields: fields{
+				Client: fakeClient{
+					t: t,
+					secretMap: map[string]corev1.Secret{
+						"mysec": {
+							Data: map[string][]byte{
+								"token": []byte(`foobar`),
+							},
+						},
+					},
+					err: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "Secret"}, "secret"),
+				},
+				Namespace: "default",
+			},
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "mysec",
+				Property: "token",
+			},
+			wantErr: true,
+		},
 		{
 		{
 			name: "err GetSecretMap",
 			name: "err GetSecretMap",
 			fields: fields{
 			fields: fields{