github.com/imran-kn/cilium-fork@v1.6.9/pkg/k8s/network_policy.go (about)

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