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  }