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 }