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  }