Browse Source

merge main into refactor/cognitifeComplexity

Arthur 4 years ago
parent
commit
dc8398b6c1

+ 3 - 0
.github/PAUL.yaml

@@ -1,5 +1,8 @@
 maintainers:
 - knelasevero
+- gusfcarvalho
+- sebagomez
+- serdarkalayci
 - riccardomc
 - iamcaleberic
 - jonatasbaldin

+ 3 - 6
.github/workflows/ci.yml

@@ -21,6 +21,9 @@ env:
   # a step 'if env.GHCR_USERNAME' != ""', so we copy these to succinctly test whether
   # credentials have been provided before trying to run steps that need them.
   GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }}
+  
+  # Sonar
+  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
 
 jobs:
   detect-noop:
@@ -175,12 +178,6 @@ jobs:
           export KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT=true
           make test
 
-      - name: Publish Unit Test Coverage
-        uses: codecov/codecov-action@v2.0.3
-        with:
-          flags: unittests
-          file: ./cover.out
-
 
   publish-artifacts:
     runs-on: ubuntu-18.04

+ 1 - 1
.github/workflows/e2e.yml

@@ -149,7 +149,7 @@ jobs:
         make test.e2e
 
     # Update check run called "integration-fork"
-    - uses: actions/github-script@v4.1
+    - uses: actions/github-script@v5
       id: update-check-run
       if: ${{ always() }}
       env:

+ 1 - 1
.github/workflows/ok-to-test.yml

@@ -32,4 +32,4 @@ jobs:
         reaction-token: ${{ secrets.GITHUB_TOKEN }}
         issue-type: pull-request
         commands: ok-to-test
-        permission: write
+        permission: maintain

+ 6 - 6
README.md

@@ -37,12 +37,12 @@ Multiple people and organizations are joining efforts to create a single Externa
 
 | Provider                                                            | Stability |                  Contact                   |
 | ------------------------------------------------------------------- | :-------: | :----------------------------------------: |
