github.com/cilium/cilium@v1.16.2/pkg/k8s/apis/cilium.io/utils/utils.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package utils
     5  
     6  import (
     7  	"github.com/sirupsen/logrus"
     8  	"k8s.io/apimachinery/pkg/types"
     9  
    10  	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    11  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    12  	"github.com/cilium/cilium/pkg/labels"
    13  	"github.com/cilium/cilium/pkg/logging"
    14  	"github.com/cilium/cilium/pkg/logging/logfields"
    15  	"github.com/cilium/cilium/pkg/policy/api"
    16  )
    17  
    18  const (
    19  	// subsysK8s is the value for logfields.LogSubsys
    20  	subsysK8s = "k8s"
    21  	// podPrefixLbl is the value the prefix used in the label selector to
    22  	// represent pods on the default namespace.
    23  	podPrefixLbl = labels.LabelSourceK8sKeyPrefix + k8sConst.PodNamespaceLabel
    24  
    25  	// podAnyPrefixLbl is the value of the prefix used in the label selector to
    26  	// represent pods in the default namespace for any source type.
    27  	podAnyPrefixLbl = labels.LabelSourceAnyKeyPrefix + k8sConst.PodNamespaceLabel
    28  
    29  	// podK8SNamespaceLabelsPrefix is the prefix use in the label selector for namespace labels.
    30  	podK8SNamespaceLabelsPrefix = labels.LabelSourceK8sKeyPrefix + k8sConst.PodNamespaceMetaLabelsPrefix
    31  	// podAnyNamespaceLabelsPrefix is the prefix use in the label selector for namespace labels
    32  	// for any source type.
    33  	podAnyNamespaceLabelsPrefix = labels.LabelSourceAnyKeyPrefix + k8sConst.PodNamespaceMetaLabelsPrefix
    34  
    35  	// podInitLbl is the label used in a label selector to match on
    36  	// initializing pods.
    37  	podInitLbl = labels.LabelSourceReservedKeyPrefix + labels.IDNameInit
    38  
    39  	// ResourceTypeCiliumNetworkPolicy is the resource type used for the
    40  	// PolicyLabelDerivedFrom label
    41  	ResourceTypeCiliumNetworkPolicy = "CiliumNetworkPolicy"
    42  
    43  	// ResourceTypeCiliumClusterwideNetworkPolicy is the resource type used for the
    44  	// PolicyLabelDerivedFrom label
    45  	ResourceTypeCiliumClusterwideNetworkPolicy = "CiliumClusterwideNetworkPolicy"
    46  )
    47  
    48  var (
    49  	// log is the k8s package logger object.
    50  	log = logging.DefaultLogger.WithField(logfields.LogSubsys, subsysK8s)
    51  )
    52  
    53  // GetPolicyLabels returns a LabelArray for the given namespace and name.
    54  func GetPolicyLabels(ns, name string, uid types.UID, derivedFrom string) labels.LabelArray {
    55  	// Keep labels sorted by the key.
    56  	labelsArr := labels.LabelArray{
    57  		labels.NewLabel(k8sConst.PolicyLabelDerivedFrom, derivedFrom, labels.LabelSourceK8s),
    58  		labels.NewLabel(k8sConst.PolicyLabelName, name, labels.LabelSourceK8s),
    59  	}
    60  
    61  	// For clusterwide policy namespace will be empty.
    62  	if ns != "" {
    63  		nsLabel := labels.NewLabel(k8sConst.PolicyLabelNamespace, ns, labels.LabelSourceK8s)
    64  		labelsArr = append(labelsArr, nsLabel)
    65  	}
    66  
    67  	srcLabel := labels.NewLabel(k8sConst.PolicyLabelUID, string(uid), labels.LabelSourceK8s)
    68  	return append(labelsArr, srcLabel)
    69  }
    70  
    71  // getEndpointSelector converts the provided labelSelector into an EndpointSelector,
    72  // adding the relevant matches for namespaces based on the provided options.
    73  // If no namespace is provided then it is assumed that the selector is global to the cluster
    74  // this is when translating selectors for CiliumClusterwideNetworkPolicy.
    75  func getEndpointSelector(namespace string, labelSelector *slim_metav1.LabelSelector, addK8sPrefix, matchesInit bool) api.EndpointSelector {
    76  	es := api.NewESFromK8sLabelSelector("", labelSelector)
    77  
    78  	// The k8s prefix must not be added to reserved labels.
    79  	if addK8sPrefix && es.HasKeyPrefix(labels.LabelSourceReservedKeyPrefix) {
    80  		return es
    81  	}
    82  
    83  	// The user can explicitly specify the namespace in the
    84  	// FromEndpoints selector. If omitted, we limit the
    85  	// scope to the namespace the policy lives in.
    86  	//
    87  	// Policies applying on initializing pods are a special case.
    88  	// Those pods don't have any labels, so they don't have a namespace label either.
    89  	// Don't add a namespace label to those endpoint selectors, or we wouldn't be
    90  	// able to match on those pods.
    91  	if !es.HasKey(podPrefixLbl) && !es.HasKey(podAnyPrefixLbl) {
    92  		if namespace == "" {
    93  			// For a clusterwide policy if a namespace is not specified in the labels we add
    94  			// a selector to only match endpoints that contains a namespace label.
    95  			// This is to make sure that we are only allowing traffic for cilium managed k8s endpoints
    96  			// and even if a wildcard is provided in the selector we don't proceed with a truly
    97  			// empty(allow all) endpoint selector for the policy.
    98  			if !matchesInit {
    99  				es.AddMatchExpression(podPrefixLbl, slim_metav1.LabelSelectorOpExists, []string{})
   100  			}
   101  		} else if !es.HasKeyPrefix(podK8SNamespaceLabelsPrefix) && !es.HasKeyPrefix(podAnyNamespaceLabelsPrefix) {
   102  			es.AddMatch(podPrefixLbl, namespace)
   103  		}
   104  	}
   105  
   106  	return es
   107  }
   108  
   109  func parseToCiliumIngressCommonRule(namespace string, es api.EndpointSelector, ing api.IngressCommonRule) api.IngressCommonRule {
   110  	matchesInit := matchesPodInit(es)
   111  	var retRule api.IngressCommonRule
   112  
   113  	if ing.FromEndpoints != nil {
   114  		retRule.FromEndpoints = make([]api.EndpointSelector, len(ing.FromEndpoints))
   115  		for j, ep := range ing.FromEndpoints {
   116  			retRule.FromEndpoints[j] = getEndpointSelector(namespace, ep.LabelSelector, true, matchesInit)
   117  		}
   118  	}
   119  
   120  	if ing.FromNodes != nil {
   121  		retRule.FromNodes = make([]api.EndpointSelector, len(ing.FromNodes))
   122  		for j, node := range ing.FromNodes {
   123  			retRule.FromNodes[j] = api.NewESFromK8sLabelSelector("", node.LabelSelector)
   124  		}
   125  	}
   126  
   127  	if ing.FromCIDR != nil {
   128  		retRule.FromCIDR = make([]api.CIDR, len(ing.FromCIDR))
   129  		copy(retRule.FromCIDR, ing.FromCIDR)
   130  	}
   131  
   132  	if ing.FromCIDRSet != nil {
   133  		retRule.FromCIDRSet = make([]api.CIDRRule, len(ing.FromCIDRSet))
   134  		copy(retRule.FromCIDRSet, ing.FromCIDRSet)
   135  	}
   136  
   137  	if ing.FromRequires != nil {
   138  		retRule.FromRequires = make([]api.EndpointSelector, len(ing.FromRequires))
   139  		for j, ep := range ing.FromRequires {
   140  			retRule.FromRequires[j] = getEndpointSelector(namespace, ep.LabelSelector, false, matchesInit)
   141  		}
   142  	}
   143  
   144  	if ing.FromEntities != nil {
   145  		retRule.FromEntities = make([]api.Entity, len(ing.FromEntities))
   146  		copy(retRule.FromEntities, ing.FromEntities)
   147  	}
   148  
   149  	if ing.FromGroups != nil {
   150  		retRule.FromGroups = make([]api.Groups, len(ing.FromGroups))
   151  		copy(retRule.FromGroups, ing.FromGroups)
   152  	}
   153  
   154  	return retRule
   155  }
   156  
   157  func parseToCiliumIngressRule(namespace string, es api.EndpointSelector, inRules []api.IngressRule) []api.IngressRule {
   158  	var retRules []api.IngressRule
   159  
   160  	if inRules != nil {
   161  		retRules = make([]api.IngressRule, len(inRules))
   162  		for i, ing := range inRules {
   163  			if ing.ToPorts != nil {
   164  				retRules[i].ToPorts = make([]api.PortRule, len(ing.ToPorts))
   165  				copy(retRules[i].ToPorts, ing.ToPorts)
   166  			}
   167  			if ing.ICMPs != nil {
   168  				retRules[i].ICMPs = make(api.ICMPRules, len(ing.ICMPs))
   169  				copy(retRules[i].ICMPs, ing.ICMPs)
   170  			}
   171  			retRules[i].IngressCommonRule = parseToCiliumIngressCommonRule(namespace, es, ing.IngressCommonRule)
   172  			retRules[i].Authentication = ing.Authentication.DeepCopy()
   173  			retRules[i].SetAggregatedSelectors()
   174  		}
   175  	}
   176  	return retRules
   177  }
   178  
   179  func parseToCiliumIngressDenyRule(namespace string, es api.EndpointSelector, inRules []api.IngressDenyRule) []api.IngressDenyRule {
   180  	var retRules []api.IngressDenyRule
   181  
   182  	if inRules != nil {
   183  		retRules = make([]api.IngressDenyRule, len(inRules))
   184  		for i, ing := range inRules {
   185  			if ing.ToPorts != nil {
   186  				retRules[i].ToPorts = make([]api.PortDenyRule, len(ing.ToPorts))
   187  				copy(retRules[i].ToPorts, ing.ToPorts)
   188  			}
   189  			if ing.ICMPs != nil {
   190  				retRules[i].ICMPs = make(api.ICMPRules, len(ing.ICMPs))
   191  				copy(retRules[i].ICMPs, ing.ICMPs)
   192  			}
   193  			retRules[i].IngressCommonRule = parseToCiliumIngressCommonRule(namespace, es, ing.IngressCommonRule)
   194  			retRules[i].SetAggregatedSelectors()
   195  		}
   196  	}
   197  	return retRules
   198  }
   199  
   200  func parseToCiliumEgressCommonRule(namespace string, es api.EndpointSelector, egr api.EgressCommonRule) api.EgressCommonRule {
   201  	matchesInit := matchesPodInit(es)
   202  	var retRule api.EgressCommonRule
   203  	if egr.ToEndpoints != nil {
   204  		retRule.ToEndpoints = make([]api.EndpointSelector, len(egr.ToEndpoints))
   205  		for j, ep := range egr.ToEndpoints {
   206  			retRule.ToEndpoints[j] = getEndpointSelector(namespace, ep.LabelSelector, true, matchesInit)
   207  		}
   208  	}
   209  
   210  	if egr.ToCIDR != nil {
   211  		retRule.ToCIDR = make([]api.CIDR, len(egr.ToCIDR))
   212  		copy(retRule.ToCIDR, egr.ToCIDR)
   213  	}
   214  
   215  	if egr.ToCIDRSet != nil {
   216  		retRule.ToCIDRSet = make(api.CIDRRuleSlice, len(egr.ToCIDRSet))
   217  		copy(retRule.ToCIDRSet, egr.ToCIDRSet)
   218  	}
   219  
   220  	if egr.ToRequires != nil {
   221  		retRule.ToRequires = make([]api.EndpointSelector, len(egr.ToRequires))
   222  		for j, ep := range egr.ToRequires {
   223  			retRule.ToRequires[j] = getEndpointSelector(namespace, ep.LabelSelector, false, matchesInit)
   224  		}
   225  	}
   226  
   227  	if egr.ToServices != nil {
   228  		retRule.ToServices = make([]api.Service, len(egr.ToServices))
   229  		copy(retRule.ToServices, egr.ToServices)
   230  	}
   231  
   232  	if egr.ToEntities != nil {
   233  		retRule.ToEntities = make([]api.Entity, len(egr.ToEntities))
   234  		copy(retRule.ToEntities, egr.ToEntities)
   235  	}
   236  
   237  	if egr.ToNodes != nil {
   238  		retRule.ToNodes = make([]api.EndpointSelector, len(egr.ToNodes))
   239  		for j, node := range egr.ToNodes {
   240  			retRule.ToNodes[j] = api.NewESFromK8sLabelSelector("", node.LabelSelector)
   241  		}
   242  	}
   243  
   244  	if egr.ToGroups != nil {
   245  		retRule.ToGroups = make([]api.Groups, len(egr.ToGroups))
   246  		copy(retRule.ToGroups, egr.ToGroups)
   247  	}
   248  
   249  	return retRule
   250  }
   251  
   252  func parseToCiliumEgressRule(namespace string, es api.EndpointSelector, inRules []api.EgressRule) []api.EgressRule {
   253  	var retRules []api.EgressRule
   254  
   255  	if inRules != nil {
   256  		retRules = make([]api.EgressRule, len(inRules))
   257  		for i, egr := range inRules {
   258  			if egr.ToPorts != nil {
   259  				retRules[i].ToPorts = make([]api.PortRule, len(egr.ToPorts))
   260  				copy(retRules[i].ToPorts, egr.ToPorts)
   261  			}
   262  
   263  			if egr.ICMPs != nil {
   264  				retRules[i].ICMPs = make(api.ICMPRules, len(egr.ICMPs))
   265  				copy(retRules[i].ICMPs, egr.ICMPs)
   266  			}
   267  
   268  			if egr.ToFQDNs != nil {
   269  				retRules[i].ToFQDNs = make([]api.FQDNSelector, len(egr.ToFQDNs))
   270  				copy(retRules[i].ToFQDNs, egr.ToFQDNs)
   271  			}
   272  
   273  			retRules[i].EgressCommonRule = parseToCiliumEgressCommonRule(namespace, es, egr.EgressCommonRule)
   274  			retRules[i].Authentication = egr.Authentication
   275  			retRules[i].SetAggregatedSelectors()
   276  		}
   277  	}
   278  	return retRules
   279  }
   280  
   281  func parseToCiliumEgressDenyRule(namespace string, es api.EndpointSelector, inRules []api.EgressDenyRule) []api.EgressDenyRule {
   282  	var retRules []api.EgressDenyRule
   283  
   284  	if inRules != nil {
   285  		retRules = make([]api.EgressDenyRule, len(inRules))
   286  		for i, egr := range inRules {
   287  			if egr.ToPorts != nil {
   288  				retRules[i].ToPorts = make([]api.PortDenyRule, len(egr.ToPorts))
   289  				copy(retRules[i].ToPorts, egr.ToPorts)
   290  			}
   291  
   292  			if egr.ICMPs != nil {
   293  				retRules[i].ICMPs = make(api.ICMPRules, len(egr.ICMPs))
   294  				copy(retRules[i].ICMPs, egr.ICMPs)
   295  			}
   296  
   297  			retRules[i].EgressCommonRule = parseToCiliumEgressCommonRule(namespace, es, egr.EgressCommonRule)
   298  			retRules[i].SetAggregatedSelectors()
   299  		}
   300  	}
   301  	return retRules
   302  }
   303  
   304  func matchesPodInit(epSelector api.EndpointSelector) bool {
   305  	if epSelector.LabelSelector == nil {
   306  		return false
   307  	}
   308  	return epSelector.HasKey(podInitLbl)
   309  }
   310  
   311  // namespacesAreValid checks the set of namespaces from a rule returns true if
   312  // they are not specified, or if they are specified and match the namespace
   313  // where the rule is being inserted.
   314  func namespacesAreValid(namespace string, userNamespaces []string) bool {
   315  	return len(userNamespaces) == 0 ||
   316  		(len(userNamespaces) == 1 && userNamespaces[0] == namespace)
   317  }
   318  
   319  // ParseToCiliumRule returns an api.Rule with all the labels parsed into cilium
   320  // labels. If the namespace provided is empty then the rule is cluster scoped, this
   321  // might happen in case of CiliumClusterwideNetworkPolicy which enforces a policy on the cluster
   322  // instead of the particular namespace.
   323  func ParseToCiliumRule(namespace, name string, uid types.UID, r *api.Rule) *api.Rule {
   324  	retRule := &api.Rule{}
   325  	if r.EndpointSelector.LabelSelector != nil {
   326  		retRule.EndpointSelector = api.NewESFromK8sLabelSelector("", r.EndpointSelector.LabelSelector)
   327  		// The PodSelector should only reflect to the same namespace
   328  		// the policy is being stored, thus we add the namespace to
   329  		// the MatchLabels map.
   330  		//
   331  		// Policies applying to all namespaces are a special case.
   332  		// Such policies can match on any traffic from Pods or Nodes,
   333  		// so it wouldn't make sense to inject a namespace match for
   334  		// those policies.
   335  		if namespace != "" {
   336  			userNamespace, present := r.EndpointSelector.GetMatch(podPrefixLbl)
   337  			if present && !namespacesAreValid(namespace, userNamespace) {
   338  				log.WithFields(logrus.Fields{
   339  					logfields.K8sNamespace:              namespace,
   340  					logfields.CiliumNetworkPolicyName:   name,
   341  					logfields.K8sNamespace + ".illegal": userNamespace,
   342  				}).Warn("CiliumNetworkPolicy contains illegal namespace match in EndpointSelector." +
   343  					" EndpointSelector always applies in namespace of the policy resource, removing illegal namespace match'.")
   344  			}
   345  			retRule.EndpointSelector.AddMatch(podPrefixLbl, namespace)
   346  		}
   347  	} else if r.NodeSelector.LabelSelector != nil {
   348  		retRule.NodeSelector = api.NewESFromK8sLabelSelector("", r.NodeSelector.LabelSelector)
   349  	}
   350  
   351  	retRule.Ingress = parseToCiliumIngressRule(namespace, r.EndpointSelector, r.Ingress)
   352  	retRule.IngressDeny = parseToCiliumIngressDenyRule(namespace, r.EndpointSelector, r.IngressDeny)
   353  	retRule.Egress = parseToCiliumEgressRule(namespace, r.EndpointSelector, r.Egress)
   354  	retRule.EgressDeny = parseToCiliumEgressDenyRule(namespace, r.EndpointSelector, r.EgressDeny)
   355  
   356  	retRule.Labels = ParseToCiliumLabels(namespace, name, uid, r.Labels)
   357  
   358  	retRule.Description = r.Description
   359  	retRule.EnableDefaultDeny = r.EnableDefaultDeny
   360  
   361  	return retRule
   362  }
   363  
   364  // ParseToCiliumLabels returns all ruleLbls appended with a specific label that
   365  // represents the given namespace and name along with a label that specifies
   366  // these labels were derived from a CiliumNetworkPolicy.
   367  func ParseToCiliumLabels(namespace, name string, uid types.UID, ruleLbs labels.LabelArray) labels.LabelArray {
   368  	resourceType := ResourceTypeCiliumNetworkPolicy
   369  	if namespace == "" {
   370  		resourceType = ResourceTypeCiliumClusterwideNetworkPolicy
   371  	}
   372  
   373  	policyLbls := GetPolicyLabels(namespace, name, uid, resourceType)
   374  	return append(policyLbls, ruleLbs...).Sort()
   375  }