github.com/cilium/cilium@v1.16.2/pkg/policy/selectorcache.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package policy
     5  
     6  import (
     7  	"net"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/sirupsen/logrus"
    12  
    13  	"github.com/cilium/cilium/api/v1/models"
    14  	"github.com/cilium/cilium/pkg/identity"
    15  	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    16  	"github.com/cilium/cilium/pkg/labels"
    17  	"github.com/cilium/cilium/pkg/lock"
    18  	"github.com/cilium/cilium/pkg/logging/logfields"
    19  	"github.com/cilium/cilium/pkg/policy/api"
    20  )
    21  
    22  // scIdentity is the information we need about a an identity that rules can select
    23  type scIdentity struct {
    24  	NID       identity.NumericIdentity
    25  	lbls      labels.LabelArray
    26  	nets      []*net.IPNet // Most specific CIDR for the identity, if any.
    27  	computed  bool         // nets has been computed
    28  	namespace string       // value of the namespace label, or ""
    29  }
    30  
    31  // scIdentityCache is a cache of Identities keyed by the numeric identity
    32  type scIdentityCache map[identity.NumericIdentity]scIdentity
    33  
    34  func newIdentity(nid identity.NumericIdentity, lbls labels.LabelArray) scIdentity {
    35  	return scIdentity{
    36  		NID:       nid,
    37  		lbls:      lbls,
    38  		nets:      getLocalScopeNets(nid, lbls),
    39  		namespace: lbls.Get(labels.LabelSourceK8sKeyPrefix + k8sConst.PodNamespaceLabel),
    40  		computed:  true,
    41  	}
    42  }
    43  
    44  // getLocalScopeNets returns the most specific CIDR for a local scope identity.
    45  func getLocalScopeNets(id identity.NumericIdentity, lbls labels.LabelArray) []*net.IPNet {
    46  	if id.HasLocalScope() {
    47  		var mostSpecificCidr *net.IPNet
    48  		maskSize := -1 // allow for 0-length prefix (e.g., "0.0.0.0/0")
    49  		for _, lbl := range lbls {
    50  			if lbl.Source == labels.LabelSourceCIDR {
    51  				// Reverse the transformation done in labels.maskedIPToLabel()
    52  				// as ':' is not allowed within a k8s label, colons are represented
    53  				// with '-'.
    54  				cidr := strings.ReplaceAll(lbl.Key, "-", ":")
    55  				_, netIP, err := net.ParseCIDR(cidr)
    56  				if err == nil {
    57  					if ms, _ := netIP.Mask.Size(); ms > maskSize {
    58  						mostSpecificCidr = netIP
    59  						maskSize = ms
    60  					}
    61  				}
    62  			}
    63  		}
    64  		if mostSpecificCidr != nil {
    65  			return []*net.IPNet{mostSpecificCidr}
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  func getIdentityCache(ids identity.IdentityMap) scIdentityCache {
    72  	idCache := make(map[identity.NumericIdentity]scIdentity, len(ids))
    73  	for nid, lbls := range ids {
    74  		idCache[nid] = newIdentity(nid, lbls)
    75  	}
    76  	return idCache
    77  }
    78  
    79  // userNotification stores the information needed to call
    80  // IdentitySelectionUpdated callbacks to notify users of selector's
    81  // identity changes. These are queued to be able to call the callbacks
    82  // in FIFO order while not holding any locks.
    83  type userNotification struct {
    84  	user     CachedSelectionUser
    85  	selector CachedSelector
    86  	added    []identity.NumericIdentity
    87  	deleted  []identity.NumericIdentity
    88  	wg       *sync.WaitGroup
    89  }
    90  
    91  // SelectorCache caches identities, identity selectors, and the
    92  // subsets of identities each selector selects.
    93  type SelectorCache struct {
    94  	mutex lock.RWMutex
    95  
    96  	// idCache contains all known identities as informed by the
    97  	// kv-store and the local identity facility via our
    98  	// UpdateIdentities() function.
    99  	idCache scIdentityCache
   100  
   101  	// map key is the string representation of the selector being cached.
   102  	selectors map[string]*identitySelector
   103  
   104  	localIdentityNotifier identityNotifier
   105  
   106  	// userCond is a condition variable for receiving signals
   107  	// about addition of new elements in userNotes
   108  	userCond *sync.Cond
   109  	// userMutex protects userNotes and is linked to userCond
   110  	userMutex lock.Mutex
   111  	// userNotes holds a FIFO list of user notifications to be made
   112  	userNotes []userNotification
   113  
   114  	// used to lazily start the handler for user notifications.
   115  	startNotificationsHandlerOnce sync.Once
   116  }
   117  
   118  // GetModel returns the API model of the SelectorCache.
   119  func (sc *SelectorCache) GetModel() models.SelectorCache {
   120  	sc.mutex.RLock()
   121  	defer sc.mutex.RUnlock()
   122  
   123  	selCacheMdl := make(models.SelectorCache, 0, len(sc.selectors))
   124  
   125  	for selector, idSel := range sc.selectors {
   126  		selections := idSel.GetSelections()
   127  		ids := make([]int64, 0, len(selections))
   128  		for i := range selections {
   129  			ids = append(ids, int64(selections[i]))
   130  		}
   131  		selMdl := &models.SelectorIdentityMapping{
   132  			Selector:   selector,
   133  			Identities: ids,
   134  			Users:      int64(idSel.numUsers()),
   135  			Labels:     labelArrayToModel(idSel.GetMetadataLabels()),
   136  		}
   137  		selCacheMdl = append(selCacheMdl, selMdl)
   138  	}
   139  
   140  	return selCacheMdl
   141  }
   142  
   143  func labelArrayToModel(arr labels.LabelArray) models.LabelArray {
   144  	lbls := make(models.LabelArray, 0, len(arr))
   145  	for _, l := range arr {
   146  		lbls = append(lbls, &models.Label{
   147  			Key:    l.Key,
   148  			Value:  l.Value,
   149  			Source: l.Source,
   150  		})
   151  	}
   152  	return lbls
   153  }
   154  
   155  func (sc *SelectorCache) handleUserNotifications() {
   156  	for {
   157  		sc.userMutex.Lock()
   158  		for len(sc.userNotes) == 0 {
   159  			sc.userCond.Wait()
   160  		}
   161  		// get the current batch of notifications and release the lock so that SelectorCache
   162  		// can't block on userMutex while we call IdentitySelectionUpdated callbacks below.
   163  		notifications := sc.userNotes
   164  		sc.userNotes = nil
   165  		sc.userMutex.Unlock()
   166  
   167  		for _, n := range notifications {
   168  			n.user.IdentitySelectionUpdated(n.selector, n.added, n.deleted)
   169  			n.wg.Done()
   170  		}
   171  	}
   172  }
   173  
   174  func (sc *SelectorCache) queueUserNotification(user CachedSelectionUser, selector CachedSelector, added, deleted []identity.NumericIdentity, wg *sync.WaitGroup) {
   175  	sc.startNotificationsHandlerOnce.Do(func() {
   176  		go sc.handleUserNotifications()
   177  	})
   178  	wg.Add(1)
   179  	sc.userMutex.Lock()
   180  	sc.userNotes = append(sc.userNotes, userNotification{
   181  		user:     user,
   182  		selector: selector,
   183  		added:    added,
   184  		deleted:  deleted,
   185  		wg:       wg,
   186  	})
   187  	sc.userMutex.Unlock()
   188  	sc.userCond.Signal()
   189  }
   190  
   191  // NewSelectorCache creates a new SelectorCache with the given identities.
   192  func NewSelectorCache(ids identity.IdentityMap) *SelectorCache {
   193  	sc := &SelectorCache{
   194  		idCache:   getIdentityCache(ids),
   195  		selectors: make(map[string]*identitySelector),
   196  	}
   197  	sc.userCond = sync.NewCond(&sc.userMutex)
   198  	return sc
   199  }
   200  
   201  // SetLocalIdentityNotifier injects the provided identityNotifier into the
   202  // SelectorCache. Currently, this is used to inject the FQDN subsystem into
   203  // the SelectorCache so the SelectorCache can notify the FQDN subsystem when
   204  // it should be aware of a given FQDNSelector for which CIDR identities need
   205  // to be provided upon DNS lookups which corespond to said FQDNSelector.
   206  func (sc *SelectorCache) SetLocalIdentityNotifier(pop identityNotifier) {
   207  	sc.localIdentityNotifier = pop
   208  }
   209  
   210  var (
   211  	// Empty slice of numeric identities used for all selectors that select nothing
   212  	emptySelection identity.NumericIdentitySlice
   213  	// wildcardSelectorKey is used to compare if a key is for a wildcard
   214  	wildcardSelectorKey = api.WildcardEndpointSelector.LabelSelector.String()
   215  	// noneSelectorKey is used to compare if a key is for "reserved:none"
   216  	noneSelectorKey = api.EndpointSelectorNone.LabelSelector.String()
   217  )
   218  
   219  // identityNotifier provides a means for other subsystems to be made aware of a
   220  // given FQDNSelector (currently pkg/fqdn) so that said subsystems can notify
   221  // the IPCache about IPs which correspond to said FQDNSelector.
   222  // This is necessary as there is nothing intrinsic about an IP that says that
   223  // it corresponds to a given FQDNSelector; this relationship is contained only
   224  // via DNS responses, which are handled externally.
   225  type identityNotifier interface {
   226  	// RegisterFQDNSelector exposes this FQDNSelector so that the identity labels
   227  	// of IPs contained in a DNS response that matches said selector can be
   228  	// associated with that selector.
   229  	RegisterFQDNSelector(selector api.FQDNSelector)
   230  
   231  	// UnregisterFQDNSelector removes this FQDNSelector from the set of
   232  	// IPs which are being tracked by the identityNotifier. The result
   233  	// of this is that an IP may be evicted from IPCache if it is no longer
   234  	// selected by any other FQDN selector.
   235  	// This occurs when there are no more users of a given FQDNSelector for the
   236  	// SelectorCache.
   237  	UnregisterFQDNSelector(selector api.FQDNSelector)
   238  }
   239  
   240  // AddFQDNSelector adds the given api.FQDNSelector in to the selector cache. If
   241  // an identical EndpointSelector has already been cached, the corresponding
   242  // CachedSelector is returned, otherwise one is created and added to the cache.
   243  func (sc *SelectorCache) AddFQDNSelector(user CachedSelectionUser, lbls labels.LabelArray, fqdnSelec api.FQDNSelector) (cachedSelector CachedSelector, added bool) {
   244  	key := fqdnSelec.String()
   245  
   246  	sc.mutex.Lock()
   247  	defer sc.mutex.Unlock()
   248  
   249  	// If the selector already exists, use it.
   250  	idSel, exists := sc.selectors[key]
   251  	if exists {
   252  		return idSel, idSel.addUser(user)
   253  	}
   254  
   255  	source := &fqdnSelector{
   256  		selector: fqdnSelec,
   257  	}
   258  
   259  	// Make the FQDN subsystem aware of this selector
   260  	sc.localIdentityNotifier.RegisterFQDNSelector(source.selector)
   261  
   262  	return sc.addSelector(user, lbls, key, source)
   263  }
   264  
   265  func (sc *SelectorCache) addSelector(user CachedSelectionUser, lbls labels.LabelArray, key string, source selectorSource) (CachedSelector, bool) {
   266  	idSel := &identitySelector{
   267  		key:              key,
   268  		users:            make(map[CachedSelectionUser]struct{}),
   269  		cachedSelections: make(map[identity.NumericIdentity]struct{}),
   270  		source:           source,
   271  		metadataLbls:     lbls,
   272  	}
   273  	sc.selectors[key] = idSel
   274  
   275  	// Scan the cached set of IDs to determine any new matchers
   276  	for nid, identity := range sc.idCache {
   277  		if idSel.source.matches(identity) {
   278  			idSel.cachedSelections[nid] = struct{}{}
   279  		}
   280  	}
   281  
   282  	// Note: No notifications are sent for the existing
   283  	// identities. Caller must use GetSelections() to get the
   284  	// current selections after adding a selector. This way the
   285  	// behavior is the same between the two cases here (selector
   286  	// is already cached, or is a new one).
   287  
   288  	// Create the immutable slice representation of the selected
   289  	// numeric identities
   290  	idSel.updateSelections()
   291  
   292  	return idSel, idSel.addUser(user)
   293  
   294  }
   295  
   296  // FindCachedIdentitySelector finds the given api.EndpointSelector in the
   297  // selector cache, returning nil if one can not be found.
   298  func (sc *SelectorCache) FindCachedIdentitySelector(selector api.EndpointSelector) CachedSelector {
   299  	key := selector.CachedString()
   300  	sc.mutex.Lock()
   301  	idSel := sc.selectors[key]
   302  	sc.mutex.Unlock()
   303  	return idSel
   304  }
   305  
   306  // AddIdentitySelector adds the given api.EndpointSelector in to the
   307  // selector cache. If an identical EndpointSelector has already been
   308  // cached, the corresponding CachedSelector is returned, otherwise one
   309  // is created and added to the cache.
   310  func (sc *SelectorCache) AddIdentitySelector(user CachedSelectionUser, lbls labels.LabelArray, selector api.EndpointSelector) (cachedSelector CachedSelector, added bool) {
   311  	// The key returned here may be different for equivalent
   312  	// labelselectors, if the selector's requirements are stored
   313  	// in different orders. When this happens we'll be tracking
   314  	// essentially two copies of the same selector.
   315  	key := selector.CachedString()
   316  	sc.mutex.Lock()
   317  	defer sc.mutex.Unlock()
   318  	idSel, exists := sc.selectors[key]
   319  	if exists {
   320  		return idSel, idSel.addUser(user)
   321  	}
   322  
   323  	// Selectors are never modified once a rule is placed in the policy repository,
   324  	// so no need to deep copy.
   325  	source := &labelIdentitySelector{
   326  		selector: selector,
   327  	}
   328  	// check is selector has a namespace match or requirement
   329  	if namespaces, ok := selector.GetMatch(labels.LabelSourceK8sKeyPrefix + k8sConst.PodNamespaceLabel); ok {
   330  		source.namespaces = namespaces
   331  	}
   332  
   333  	return sc.addSelector(user, lbls, key, source)
   334  }
   335  
   336  // lock must be held
   337  func (sc *SelectorCache) removeSelectorLocked(selector CachedSelector, user CachedSelectionUser) {
   338  	key := selector.String()
   339  	sel, exists := sc.selectors[key]
   340  	if exists {
   341  		if sel.removeUser(user) {
   342  			sel.source.remove(sc.localIdentityNotifier)
   343  			delete(sc.selectors, key)
   344  		}
   345  	}
   346  }
   347  
   348  // RemoveSelector removes CachedSelector for the user.
   349  func (sc *SelectorCache) RemoveSelector(selector CachedSelector, user CachedSelectionUser) {
   350  	sc.mutex.Lock()
   351  	sc.removeSelectorLocked(selector, user)
   352  	sc.mutex.Unlock()
   353  
   354  }
   355  
   356  // RemoveSelectors removes CachedSelectorSlice for the user.
   357  func (sc *SelectorCache) RemoveSelectors(selectors CachedSelectorSlice, user CachedSelectionUser) {
   358  	sc.mutex.Lock()
   359  	for _, selector := range selectors {
   360  		sc.removeSelectorLocked(selector, user)
   361  	}
   362  	sc.mutex.Unlock()
   363  }
   364  
   365  // ChangeUser changes the CachedSelectionUser that gets updates on the
   366  // updates on the cached selector.
   367  func (sc *SelectorCache) ChangeUser(selector CachedSelector, from, to CachedSelectionUser) {
   368  	key := selector.String()
   369  	sc.mutex.Lock()
   370  	idSel, exists := sc.selectors[key]
   371  	if exists {
   372  		// Add before remove so that the count does not dip to zero in between,
   373  		// as this causes FQDN unregistration (if applicable).
   374  		idSel.addUser(to)
   375  		// ignoring the return value as we have just added a user above
   376  		idSel.removeUser(from)
   377  	}
   378  	sc.mutex.Unlock()
   379  }
   380  
   381  // UpdateIdentities propagates identity updates to selectors
   382  //
   383  // The caller is responsible for making sure the same identity is not
   384  // present in both 'added' and 'deleted'.
   385  //
   386  // Caller should Wait() on the returned sync.WaitGroup before triggering any
   387  // policy updates. Policy updates may need Endpoint locks, so this Wait() can
   388  // deadlock if the caller is holding any endpoint locks.
   389  func (sc *SelectorCache) UpdateIdentities(added, deleted identity.IdentityMap, wg *sync.WaitGroup) {
   390  	sc.mutex.Lock()
   391  	defer sc.mutex.Unlock()
   392  
   393  	// Update idCache so that newly added selectors get
   394  	// prepopulated with all matching numeric identities.
   395  	for numericID := range deleted {
   396  		if old, exists := sc.idCache[numericID]; exists {
   397  			log.WithFields(logrus.Fields{
   398  				logfields.Identity: numericID,
   399  				logfields.Labels:   old.lbls,
   400  			}).Debug("UpdateIdentities: Deleting identity")
   401  			delete(sc.idCache, numericID)
   402  		} else {
   403  			log.WithFields(logrus.Fields{
   404  				logfields.Identity: numericID,
   405  			}).Warning("UpdateIdentities: Skipping Delete of a non-existing identity")
   406  			delete(deleted, numericID)
   407  		}
   408  	}
   409  	for numericID, lbls := range added {
   410  		if old, exists := sc.idCache[numericID]; exists {
   411  			// Skip if no change. Not skipping if label
   412  			// order is different, but identity labels are
   413  			// sorted for the kv-store, so there should
   414  			// not be too many false negatives.
   415  			if lbls.Equals(old.lbls) {
   416  				log.WithFields(logrus.Fields{
   417  					logfields.Identity: numericID,
   418  				}).Debug("UpdateIdentities: Skipping add of an existing identical identity")
   419  				delete(added, numericID)
   420  				continue
   421  			}
   422  			scopedLog := log.WithFields(logrus.Fields{
   423  				logfields.Identity:         numericID,
   424  				logfields.Labels:           old.lbls,
   425  				logfields.Labels + "(new)": lbls},
   426  			)
   427  			msg := "UpdateIdentities: Updating an existing identity"
   428  			// Warn if any other ID has their labels change, besides local
   429  			// host. The local host can have its labels change at runtime if
   430  			// the kube-apiserver is running on the local host, see
   431  			// ipcache.TriggerLabelInjection().
   432  			if numericID == identity.ReservedIdentityHost {
   433  				scopedLog.Debug(msg)
   434  			} else {
   435  				scopedLog.Warning(msg)
   436  			}
   437  		} else {
   438  			log.WithFields(logrus.Fields{
   439  				logfields.Identity: numericID,
   440  				logfields.Labels:   lbls,
   441  			}).Debug("UpdateIdentities: Adding a new identity")
   442  		}
   443  		sc.idCache[numericID] = newIdentity(numericID, lbls)
   444  	}
   445  
   446  	if len(deleted)+len(added) > 0 {
   447  		// Iterate through all locally used identity selectors and
   448  		// update the cached numeric identities as required.
   449  		for _, idSel := range sc.selectors {
   450  			var adds, dels []identity.NumericIdentity
   451  			for numericID := range deleted {
   452  				if _, exists := idSel.cachedSelections[numericID]; exists {
   453  					dels = append(dels, numericID)
   454  					delete(idSel.cachedSelections, numericID)
   455  				}
   456  			}
   457  			for numericID := range added {
   458  				matches := idSel.source.matches(sc.idCache[numericID])
   459  				_, exists := idSel.cachedSelections[numericID]
   460  				if matches && !exists {
   461  					adds = append(adds, numericID)
   462  					idSel.cachedSelections[numericID] = struct{}{}
   463  				} else if !matches && exists {
   464  					// identity was mutated and no longer matches
   465  					dels = append(dels, numericID)
   466  					delete(idSel.cachedSelections, numericID)
   467  				}
   468  			}
   469  			if len(dels)+len(adds) > 0 {
   470  				idSel.updateSelections()
   471  				idSel.notifyUsers(sc, adds, dels, wg)
   472  			}
   473  		}
   474  	}
   475  }
   476  
   477  // GetNetsLocked returns the most specific CIDR for an identity. For the "World" identity
   478  // it returns both IPv4 and IPv6.
   479  func (sc *SelectorCache) GetNetsLocked(id identity.NumericIdentity) []*net.IPNet {
   480  	ident, ok := sc.idCache[id]
   481  	if !ok {
   482  		return nil
   483  	}
   484  	if !ident.computed {
   485  		log.WithFields(logrus.Fields{
   486  			logfields.Identity: id,
   487  			logfields.Labels:   ident.lbls,
   488  		}).Warning("GetNetsLocked: Identity with missing nets!")
   489  	}
   490  	return ident.nets
   491  }