-| [Azure KV](https://external-secrets.io/provider-azure-key-vault/)   |   alpha   | @ahmedmus-1A @asnowfix @ncourbet-1A @1A-mj |
-| [IBM SM](https://external-secrets.io/provider-ibm-secrets-manager/) |   alpha   |   @knelasevero @sebagomez @ricardoptcosta  |
-| [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/) |   alpha   |   @AndreyZamyslov @knelasevero          |
-| [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) |   alpha   |   @Jabray5          |
-| Alibaba Cloud KMS                                                   |   alpha  | @ElsaChelala                                |
-| [Oracle Vault]( https://external-secrets.io/provider-oracle-vault)  |   alpha  | @KianTigger                                 |
+| [Azure KV](https://external-secrets.io/provider-azure-key-vault/)   |   alpha   | [@ahmedmus-1A](https://github.com/ahmedmus-1A) [@asnowfix](https://github.com/asnowfix) [@ncourbet-1A](https://github.com/ncourbet-1A) [@1A-mj](https://github.com/1A-mj) |
+| [IBM SM](https://external-secrets.io/provider-ibm-secrets-manager/) |   alpha   |   [@knelasevero](https://github.com/knelasevero) [@sebagomez](https://github.com/sebagomez) [@ricardoptcosta](https://github.com/ricardoptcosta)  |
+| [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/) |   alpha   |   [@AndreyZamyslov](https://github.com/AndreyZamyslov) [@knelasevero](https://github.com/knelasevero)          |
+| [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) |   alpha   |   [@Jabray5](https://github.com/Jabray5)          |
+| Alibaba Cloud KMS                                                   |   alpha  | [@ElsaChelala](https://github.com/ElsaChelala)                                |
+| [Oracle Vault]( https://external-secrets.io/provider-oracle-vault)  |   alpha  | [@KianTigger](https://github.com/KianTigger)                                 |
 
 
 ## Documentation

+ 4 - 0
apis/externalsecrets/v1alpha1/externalsecret_types.go

@@ -103,6 +103,10 @@ type ExternalSecretTarget struct {
 	// Template defines a blueprint for the created Secret resource.
 	// +optional
 	Template *ExternalSecretTemplate `json:"template,omitempty"`
+
+	// Immutable defines if the final secret will be immutable
+	// +optional
+	Immutable bool `json:"immutable,omitempty"`
 }
 
 // ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.<key>) and the Provider data.

+ 29 - 0
apis/externalsecrets/v1alpha1/secretstore_vault_types.go

@@ -25,6 +25,31 @@ const (
 	VaultKVStoreV2 VaultKVStoreVersion = "v2"
 )
 
+type CAProviderType string
+
+const (
+	CAProviderTypeSecret    CAProviderType = "Secret"
+	CAProviderTypeConfigMap CAProviderType = "ConfigMap"
+)
+
+// Defines a location to fetch the cert for the vault provider from.
+type CAProvider struct {
+	// The type of provider to use such as "Secret", or "ConfigMap".
+	// +kubebuilder:validation:Enum="Secret";"ConfigMap"
+	Type CAProviderType `json:"type"`
+
+	// The name of the object located at the provider type.
+	Name string `json:"name"`
+
+	// The key the value inside of the provider type to use, only used with "Secret" type
+	// +kubebuilder:validation:Optional
+	Key string `json:"key,omitempty"`
+
+	// The namespace the Provider type is in.
+	// +kubebuilder:default:="Default"
+	Namespace string `json:"namespace"`
+}
+
 // Configures an store to sync secrets using a HashiCorp Vault
 // KV backend.
 type VaultProvider struct {
@@ -59,6 +84,10 @@ type VaultProvider struct {
 	// are used to validate the TLS connection.
 	// +optional
 	CABundle []byte `json:"caBundle,omitempty"`
+
+	// The provider for the CA bundle to use to validate Vault server certificate.
+	// +optional
+	CAProvider *CAProvider `json:"caProvider,omitempty"`
 }
 
 // VaultAuth is the configuration used to authenticate with a Vault server.

+ 20 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -211,6 +211,21 @@ func (in *AzureKVProvider) DeepCopy() *AzureKVProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CAProvider) DeepCopyInto(out *CAProvider) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAProvider.
+func (in *CAProvider) DeepCopy() *CAProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(CAProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ClusterSecretStore) DeepCopyInto(out *ClusterSecretStore) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
@@ -1108,6 +1123,11 @@ func (in *VaultProvider) DeepCopyInto(out *VaultProvider) {
 		*out = make([]byte, len(*in))
 		copy(*out, *in)
 	}
+	if in.CAProvider != nil {
+		in, out := &in.CAProvider, &out.CAProvider
+		*out = new(CAProvider)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultProvider.

+ 2 - 2
deploy/charts/external-secrets/Chart.yaml

@@ -2,8 +2,8 @@ apiVersion: v2
 name: external-secrets
 description: External secret management for Kubernetes
 type: application
-version: "0.3.5"
-appVersion: "v0.3.5"
+version: "0.3.6"
+appVersion: "v0.3.6"
 kubeVersion: ">= 1.11.0-0"
 keywords:
   - kubernetes-external-secrets

+ 1 - 1
deploy/charts/external-secrets/README.md

@@ -4,7 +4,7 @@
 
 [//]: # (README.md generated by gotmpl. DO NOT EDIT.)
 
-![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.3.5](https://img.shields.io/badge/Version-0.3.5-informational?style=flat-square)
+![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.3.6](https://img.shields.io/badge/Version-0.3.6-informational?style=flat-square)
 
 External secret management for Kubernetes
 

+ 28 - 0
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -732,6 +732,34 @@ spec:
                           are used to validate the TLS connection.
                         format: byte
                         type: string
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          Vault server certificate.
+                        properties:
+                          key:
+                            description: The key the value inside of the provider
+                              type to use, only used with "Secret" type
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            type: string
+                          namespace:
+                            default: Default
+                            description: The namespace the Provider type is in.
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - namespace
+                        - type
+                        type: object
                       namespace:
                         description: 'Name of the vault namespace. Namespaces is a
                           set of features within Vault Enterprise that allows Vault

+ 3 - 0
deploy/crds/external-secrets.io_externalsecrets.yaml

@@ -132,6 +132,9 @@ spec:
                     description: CreationPolicy defines rules on how to create the
                       resulting Secret Defaults to 'Owner'
                     type: string
+                  immutable:
+                    description: Immutable defines if the final secret will be immutable
+                    type: boolean
                   name:
                     description: Name defines the name of the Secret resource to be
                       managed This field is immutable Defaults to the .metadata.name

+ 28 - 0
deploy/crds/external-secrets.io_secretstores.yaml

@@ -732,6 +732,34 @@ spec:
                           are used to validate the TLS connection.
                         format: byte
                         type: string
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          Vault server certificate.
+                        properties:
+                          key:
+                            description: The key the value inside of the provider
+                              type to use, only used with "Secret" type
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            type: string
+                          namespace:
+                            default: Default
+                            description: The namespace the Provider type is in.
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - namespace
+                        - type
+                        type: object
                       namespace:
                         description: 'Name of the vault namespace. Namespaces is a
                           set of features within Vault Enterprise that allows Vault

+ 11 - 0
docs/snippets/full-secret-store.yaml

@@ -43,7 +43,18 @@ spec:
       version: "v2"
       # vault enterprise namespace: https://www.vaultproject.io/docs/enterprise/namespaces
       namespace: "a-team"
+      # base64 encoded string of certificate
       caBundle: "..."
+      # Instead of caBundle you can also specify a caProvider
+      # this will retrieve the cert from a Secret or ConfigMap
+      caProvider:
+        # Can be Secret or ConfigMap
+        type: "Secret"
+        # This is optional, if not specified will be 'Default'
+        namespace: "my-cert-secret-namespace"
+        name: "my-cert-secret"
+        key: "cert-key"
+
       auth:
         # static token: https://www.vaultproject.io/docs/auth/token
         tokenSecretRef:

+ 5 - 3
e2e/framework/addon/vault.go

@@ -65,6 +65,8 @@ type Vault struct {
 	AppRolePath   string
 }
 
+const privatePemType = "RSA PRIVATE KEY"
+
 func NewVault(namespace string) *Vault {
 	repo := "hashicorp-" + namespace
 	return &Vault{
@@ -298,7 +300,7 @@ func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []b
 		return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate vault server cert")
 	}
 	serverKeyPem := pem.EncodeToMemory(&pem.Block{
-		Type:  "RSA PRIVATE KEY",
+		Type:  privatePemType,
 		Bytes: x509.MarshalPKCS1PrivateKey(serverKey)},
 	)
 	// gen client ca + certs
@@ -311,7 +313,7 @@ func genVaultCertificates(namespace string) ([]byte, []byte, []byte, []byte, []b
 		return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to generate vault server cert")
 	}
 	clientKeyPem := pem.EncodeToMemory(&pem.Block{
-		Type:  "RSA PRIVATE KEY",
+		Type:  privatePemType,
 		Bytes: x509.MarshalPKCS1PrivateKey(clientKey)},
 	)
 	return serverRootPem, serverPem, serverKeyPem, clientRootPem, clientPem, clientKeyPem, err
@@ -323,7 +325,7 @@ func genVaultJWTKeys() ([]byte, []byte, string, error) {
 		return nil, nil, "", err
 	}
 	privPem := pem.EncodeToMemory(&pem.Block{
-		Type:  "RSA PRIVATE KEY",
+		Type:  privatePemType,
 		Bytes: x509.MarshalPKCS1PrivateKey(key),
 	})
 	pk, err := x509.MarshalPKIXPublicKey(&key.PublicKey)

+ 5 - 3
e2e/suite/aws/provider.go

@@ -42,6 +42,8 @@ type SMProvider struct {
 	framework *framework.Framework
 }
 
+const secretName = "provider-secret"
+
 func newSMProvider(f *framework.Framework, url string) *SMProvider {
 	sess, err := session.NewSessionWithOptions(session.Options{
 		Config: aws.Config{
@@ -82,7 +84,7 @@ func (s *SMProvider) BeforeEach() {
 	By("creating a AWS SM credentials secret")
 	awsCreds := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      "provider-secret",
+			Name:      secretName,
 			Namespace: s.framework.Namespace.Name,
 		},
 		StringData: map[string]string{
@@ -107,11 +109,11 @@ func (s *SMProvider) BeforeEach() {
 					Auth: esv1alpha1.AWSAuth{
 						SecretRef: &esv1alpha1.AWSAuthSecretRef{
 							AccessKeyID: esmeta.SecretKeySelector{
-								Name: "provider-secret",
+								Name: secretName,
 								Key:  "kid",
 							},
 							SecretAccessKey: esmeta.SecretKeySelector{
-								Name: "provider-secret",
+								Name: secretName,
 								Key:  "sak",
 							},
 						},

+ 25 - 1
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -180,13 +180,21 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		log.V(1).Info("skipping refresh", "rv", getResourceVersion(externalSecret))
 		return ctrl.Result{RequeueAfter: refreshInt}, nil
 	}
+	if !shouldReconcile(externalSecret) {
+		log.V(1).Info("stopping reconciling", "rv", getResourceVersion(externalSecret))
+		return ctrl.Result{
+			RequeueAfter: 0,
+			Requeue:      false,
+		}, nil
+	}
 
 	secret := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      secretName,
 			Namespace: externalSecret.Namespace,
 		},
-		Data: make(map[string][]byte),
+		Immutable: &externalSecret.Spec.Target.Immutable,
+		Data:      make(map[string][]byte),
 	}
 
 	mutationFunc := func() error {
@@ -314,6 +322,22 @@ func shouldRefresh(es esv1alpha1.ExternalSecret) bool {
 	return !es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).After(time.Now())
 }
 
+func shouldReconcile(es esv1alpha1.ExternalSecret) bool {
+	if es.Spec.Target.Immutable && hasSyncedCondition(es) {
+		return false
+	}
+	return true
+}
+
+func hasSyncedCondition(es esv1alpha1.ExternalSecret) bool {
+	for _, condition := range es.Status.Conditions {
+		if condition.Reason == "SecretSynced" {
+			return true
+		}
+	}
+	return false
+}
+
 // isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
 func isSecretValid(existingSecret v1.Secret) bool {
 	// if target secret doesn't exist, or annotations as not set, we need to refresh

+ 38 - 1
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -307,7 +307,7 @@ var _ = Describe("ExternalSecret controller", func() {
 			Expect(hasFieldOwnership(
 				secret.ObjectMeta,
 				"external-secrets",
-				fmt.Sprintf("{\"f:data\":{\"f:targetProperty\":{}},\"f:metadata\":{\"f:annotations\":{\"f:%s\":{}}}}", esv1alpha1.AnnotationDataHash)),
+				fmt.Sprintf("{\"f:data\":{\"f:targetProperty\":{}},\"f:immutable\":{},\"f:metadata\":{\"f:annotations\":{\"f:%s\":{}}}}", esv1alpha1.AnnotationDataHash)),
 			).To(BeTrue())
 			Expect(hasFieldOwnership(secret.ObjectMeta, "fake.manager", "{\"f:data\":{\".\":{},\"f:pre-existing-key\":{}},\"f:type\":{}}")).To(BeTrue())
 		}
@@ -1138,6 +1138,43 @@ var _ = Describe("ExternalSecret refresh logic", func() {
 	})
 })
 
+var _ = Describe("Controller Reconcile logic", func() {
+	Context("controller reconcile", func() {
+		It("should reconcile when resource is not synced", func() {
+			Expect(shouldReconcile(esv1alpha1.ExternalSecret{
+				Status: esv1alpha1.ExternalSecretStatus{
+					SyncedResourceVersion: "some resource version",
+					Conditions:            []esv1alpha1.ExternalSecretStatusCondition{{Reason: "NotASecretSynced"}},
+				},
+			})).To(BeTrue())
+		})
+
+		It("should reconcile when secret isn't immutable", func() {
+			Expect(shouldReconcile(esv1alpha1.ExternalSecret{
+				Spec: esv1alpha1.ExternalSecretSpec{
+					Target: esv1alpha1.ExternalSecretTarget{
+						Immutable: false,
+					},
+				},
+			})).To(BeTrue())
+		})
+
+		It("should not reconcile if secret is immutable and has synced condition", func() {
+			Expect(shouldReconcile(esv1alpha1.ExternalSecret{
+				Spec: esv1alpha1.ExternalSecretSpec{
+					Target: esv1alpha1.ExternalSecretTarget{
+						Immutable: true,
+					},
+				},
+				Status: esv1alpha1.ExternalSecretStatus{
+					SyncedResourceVersion: "some resource version",
+					Conditions:            []esv1alpha1.ExternalSecretStatusCondition{{Reason: "SecretSynced"}},
+				},
+			})).To(BeFalse())
+		})
+	})
+})
+
 // CreateNamespace creates a new namespace in the cluster.
 func CreateNamespace(baseName string, c client.Client) (string, error) {
 	genName := fmt.Sprintf("ctrl-test-%v", baseName)

+ 24 - 14
pkg/provider/aws/auth/auth_test.go

@@ -37,7 +37,17 @@ import (
 	fakesess "github.com/external-secrets/external-secrets/pkg/provider/aws/auth/fake"
 )
 
+const (
+	myServiceAcc = "my-service-account"
+	otherNs      = "other-ns"
+)
+
 func TestNewSession(t *testing.T) {
+	const (
+		esNamespace    = "es-namespace"
+		platformTeamNs = "platform-team-ns"
+	)
+
 	rows := []TestSessionRow{
 		{
 			name:      "nil store",
@@ -261,7 +271,7 @@ func TestNewSession(t *testing.T) {
 		},
 		{
 			name:      "ClusterStore should use credentials from a specific namespace",
-			namespace: "es-namespace",
+			namespace: esNamespace,
 			store: &esv1alpha1.ClusterSecretStore{
 				TypeMeta: metav1.TypeMeta{
 					APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
@@ -274,12 +284,12 @@ func TestNewSession(t *testing.T) {
 								SecretRef: &esv1alpha1.AWSAuthSecretRef{
 									AccessKeyID: esmeta.SecretKeySelector{
 										Name:      "onesecret",
-										Namespace: aws.String("platform-team-ns"),
+										Namespace: aws.String(platformTeamNs),
 										Key:       "one",
 									},
 									SecretAccessKey: esmeta.SecretKeySelector{
 										Name:      "onesecret",
-										Namespace: aws.String("platform-team-ns"),
+										Namespace: aws.String(platformTeamNs),
 										Key:       "two",
 									},
 								},
@@ -292,7 +302,7 @@ func TestNewSession(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "onesecret",
-						Namespace: "platform-team-ns",
+						Namespace: platformTeamNs,
 					},
 					Data: map[string][]byte{
 						"one": []byte("1111"),
@@ -306,7 +316,7 @@ func TestNewSession(t *testing.T) {
 		},
 		{
 			name:      "namespace is mandatory when using ClusterStore with SecretKeySelector",
-			namespace: "es-namespace",
+			namespace: esNamespace,
 			store: &esv1alpha1.ClusterSecretStore{
 				TypeMeta: metav1.TypeMeta{
 					APIVersion: esv1alpha1.ClusterSecretStoreKindAPIVersion,
@@ -335,19 +345,19 @@ func TestNewSession(t *testing.T) {
 		},
 		{
 			name:      "jwt auth via cluster secret store",
-			namespace: "es-namespace",
+			namespace: esNamespace,
 			sa: &v1.ServiceAccount{
 				ObjectMeta: metav1.ObjectMeta{
-					Name:      "my-service-account",
-					Namespace: "other-ns",
+					Name:      myServiceAcc,
+					Namespace: otherNs,
 					Annotations: map[string]string{
 						roleARNAnnotation: "my-sa-role",
 					},
 				},
 			},
 			jwtProvider: func(name, namespace, roleArn, region string) (credentials.Provider, error) {
-				assert.Equal(t, "my-service-account", name)
-				assert.Equal(t, "other-ns", namespace)
+				assert.Equal(t, myServiceAcc, name)
+				assert.Equal(t, otherNs, namespace)
 				assert.Equal(t, "my-sa-role", roleArn)
 				return fakesess.CredentialsProvider{
 					RetrieveFunc: func() (credentials.Value, error) {
@@ -372,8 +382,8 @@ func TestNewSession(t *testing.T) {
 							Auth: esv1alpha1.AWSAuth{
 								JWTAuth: &esv1alpha1.AWSJWTAuth{
 									ServiceAccountRef: &esmeta.ServiceAccountSelector{
-										Name:      "my-service-account",
-										Namespace: aws.String("other-ns"),
+										Name:      myServiceAcc,
+										Namespace: aws.String(otherNs),
 									},
 								},
 							},
@@ -424,8 +434,8 @@ func testRow(t *testing.T, row TestSessionRow) {
 	}
 	err := kc.Create(context.Background(), &authv1.TokenRequest{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      "my-service-account",
-			Namespace: "other-ns",
+			Name:      myServiceAcc,
+			Namespace: otherNs,
 		},
 	})
 	assert.Nil(t, err)

+ 5 - 3
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -42,6 +42,8 @@ type secretsManagerTestCase struct {
 	expectedCounter *int
 }
 
+const unexpectedErrorString = "[%d] unexpected error: %s, expected: '%s'"
+
 func makeValidSecretsManagerTestCase() *secretsManagerTestCase {
 	smtc := secretsManagerTestCase{
 		fakeClient:     fakesm.NewClient(),
@@ -174,7 +176,7 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 		}
 		out, err := sm.GetSecret(context.Background(), *v.remoteRef)
 		if !ErrorContains(err, v.expectError) {
-			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
+			t.Errorf(unexpectedErrorString, k, err.Error(), v.expectError)
 		}
 		if err == nil && string(out) != v.expectedSecret {
 			t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
@@ -223,7 +225,7 @@ func TestCaching(t *testing.T) {
 		sm.client = v.fakeClient
 		out, err := sm.GetSecret(context.Background(), *v.remoteRef)
 		if !ErrorContains(err, v.expectError) {
-			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
+			t.Errorf(unexpectedErrorString, k, err.Error(), v.expectError)
 		}
 		if err == nil && string(out) != v.expectedSecret {
 			t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
@@ -269,7 +271,7 @@ func TestGetSecretMap(t *testing.T) {
 		}
 		out, err := sm.GetSecretMap(context.Background(), *v.remoteRef)
 		if !ErrorContains(err, v.expectError) {
-			t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
+			t.Errorf(unexpectedErrorString, k, err.Error(), v.expectError)
 		}
 		if err == nil && !cmp.Equal(out, v.expectedData) {
 			t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)

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

@@ -177,7 +177,7 @@ func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, strin
 		return nil, "", fmt.Errorf("missing clientID/clientSecret in store config")
 	}
 	clusterScoped := false
-	if a.store.GetObjectMeta().String() == esv1alpha1.ClusterSecretStoreKind {
+	if a.store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind {
 		clusterScoped = true
 	}
 	if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {

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

@@ -73,6 +73,14 @@ func TestNewClientNoCreds(t *testing.T) {
 	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
 	tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found")
 	tassert.Nil(t, secretClient)
+	store.TypeMeta.Kind = esv1alpha1.ClusterSecretStoreKind
+	store.TypeMeta.APIVersion = esv1alpha1.ClusterSecretStoreKindAPIVersion
+	ns := "default"
+	store.Spec.Provider.AzureKV.AuthSecretRef.ClientID.Namespace = &ns
+	store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret.Namespace = &ns
+	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	tassert.EqualError(t, err, "could not find secret default/user: secrets \"user\" not found")
+	tassert.Nil(t, secretClient)
 }
 
 const (

+ 5 - 3
pkg/provider/schema/schema_test.go

@@ -26,6 +26,8 @@ import (
 
 type PP struct{}
 
+const shouldBeRegistered = "provider should be registered"
+
 // New constructs a SecretsManager Provider.
 func (p *PP) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
 	return p, nil
@@ -113,7 +115,7 @@ func runTest(t *testing.T, name string, provider *esv1alpha1.SecretStoreProvider
 	}
 	Register(testProvider, secretStore.Spec.Provider)
 	p1, ok := GetProviderByName(name)
-	assert.True(t, ok, "provider should be registered")
+	assert.True(t, ok, shouldBeRegistered)
 	assert.Equal(t, testProvider, p1)
 	p2, err := GetProvider(secretStore)
 	assert.Nil(t, err)
@@ -139,7 +141,7 @@ func TestForceRegister(t *testing.T) {
 		},
 	})
 	p1, ok := GetProviderByName("aws")
-	assert.True(t, ok, "provider should be registered")
+	assert.True(t, ok, shouldBeRegistered)
 	assert.Equal(t, testProvider, p1)
 	p2, err := GetProvider(secretStore)
 	assert.Nil(t, err)
@@ -162,7 +164,7 @@ func TestRegisterGCP(t *testing.T) {
 
 	ForceRegister(testProvider, secretStore.Spec.Provider)
 	p1, ok := GetProviderByName("gcpsm")
-	assert.True(t, ok, "provider should be registered")
+	assert.True(t, ok, shouldBeRegistered)
 	assert.Equal(t, testProvider, p1)
 
 	p2, err := GetProvider(secretStore)

+ 24 - 0
pkg/provider/vault/fake/vault.go

@@ -26,6 +26,10 @@ type MockRawRequestWithContextFn func(ctx context.Context, r *vault.Request) (*v
 
 type MockSetTokenFn func(v string)
 
+type MockTokenFn func() string
+
+type MockClearTokenFn func()
+
 type MockSetNamespaceFn func(namespace string)
 
 func NewMockNewRequestFn(req *vault.Request) MockNewRequestFn {
@@ -57,6 +61,16 @@ func NewSetTokenFn(ofn ...func(v string)) MockSetTokenFn {
 	}
 }
 
+func NewTokenFn(v string) MockTokenFn {
+	return func() string {
+		return v
+	}
+}
+
+func NewClearTokenFn() MockClearTokenFn {
+	return func() {}
+}
+
 func NewSetNamespaceFn() MockSetNamespaceFn {
 	return func(namespace string) {}
 }
@@ -65,6 +79,8 @@ type VaultClient struct {
 	MockNewRequest            MockNewRequestFn
 	MockRawRequestWithContext MockRawRequestWithContextFn
 	MockSetToken              MockSetTokenFn
+	MockToken                 MockTokenFn
+	MockClearToken            MockClearTokenFn
 	MockSetNamespace          MockSetNamespaceFn
 }
 
@@ -80,6 +96,14 @@ func (c *VaultClient) SetToken(v string) {
 	c.MockSetToken(v)
 }
 
+func (c *VaultClient) Token() string {
+	return c.MockToken()
+}
+
+func (c *VaultClient) ClearToken() {
+	c.MockClearToken()
+}
+
 func (c *VaultClient) SetNamespace(namespace string) {
 	c.MockSetNamespace(namespace)
 }

+ 82 - 4
pkg/provider/vault/vault.go

@@ -66,14 +66,21 @@ const (
 
 	errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
 	errSecretKeyFmt  = "cannot find secret data for key: %q"
+	errConfigMapFmt  = "cannot find config map data for key: %q"
 
 	errClientTLSAuth = "error from Client TLS Auth: %q"
+
+	errVaultRevokeToken = "error while revoking token: %w"
+
+	errUnknownCAProvider = "unknown caProvider type given"
 )
 
 type Client interface {
 	NewRequest(method, requestPath string) *vault.Request
 	RawRequestWithContext(ctx context.Context, r *vault.Request) (*vault.Response, error)
 	SetToken(v string)
+	Token() string
+	ClearToken()
 	SetNamespace(namespace string)
 }
 
@@ -156,6 +163,15 @@ func (v *client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecret
 }
 
 func (v *client) Close(ctx context.Context) error {
+	// Revoke the token if we have one set and it wasn't sourced from a TokenSecretRef
+	if v.client.Token() != "" && v.store.Auth.TokenSecretRef == nil {
+		req := v.client.NewRequest(http.MethodPost, "/v1/auth/token/revoke-self")
+		_, err := v.client.RawRequestWithContext(ctx, req)
+		if err != nil {
+			return fmt.Errorf(errVaultRevokeToken, err)
+		}
+		v.client.ClearToken()
+	}
 	return nil
 }
 
@@ -220,14 +236,40 @@ func (v *client) newConfig() (*vault.Config, error) {
 	cfg := vault.DefaultConfig()
 	cfg.Address = v.store.Server
 
-	if len(v.store.CABundle) == 0 {
+	if len(v.store.CABundle) == 0 && v.store.CAProvider == nil {
 		return cfg, nil
 	}
 
 	caCertPool := x509.NewCertPool()
-	ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
-	if !ok {
-		return nil, errors.New(errVaultCert)
+
+	if len(v.store.CABundle) > 0 {
+		ok := caCertPool.AppendCertsFromPEM(v.store.CABundle)
+		if !ok {
+			return nil, errors.New(errVaultCert)
+		}
+	}
+
+	if v.store.CAProvider != nil {
+		var cert []byte
+		var err error
+
+		switch v.store.CAProvider.Type {
+		case esv1alpha1.CAProviderTypeSecret:
+			cert, err = getCertFromSecret(v)
+		case esv1alpha1.CAProviderTypeConfigMap:
+			cert, err = getCertFromConfigMap(v)
+		default:
+			return nil, errors.New(errUnknownCAProvider)
+		}
+
+		if err != nil {
+			return nil, err
+		}
+
+		ok := caCertPool.AppendCertsFromPEM(cert)
+		if !ok {
+			return nil, errors.New(errVaultCert)
+		}
 	}
 
 	if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
@@ -237,6 +279,42 @@ func (v *client) newConfig() (*vault.Config, error) {
 	return cfg, nil
 }
 
+func getCertFromSecret(v *client) ([]byte, error) {
+	secretRef := esmeta.SecretKeySelector{
+		Name:      v.store.CAProvider.Name,
+		Namespace: &v.store.CAProvider.Namespace,
+		Key:       v.store.CAProvider.Key,
+	}
+	ctx := context.Background()
+	res, err := v.secretKeyRef(ctx, &secretRef)
+	if err != nil {
+		return nil, fmt.Errorf(errVaultCert, err)
+	}
+
+	return []byte(res), nil
+}
+
+func getCertFromConfigMap(v *client) ([]byte, error) {
+	objKey := types.NamespacedName{
+		Namespace: v.store.CAProvider.Namespace,
+		Name:      v.store.CAProvider.Name,
+	}
+
+	configMapRef := &corev1.ConfigMap{}
+	ctx := context.Background()
+	err := v.kube.Get(ctx, objKey, configMapRef)
+	if err != nil {
+		return nil, fmt.Errorf(errVaultCert, err)
+	}
+
+	val, ok := configMapRef.Data[v.store.CAProvider.Key]
+	if !ok {
+		return nil, fmt.Errorf(errConfigMapFmt, v.store.CAProvider.Key)
+	}
+
+	return []byte(val), nil
+}
+
 func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) error {
 	tokenExists, err := setSecretKeyToken(ctx, v, client)
 	if tokenExists {

File diff suppressed because it is too large
+ 169 - 42
pkg/provider/vault/vault_test.go