github.com/cilium/cilium@v1.16.2/pkg/policy/api/selector.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package api 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "strings" 10 11 k8sLbls "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels" 12 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 13 validation "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1/validation" 14 "github.com/cilium/cilium/pkg/labels" 15 "github.com/cilium/cilium/pkg/logging" 16 "github.com/cilium/cilium/pkg/logging/logfields" 17 "github.com/cilium/cilium/pkg/metrics" 18 ) 19 20 var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "policy-api") 21 22 // EndpointSelector is a wrapper for k8s LabelSelector. 23 type EndpointSelector struct { 24 *slim_metav1.LabelSelector `json:",inline"` 25 26 // requirements provides a cache for a k8s-friendly format of the 27 // LabelSelector, which allows more efficient matching in Matches(). 28 // 29 // Kept as a pointer to allow EndpointSelector to be used as a map key. 30 requirements *k8sLbls.Requirements `json:"-"` 31 32 // cachedLabelSelectorString is the cached representation of the 33 // LabelSelector for this EndpointSelector. It is populated when 34 // EndpointSelectors are created via `NewESFromMatchRequirements`. It is 35 // immutable after its creation. 36 cachedLabelSelectorString string `json:"-"` 37 } 38 39 // LabelSelectorString returns a user-friendly string representation of 40 // EndpointSelector. 41 func (n *EndpointSelector) LabelSelectorString() string { 42 if n != nil && n.LabelSelector == nil { 43 return "<all>" 44 } 45 return slim_metav1.FormatLabelSelector(n.LabelSelector) 46 } 47 48 // String returns a string representation of EndpointSelector. 49 func (n EndpointSelector) String() string { 50 j, _ := n.MarshalJSON() 51 return string(j) 52 } 53 54 // CachedString returns the cached string representation of the LabelSelector 55 // for this EndpointSelector. 56 func (n EndpointSelector) CachedString() string { 57 return n.cachedLabelSelectorString 58 } 59 60 // UnmarshalJSON unmarshals the endpoint selector from the byte array. 61 func (n *EndpointSelector) UnmarshalJSON(b []byte) error { 62 n.LabelSelector = &slim_metav1.LabelSelector{} 63 err := json.Unmarshal(b, n.LabelSelector) 64 if err != nil { 65 return err 66 } 67 if n.MatchLabels != nil { 68 ml := map[string]string{} 69 for k, v := range n.MatchLabels { 70 ml[labels.GetExtendedKeyFrom(k)] = v 71 } 72 n.MatchLabels = ml 73 } 74 if n.MatchExpressions != nil { 75 newMatchExpr := make([]slim_metav1.LabelSelectorRequirement, len(n.MatchExpressions)) 76 for i, v := range n.MatchExpressions { 77 v.Key = labels.GetExtendedKeyFrom(v.Key) 78 newMatchExpr[i] = v 79 } 80 n.MatchExpressions = newMatchExpr 81 } 82 n.requirements = labelSelectorToRequirements(n.LabelSelector) 83 n.cachedLabelSelectorString = n.LabelSelector.String() 84 return nil 85 } 86 87 // MarshalJSON returns a JSON representation of the byte array. 88 func (n EndpointSelector) MarshalJSON() ([]byte, error) { 89 ls := slim_metav1.LabelSelector{} 90 91 if n.LabelSelector == nil { 92 return json.Marshal(ls) 93 } 94 95 if n.MatchLabels != nil { 96 newLabels := map[string]string{} 97 for k, v := range n.MatchLabels { 98 newLabels[labels.GetCiliumKeyFrom(k)] = v 99 } 100 ls.MatchLabels = newLabels 101 } 102 if n.MatchExpressions != nil { 103 newMatchExpr := make([]slim_metav1.LabelSelectorRequirement, len(n.MatchExpressions)) 104 for i, v := range n.MatchExpressions { 105 v.Key = labels.GetCiliumKeyFrom(v.Key) 106 newMatchExpr[i] = v 107 } 108 ls.MatchExpressions = newMatchExpr 109 } 110 return json.Marshal(ls) 111 } 112 113 // HasKeyPrefix checks if the endpoint selector contains the given key prefix in 114 // its MatchLabels map and MatchExpressions slice. 115 func (n EndpointSelector) HasKeyPrefix(prefix string) bool { 116 for k := range n.MatchLabels { 117 if strings.HasPrefix(k, prefix) { 118 return true 119 } 120 } 121 for _, v := range n.MatchExpressions { 122 if strings.HasPrefix(v.Key, prefix) { 123 return true 124 } 125 } 126 return false 127 } 128 129 // HasKey checks if the endpoint selector contains the given key in 130 // its MatchLabels map or in its MatchExpressions slice. 131 func (n EndpointSelector) HasKey(key string) bool { 132 if _, ok := n.MatchLabels[key]; ok { 133 return true 134 } 135 for _, v := range n.MatchExpressions { 136 if v.Key == key { 137 return true 138 } 139 } 140 return false 141 } 142 143 // GetMatch checks for a match on the specified key, and returns the value that 144 // the key must match, and true. If a match cannot be found, returns nil, false. 145 func (n EndpointSelector) GetMatch(key string) ([]string, bool) { 146 if value, ok := n.MatchLabels[key]; ok { 147 return []string{value}, true 148 } 149 for _, v := range n.MatchExpressions { 150 if v.Key == key && v.Operator == slim_metav1.LabelSelectorOpIn { 151 return v.Values, true 152 } 153 } 154 return nil, false 155 } 156 157 // labelSelectorToRequirements turns a kubernetes Selector into a slice of 158 // requirements equivalent to the selector. These are cached internally in the 159 // EndpointSelector to speed up Matches(). 160 // 161 // This validates the labels, which can be expensive (and may fail..) 162 // If there's an error, the selector will be nil and the Matches() 163 // implementation will refuse to match any labels. 164 func labelSelectorToRequirements(labelSelector *slim_metav1.LabelSelector) *k8sLbls.Requirements { 165 selector, err := slim_metav1.LabelSelectorAsSelector(labelSelector) 166 if err != nil { 167 metrics.PolicyChangeTotal.WithLabelValues(metrics.LabelValueOutcomeFail).Inc() 168 log.WithError(err).WithField(logfields.EndpointLabelSelector, 169 logfields.Repr(labelSelector)).Error("unable to construct selector in label selector") 170 return nil 171 } 172 metrics.PolicyChangeTotal.WithLabelValues(metrics.LabelValueOutcomeSuccess).Inc() 173 174 requirements, selectable := selector.Requirements() 175 if !selectable { 176 return nil 177 } 178 return &requirements 179 } 180 181 // NewESFromLabels creates a new endpoint selector from the given labels. 182 func NewESFromLabels(lbls ...labels.Label) EndpointSelector { 183 ml := map[string]string{} 184 for _, lbl := range lbls { 185 ml[lbl.GetExtendedKey()] = lbl.Value 186 } 187 188 return NewESFromMatchRequirements(ml, nil) 189 } 190 191 // NewESFromMatchRequirements creates a new endpoint selector from the given 192 // match specifications: An optional set of labels that must match, and 193 // an optional slice of LabelSelectorRequirements. 194 // 195 // If the caller intends to reuse 'matchLabels' or 'reqs' after creating the 196 // EndpointSelector, they must make a copy of the parameter. 197 func NewESFromMatchRequirements(matchLabels map[string]string, reqs []slim_metav1.LabelSelectorRequirement) EndpointSelector { 198 labelSelector := &slim_metav1.LabelSelector{ 199 MatchLabels: matchLabels, 200 MatchExpressions: reqs, 201 } 202 return EndpointSelector{ 203 LabelSelector: labelSelector, 204 requirements: labelSelectorToRequirements(labelSelector), 205 cachedLabelSelectorString: labelSelector.String(), 206 } 207 } 208 209 // SyncRequirementsWithLabelSelector ensures that the requirements within the 210 // specified EndpointSelector are in sync with the LabelSelector. This is 211 // because the LabelSelector has publicly accessible fields, which can be 212 // updated without concurrently updating the requirements, so the two fields can 213 // become out of sync. 214 func (n *EndpointSelector) SyncRequirementsWithLabelSelector() { 215 n.requirements = labelSelectorToRequirements(n.LabelSelector) 216 } 217 218 // newReservedEndpointSelector returns a selector that matches on all 219 // endpoints with the specified reserved label. 220 func newReservedEndpointSelector(ID string) EndpointSelector { 221 reservedLabels := labels.NewLabel(ID, "", labels.LabelSourceReserved) 222 return NewESFromLabels(reservedLabels) 223 } 224 225 var ( 226 // WildcardEndpointSelector is a wildcard endpoint selector matching 227 // all endpoints that can be described with labels. 228 WildcardEndpointSelector = NewESFromLabels() 229 230 // ReservedEndpointSelectors map reserved labels to EndpointSelectors 231 // that will match those endpoints. 232 ReservedEndpointSelectors = map[string]EndpointSelector{ 233 labels.IDNameHost: newReservedEndpointSelector(labels.IDNameHost), 234 labels.IDNameRemoteNode: newReservedEndpointSelector(labels.IDNameRemoteNode), 235 labels.IDNameWorld: newReservedEndpointSelector(labels.IDNameWorld), 236 labels.IDNameWorldIPv4: newReservedEndpointSelector(labels.IDNameWorldIPv4), 237 labels.IDNameWorldIPv6: newReservedEndpointSelector(labels.IDNameWorldIPv6), 238 } 239 ) 240 241 // NewESFromK8sLabelSelector returns a new endpoint selector from the label 242 // where it the given srcPrefix will be encoded in the label's keys. 243 func NewESFromK8sLabelSelector(srcPrefix string, lss ...*slim_metav1.LabelSelector) EndpointSelector { 244 var ( 245 matchLabels map[string]string 246 matchExpressions []slim_metav1.LabelSelectorRequirement 247 ) 248 for _, ls := range lss { 249 if ls == nil { 250 continue 251 } 252 if ls.MatchLabels != nil { 253 if matchLabels == nil { 254 matchLabels = map[string]string{} 255 } 256 for k, v := range ls.MatchLabels { 257 matchLabels[srcPrefix+k] = v 258 } 259 } 260 if ls.MatchExpressions != nil { 261 if matchExpressions == nil { 262 matchExpressions = make([]slim_metav1.LabelSelectorRequirement, 0, len(ls.MatchExpressions)) 263 } 264 for _, v := range ls.MatchExpressions { 265 v.Key = srcPrefix + v.Key 266 matchExpressions = append(matchExpressions, v) 267 } 268 } 269 } 270 return NewESFromMatchRequirements(matchLabels, matchExpressions) 271 } 272 273 // AddMatch adds a match for 'key' == 'value' to the endpoint selector. 274 func (n *EndpointSelector) AddMatch(key, value string) { 275 if n.MatchLabels == nil { 276 n.MatchLabels = map[string]string{} 277 } 278 n.MatchLabels[key] = value 279 n.requirements = labelSelectorToRequirements(n.LabelSelector) 280 n.cachedLabelSelectorString = n.LabelSelector.String() 281 } 282 283 // AddMatchExpression adds a match expression to label selector of the endpoint selector. 284 func (n *EndpointSelector) AddMatchExpression(key string, op slim_metav1.LabelSelectorOperator, values []string) { 285 n.MatchExpressions = append(n.MatchExpressions, slim_metav1.LabelSelectorRequirement{ 286 Key: key, 287 Operator: op, 288 Values: values, 289 }) 290 291 // Update cache of the EndopintSelector from the embedded label selector. 292 // This is to make sure we have updates caches containing the required selectors. 293 n.requirements = labelSelectorToRequirements(n.LabelSelector) 294 n.cachedLabelSelectorString = n.LabelSelector.String() 295 } 296 297 // Matches returns true if the endpoint selector Matches the `lblsToMatch`. 298 // Returns always true if the endpoint selector contains the reserved label for 299 // "all". 300 func (n *EndpointSelector) Matches(lblsToMatch k8sLbls.Labels) bool { 301 // Try to update cached requirements for this EndpointSelector if possible. 302 if n.requirements == nil { 303 n.requirements = labelSelectorToRequirements(n.LabelSelector) 304 // Nil indicates that requirements failed validation in some way, 305 // so we cannot parse the labels for matching purposes; thus, we cannot 306 // match if labels cannot be parsed, so return false. 307 if n.requirements == nil { 308 return false 309 } 310 } 311 for _, req := range *n.requirements { 312 if !req.Matches(lblsToMatch) { 313 return false 314 } 315 } 316 return true 317 } 318 319 // IsWildcard returns true if the endpoint selector selects all endpoints. 320 func (n *EndpointSelector) IsWildcard() bool { 321 return n.LabelSelector != nil && 322 len(n.LabelSelector.MatchLabels)+len(n.LabelSelector.MatchExpressions) == 0 323 } 324 325 // ConvertToLabelSelectorRequirementSlice converts the MatchLabels and 326 // MatchExpressions within the specified EndpointSelector into a list of 327 // LabelSelectorRequirements. 328 func (n *EndpointSelector) ConvertToLabelSelectorRequirementSlice() []slim_metav1.LabelSelectorRequirement { 329 requirements := make([]slim_metav1.LabelSelectorRequirement, 0, len(n.MatchExpressions)+len(n.MatchLabels)) 330 // Append already existing match expressions. 331 requirements = append(requirements, n.MatchExpressions...) 332 // Convert each MatchLables to LabelSelectorRequirement. 333 for key, value := range n.MatchLabels { 334 requirementFromMatchLabels := slim_metav1.LabelSelectorRequirement{ 335 Key: key, 336 Operator: slim_metav1.LabelSelectorOpIn, 337 Values: []string{value}, 338 } 339 requirements = append(requirements, requirementFromMatchLabels) 340 } 341 return requirements 342 } 343 344 // sanitize returns an error if the EndpointSelector's LabelSelector is invalid. 345 func (n *EndpointSelector) sanitize() error { 346 errList := validation.ValidateLabelSelector(n.LabelSelector, validation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, nil) 347 if len(errList) > 0 { 348 return fmt.Errorf("invalid label selector: %w", errList.ToAggregate()) 349 } 350 return nil 351 } 352 353 // EndpointSelectorSlice is a slice of EndpointSelectors that can be sorted. 354 type EndpointSelectorSlice []EndpointSelector 355 356 func (s EndpointSelectorSlice) Len() int { return len(s) } 357 func (s EndpointSelectorSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 358 359 func (s EndpointSelectorSlice) Less(i, j int) bool { 360 strI := s[i].LabelSelectorString() 361 strJ := s[j].LabelSelectorString() 362 363 return strings.Compare(strI, strJ) < 0 364 } 365 366 // Matches returns true if any of the EndpointSelectors in the slice match the 367 // provided labels 368 func (s EndpointSelectorSlice) Matches(ctx labels.LabelArray) bool { 369 for _, selector := range s { 370 if selector.Matches(ctx) { 371 return true 372 } 373 } 374 375 return false 376 } 377 378 // SelectsAllEndpoints returns whether the EndpointSelectorSlice selects all 379 // endpoints, which is true if the wildcard endpoint selector is present in the 380 // slice. 381 func (s EndpointSelectorSlice) SelectsAllEndpoints() bool { 382 for _, selector := range s { 383 if selector.IsWildcard() { 384 return true 385 } 386 } 387 return false 388 }