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 }