github.com/cilium/cilium@v1.16.2/pkg/policy/api/rule_validation.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package api 5 6 import ( 7 "errors" 8 "fmt" 9 "net/netip" 10 "strconv" 11 "strings" 12 13 "github.com/cilium/cilium/pkg/iana" 14 "github.com/cilium/cilium/pkg/option" 15 ) 16 17 const ( 18 maxPorts = 40 19 maxICMPFields = 40 20 ) 21 22 var ( 23 ErrFromToNodesRequiresNodeSelectorOption = fmt.Errorf("FromNodes/ToNodes rules can only be applied when the %q flag is set", option.EnableNodeSelectorLabels) 24 ) 25 26 // Sanitize validates and sanitizes a policy rule. Minor edits such as 27 // capitalization of the protocol name are automatically fixed up. More 28 // fundamental violations will cause an error to be returned. 29 func (r *Rule) Sanitize() error { 30 // Fill in the default traffic posture of this Rule. 31 // Default posture is per-direction (ingress or egress), 32 // if there is a peer selector for that direction, the 33 // default is deny, else allow. 34 if r.EnableDefaultDeny.Egress == nil { 35 x := len(r.Egress) > 0 || len(r.EgressDeny) > 0 36 r.EnableDefaultDeny.Egress = &x 37 } 38 if r.EnableDefaultDeny.Ingress == nil { 39 x := len(r.Ingress) > 0 || len(r.IngressDeny) > 0 40 r.EnableDefaultDeny.Ingress = &x 41 } 42 43 if r.EndpointSelector.LabelSelector == nil && r.NodeSelector.LabelSelector == nil { 44 return fmt.Errorf("rule must have one of EndpointSelector or NodeSelector") 45 } 46 if r.EndpointSelector.LabelSelector != nil && r.NodeSelector.LabelSelector != nil { 47 return fmt.Errorf("rule cannot have both EndpointSelector and NodeSelector") 48 } 49 50 if r.EndpointSelector.LabelSelector != nil { 51 if err := r.EndpointSelector.sanitize(); err != nil { 52 return err 53 } 54 } 55 56 var hostPolicy bool 57 if r.NodeSelector.LabelSelector != nil { 58 if err := r.NodeSelector.sanitize(); err != nil { 59 return err 60 } 61 hostPolicy = true 62 } 63 64 for i := range r.Ingress { 65 if err := r.Ingress[i].sanitize(); err != nil { 66 return err 67 } 68 if hostPolicy { 69 if len(countL7Rules(r.Ingress[i].ToPorts)) > 0 { 70 return fmt.Errorf("host policies do not support L7 rules yet") 71 } 72 } 73 } 74 75 for i := range r.Egress { 76 if err := r.Egress[i].sanitize(); err != nil { 77 return err 78 } 79 if hostPolicy { 80 if len(countL7Rules(r.Egress[i].ToPorts)) > 0 { 81 return fmt.Errorf("host policies do not support L7 rules yet") 82 } 83 } 84 } 85 86 return nil 87 } 88 89 func countL7Rules(ports []PortRule) map[string]int { 90 result := make(map[string]int) 91 for _, port := range ports { 92 if !port.Rules.IsEmpty() { 93 result["DNS"] += len(port.Rules.DNS) 94 result["HTTP"] += len(port.Rules.HTTP) 95 result["Kafka"] += len(port.Rules.Kafka) 96 } 97 } 98 return result 99 } 100 101 func (i *IngressRule) sanitize() error { 102 var retErr error 103 104 l3Members := map[string]int{ 105 "FromEndpoints": len(i.FromEndpoints), 106 "FromCIDR": len(i.FromCIDR), 107 "FromCIDRSet": len(i.FromCIDRSet), 108 "FromEntities": len(i.FromEntities), 109 "FromNodes": len(i.FromNodes), 110 "FromGroups": len(i.FromGroups), 111 } 112 l7Members := countL7Rules(i.ToPorts) 113 l7IngressSupport := map[string]bool{ 114 "DNS": false, 115 "Kafka": true, 116 "HTTP": true, 117 } 118 119 for m1 := range l3Members { 120 for m2 := range l3Members { 121 if m2 != m1 && l3Members[m1] > 0 && l3Members[m2] > 0 { 122 return fmt.Errorf("Combining %s and %s is not supported yet", m1, m2) 123 } 124 } 125 } 126 127 if len(l7Members) > 0 && !option.Config.EnableL7Proxy { 128 return errors.New("L7 policy is not supported since L7 proxy is not enabled") 129 } 130 for member := range l7Members { 131 if l7Members[member] > 0 && !l7IngressSupport[member] { 132 return fmt.Errorf("L7 protocol %s is not supported on ingress yet", member) 133 } 134 } 135 136 if len(i.ICMPs) > 0 && !option.Config.EnableICMPRules { 137 return fmt.Errorf("ICMP rules can only be applied when the %q flag is set", option.EnableICMPRules) 138 } 139 140 if len(i.ICMPs) > 0 && len(i.ToPorts) > 0 { 141 return fmt.Errorf("The ICMPs block may only be present without ToPorts. Define a separate rule to use ToPorts.") 142 } 143 144 if len(i.FromNodes) > 0 && !option.Config.EnableNodeSelectorLabels { 145 retErr = ErrFromToNodesRequiresNodeSelectorOption 146 } 147 148 for _, es := range i.FromEndpoints { 149 if err := es.sanitize(); err != nil { 150 return err 151 } 152 } 153 154 for _, es := range i.FromRequires { 155 if err := es.sanitize(); err != nil { 156 return err 157 } 158 } 159 160 for n := range i.ToPorts { 161 if err := i.ToPorts[n].sanitize(true); err != nil { 162 return err 163 } 164 } 165 166 for n := range i.ICMPs { 167 if err := i.ICMPs[n].verify(); err != nil { 168 return err 169 } 170 } 171 172 for _, ns := range i.FromNodes { 173 if err := ns.sanitize(); err != nil { 174 return err 175 } 176 } 177 178 for n := range i.FromCIDR { 179 if err := i.FromCIDR[n].sanitize(); err != nil { 180 return err 181 } 182 } 183 184 for n := range i.FromCIDRSet { 185 if err := i.FromCIDRSet[n].sanitize(); err != nil { 186 return err 187 } 188 } 189 190 for _, fromEntity := range i.FromEntities { 191 _, ok := EntitySelectorMapping[fromEntity] 192 if !ok { 193 return fmt.Errorf("unsupported entity: %s", fromEntity) 194 } 195 } 196 197 i.SetAggregatedSelectors() 198 199 return retErr 200 } 201 202 // countNonGeneratedRules counts the number of CIDRRule items which are not 203 // `Generated`, i.e. were directly provided by the user. 204 // The `Generated` field is currently only set by the `ToServices` 205 // implementation, which extracts service endpoints and translates them as 206 // ToCIDRSet rules before the CNP is passed to the policy repository. 207 // Therefore, we want to allow the combination of ToCIDRSet and ToServices 208 // rules, if (and only if) the ToCIDRSet only contains `Generated` entries. 209 func countNonGeneratedCIDRRules(s CIDRRuleSlice) int { 210 n := 0 211 for _, c := range s { 212 if !c.Generated { 213 n++ 214 } 215 } 216 return n 217 } 218 219 func (e *EgressRule) sanitize() error { 220 var retErr error 221 222 l3Members := map[string]int{ 223 "ToCIDR": len(e.ToCIDR), 224 "ToCIDRSet": countNonGeneratedCIDRRules(e.ToCIDRSet), 225 "ToEndpoints": len(e.ToEndpoints), 226 "ToEntities": len(e.ToEntities), 227 "ToServices": len(e.ToServices), 228 "ToFQDNs": len(e.ToFQDNs), 229 "ToGroups": len(e.ToGroups), 230 "ToNodes": len(e.ToNodes), 231 } 232 l3DependentL4Support := map[interface{}]bool{ 233 "ToCIDR": true, 234 "ToCIDRSet": true, 235 "ToEndpoints": true, 236 "ToEntities": true, 237 "ToServices": false, // see https://github.com/cilium/cilium/issues/20067 238 "ToFQDNs": true, 239 "ToGroups": true, 240 "ToNodes": true, 241 } 242 l7Members := countL7Rules(e.ToPorts) 243 l7EgressSupport := map[string]bool{ 244 "DNS": true, 245 "Kafka": true, 246 "HTTP": true, 247 } 248 249 for m1 := range l3Members { 250 for m2 := range l3Members { 251 if m2 != m1 && l3Members[m1] > 0 && l3Members[m2] > 0 { 252 return fmt.Errorf("Combining %s and %s is not supported yet", m1, m2) 253 } 254 } 255 } 256 for member := range l3Members { 257 if l3Members[member] > 0 && len(e.ToPorts) > 0 && !l3DependentL4Support[member] { 258 return fmt.Errorf("Combining %s and ToPorts is not supported yet", member) 259 } 260 } 261 262 if len(l7Members) > 0 && !option.Config.EnableL7Proxy { 263 return errors.New("L7 policy is not supported since L7 proxy is not enabled") 264 } 265 for member := range l7Members { 266 if l7Members[member] > 0 && !l7EgressSupport[member] { 267 return fmt.Errorf("L7 protocol %s is not supported on egress yet", member) 268 } 269 } 270 271 if len(e.ICMPs) > 0 && !option.Config.EnableICMPRules { 272 return fmt.Errorf("ICMP rules can only be applied when the %q flag is set", option.EnableICMPRules) 273 } 274 275 if len(e.ICMPs) > 0 && len(e.ToPorts) > 0 { 276 return fmt.Errorf("The ICMPs block may only be present without ToPorts. Define a separate rule to use ToPorts.") 277 } 278 279 if len(e.ToNodes) > 0 && !option.Config.EnableNodeSelectorLabels { 280 retErr = ErrFromToNodesRequiresNodeSelectorOption 281 } 282 283 for _, es := range e.ToEndpoints { 284 if err := es.sanitize(); err != nil { 285 return err 286 } 287 } 288 289 for _, es := range e.ToRequires { 290 if err := es.sanitize(); err != nil { 291 return err 292 } 293 } 294 295 for i := range e.ToPorts { 296 if err := e.ToPorts[i].sanitize(false); err != nil { 297 return err 298 } 299 } 300 301 for n := range e.ICMPs { 302 if err := e.ICMPs[n].verify(); err != nil { 303 return err 304 } 305 } 306 307 for _, ns := range e.ToNodes { 308 if err := ns.sanitize(); err != nil { 309 return err 310 } 311 } 312 313 for i := range e.ToCIDR { 314 if err := e.ToCIDR[i].sanitize(); err != nil { 315 return err 316 } 317 } 318 for i := range e.ToCIDRSet { 319 if err := e.ToCIDRSet[i].sanitize(); err != nil { 320 return err 321 } 322 } 323 324 for _, toEntity := range e.ToEntities { 325 _, ok := EntitySelectorMapping[toEntity] 326 if !ok { 327 return fmt.Errorf("unsupported entity: %s", toEntity) 328 } 329 } 330 331 for i := range e.ToFQDNs { 332 err := e.ToFQDNs[i].sanitize() 333 if err != nil { 334 return err 335 } 336 } 337 338 e.SetAggregatedSelectors() 339 340 return retErr 341 } 342 343 func (pr *L7Rules) sanitize(ports []PortProtocol) error { 344 nTypes := 0 345 346 if pr.HTTP != nil { 347 nTypes++ 348 for i := range pr.HTTP { 349 if err := pr.HTTP[i].Sanitize(); err != nil { 350 return err 351 } 352 } 353 } 354 355 if pr.Kafka != nil { 356 nTypes++ 357 for i := range pr.Kafka { 358 if err := pr.Kafka[i].Sanitize(); err != nil { 359 return err 360 } 361 } 362 } 363 364 if pr.DNS != nil { 365 // Forthcoming TPROXY redirection restricts DNS proxy to the standard DNS port (53). 366 // Require the port 53 be explicitly configured, and disallow other port numbers. 367 if len(ports) == 0 { 368 return fmt.Errorf("Port 53 must be specified for DNS rules") 369 } 370 371 nTypes++ 372 for i := range pr.DNS { 373 if err := pr.DNS[i].Sanitize(); err != nil { 374 return err 375 } 376 } 377 } 378 379 if pr.L7 != nil && pr.L7Proto == "" { 380 return fmt.Errorf("'l7' may only be specified when a 'l7proto' is also specified") 381 } 382 if pr.L7Proto != "" { 383 nTypes++ 384 for i := range pr.L7 { 385 if err := pr.L7[i].Sanitize(); err != nil { 386 return err 387 } 388 } 389 } 390 391 if nTypes > 1 { 392 return fmt.Errorf("multiple L7 protocol rule types specified in single rule") 393 } 394 return nil 395 } 396 397 // It is not allowed to configure an ingress listener, but we still 398 // have some unit tests relying on this. So, allow overriding this check in the unit tests. 399 var TestAllowIngressListener = false 400 401 func (pr *PortRule) sanitize(ingress bool) error { 402 hasDNSRules := pr.Rules != nil && len(pr.Rules.DNS) > 0 403 if ingress && hasDNSRules { 404 return fmt.Errorf("DNS rules are not allowed on ingress") 405 } 406 407 if len(pr.ServerNames) > 0 && !pr.Rules.IsEmpty() && pr.TerminatingTLS == nil { 408 return fmt.Errorf("ServerNames are not allowed with L7 rules without TLS termination") 409 } 410 for _, sn := range pr.ServerNames { 411 if sn == "" { 412 return fmt.Errorf("Empty server name is not allowed") 413 } 414 } 415 416 if len(pr.Ports) > maxPorts { 417 return fmt.Errorf("too many ports, the max is %d", maxPorts) 418 } 419 haveZeroPort := false 420 for i := range pr.Ports { 421 var isZero bool 422 var err error 423 if isZero, err = pr.Ports[i].sanitize(hasDNSRules); err != nil { 424 return err 425 } 426 if isZero { 427 haveZeroPort = true 428 } 429 // DNS L7 rules can be TCP, UDP or ANY, all others are TCP only. 430 switch { 431 case pr.Rules.IsEmpty(), hasDNSRules: 432 // nothing to do if no rules OR they are DNS rules (note the comma above) 433 case pr.Ports[i].Protocol != ProtoTCP: 434 return fmt.Errorf("L7 rules can only apply to TCP (not %s) except for DNS rules", pr.Ports[i].Protocol) 435 } 436 } 437 438 listener := pr.Listener 439 if listener != nil { 440 // For now we have only tested custom listener support on the egress path. TODO 441 // (jrajahalme): Lift this limitation in follow-up work once proper testing has been 442 // done on the ingress path. 443 if ingress && !TestAllowIngressListener { 444 return fmt.Errorf("Listener is not allowed on ingress (%s)", listener.Name) 445 } 446 // There is no quarantee that Listener will support Cilium policy enforcement. Even 447 // now proxylib-based enforcement (e.g, Kafka) may work, but has not been tested. 448 // TODO (jrajahalme): Lift this limitation in follow-up work for proxylib based 449 // parsers if needed and when tested. 450 if !pr.Rules.IsEmpty() { 451 return fmt.Errorf("Listener is not allowed with L7 rules (%s)", listener.Name) 452 } 453 } 454 455 // Sanitize L7 rules 456 if !pr.Rules.IsEmpty() { 457 if haveZeroPort { 458 return fmt.Errorf("L7 rules can not be used when a port is 0") 459 } 460 461 if err := pr.Rules.sanitize(pr.Ports); err != nil { 462 return err 463 } 464 } 465 return nil 466 } 467 468 func (pp *PortProtocol) sanitize(hasDNSRules bool) (isZero bool, err error) { 469 if pp.Port == "" { 470 return isZero, fmt.Errorf("Port must be specified") 471 } 472 473 // Port names are formatted as IANA Service Names. This means that 474 // some legal numeric literals are no longer considered numbers, e.g, 475 // 0x10 is now considered a name rather than number 16. 476 if iana.IsSvcName(pp.Port) { 477 pp.Port = strings.ToLower(pp.Port) // Normalize for case insensitive comparison 478 } else { 479 p, err := strconv.ParseUint(pp.Port, 0, 16) 480 if err != nil { 481 return isZero, fmt.Errorf("Unable to parse port: %w", err) 482 } 483 isZero = p == 0 484 if hasDNSRules && pp.EndPort > int32(p) { 485 return isZero, errors.New("DNS rules do not support port ranges") 486 } 487 } 488 489 pp.Protocol, err = ParseL4Proto(string(pp.Protocol)) 490 return isZero, err 491 } 492 493 func (ir *ICMPRule) verify() error { 494 if len(ir.Fields) > maxICMPFields { 495 return fmt.Errorf("too many types, the max is %d", maxICMPFields) 496 } 497 498 for _, f := range ir.Fields { 499 if f.Family != IPv4Family && f.Family != IPv6Family && f.Family != "" { 500 return fmt.Errorf("wrong family: %s", f.Family) 501 } 502 } 503 504 return nil 505 } 506 507 // sanitize the given CIDR. 508 func (c CIDR) sanitize() error { 509 strCIDR := string(c) 510 if strCIDR == "" { 511 return fmt.Errorf("IP must be specified") 512 } 513 514 prefix, err := netip.ParsePrefix(strCIDR) 515 if err != nil { 516 _, err := netip.ParseAddr(strCIDR) 517 if err != nil { 518 return fmt.Errorf("unable to parse CIDR: %w", err) 519 } 520 return nil 521 } 522 prefixLength := prefix.Bits() 523 if prefixLength < 0 { 524 return fmt.Errorf("CIDR cannot specify non-contiguous mask %s", prefix) 525 } 526 527 return nil 528 } 529 530 // sanitize validates a CIDRRule by checking that the CIDR prefix itself is 531 // valid, and ensuring that all of the exception CIDR prefixes are contained 532 // within the allowed CIDR prefix. 533 func (c *CIDRRule) sanitize() error { 534 if c.CIDRGroupRef != "" { 535 // When a CIDRGroupRef is set, we don't need to validate the CIDR 536 return nil 537 } 538 // Only allow notation <IP address>/<prefix>. Note that this differs from 539 // the logic in api.CIDR.Sanitize(). 540 prefix, err := netip.ParsePrefix(string(c.Cidr)) 541 if err != nil { 542 return fmt.Errorf("Unable to parse CIDRRule %q: %w", c.Cidr, err) 543 } 544 545 prefixLength := prefix.Bits() 546 if prefixLength < 0 { 547 return fmt.Errorf("CIDR cannot specify non-contiguous mask %s", prefix) 548 } 549 550 // Ensure that each provided exception CIDR prefix is formatted correctly, 551 // and is contained within the CIDR prefix to/from which we want to allow 552 // traffic. 553 for _, p := range c.ExceptCIDRs { 554 except, err := netip.ParsePrefix(string(p)) 555 if err != nil { 556 return err 557 } 558 559 // Note: this also checks that the allow CIDR prefix and the exception 560 // CIDR prefixes are part of the same address family. 561 if !prefix.Contains(except.Addr()) { 562 return fmt.Errorf("allow CIDR prefix %s does not contain "+ 563 "exclude CIDR prefix %s", c.Cidr, p) 564 } 565 } 566 567 return nil 568 }