github.com/cilium/cilium@v1.16.2/pkg/policy/api/fqdn.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/cilium/cilium/pkg/fqdn/dns"
    12  	"github.com/cilium/cilium/pkg/fqdn/matchpattern"
    13  	"github.com/cilium/cilium/pkg/labels"
    14  )
    15  
    16  var (
    17  	// allowedMatchNameChars tests that MatchName contains only valid DNS characters
    18  	allowedMatchNameChars = regexp.MustCompile("^[-a-zA-Z0-9_.]+$")
    19  
    20  	// allowedPatternChars tests that the MatchPattern field contains only the
    21  	// characters we want in our wilcard scheme.
    22  	allowedPatternChars = regexp.MustCompile("^[-a-zA-Z0-9_.*]+$") // the * inside the [] is a literal *
    23  
    24  	// FQDNMatchNameRegexString is a regex string which matches what's expected
    25  	// in the MatchName field in the FQDNSelector. This should be kept in-sync
    26  	// with the marker comment for validation. There's no way to use a Golang
    27  	// variable in the marker comment, so it's left up to the developer.
    28  	FQDNMatchNameRegexString = `^([-a-zA-Z0-9_]+[.]?)+$`
    29  
    30  	// FQDNMatchPatternRegexString is a regex string which matches what's expected
    31  	// in the MatchPattern field in the FQDNSelector. This should be kept in-sync
    32  	// with the marker comment for validation. There's no way to use a Golang
    33  	// variable in the marker comment, so it's left up to the developer.
    34  	FQDNMatchPatternRegexString = `^([-a-zA-Z0-9_*]+[.]?)+$`
    35  )
    36  
    37  type FQDNSelector struct {
    38  	// MatchName matches literal DNS names. A trailing "." is automatically added
    39  	// when missing.
    40  	//
    41  	// +kubebuilder:validation:Pattern=`^([-a-zA-Z0-9_]+[.]?)+$`
    42  	// +kubebuilder:validation:OneOf
    43  	MatchName string `json:"matchName,omitempty"`
    44  
    45  	// MatchPattern allows using wildcards to match DNS names. All wildcards are
    46  	// case insensitive. The wildcards are:
    47  	// - "*" matches 0 or more DNS valid characters, and may occur anywhere in
    48  	// the pattern. As a special case a "*" as the leftmost character, without a
    49  	// following "." matches all subdomains as well as the name to the right.
    50  	// A trailing "." is automatically added when missing.
    51  	//
    52  	// Examples:
    53  	// `*.cilium.io` matches subomains of cilium at that level
    54  	//   www.cilium.io and blog.cilium.io match, cilium.io and google.com do not
    55  	// `*cilium.io` matches cilium.io and all subdomains ends with "cilium.io"
    56  	//   except those containing "." separator, subcilium.io and sub-cilium.io match,
    57  	//   www.cilium.io and blog.cilium.io does not
    58  	// sub*.cilium.io matches subdomains of cilium where the subdomain component
    59  	// begins with "sub"
    60  	//   sub.cilium.io and subdomain.cilium.io match, www.cilium.io,
    61  	//   blog.cilium.io, cilium.io and google.com do not
    62  	//
    63  	// +kubebuilder:validation:Pattern=`^([-a-zA-Z0-9_*]+[.]?)+$`
    64  	// +kubebuilder:validation:OneOf
    65  	MatchPattern string `json:"matchPattern,omitempty"`
    66  }
    67  
    68  func (s *FQDNSelector) String() string {
    69  	const m = "MatchName: "
    70  	const mm = ", MatchPattern: "
    71  	var str strings.Builder
    72  	str.Grow(len(m) + len(mm) + len(s.MatchName) + len(s.MatchPattern))
    73  	str.WriteString(m)
    74  	str.WriteString(s.MatchName)
    75  	str.WriteString(mm)
    76  	str.WriteString(s.MatchPattern)
    77  	return str.String()
    78  }
    79  
    80  // IdentityLabel returns the label which needs to be added to each identity
    81  // selected by this selector. The identity label is based on the MatchName
    82  // if set, otherwise on the MatchPattern. This matches the behavior of the
    83  // ToRegex function
    84  func (s *FQDNSelector) IdentityLabel() labels.Label {
    85  	match := s.MatchPattern
    86  	if s.MatchName != "" {
    87  		match = s.MatchName
    88  	}
    89  
    90  	return labels.NewLabel(match, "", labels.LabelSourceFQDN)
    91  }
    92  
    93  // sanitize for FQDNSelector is a little wonky. While we do more processing
    94  // when using MatchName the basic requirement is that is a valid regexp. We
    95  // test that it can compile here.
    96  func (s *FQDNSelector) sanitize() error {
    97  	if len(s.MatchName) > 0 && len(s.MatchPattern) > 0 {
    98  		return fmt.Errorf("only one of MatchName or MatchPattern is allowed in an FQDNSelector")
    99  	}
   100  	if len(s.MatchName) > 0 && !allowedMatchNameChars.MatchString(s.MatchName) {
   101  		return fmt.Errorf("Invalid characters in MatchName: \"%s\". Only 0-9, a-z, A-Z and . and - characters are allowed", s.MatchName)
   102  	}
   103  
   104  	if len(s.MatchPattern) > 0 && !allowedPatternChars.MatchString(s.MatchPattern) {
   105  		return fmt.Errorf("Invalid characters in MatchPattern: \"%s\". Only 0-9, a-z, A-Z and ., - and * characters are allowed", s.MatchPattern)
   106  	}
   107  	_, err := matchpattern.Validate(s.MatchPattern)
   108  	return err
   109  }
   110  
   111  // ToRegex converts the given FQDNSelector to its corresponding regular
   112  // expression. If the MatchName field is set in the selector, it performs all
   113  // needed formatting to ensure that the field is a valid regular expression.
   114  func (s *FQDNSelector) ToRegex() (*regexp.Regexp, error) {
   115  	var preparedMatch string
   116  	if s.MatchName != "" {
   117  		preparedMatch = dns.FQDN(s.MatchName)
   118  	} else {
   119  		preparedMatch = matchpattern.Sanitize(s.MatchPattern)
   120  	}
   121  
   122  	regex, err := matchpattern.Validate(preparedMatch)
   123  	return regex, err
   124  }
   125  
   126  // PortRuleDNS is a list of allowed DNS lookups.
   127  type PortRuleDNS FQDNSelector
   128  
   129  // Sanitize checks that the matchName in the portRule can be compiled as a
   130  // regex. It does not check that a DNS name is a valid DNS name.
   131  func (r *PortRuleDNS) Sanitize() error {
   132  	if len(r.MatchName) > 0 && !allowedMatchNameChars.MatchString(r.MatchName) {
   133  		return fmt.Errorf("Invalid characters in MatchName: \"%s\". Only 0-9, a-z, A-Z and . and - characters are allowed", r.MatchName)
   134  	}
   135  
   136  	if len(r.MatchPattern) > 0 && !allowedPatternChars.MatchString(r.MatchPattern) {
   137  		return fmt.Errorf("Invalid characters in MatchPattern: \"%s\". Only 0-9, a-z, A-Z and ., - and * characters are allowed", r.MatchPattern)
   138  	}
   139  	_, err := matchpattern.Validate(r.MatchPattern)
   140  	return err
   141  }
   142  
   143  // GetAsEndpointSelectors returns a FQDNSelector as a single EntityNone
   144  // EndpointSelector slice.
   145  // Note that toFQDNs behaves differently than most other rules. The presence of
   146  // any toFQDNs rules means the endpoint must enforce policy, but the IPs are later
   147  // added as toCIDRSet entries and processed as such.
   148  func (s *FQDNSelector) GetAsEndpointSelectors() EndpointSelectorSlice {
   149  	return []EndpointSelector{EndpointSelectorNone}
   150  }
   151  
   152  // FQDNSelectorSlice is a wrapper type for []FQDNSelector to make is simpler to
   153  // bind methods.
   154  type FQDNSelectorSlice []FQDNSelector
   155  
   156  // GetAsEndpointSelectors will return a single EntityNone if any
   157  // toFQDNs rules exist, and a nil slice otherwise.
   158  func (s FQDNSelectorSlice) GetAsEndpointSelectors() EndpointSelectorSlice {
   159  	for _, rule := range s {
   160  		return rule.GetAsEndpointSelectors()
   161  	}
   162  	return nil
   163  }