github.com/cilium/cilium@v1.16.2/pkg/k8s/network_policy.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package k8s
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/cilium/cilium/pkg/annotation"
    10  	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    11  	k8sCiliumUtils "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/utils"
    12  	slim_networkingv1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/networking/v1"
    13  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    14  	k8sUtils "github.com/cilium/cilium/pkg/k8s/utils"
    15  	"github.com/cilium/cilium/pkg/labels"
    16  	"github.com/cilium/cilium/pkg/logging/logfields"
    17  	"github.com/cilium/cilium/pkg/policy"
    18  	"github.com/cilium/cilium/pkg/policy/api"
    19  )
    20  
    21  const (
    22  	resourceTypeNetworkPolicy = "NetworkPolicy"
    23  )
    24  
    25  var (
    26  	allowAllNamespacesRequirement = slim_metav1.LabelSelectorRequirement{
    27  		Key:      k8sConst.PodNamespaceLabel,
    28  		Operator: slim_metav1.LabelSelectorOpExists,
    29  	}
    30  )
    31  
    32  // GetPolicyLabelsv1 extracts the name of np. It uses the name  from the Cilium
    33  // annotation if present. If the policy's annotations do not contain
    34  // the Cilium annotation, the policy's name field is used instead.
    35  func GetPolicyLabelsv1(np *slim_networkingv1.NetworkPolicy) labels.LabelArray {
    36  	if np == nil {
    37  		log.Warningf("unable to extract policy labels because provided NetworkPolicy is nil")
    38  		return nil
    39  	}
    40  
    41  	policyName, _ := annotation.Get(np, annotation.PolicyName, annotation.PolicyNameAlias)
    42  	policyUID := np.UID
    43  
    44  	if policyName == "" {
    45  		policyName = np.Name
    46  	}
    47  
    48  	// Here we are using ExtractNamespaceOrDefault instead of ExtractNamespace because we know
    49  	// for sure that the Object is namespace scoped, so if no namespace is provided instead
    50  	// of assuming that the Object is cluster scoped we return the default namespace.
    51  	ns := k8sUtils.ExtractNamespaceOrDefault(&np.ObjectMeta)
    52  
    53  	return k8sCiliumUtils.GetPolicyLabels(ns, policyName, policyUID, resourceTypeNetworkPolicy)
    54  }
    55  
    56  func parseNetworkPolicyPeer(namespace string, peer *slim_networkingv1.NetworkPolicyPeer) *api.EndpointSelector {
    57  	if peer == nil {
    58  		return nil
    59  	}
    60  
    61  	var retSel *api.EndpointSelector
    62  
    63  	if peer.NamespaceSelector != nil {
    64  		namespaceSelector := &slim_metav1.LabelSelector{
    65  			MatchLabels: make(map[string]string, len(peer.NamespaceSelector.MatchLabels)),
    66  		}
    67  		// We use our own special label prefix for namespace metadata,
    68  		// thus we need to prefix that prefix to all NamespaceSelector.MatchLabels
    69  		for k, v := range peer.NamespaceSelector.MatchLabels {
    70  			namespaceSelector.MatchLabels[policy.JoinPath(k8sConst.PodNamespaceMetaLabels, k)] = v
    71  		}
    72  
    73  		// We use our own special label prefix for namespace metadata,
    74  		// thus we need to prefix that prefix to all NamespaceSelector.MatchLabels
    75  		for _, matchExp := range peer.NamespaceSelector.MatchExpressions {
    76  			lsr := slim_metav1.LabelSelectorRequirement{
    77  				Key:      policy.JoinPath(k8sConst.PodNamespaceMetaLabels, matchExp.Key),
    78  				Operator: matchExp.Operator,
    79  			}
    80  			if matchExp.Values != nil {
    81  				lsr.Values = make([]string, len(matchExp.Values))
    82  				copy(lsr.Values, matchExp.Values)
    83  			}
    84  			namespaceSelector.MatchExpressions =
    85  				append(namespaceSelector.MatchExpressions, lsr)
    86  		}
    87  
    88  		// Empty namespace selector selects all namespaces (i.e., a namespace
    89  		// label exists).
    90  		if len(namespaceSelector.MatchLabels) == 0 && len(namespaceSelector.MatchExpressions) == 0 {
    91  			namespaceSelector.MatchExpressions = []slim_metav1.LabelSelectorRequirement{allowAllNamespacesRequirement}
    92  		}
    93  
    94  		selector := api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, namespaceSelector, peer.PodSelector)
    95  		retSel = &selector
    96  	} else if peer.PodSelector != nil {
    97  		podSelector := parsePodSelector(peer.PodSelector, namespace)
    98  		selector := api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, podSelector)
    99  		retSel = &selector
   100  	}
   101  
   102  	return retSel
   103  }
   104  
   105  func hasV1PolicyType(pTypes []slim_networkingv1.PolicyType, typ slim_networkingv1.PolicyType) bool {
   106  	for _, pType := range pTypes {
   107  		if pType == typ {
   108  			return true
   109  		}
   110  	}
   111  	return false
   112  }
   113  
   114  // ParseNetworkPolicy parses a k8s NetworkPolicy. Returns a list of
   115  // Cilium policy rules that can be added, along with an error if there was an
   116  // error sanitizing the rules.
   117  func ParseNetworkPolicy(np *slim_networkingv1.NetworkPolicy) (api.Rules, error) {
   118  
   119  	if np == nil {
   120  		return nil, fmt.Errorf("cannot parse NetworkPolicy because it is nil")
   121  	}
   122  
   123  	ingresses := []api.IngressRule{}
   124  	egresses := []api.EgressRule{}
   125  
   126  	// Since we know that the object NetworkPolicy is namespace scoped we assign
   127  	// namespace to default namespace if the field is empty in the object.
   128  	namespace := k8sUtils.ExtractNamespaceOrDefault(&np.ObjectMeta)
   129  
   130  	for _, iRule := range np.Spec.Ingress {
   131  		fromRules := []api.IngressRule{}
   132  		if iRule.From != nil && len(iRule.From) > 0 {
   133  			for _, rule := range iRule.From {
   134  				ingress := api.IngressRule{}
   135  				endpointSelector := parseNetworkPolicyPeer(namespace, &rule)
   136  
   137  				if endpointSelector != nil {
   138  					ingress.FromEndpoints = append(ingress.FromEndpoints, *endpointSelector)
   139  				} else {
   140  					// No label-based selectors were in NetworkPolicyPeer.
   141  					log.WithField(logfields.K8sNetworkPolicyName, np.Name).Debug("NetworkPolicyPeer does not have PodSelector or NamespaceSelector")
   142  				}
   143  
   144  				// Parse CIDR-based parts of rule.
   145  				if rule.IPBlock != nil {
   146  					ingress.FromCIDRSet = append(ingress.FromCIDRSet, ipBlockToCIDRRule(rule.IPBlock))
   147  				}
   148  
   149  				fromRules = append(fromRules, ingress)
   150  			}
   151  		} else {
   152  			// Based on NetworkPolicyIngressRule docs:
   153  			//   From []NetworkPolicyPeer
   154  			//   If this field is empty or missing, this rule matches all
   155  			//   sources (traffic not restricted by source).
   156  			ingress := api.IngressRule{}
   157  			ingress.FromEndpoints = append(ingress.FromEndpoints, api.WildcardEndpointSelector)
   158  
   159  			fromRules = append(fromRules, ingress)
   160  		}
   161  
   162  		// We apply the ports to all rules generated from the From section
   163  		if iRule.Ports != nil && len(iRule.Ports) > 0 {
   164  			toPorts := parsePorts(iRule.Ports)
   165  			for i := range fromRules {
   166  				fromRules[i].ToPorts = toPorts
   167  			}
   168  		}
   169  
   170  		ingresses = append(ingresses, fromRules...)
   171  	}
   172  
   173  	for _, eRule := range np.Spec.Egress {
   174  		toRules := []api.EgressRule{}
   175  
   176  		if eRule.To != nil && len(eRule.To) > 0 {
   177  			for _, rule := range eRule.To {
   178  				egress := api.EgressRule{}
   179  				if rule.NamespaceSelector != nil || rule.PodSelector != nil {
   180  					endpointSelector := parseNetworkPolicyPeer(namespace, &rule)
   181  
   182  					if endpointSelector != nil {
   183  						egress.ToEndpoints = append(egress.ToEndpoints, *endpointSelector)
   184  					} else {
   185  						log.WithField(logfields.K8sNetworkPolicyName, np.Name).Debug("NetworkPolicyPeer does not have PodSelector or NamespaceSelector")
   186  					}
   187  				}
   188  				if rule.IPBlock != nil {
   189  					egress.ToCIDRSet = append(egress.ToCIDRSet, ipBlockToCIDRRule(rule.IPBlock))
   190  				}
   191  
   192  				toRules = append(toRules, egress)
   193  			}
   194  		} else {
   195  			// Based on NetworkPolicyEgressRule docs:
   196  			//   To []NetworkPolicyPeer
   197  			//   If this field is empty or missing, this rule matches all
   198  			//   destinations (traffic not restricted by destination)
   199  			egress := api.EgressRule{}
   200  			egress.ToEndpoints = append(egress.ToEndpoints, api.WildcardEndpointSelector)
   201  
   202  			toRules = append(toRules, egress)
   203  		}
   204  
   205  		// We apply the ports to all rules generated from the To section
   206  		if eRule.Ports != nil && len(eRule.Ports) > 0 {
   207  			toPorts := parsePorts(eRule.Ports)
   208  			for i := range toRules {
   209  				toRules[i].ToPorts = toPorts
   210  			}
   211  		}
   212  
   213  		egresses = append(egresses, toRules...)
   214  	}
   215  
   216  	// Convert the k8s default-deny model to the Cilium default-deny model
   217  	//spec:
   218  	//  podSelector: {}
   219  	//  policyTypes:
   220  	//	  - Ingress
   221  	// Since k8s 1.7 doesn't contain any PolicyTypes, we default deny
   222  	// if podSelector is empty and the policyTypes is not egress
   223  	if len(ingresses) == 0 &&
   224  		(hasV1PolicyType(np.Spec.PolicyTypes, slim_networkingv1.PolicyTypeIngress) ||
   225  			!hasV1PolicyType(np.Spec.PolicyTypes, slim_networkingv1.PolicyTypeEgress)) {
   226  		ingresses = []api.IngressRule{{}}
   227  	}
   228  
   229  	// Convert the k8s default-deny model to the Cilium default-deny model
   230  	//spec:
   231  	//  podSelector: {}
   232  	//  policyTypes:
   233  	//	  - Egress
   234  	if len(egresses) == 0 && hasV1PolicyType(np.Spec.PolicyTypes, slim_networkingv1.PolicyTypeEgress) {
   235  		egresses = []api.EgressRule{{}}
   236  	}
   237  
   238  	podSelector := parsePodSelector(&np.Spec.PodSelector, namespace)
   239  
   240  	// The next patch will pass the UID.
   241  	rule := api.NewRule().
   242  		WithEndpointSelector(api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, podSelector)).
   243  		WithLabels(GetPolicyLabelsv1(np)).
   244  		WithIngressRules(ingresses).
   245  		WithEgressRules(egresses)
   246  
   247  	if err := rule.Sanitize(); err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	return api.Rules{rule}, nil
   252  }
   253  
   254  func parsePodSelector(podSelectorIn *slim_metav1.LabelSelector, namespace string) *slim_metav1.LabelSelector {
   255  	podSelector := &slim_metav1.LabelSelector{
   256  		MatchLabels: make(map[string]slim_metav1.MatchLabelsValue, len(podSelectorIn.MatchLabels)),
   257  	}
   258  	for k, v := range podSelectorIn.MatchLabels {
   259  		podSelector.MatchLabels[k] = v
   260  	}
   261  	// The PodSelector should only reflect to the same namespace
   262  	// the policy is being stored, thus we add the namespace to
   263  	// the MatchLabels map.
   264  	podSelector.MatchLabels[k8sConst.PodNamespaceLabel] = namespace
   265  
   266  	for _, matchExp := range podSelectorIn.MatchExpressions {
   267  		lsr := slim_metav1.LabelSelectorRequirement{
   268  			Key:      matchExp.Key,
   269  			Operator: matchExp.Operator,
   270  		}
   271  		if matchExp.Values != nil {
   272  			lsr.Values = make([]string, len(matchExp.Values))
   273  			copy(lsr.Values, matchExp.Values)
   274  		}
   275  		podSelector.MatchExpressions =
   276  			append(podSelector.MatchExpressions, lsr)
   277  	}
   278  	return podSelector
   279  }
   280  
   281  func ipBlockToCIDRRule(block *slim_networkingv1.IPBlock) api.CIDRRule {
   282  	cidrRule := api.CIDRRule{}
   283  	cidrRule.Cidr = api.CIDR(block.CIDR)
   284  	for _, v := range block.Except {
   285  		cidrRule.ExceptCIDRs = append(cidrRule.ExceptCIDRs, api.CIDR(v))
   286  	}
   287  	return cidrRule
   288  }
   289  
   290  // parsePorts converts list of K8s NetworkPolicyPorts to Cilium PortRules.
   291  func parsePorts(ports []slim_networkingv1.NetworkPolicyPort) []api.PortRule {
   292  	portRules := []api.PortRule{}
   293  	for _, port := range ports {
   294  		protocol := api.ProtoTCP
   295  		if port.Protocol != nil {
   296  			protocol, _ = api.ParseL4Proto(string(*port.Protocol))
   297  		}
   298  
   299  		portStr := "0"
   300  		var endPort int32
   301  		if port.Port != nil {
   302  			portStr = port.Port.String()
   303  		}
   304  		if port.EndPort != nil {
   305  			endPort = *port.EndPort
   306  		}
   307  
   308  		portRule := api.PortRule{
   309  			Ports: []api.PortProtocol{
   310  				{Port: portStr, EndPort: endPort, Protocol: protocol},
   311  			},
   312  		}
   313  
   314  		portRules = append(portRules, portRule)
   315  	}
   316  
   317  	return portRules
   318  }