istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/istio-iptables/pkg/config/config.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  package config
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"net"
    21  	"net/netip"
    22  	"os/user"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/miekg/dns"
    28  
    29  	"istio.io/istio/pkg/env"
    30  	"istio.io/istio/pkg/log"
    31  	netutil "istio.io/istio/pkg/util/net"
    32  	"istio.io/istio/tools/istio-iptables/pkg/constants"
    33  )
    34  
    35  func DefaultConfig() *Config {
    36  	return &Config{
    37  		RestoreFormat:           true,
    38  		ProxyPort:               "15001",
    39  		InboundCapturePort:      "15006",
    40  		InboundTunnelPort:       "15008",
    41  		InboundTProxyMark:       "1337",
    42  		InboundTProxyRouteTable: "133",
    43  		IptablesProbePort:       constants.DefaultIptablesProbePortUint,
    44  		ProbeTimeout:            constants.DefaultProbeTimeout,
    45  		OwnerGroupsInclude:      constants.OwnerGroupsInclude.DefaultValue,
    46  		OwnerGroupsExclude:      constants.OwnerGroupsExclude.DefaultValue,
    47  		HostIPv4LoopbackCidr:    constants.HostIPv4LoopbackCidr.DefaultValue,
    48  	}
    49  }
    50  
    51  // Command line options
    52  // nolint: maligned
    53  type Config struct {
    54  	ProxyPort               string        `json:"PROXY_PORT"`
    55  	InboundCapturePort      string        `json:"INBOUND_CAPTURE_PORT"`
    56  	InboundTunnelPort       string        `json:"INBOUND_TUNNEL_PORT"`
    57  	ProxyUID                string        `json:"PROXY_UID"`
    58  	ProxyGID                string        `json:"PROXY_GID"`
    59  	InboundInterceptionMode string        `json:"INBOUND_INTERCEPTION_MODE"`
    60  	InboundTProxyMark       string        `json:"INBOUND_TPROXY_MARK"`
    61  	InboundTProxyRouteTable string        `json:"INBOUND_TPROXY_ROUTE_TABLE"`
    62  	InboundPortsInclude     string        `json:"INBOUND_PORTS_INCLUDE"`
    63  	InboundPortsExclude     string        `json:"INBOUND_PORTS_EXCLUDE"`
    64  	OwnerGroupsInclude      string        `json:"OUTBOUND_OWNER_GROUPS_INCLUDE"`
    65  	OwnerGroupsExclude      string        `json:"OUTBOUND_OWNER_GROUPS_EXCLUDE"`
    66  	OutboundPortsInclude    string        `json:"OUTBOUND_PORTS_INCLUDE"`
    67  	OutboundPortsExclude    string        `json:"OUTBOUND_PORTS_EXCLUDE"`
    68  	OutboundIPRangesInclude string        `json:"OUTBOUND_IPRANGES_INCLUDE"`
    69  	OutboundIPRangesExclude string        `json:"OUTBOUND_IPRANGES_EXCLUDE"`
    70  	KubeVirtInterfaces      string        `json:"KUBE_VIRT_INTERFACES"`
    71  	ExcludeInterfaces       string        `json:"EXCLUDE_INTERFACES"`
    72  	IptablesProbePort       uint16        `json:"IPTABLES_PROBE_PORT"`
    73  	ProbeTimeout            time.Duration `json:"PROBE_TIMEOUT"`
    74  	DryRun                  bool          `json:"DRY_RUN"`
    75  	RestoreFormat           bool          `json:"RESTORE_FORMAT"`
    76  	SkipRuleApply           bool          `json:"SKIP_RULE_APPLY"`
    77  	RunValidation           bool          `json:"RUN_VALIDATION"`
    78  	RedirectDNS             bool          `json:"REDIRECT_DNS"`
    79  	DropInvalid             bool          `json:"DROP_INVALID"`
    80  	CaptureAllDNS           bool          `json:"CAPTURE_ALL_DNS"`
    81  	EnableIPv6              bool          `json:"ENABLE_INBOUND_IPV6"`
    82  	DNSServersV4            []string      `json:"DNS_SERVERS_V4"`
    83  	DNSServersV6            []string      `json:"DNS_SERVERS_V6"`
    84  	NetworkNamespace        string        `json:"NETWORK_NAMESPACE"`
    85  	CNIMode                 bool          `json:"CNI_MODE"`
    86  	TraceLogging            bool          `json:"IPTABLES_TRACE_LOGGING"`
    87  	DualStack               bool          `json:"DUAL_STACK"`
    88  	HostIP                  netip.Addr    `json:"HOST_IP"`
    89  	HostIPv4LoopbackCidr    string        `json:"HOST_IPV4_LOOPBACK_CIDR"`
    90  }
    91  
    92  func (c *Config) String() string {
    93  	output, err := json.MarshalIndent(c, "", "\t")
    94  	if err != nil {
    95  		log.Errorf("Unable to marshal config object: %v", err)
    96  	}
    97  	return string(output)
    98  }
    99  
   100  func (c *Config) Print() {
   101  	var b strings.Builder
   102  	b.WriteString(fmt.Sprintf("PROXY_PORT=%s\n", c.ProxyPort))
   103  	b.WriteString(fmt.Sprintf("PROXY_INBOUND_CAPTURE_PORT=%s\n", c.InboundCapturePort))
   104  	b.WriteString(fmt.Sprintf("PROXY_TUNNEL_PORT=%s\n", c.InboundTunnelPort))
   105  	b.WriteString(fmt.Sprintf("PROXY_UID=%s\n", c.ProxyUID))
   106  	b.WriteString(fmt.Sprintf("PROXY_GID=%s\n", c.ProxyGID))
   107  	b.WriteString(fmt.Sprintf("INBOUND_INTERCEPTION_MODE=%s\n", c.InboundInterceptionMode))
   108  	b.WriteString(fmt.Sprintf("INBOUND_TPROXY_MARK=%s\n", c.InboundTProxyMark))
   109  	b.WriteString(fmt.Sprintf("INBOUND_TPROXY_ROUTE_TABLE=%s\n", c.InboundTProxyRouteTable))
   110  	b.WriteString(fmt.Sprintf("INBOUND_PORTS_INCLUDE=%s\n", c.InboundPortsInclude))
   111  	b.WriteString(fmt.Sprintf("INBOUND_PORTS_EXCLUDE=%s\n", c.InboundPortsExclude))
   112  	b.WriteString(fmt.Sprintf("OUTBOUND_OWNER_GROUPS_INCLUDE=%s\n", c.OwnerGroupsInclude))
   113  	b.WriteString(fmt.Sprintf("OUTBOUND_OWNER_GROUPS_EXCLUDE=%s\n", c.OwnerGroupsExclude))
   114  	b.WriteString(fmt.Sprintf("OUTBOUND_IP_RANGES_INCLUDE=%s\n", c.OutboundIPRangesInclude))
   115  	b.WriteString(fmt.Sprintf("OUTBOUND_IP_RANGES_EXCLUDE=%s\n", c.OutboundIPRangesExclude))
   116  	b.WriteString(fmt.Sprintf("OUTBOUND_PORTS_INCLUDE=%s\n", c.OutboundPortsInclude))
   117  	b.WriteString(fmt.Sprintf("OUTBOUND_PORTS_EXCLUDE=%s\n", c.OutboundPortsExclude))
   118  	b.WriteString(fmt.Sprintf("KUBE_VIRT_INTERFACES=%s\n", c.KubeVirtInterfaces))
   119  	// TODO consider renaming this env var to ENABLE_IPV6 - nothing about it is specific to "INBOUND"
   120  	b.WriteString(fmt.Sprintf("ENABLE_INBOUND_IPV6=%t\n", c.EnableIPv6))
   121  	// TODO remove this flag - "dual stack" should just mean
   122  	// - supports IPv6
   123  	// - supports pods with more than one podIP
   124  	// The former already has a flag, the latter is something we should do by default and is a bug where we do not
   125  	b.WriteString(fmt.Sprintf("DUAL_STACK=%t\n", c.DualStack))
   126  	b.WriteString(fmt.Sprintf("DNS_CAPTURE=%t\n", c.RedirectDNS))
   127  	b.WriteString(fmt.Sprintf("DROP_INVALID=%t\n", c.DropInvalid))
   128  	b.WriteString(fmt.Sprintf("CAPTURE_ALL_DNS=%t\n", c.CaptureAllDNS))
   129  	b.WriteString(fmt.Sprintf("DNS_SERVERS=%s,%s\n", c.DNSServersV4, c.DNSServersV6))
   130  	b.WriteString(fmt.Sprintf("NETWORK_NAMESPACE=%s\n", c.NetworkNamespace))
   131  	b.WriteString(fmt.Sprintf("CNI_MODE=%s\n", strconv.FormatBool(c.CNIMode)))
   132  	b.WriteString(fmt.Sprintf("EXCLUDE_INTERFACES=%s\n", c.ExcludeInterfaces))
   133  	log.Infof("Istio iptables variables:\n%s", b.String())
   134  }
   135  
   136  func (c *Config) Validate() error {
   137  	if err := ValidateOwnerGroups(c.OwnerGroupsInclude, c.OwnerGroupsExclude); err != nil {
   138  		return err
   139  	}
   140  	return ValidateIPv4LoopbackCidr(c.HostIPv4LoopbackCidr)
   141  }
   142  
   143  var envoyUserVar = env.Register(constants.EnvoyUser, "istio-proxy", "Envoy proxy username")
   144  
   145  func (c *Config) FillConfigFromEnvironment() error {
   146  	// Fill in env-var only options
   147  	c.OwnerGroupsInclude = constants.OwnerGroupsInclude.Get()
   148  	c.OwnerGroupsExclude = constants.OwnerGroupsExclude.Get()
   149  
   150  	c.HostIPv4LoopbackCidr = constants.HostIPv4LoopbackCidr.Get()
   151  
   152  	// TODO: Make this more configurable, maybe with an allowlist of users to be captured for output instead of a denylist.
   153  	if c.ProxyUID == "" {
   154  		usr, err := user.Lookup(envoyUserVar.Get())
   155  		var userID string
   156  		// Default to the UID of ENVOY_USER
   157  		if err != nil {
   158  			userID = constants.DefaultProxyUID
   159  		} else {
   160  			userID = usr.Uid
   161  		}
   162  		c.ProxyUID = userID
   163  	}
   164  
   165  	// For TPROXY as its uid and gid are same.
   166  	if c.ProxyGID == "" {
   167  		c.ProxyGID = c.ProxyUID
   168  	}
   169  	// Detect whether IPv6 is enabled by checking if the pod's IP address is IPv4 or IPv6.
   170  	// TODO remove this check, it will break with more than one pod IP
   171  	hostIP, isIPv6, err := getLocalIP(c.DualStack)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	c.HostIP = hostIP
   177  	c.EnableIPv6 = isIPv6
   178  
   179  	// Lookup DNS nameservers. We only do this if DNS is enabled in case of some obscure theoretical
   180  	// case where reading /etc/resolv.conf could fail.
   181  	// If capture all DNS option is enabled, we don't need to read from the dns resolve conf. All
   182  	// traffic to port 53 will be captured.
   183  	if c.RedirectDNS && !c.CaptureAllDNS {
   184  		dnsConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
   185  		if err != nil {
   186  			return fmt.Errorf("failed to load /etc/resolv.conf: %v", err)
   187  		}
   188  		c.DNSServersV4, c.DNSServersV6 = netutil.IPsSplitV4V6(dnsConfig.Servers)
   189  	}
   190  	return nil
   191  }
   192  
   193  // mock net.InterfaceAddrs to make its unit test become available
   194  var (
   195  	LocalIPAddrs = net.InterfaceAddrs
   196  )
   197  
   198  // getLocalIP returns one of the local IP address and it should support IPv6 or not
   199  func getLocalIP(dualStack bool) (netip.Addr, bool, error) {
   200  	var isIPv6 bool
   201  	var ipAddrs []netip.Addr
   202  	addrs, err := LocalIPAddrs()
   203  	if err != nil {
   204  		return netip.Addr{}, isIPv6, err
   205  	}
   206  
   207  	for _, a := range addrs {
   208  		if ipnet, ok := a.(*net.IPNet); ok {
   209  			ip := ipnet.IP
   210  			ipAddr, ok := netip.AddrFromSlice(ip)
   211  			if !ok {
   212  				continue
   213  			}
   214  			// unwrap the IPv4-mapped IPv6 address
   215  			unwrapAddr := ipAddr.Unmap()
   216  			if !unwrapAddr.IsLoopback() && !unwrapAddr.IsLinkLocalUnicast() && !unwrapAddr.IsLinkLocalMulticast() {
   217  				isIPv6 = unwrapAddr.Is6()
   218  				ipAddrs = append(ipAddrs, unwrapAddr)
   219  				if !dualStack {
   220  					return unwrapAddr, isIPv6, nil
   221  				}
   222  				if isIPv6 {
   223  					break
   224  				}
   225  			}
   226  		}
   227  	}
   228  
   229  	if len(ipAddrs) > 0 {
   230  		return ipAddrs[0], isIPv6, nil
   231  	}
   232  
   233  	return netip.Addr{}, isIPv6, fmt.Errorf("no valid local IP address found")
   234  }