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  }