...

Source file src/github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/externalsecret_controller.go

Documentation: github.com/external-secrets/external-secrets/pkg/controllers/externalsecret

     1  /*
     2  Licensed under the Apache License, Version 2.0 (the "License");
     3  you may not use this file except in compliance with the License.
     4  You may obtain a copy of the License at
     5  
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  
     8  Unless required by applicable law or agreed to in writing, software
     9  distributed under the License is distributed on an "AS IS" BASIS,
    10  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  See the License for the specific language governing permissions and
    12  limitations under the License.
    13  */
    14  
    15  package externalsecret
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/go-logr/logr"
    25  	"github.com/prometheus/client_golang/prometheus"
    26  	v1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/equality"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/client-go/rest"
    33  	"k8s.io/client-go/tools/record"
    34  	ctrl "sigs.k8s.io/controller-runtime"
    35  	"sigs.k8s.io/controller-runtime/pkg/builder"
    36  	"sigs.k8s.io/controller-runtime/pkg/client"
    37  	"sigs.k8s.io/controller-runtime/pkg/controller"
    38  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    39  
    40  	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
    41  	// Metrics.
    42  	"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
    43  	ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
    44  	// Loading registered generators.
    45  	_ "github.com/external-secrets/external-secrets/pkg/generator/register"
    46  	// Loading registered providers.
    47  	_ "github.com/external-secrets/external-secrets/pkg/provider/register"
    48  	"github.com/external-secrets/external-secrets/pkg/utils"
    49  )
    50  
    51  const (
    52  	fieldOwnerTemplate      = "externalsecrets.external-secrets.io/%v"
    53  	errGetES                = "could not get ExternalSecret"
    54  	errConvert              = "could not apply conversion strategy to keys: %v"
    55  	errDecode               = "could not apply decoding strategy to %v[%d]: %v"
    56  	errGenerate             = "could not generate [%d]: %w"
    57  	errRewrite              = "could not rewrite spec.dataFrom[%d]: %v"
    58  	errInvalidKeys          = "secret keys from spec.dataFrom.%v[%d] can only have alphanumeric,'-', '_' or '.' characters. Convert them using rewrite (https://external-secrets.io/latest/guides-datafrom-rewrite)"
    59  	errUpdateSecret         = "could not update Secret"
    60  	errPatchStatus          = "unable to patch status"
    61  	errGetExistingSecret    = "could not get existing secret: %w"
    62  	errSetCtrlReference     = "could not set ExternalSecret controller reference: %w"
    63  	errFetchTplFrom         = "error fetching templateFrom data: %w"
    64  	errGetSecretData        = "could not get secret data from provider"
    65  	errDeleteSecret         = "could not delete secret"
    66  	errApplyTemplate        = "could not apply template: %w"
    67  	errExecTpl              = "could not execute template: %w"
    68  	errInvalidCreatePolicy  = "invalid creationPolicy=%s. Can not delete secret i do not own"
    69  	errPolicyMergeNotFound  = "the desired secret %s was not found. With creationPolicy=Merge the secret won't be created"
    70  	errPolicyMergeGetSecret = "unable to get secret %s: %w"
    71  	errPolicyMergeMutate    = "unable to mutate secret %s: %w"
    72  	errPolicyMergePatch     = "unable to patch secret %s: %w"
    73  )
    74  
    75  // Reconciler reconciles a ExternalSecret object.
    76  type Reconciler struct {
    77  	client.Client
    78  	Log                       logr.Logger
    79  	Scheme                    *runtime.Scheme
    80  	RestConfig                *rest.Config
    81  	ControllerClass           string
    82  	RequeueInterval           time.Duration
    83  	ClusterSecretStoreEnabled bool
    84  	EnableFloodGate           bool
    85  	recorder                  record.EventRecorder
    86  }
    87  
    88  // Reconcile implements the main reconciliation loop
    89  // for watched objects (ExternalSecret, ClusterSecretStore and SecretStore),
    90  // and updates/creates a Kubernetes secret based on them.
    91  func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    92  	log := r.Log.WithValues("ExternalSecret", req.NamespacedName)
    93  
    94  	resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
    95  	start := time.Now()
    96  
    97  	syncCallsError := esmetrics.GetCounterVec(esmetrics.SyncCallsErrorKey)
    98  
    99  	// use closures to dynamically update resourceLabels
   100  	defer func() {
   101  		esmetrics.GetGaugeVec(esmetrics.ExternalSecretReconcileDurationKey).With(resourceLabels).Set(float64(time.Since(start)))
   102  		esmetrics.GetCounterVec(esmetrics.SyncCallsKey).With(resourceLabels).Inc()
   103  	}()
   104  
   105  	var externalSecret esv1beta1.ExternalSecret
   106  	err := r.Get(ctx, req.NamespacedName, &externalSecret)
   107  
   108  	if err != nil {
   109  		if apierrors.IsNotFound(err) {
   110  			conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretDeleted, v1.ConditionFalse, esv1beta1.ConditionReasonSecretDeleted, "Secret was deleted")
   111  			SetExternalSecretCondition(&esv1beta1.ExternalSecret{
   112  				ObjectMeta: metav1.ObjectMeta{
   113  					Name:      req.Name,
   114  					Namespace: req.Namespace,
   115  				},
   116  			}, *conditionSynced)
   117  
   118  			return ctrl.Result{}, nil
   119  		}
   120  
   121  		log.Error(err, errGetES)
   122  		syncCallsError.With(resourceLabels).Inc()
   123  
   124  		return ctrl.Result{}, err
   125  	}
   126  
   127  	timeSinceLastRefresh := 0 * time.Second
   128  	if !externalSecret.Status.RefreshTime.IsZero() {
   129  		timeSinceLastRefresh = time.Since(externalSecret.Status.RefreshTime.Time)
   130  	}
   131  
   132  	// skip reconciliation if deletion timestamp is set on external secret
   133  	if externalSecret.DeletionTimestamp != nil {
   134  		log.Info("skipping as it is in deletion")
   135  		return ctrl.Result{}, nil
   136  	}
   137  
   138  	// if extended metrics is enabled, refine the time series vector
   139  	resourceLabels = ctrlmetrics.RefineLabels(resourceLabels, externalSecret.Labels)
   140  
   141  	if shouldSkipClusterSecretStore(r, externalSecret) {
   142  		log.Info("skipping cluster secret store as it is disabled")
   143  		return ctrl.Result{}, nil
   144  	}
   145  
   146  	// skip when pointing to an unmanaged store
   147  	skip, err := shouldSkipUnmanagedStore(ctx, req.Namespace, r, externalSecret)
   148  	if skip {
   149  		log.Info("skipping unmanaged store as it points to a unmanaged controllerClass")
   150  		return ctrl.Result{}, nil
   151  	}
   152  
   153  	refreshInt := r.RequeueInterval
   154  	if externalSecret.Spec.RefreshInterval != nil {
   155  		refreshInt = externalSecret.Spec.RefreshInterval.Duration
   156  	}
   157  
   158  	// Target Secret Name should default to the ExternalSecret name if not explicitly specified
   159  	secretName := externalSecret.Spec.Target.Name
   160  	if secretName == "" {
   161  		secretName = externalSecret.ObjectMeta.Name
   162  	}
   163  
   164  	// fetch external secret, we need to ensure that it exists, and it's hashmap corresponds
   165  	var existingSecret v1.Secret
   166  	err = r.Get(ctx, types.NamespacedName{
   167  		Name:      secretName,
   168  		Namespace: externalSecret.Namespace,
   169  	}, &existingSecret)
   170  	if err != nil && !apierrors.IsNotFound(err) {
   171  		log.Error(err, errGetExistingSecret)
   172  		return ctrl.Result{}, err
   173  	}
   174  
   175  	// refresh should be skipped if
   176  	// 1. resource generation hasn't changed
   177  	// 2. refresh interval is 0
   178  	// 3. if we're still within refresh-interval
   179  	if !shouldRefresh(externalSecret) && isSecretValid(existingSecret) {
   180  		refreshInt = (externalSecret.Spec.RefreshInterval.Duration - timeSinceLastRefresh) + 5*time.Second
   181  		log.V(1).Info("skipping refresh", "rv", getResourceVersion(externalSecret), "nr", refreshInt.Seconds())
   182  		return ctrl.Result{RequeueAfter: refreshInt}, nil
   183  	}
   184  	if !shouldReconcile(externalSecret) {
   185  		log.V(1).Info("stopping reconciling", "rv", getResourceVersion(externalSecret))
   186  		return ctrl.Result{}, nil
   187  	}
   188  
   189  	// patch status when done processing
   190  	p := client.MergeFrom(externalSecret.DeepCopy())
   191  	defer func() {
   192  		err = r.Status().Patch(ctx, &externalSecret, p)
   193  		if err != nil {
   194  			log.Error(err, errPatchStatus)
   195  		}
   196  	}()
   197  
   198  	secret := &v1.Secret{
   199  		ObjectMeta: metav1.ObjectMeta{
   200  			Name:      secretName,
   201  			Namespace: externalSecret.Namespace,
   202  		},
   203  		Immutable: &externalSecret.Spec.Target.Immutable,
   204  		Data:      make(map[string][]byte),
   205  	}
   206  
   207  	dataMap, err := r.getProviderSecretData(ctx, &externalSecret)
   208  	if err != nil {
   209  		r.markAsFailed(log, errGetSecretData, err, &externalSecret, syncCallsError.With(resourceLabels))
   210  		return ctrl.Result{}, err
   211  	}
   212  
   213  	// if no data was found we can delete the secret if needed.
   214  	if len(dataMap) == 0 {
   215  		switch externalSecret.Spec.Target.DeletionPolicy {
   216  		// delete secret and return early.
   217  		case esv1beta1.DeletionPolicyDelete:
   218  			// safeguard that we only can delete secrets we own
   219  			// this is also implemented in the es validation webhook
   220  			if externalSecret.Spec.Target.CreationPolicy != esv1beta1.CreatePolicyOwner {
   221  				err := fmt.Errorf(errInvalidCreatePolicy, externalSecret.Spec.Target.CreationPolicy)
   222  				r.markAsFailed(log, errDeleteSecret, err, &externalSecret, syncCallsError.With(resourceLabels))
   223  				return ctrl.Result{}, err
   224  			}
   225  
   226  			if err := r.Delete(ctx, secret); err != nil && !apierrors.IsNotFound(err) {
   227  				r.markAsFailed(log, errDeleteSecret, err, &externalSecret, syncCallsError.With(resourceLabels))
   228  				return ctrl.Result{}, err
   229  			}
   230  
   231  			conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, esv1beta1.ConditionReasonSecretDeleted, "secret deleted due to DeletionPolicy")
   232  			SetExternalSecretCondition(&externalSecret, *conditionSynced)
   233  			return ctrl.Result{RequeueAfter: refreshInt}, nil
   234  		// In case provider secrets don't exist the kubernetes secret will be kept as-is.
   235  		case esv1beta1.DeletionPolicyRetain:
   236  			r.markAsDone(&externalSecret, start, log)
   237  			return ctrl.Result{RequeueAfter: refreshInt}, nil
   238  		// noop, handled below
   239  		case esv1beta1.DeletionPolicyMerge:
   240  		}
   241  	}
   242  
   243  	mutationFunc := func() error {
   244  		if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
   245  			err = controllerutil.SetControllerReference(&externalSecret, &secret.ObjectMeta, r.Scheme)
   246  			if err != nil {
   247  				return fmt.Errorf(errSetCtrlReference, err)
   248  			}
   249  		}
   250  		if secret.Data == nil {
   251  			secret.Data = make(map[string][]byte)
   252  		}
   253  		// diff existing keys
   254  		keys, err := getManagedDataKeys(&existingSecret, externalSecret.Name)
   255  		if err != nil {
   256  			return err
   257  		}
   258  		// Sanitize data map for any updates on the ES
   259  		for _, key := range keys {
   260  			if dataMap[key] == nil {
   261  				secret.Data[key] = nil
   262  				// Sanitizing any templated / updated keys
   263  				delete(secret.Data, key)
   264  			}
   265  		}
   266  		err = r.applyTemplate(ctx, &externalSecret, secret, dataMap)
   267  		if err != nil {
   268  			return fmt.Errorf(errApplyTemplate, err)
   269  		}
   270  		if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
   271  			lblValue := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
   272  			secret.Labels[esv1beta1.LabelOwner] = lblValue
   273  		}
   274  
   275  		secret.Annotations[esv1beta1.AnnotationDataHash] = r.computeDataHashAnnotation(&existingSecret, secret)
   276  
   277  		return nil
   278  	}
   279  
   280  	switch externalSecret.Spec.Target.CreationPolicy { //nolint:exhaustive
   281  	case esv1beta1.CreatePolicyMerge:
   282  		err = patchSecret(ctx, r.Client, r.Scheme, secret, mutationFunc, externalSecret.Name)
   283  		if err == nil {
   284  			externalSecret.Status.Binding = v1.LocalObjectReference{Name: secret.Name}
   285  		}
   286  	case esv1beta1.CreatePolicyNone:
   287  		log.V(1).Info("secret creation skipped due to creationPolicy=None")
   288  		err = nil
   289  	default:
   290  		var created bool
   291  		created, err = createOrUpdate(ctx, r.Client, secret, mutationFunc, externalSecret.Name)
   292  		if err == nil {
   293  			externalSecret.Status.Binding = v1.LocalObjectReference{Name: secret.Name}
   294  		}
   295  		// cleanup orphaned secrets
   296  		if created {
   297  			delErr := deleteOrphanedSecrets(ctx, r.Client, &externalSecret)
   298  			if delErr != nil {
   299  				msg := fmt.Sprintf("failed to clean up orphaned secrets: %v", delErr)
   300  				r.markAsFailed(log, msg, delErr, &externalSecret, syncCallsError.With(resourceLabels))
   301  				return ctrl.Result{}, delErr
   302  			}
   303  		}
   304  	}
   305  
   306  	if err != nil {
   307  		r.markAsFailed(log, errUpdateSecret, err, &externalSecret, syncCallsError.With(resourceLabels))
   308  		return ctrl.Result{}, err
   309  	}
   310  
   311  	r.markAsDone(&externalSecret, start, log)
   312  
   313  	return ctrl.Result{
   314  		RequeueAfter: refreshInt,
   315  	}, nil
   316  }
   317  
   318  func (r *Reconciler) markAsDone(externalSecret *esv1beta1.ExternalSecret, start time.Time, log logr.Logger) {
   319  	r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonUpdated, "Updated Secret")
   320  	conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, esv1beta1.ConditionReasonSecretSynced, "Secret was synced")
   321  	currCond := GetExternalSecretCondition(externalSecret.Status, esv1beta1.ExternalSecretReady)
   322  	SetExternalSecretCondition(externalSecret, *conditionSynced)
   323  	externalSecret.Status.RefreshTime = metav1.NewTime(start)
   324  	externalSecret.Status.SyncedResourceVersion = getResourceVersion(*externalSecret)
   325  	if currCond == nil || currCond.Status != conditionSynced.Status {
   326  		log.Info("reconciled secret") // Log once if on success in any verbosity
   327  	} else {
   328  		log.V(1).Info("reconciled secret") // Log all reconciliation cycles if higher verbosity applied
   329  	}
   330  }
   331  
   332  func (r *Reconciler) markAsFailed(log logr.Logger, msg string, err error, externalSecret *esv1beta1.ExternalSecret, counter prometheus.Counter) {
   333  	log.Error(err, msg)
   334  	r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
   335  	conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, msg)
   336  	SetExternalSecretCondition(externalSecret, *conditionSynced)
   337  	counter.Inc()
   338  }
   339  
   340  func deleteOrphanedSecrets(ctx context.Context, cl client.Client, externalSecret *esv1beta1.ExternalSecret) error {
   341  	secretList := v1.SecretList{}
   342  	lblValue := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
   343  	ls := &metav1.LabelSelector{
   344  		MatchLabels: map[string]string{
   345  			esv1beta1.LabelOwner: lblValue,
   346  		},
   347  	}
   348  	labelSelector, err := metav1.LabelSelectorAsSelector(ls)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	err = cl.List(ctx, &secretList, &client.ListOptions{LabelSelector: labelSelector})
   353  	if err != nil {
   354  		return err
   355  	}
   356  	for key, secret := range secretList.Items {
   357  		if externalSecret.Spec.Target.Name != "" && secret.Name != externalSecret.Spec.Target.Name {
   358  			err = cl.Delete(ctx, &secretList.Items[key])
   359  			if err != nil {
   360  				return err
   361  			}
   362  		}
   363  	}
   364  	return nil
   365  }
   366  
   367  func createOrUpdate(ctx context.Context, c client.Client, obj client.Object, f func() error, fieldOwner string) (bool, error) {
   368  	fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
   369  	key := client.ObjectKeyFromObject(obj)
   370  	if err := c.Get(ctx, key, obj); err != nil {
   371  		if !apierrors.IsNotFound(err) {
   372  			return false, err
   373  		}
   374  		if err := f(); err != nil {
   375  			return false, err
   376  		}
   377  		// Setting Field Owner even for CreationPolicy==Create
   378  		if err := c.Create(ctx, obj, client.FieldOwner(fqdn)); err != nil {
   379  			return false, err
   380  		}
   381  		return true, nil
   382  	}
   383  
   384  	existing := obj.DeepCopyObject()
   385  	if err := f(); err != nil {
   386  		return false, err
   387  	}
   388  
   389  	if equality.Semantic.DeepEqual(existing, obj) {
   390  		return false, nil
   391  	}
   392  
   393  	if err := c.Update(ctx, obj, client.FieldOwner(fqdn)); err != nil {
   394  		return false, err
   395  	}
   396  	return false, nil
   397  }
   398  
   399  func patchSecret(ctx context.Context, c client.Client, scheme *runtime.Scheme, secret *v1.Secret, mutationFunc func() error, fieldOwner string) error {
   400  	fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
   401  	err := c.Get(ctx, client.ObjectKeyFromObject(secret), secret.DeepCopy())
   402  	if apierrors.IsNotFound(err) {
   403  		return fmt.Errorf(errPolicyMergeNotFound, secret.Name)
   404  	}
   405  	if err != nil {
   406  		return fmt.Errorf(errPolicyMergeGetSecret, secret.Name, err)
   407  	}
   408  	existing := secret.DeepCopyObject()
   409  
   410  	err = mutationFunc()
   411  	if err != nil {
   412  		return fmt.Errorf(errPolicyMergeMutate, secret.Name, err)
   413  	}
   414  
   415  	// GVK is missing in the Secret, see:
   416  	// https://github.com/kubernetes-sigs/controller-runtime/issues/526
   417  	// https://github.com/kubernetes-sigs/controller-runtime/issues/1517
   418  	// https://github.com/kubernetes/kubernetes/issues/80609
   419  	// we need to manually set it before doing a Patch() as it depends on the GVK
   420  	gvks, unversioned, err := scheme.ObjectKinds(secret)
   421  	if err != nil {
   422  		return err
   423  	}
   424  	if !unversioned && len(gvks) == 1 {
   425  		secret.SetGroupVersionKind(gvks[0])
   426  	}
   427  
   428  	if equality.Semantic.DeepEqual(existing, secret) {
   429  		return nil
   430  	}
   431  	// Cleaning up Managed fields manually as to keep patch coherence
   432  	secret.ObjectMeta.ManagedFields = nil
   433  	// we're not able to resolve conflicts so we force ownership
   434  	// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#using-server-side-apply-in-a-controller
   435  	err = c.Patch(ctx, secret, client.Apply, client.FieldOwner(fqdn), client.ForceOwnership)
   436  	if err != nil {
   437  		return fmt.Errorf(errPolicyMergePatch, secret.Name, err)
   438  	}
   439  	return nil
   440  }
   441  
   442  func getManagedDataKeys(secret *v1.Secret, fieldOwner string) ([]string, error) {
   443  	return getManagedFieldKeys(secret, fieldOwner, func(fields map[string]interface{}) []string {
   444  		dataFields := fields["f:data"]
   445  		if dataFields == nil {
   446  			return nil
   447  		}
   448  		df, ok := dataFields.(map[string]interface{})
   449  		if !ok {
   450  			return nil
   451  		}
   452  		var keys []string
   453  		for k := range df {
   454  			keys = append(keys, k)
   455  		}
   456  		return keys
   457  	})
   458  }
   459  
   460  func getManagedFieldKeys(
   461  	secret *v1.Secret,
   462  	fieldOwner string,
   463  	process func(fields map[string]interface{}) []string,
   464  ) ([]string, error) {
   465  	fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
   466  	var keys []string
   467  	for _, v := range secret.ObjectMeta.ManagedFields {
   468  		if v.Manager != fqdn {
   469  			continue
   470  		}
   471  		fields := make(map[string]interface{})
   472  		err := json.Unmarshal(v.FieldsV1.Raw, &fields)
   473  		if err != nil {
   474  			return nil, fmt.Errorf("error unmarshaling managed fields: %w", err)
   475  		}
   476  		for _, key := range process(fields) {
   477  			if key == "." {
   478  				continue
   479  			}
   480  			keys = append(keys, strings.TrimPrefix(key, "f:"))
   481  		}
   482  	}
   483  	return keys, nil
   484  }
   485  
   486  func getResourceVersion(es esv1beta1.ExternalSecret) string {
   487  	return fmt.Sprintf("%d-%s", es.ObjectMeta.GetGeneration(), hashMeta(es.ObjectMeta))
   488  }
   489  
   490  func hashMeta(m metav1.ObjectMeta) string {
   491  	type meta struct {
   492  		annotations map[string]string
   493  		labels      map[string]string
   494  	}
   495  	return utils.ObjectHash(meta{
   496  		annotations: m.Annotations,
   497  		labels:      m.Labels,
   498  	})
   499  }
   500  
   501  func shouldSkipClusterSecretStore(r *Reconciler, es esv1beta1.ExternalSecret) bool {
   502  	return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind
   503  }
   504  
   505  // shouldSkipUnmanagedStore iterates over all secretStore references in the externalSecret spec,
   506  // fetches the store and evaluates the controllerClass property.
   507  // Returns true if any storeRef points to store with a non-matching controllerClass.
   508  func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconciler, es esv1beta1.ExternalSecret) (bool, error) {
   509  	var storeList []esv1beta1.SecretStoreRef
   510  
   511  	if es.Spec.SecretStoreRef.Name != "" {
   512  		storeList = append(storeList, es.Spec.SecretStoreRef)
   513  	}
   514  
   515  	for _, ref := range es.Spec.Data {
   516  		if ref.SourceRef != nil {
   517  			storeList = append(storeList, ref.SourceRef.SecretStoreRef)
   518  		}
   519  	}
   520  
   521  	for _, ref := range es.Spec.DataFrom {
   522  		if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
   523  			storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
   524  		}
   525  
   526  		// verify that generator's controllerClass matches
   527  		if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
   528  			genDef, err := r.getGeneratorDefinition(ctx, namespace, ref.SourceRef.GeneratorRef)
   529  			if err != nil {
   530  				return false, err
   531  			}
   532  			skipGenerator, err := shouldSkipGenerator(r, genDef)
   533  			if err != nil {
   534  				return false, err
   535  			}
   536  			if skipGenerator {
   537  				return true, nil
   538  			}
   539  		}
   540  	}
   541  
   542  	for _, ref := range storeList {
   543  		var store esv1beta1.GenericStore
   544  
   545  		switch ref.Kind {
   546  		case esv1beta1.SecretStoreKind, "":
   547  			store = &esv1beta1.SecretStore{}
   548  		case esv1beta1.ClusterSecretStoreKind:
   549  			store = &esv1beta1.ClusterSecretStore{}
   550  			namespace = ""
   551  		}
   552  
   553  		err := r.Client.Get(ctx, types.NamespacedName{
   554  			Name:      ref.Name,
   555  			Namespace: namespace,
   556  		}, store)
   557  		if err != nil {
   558  			return false, err
   559  		}
   560  		class := store.GetSpec().Controller
   561  		if class != "" && class != r.ControllerClass {
   562  			return true, nil
   563  		}
   564  	}
   565  	return false, nil
   566  }
   567  
   568  func shouldRefresh(es esv1beta1.ExternalSecret) bool {
   569  	// refresh if resource version changed
   570  	if es.Status.SyncedResourceVersion != getResourceVersion(es) {
   571  		return true
   572  	}
   573  
   574  	// skip refresh if refresh interval is 0
   575  	if es.Spec.RefreshInterval.Duration == 0 && es.Status.SyncedResourceVersion != "" {
   576  		return false
   577  	}
   578  	if es.Status.RefreshTime.IsZero() {
   579  		return true
   580  	}
   581  	return es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(time.Now())
   582  }
   583  
   584  func shouldReconcile(es esv1beta1.ExternalSecret) bool {
   585  	if es.Spec.Target.Immutable && hasSyncedCondition(es) {
   586  		return false
   587  	}
   588  	return true
   589  }
   590  
   591  func hasSyncedCondition(es esv1beta1.ExternalSecret) bool {
   592  	for _, condition := range es.Status.Conditions {
   593  		if condition.Reason == "SecretSynced" {
   594  			return true
   595  		}
   596  	}
   597  	return false
   598  }
   599  
   600  // isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
   601  func isSecretValid(existingSecret v1.Secret) bool {
   602  	// if target secret doesn't exist, or annotations as not set, we need to refresh
   603  	if existingSecret.UID == "" || existingSecret.Annotations == nil {
   604  		return false
   605  	}
   606  
   607  	// if the calculated hash is different from the calculation, then it's invalid
   608  	if existingSecret.Annotations[esv1beta1.AnnotationDataHash] != utils.ObjectHash(existingSecret.Data) {
   609  		return false
   610  	}
   611  	return true
   612  }
   613  
   614  // computeDataHashAnnotation generate a hash of the secret data combining the old key with the new keys to add or override.
   615  func (r *Reconciler) computeDataHashAnnotation(existing, secret *v1.Secret) string {
   616  	data := make(map[string][]byte)
   617  	for k, v := range existing.Data {
   618  		data[k] = v
   619  	}
   620  	for k, v := range secret.Data {
   621  		data[k] = v
   622  	}
   623  	return utils.ObjectHash(data)
   624  }
   625  
   626  // SetupWithManager returns a new controller builder that will be started by the provided Manager.
   627  func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
   628  	r.recorder = mgr.GetEventRecorderFor("external-secrets")
   629  
   630  	return ctrl.NewControllerManagedBy(mgr).
   631  		WithOptions(opts).
   632  		For(&esv1beta1.ExternalSecret{}).
   633  		Owns(&v1.Secret{}, builder.OnlyMetadata).
   634  		Complete(r)
   635  }
   636  

View as plain text