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 }