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 }