github.com/cilium/cilium@v1.16.2/operator/pkg/secretsync/secretsync.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package secretsync
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/sirupsen/logrus"
    11  	corev1 "k8s.io/api/core/v1"
    12  	"k8s.io/apimachinery/pkg/types"
    13  	"k8s.io/utils/strings/slices"
    14  	ctrl "sigs.k8s.io/controller-runtime"
    15  	"sigs.k8s.io/controller-runtime/pkg/builder"
    16  	"sigs.k8s.io/controller-runtime/pkg/client"
    17  	"sigs.k8s.io/controller-runtime/pkg/event"
    18  	"sigs.k8s.io/controller-runtime/pkg/handler"
    19  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    20  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    21  )
    22  
    23  const (
    24  	OwningSecretNamespace = "secretsync.cilium.io/owning-secret-namespace"
    25  	OwningSecretName      = "secretsync.cilium.io/owning-secret-name"
    26  )
    27  
    28  // secretSyncer syncs secrets to dedicated namespace.
    29  type secretSyncer struct {
    30  	client client.Client
    31  	logger logrus.FieldLogger
    32  
    33  	registrations    []*SecretSyncRegistration
    34  	secretNamespaces []string
    35  }
    36  
    37  type SecretSyncRegistration struct {
    38  	// RefObject defines the Kubernetes Object that is referencing a K8s Secret that needs to be synced.
    39  	RefObject client.Object
    40  	// RefObjectEnqueueFunc defines the mapping function from the reference object to the Secret.
    41  	RefObjectEnqueueFunc handler.EventHandler
    42  	// RefObjectCheckFunc defines a function that is called to check whether the given K8s Secret
    43  	// is still referenced by a reference object.
    44  	// Synced Secrets that origin from K8s Secrets that are no longer referenced by any registration are deleted.
    45  	RefObjectCheckFunc func(ctx context.Context, c client.Client, logger logrus.FieldLogger, obj *corev1.Secret) bool
    46  	// SecretsNamespace defines the name of the namespace in which the referenced K8s Secrets are to be synchronized.
    47  	SecretsNamespace string
    48  	// AdditionalWatches definites additional watches beside watching the directly referencing Kubernetes Object.
    49  	AdditionalWatches []AdditionalWatch
    50  	// DefaultSecret defines an optional reference to a TLS Secret that should be synced regardless of whether it's referenced or not.
    51  	DefaultSecret *DefaultSecret
    52  }
    53  
    54  type AdditionalWatch struct {
    55  	RefObject             client.Object
    56  	RefObjectEnqueueFunc  handler.EventHandler
    57  	RefObjectWatchOptions []builder.WatchesOption
    58  }
    59  
    60  type DefaultSecret struct {
    61  	Namespace string
    62  	Name      string
    63  }
    64  
    65  func (r SecretSyncRegistration) String() string {
    66  	return fmt.Sprintf("%T -> %q", r.RefObject, r.SecretsNamespace)
    67  }
    68  
    69  func (r SecretSyncRegistration) IsDefaultSecret(secret *corev1.Secret) bool {
    70  	return r.DefaultSecret != nil && r.DefaultSecret.Namespace == secret.Namespace && r.DefaultSecret.Name == secret.Name
    71  }
    72  
    73  func NewSecretSyncReconciler(c client.Client, logger logrus.FieldLogger, registrations []*SecretSyncRegistration) *secretSyncer {
    74  	regs := []*SecretSyncRegistration{}
    75  	secretNamespaces := []string{}
    76  	for _, r := range registrations {
    77  		if r != nil {
    78  			regs = append(regs, r)
    79  			secretNamespaces = append(secretNamespaces, r.SecretsNamespace)
    80  		}
    81  	}
    82  
    83  	return &secretSyncer{
    84  		client: c,
    85  		logger: logger,
    86  
    87  		registrations:    regs,
    88  		secretNamespaces: secretNamespaces,
    89  	}
    90  }
    91  
    92  // SetupWithManager sets up the controller with the Manager.
    93  func (r *secretSyncer) SetupWithManager(mgr ctrl.Manager) error {
    94  	r.logger.WithField("registrations", r.registrations).Info("Setting up Secret synchronization")
    95  
    96  	builder := ctrl.NewControllerManagedBy(mgr).
    97  		// Source Secrets outside of the secrets namespace
    98  		For(&corev1.Secret{}, r.notInSecretsNamespace()).
    99  		// Synced Secrets in the secrets namespace
   100  		Watches(&corev1.Secret{}, enqueueOwningSecretFromLabels(), r.deletedOrChangedInSecretsNamespace())
   101  
   102  	for _, r := range r.registrations {
   103  		// Watch main object referencing TLS secrets
   104  		builder = builder.Watches(r.RefObject, r.RefObjectEnqueueFunc)
   105  
   106  		for _, a := range r.AdditionalWatches {
   107  			builder = builder.Watches(a.RefObject, a.RefObjectEnqueueFunc, a.RefObjectWatchOptions...)
   108  		}
   109  	}
   110  
   111  	return builder.Complete(r)
   112  }
   113  
   114  func (r *secretSyncer) notInSecretsNamespace() builder.Predicates {
   115  	return builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
   116  		return !slices.Contains(r.secretNamespaces, object.GetNamespace())
   117  	}))
   118  }
   119  
   120  func enqueueOwningSecretFromLabels() handler.EventHandler {
   121  	return handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
   122  		labels := o.GetLabels()
   123  
   124  		if labels == nil {
   125  			return nil
   126  		}
   127  
   128  		owningSecretNamespace, owningSecretNamespacePresent := labels[OwningSecretNamespace]
   129  		owningSecretName, owningSecretNamePresent := labels[OwningSecretName]
   130  
   131  		if !owningSecretNamespacePresent || !owningSecretNamePresent {
   132  			return nil
   133  		}
   134  
   135  		return []reconcile.Request{
   136  			{
   137  				NamespacedName: types.NamespacedName{
   138  					Namespace: owningSecretNamespace,
   139  					Name:      owningSecretName,
   140  				},
   141  			},
   142  		}
   143  	})
   144  }
   145  
   146  func (r *secretSyncer) deletedOrChangedInSecretsNamespace() builder.Predicates {
   147  	return builder.WithPredicates(&deletedOrChangedInSecretsNamespaceStruct{
   148  		secretNamespaces: r.secretNamespaces,
   149  	})
   150  }
   151  
   152  func (r *secretSyncer) hasRegistrations() bool {
   153  	return len(r.registrations) > 0
   154  }
   155  
   156  var _ predicate.Predicate = &deletedOrChangedInSecretsNamespaceStruct{}
   157  
   158  type deletedOrChangedInSecretsNamespaceStruct struct {
   159  	secretNamespaces []string
   160  }
   161  
   162  func (r *deletedOrChangedInSecretsNamespaceStruct) Create(event.CreateEvent) bool {
   163  	return false
   164  }
   165  
   166  func (r *deletedOrChangedInSecretsNamespaceStruct) Update(event event.UpdateEvent) bool {
   167  	return slices.Contains(r.secretNamespaces, event.ObjectOld.GetNamespace())
   168  }
   169  
   170  func (r *deletedOrChangedInSecretsNamespaceStruct) Delete(event event.DeleteEvent) bool {
   171  	return slices.Contains(r.secretNamespaces, event.Object.GetNamespace())
   172  }
   173  
   174  func (r *deletedOrChangedInSecretsNamespaceStruct) Generic(event.GenericEvent) bool {
   175  	return false
   176  }