istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/plugin/sidecar_redirect.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Defines the redirect object and operations. 16 package plugin 17 18 import ( 19 "fmt" 20 "net/netip" 21 "strconv" 22 "strings" 23 24 "istio.io/api/annotation" 25 "istio.io/istio/pkg/log" 26 "istio.io/istio/tools/istio-iptables/pkg/cmd" 27 ) 28 29 const ( 30 redirectModeREDIRECT = "REDIRECT" 31 redirectModeTPROXY = "TPROXY" 32 defaultProxyStatusPort = "15020" 33 defaultRedirectToPort = "15001" 34 defaultNoRedirectUID = "1337" 35 defaultNoRedirectGID = "1337" 36 defaultRedirectMode = redirectModeREDIRECT 37 defaultRedirectIPCidr = "*" 38 defaultRedirectExcludeIPCidr = "" 39 defaultRedirectExcludePort = defaultProxyStatusPort 40 defaultKubevirtInterfaces = "" 41 defaultIncludeInboundPorts = "*" 42 defaultIncludeOutboundPorts = "" 43 defaultExcludeInterfaces = "" 44 ) 45 46 var ( 47 includeIPCidrsKey = annotation.SidecarTrafficIncludeOutboundIPRanges.Name 48 excludeIPCidrsKey = annotation.SidecarTrafficExcludeOutboundIPRanges.Name 49 excludeInboundPortsKey = annotation.SidecarTrafficExcludeInboundPorts.Name 50 includeInboundPortsKey = annotation.SidecarTrafficIncludeInboundPorts.Name 51 excludeOutboundPortsKey = annotation.SidecarTrafficExcludeOutboundPorts.Name 52 includeOutboundPortsKey = annotation.SidecarTrafficIncludeOutboundPorts.Name 53 excludeInterfacesKey = annotation.SidecarTrafficExcludeInterfaces.Name 54 55 sidecarInterceptModeKey = annotation.SidecarInterceptionMode.Name 56 sidecarPortListKey = annotation.SidecarStatusPort.Name 57 58 kubevirtInterfacesKey = annotation.SidecarTrafficKubevirtInterfaces.Name 59 60 annotationRegistry = map[string]*annotationParam{ 61 "inject": {injectAnnotationKey, "", alwaysValidFunc}, 62 "status": {sidecarStatusKey, "", alwaysValidFunc}, 63 "redirectMode": {sidecarInterceptModeKey, defaultRedirectMode, validateInterceptionMode}, 64 "ports": {sidecarPortListKey, "", validatePortList}, 65 "includeIPCidrs": {includeIPCidrsKey, defaultRedirectIPCidr, validateCIDRListWithWildcard}, 66 "excludeIPCidrs": {excludeIPCidrsKey, defaultRedirectExcludeIPCidr, validateCIDRList}, 67 "excludeInboundPorts": {excludeInboundPortsKey, defaultRedirectExcludePort, validatePortListWithWildcard}, 68 "includeInboundPorts": {includeInboundPortsKey, defaultIncludeInboundPorts, validatePortListWithWildcard}, 69 "excludeOutboundPorts": {excludeOutboundPortsKey, defaultRedirectExcludePort, validatePortListWithWildcard}, 70 "includeOutboundPorts": {includeOutboundPortsKey, defaultIncludeOutboundPorts, validatePortListWithWildcard}, 71 "kubevirtInterfaces": {kubevirtInterfacesKey, defaultKubevirtInterfaces, alwaysValidFunc}, 72 "excludeInterfaces": {excludeInterfacesKey, defaultExcludeInterfaces, alwaysValidFunc}, 73 } 74 ) 75 76 // Redirect -- the istio-cni redirect object 77 type Redirect struct { 78 targetPort string 79 redirectMode string 80 noRedirectUID string 81 noRedirectGID string 82 includeIPCidrs string 83 excludeIPCidrs string 84 excludeInboundPorts string 85 excludeOutboundPorts string 86 includeInboundPorts string 87 includeOutboundPorts string 88 kubevirtInterfaces string 89 excludeInterfaces string 90 dnsRedirect bool 91 dualStack bool 92 invalidDrop bool 93 } 94 95 type annotationValidationFunc func(value string) error 96 97 type annotationParam struct { 98 key string 99 defaultVal string 100 validator annotationValidationFunc 101 } 102 103 func alwaysValidFunc(value string) error { 104 return nil 105 } 106 107 // validateInterceptionMode validates the interceptionMode annotation 108 func validateInterceptionMode(mode string) error { 109 switch mode { 110 case redirectModeREDIRECT: 111 case redirectModeTPROXY: 112 default: 113 return fmt.Errorf("interceptionMode invalid: %v", mode) 114 } 115 return nil 116 } 117 118 func validateCIDRList(cidrs string) error { 119 if len(cidrs) > 0 { 120 for _, cidr := range strings.Split(cidrs, ",") { 121 if _, err := netip.ParsePrefix(cidr); err != nil { 122 return fmt.Errorf("failed parsing cidr '%s': %v", cidr, err) 123 } 124 } 125 } 126 return nil 127 } 128 129 func splitPorts(portsString string) []string { 130 return strings.Split(portsString, ",") 131 } 132 133 func dedupPorts(ports []string) []string { 134 dedup := make(map[string]bool) 135 keys := []string{} 136 137 for _, port := range ports { 138 if !dedup[port] { 139 dedup[port] = true 140 keys = append(keys, port) 141 } 142 } 143 return keys 144 } 145 146 func parsePort(portStr string) (uint16, error) { 147 port, err := strconv.ParseUint(strings.TrimSpace(portStr), 10, 16) 148 if err != nil { 149 return 0, fmt.Errorf("failed parsing port %q: %v", portStr, err) 150 } 151 return uint16(port), nil 152 } 153 154 func parsePorts(portsString string) ([]int, error) { 155 portsString = strings.TrimSpace(portsString) 156 ports := make([]int, 0) 157 if len(portsString) > 0 { 158 for _, portStr := range splitPorts(portsString) { 159 port, err := parsePort(portStr) 160 if err != nil { 161 return nil, err 162 } 163 ports = append(ports, int(port)) 164 } 165 } 166 return ports, nil 167 } 168 169 func validatePortList(ports string) error { 170 if _, err := parsePorts(ports); err != nil { 171 return fmt.Errorf("portList %q invalid: %v", ports, err) 172 } 173 return nil 174 } 175 176 func validatePortListWithWildcard(ports string) error { 177 if ports != "*" { 178 return validatePortList(ports) 179 } 180 return nil 181 } 182 183 // validateCIDRListWithWildcard validates the includeIPRanges parameter 184 func validateCIDRListWithWildcard(ipRanges string) error { 185 if ipRanges != "*" { 186 if e := validateCIDRList(ipRanges); e != nil { 187 return fmt.Errorf("IPRanges invalid: %v", e) 188 } 189 } 190 return nil 191 } 192 193 func getAnnotationOrDefault(name string, annotations map[string]string) (isFound bool, val string, err error) { 194 if _, ok := annotationRegistry[name]; !ok { 195 return false, "", fmt.Errorf("no registered annotation with name=%s", name) 196 } 197 // use annotation value if present 198 if val, found := annotations[annotationRegistry[name].key]; found { 199 if err := annotationRegistry[name].validator(val); err != nil { 200 return true, annotationRegistry[name].defaultVal, err 201 } 202 return true, val, nil 203 } 204 // no annotation found so use default value 205 return false, annotationRegistry[name].defaultVal, nil 206 } 207 208 // NewRedirect returns a new Redirect Object constructed from a list of ports and annotations 209 func NewRedirect(pi *PodInfo) (*Redirect, error) { 210 var isFound bool 211 var valErr error 212 213 redir := &Redirect{} 214 redir.targetPort = defaultRedirectToPort 215 isFound, redir.redirectMode, valErr = getAnnotationOrDefault("redirectMode", pi.Annotations) 216 if valErr != nil { 217 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 218 "redirectMode", isFound, valErr) 219 } 220 221 if pi.ProxyUID != nil && *pi.ProxyUID != 0 { 222 redir.noRedirectUID = fmt.Sprintf("%d", *pi.ProxyUID) 223 } else { 224 redir.noRedirectUID = defaultNoRedirectUID 225 } 226 227 if pi.ProxyGID != nil && *pi.ProxyGID != 0 { 228 redir.noRedirectGID = fmt.Sprintf("%d", *pi.ProxyGID) 229 } else { 230 redir.noRedirectGID = defaultNoRedirectGID 231 } 232 233 isFound, redir.includeIPCidrs, valErr = getAnnotationOrDefault("includeIPCidrs", pi.Annotations) 234 if valErr != nil { 235 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 236 "includeIPCidrs", isFound, valErr) 237 } 238 isFound, redir.excludeIPCidrs, valErr = getAnnotationOrDefault("excludeIPCidrs", pi.Annotations) 239 if valErr != nil { 240 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 241 "excludeIPCidrs", isFound, valErr) 242 } 243 isFound, redir.excludeInboundPorts, valErr = getAnnotationOrDefault("excludeInboundPorts", pi.Annotations) 244 if valErr != nil { 245 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 246 "excludeInboundPorts", isFound, valErr) 247 } 248 isFound, redir.includeInboundPorts, valErr = getAnnotationOrDefault("includeInboundPorts", pi.Annotations) 249 if valErr != nil { 250 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 251 "includeInboundPorts", isFound, valErr) 252 } 253 isFound, redir.excludeOutboundPorts, valErr = getAnnotationOrDefault("excludeOutboundPorts", pi.Annotations) 254 if valErr != nil { 255 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 256 "excludeOutboundPorts", isFound, valErr) 257 } 258 isFound, redir.includeOutboundPorts, valErr = getAnnotationOrDefault("includeOutboundPorts", pi.Annotations) 259 if valErr != nil { 260 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 261 "includeOutboundPorts", isFound, valErr) 262 } 263 // Add 15090 to sync with non-cni injection template 264 // TODO: Revert below once https://github.com/istio/istio/pull/23037 or its follow up is merged. 265 redir.excludeInboundPorts = strings.TrimSpace(redir.excludeInboundPorts) 266 if len(redir.excludeInboundPorts) > 0 && redir.excludeInboundPorts[len(redir.excludeInboundPorts)-1] != ',' { 267 redir.excludeInboundPorts += "," 268 } 269 redir.excludeInboundPorts += "15020,15021,15090" 270 redir.excludeInboundPorts = strings.Join(dedupPorts(splitPorts(redir.excludeInboundPorts)), ",") 271 isFound, redir.excludeInterfaces, valErr = getAnnotationOrDefault("excludeInterfaces", pi.Annotations) 272 if valErr != nil { 273 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 274 "excludeInterfaces", isFound, valErr) 275 } 276 isFound, redir.kubevirtInterfaces, valErr = getAnnotationOrDefault("kubevirtInterfaces", pi.Annotations) 277 if valErr != nil { 278 return nil, fmt.Errorf("annotation value error for value %s; annotationFound = %t: %v", 279 "kubevirtInterfaces", isFound, valErr) 280 } 281 if v, found := pi.ProxyEnvironments["ISTIO_META_DNS_CAPTURE"]; found { 282 // parse and set the bool value of dnsRedirect 283 redir.dnsRedirect, valErr = strconv.ParseBool(v) 284 if valErr != nil { 285 log.Warnf("cannot parse DNS capture environment variable %v", valErr) 286 } 287 } 288 if v, found := pi.ProxyEnvironments["ISTIO_DUAL_STACK"]; found { 289 // parse and set the bool value of dnsRedirect 290 redir.dualStack, valErr = strconv.ParseBool(v) 291 if valErr != nil { 292 log.Warnf("cannot parse dual stack environment variable %v", valErr) 293 } 294 } 295 if v, found := pi.ProxyEnvironments[cmd.InvalidDropByIptables]; found { 296 // parse and set the bool value of invalidDrop 297 redir.invalidDrop, valErr = strconv.ParseBool(v) 298 if valErr != nil { 299 log.Warnf("cannot parse invalid drop environment variable %v", valErr) 300 } 301 } 302 return redir, nil 303 }