Kaynağa Gözat

Merge pull request #329 from FGA-GCES/feat/immutable-secrets

Add immutable secrets
Lucas Severo Alves 4 yıl önce
ebeveyn
işleme
c00afc9ff7

+ 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.

+ 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

+ 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)