blob: 8bf1d917aae78b11e3891e2c24c4d6b85a5907cf [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"net"
"os"
"os/user"
"strings"
)
import (
"github.com/miekg/dns"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"istio.io/pkg/env"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/tools/istio-iptables/pkg/capture"
"github.com/apache/dubbo-go-pixiu/tools/istio-iptables/pkg/config"
"github.com/apache/dubbo-go-pixiu/tools/istio-iptables/pkg/constants"
dep "github.com/apache/dubbo-go-pixiu/tools/istio-iptables/pkg/dependencies"
"github.com/apache/dubbo-go-pixiu/tools/istio-iptables/pkg/validation"
)
var (
envoyUserVar = env.RegisterStringVar(constants.EnvoyUser, "istio-proxy", "Envoy proxy username")
// Enable interception of DNS.
dnsCaptureByAgent = env.RegisterBoolVar("ISTIO_META_DNS_CAPTURE", false,
"If set to true, enable the capture of outgoing DNS packets on port 53, redirecting to istio-agent on :15053").Get()
// InvalidDropByIptables is the flag to enable invalid drop iptables rule to drop the out of window packets
InvalidDropByIptables = env.RegisterBoolVar("INVALID_DROP", false,
"If set to true, enable the invalid drop iptables rule, default false will cause iptables reset out of window packets")
)
// mock net.InterfaceAddrs to make its unit test become available
var (
LocalIPAddrs = net.InterfaceAddrs
)
var rootCmd = &cobra.Command{
Use: "istio-iptables",
Short: "Set up iptables rules for Istio Sidecar",
Long: "istio-iptables is responsible for setting up port forwarding for Istio Sidecar.",
PreRun: bindFlags,
Run: func(cmd *cobra.Command, args []string) {
cfg := constructConfig()
if err := cfg.Validate(); err != nil {
handleErrorWithCode(err, 1)
}
var ext dep.Dependencies
if cfg.DryRun {
ext = &dep.StdoutStubDependencies{}
} else {
ext = &dep.RealDependencies{
CNIMode: cfg.CNIMode,
NetworkNamespace: cfg.NetworkNamespace,
}
}
iptConfigurator := capture.NewIptablesConfigurator(cfg, ext)
if !cfg.SkipRuleApply {
iptConfigurator.Run()
if err := capture.ConfigureRoutes(cfg, ext); err != nil {
log.Errorf("failed to configure routes: ")
handleErrorWithCode(err, 1)
}
}
if cfg.RunValidation {
hostIP, err := getLocalIP()
if err != nil {
// Assume it is not handled by istio-cni and won't reuse the ValidationErrorCode
panic(err)
}
validator := validation.NewValidator(cfg, hostIP)
if err := validator.Run(); err != nil {
handleErrorWithCode(err, constants.ValidationErrorCode)
}
}
},
}
var configureRoutesCommand = &cobra.Command{
Use: "configure-routes",
Short: "Configures iproute2 rules for the Istio sidecar",
PreRun: bindFlags,
Run: func(cmd *cobra.Command, args []string) {
cfg := constructConfig()
if err := cfg.Validate(); err != nil {
handleErrorWithCode(err, 1)
}
if !cfg.SkipRuleApply {
if err := capture.ConfigureRoutes(cfg, nil); err != nil {
handleErrorWithCode(err, 1)
}
}
},
}
func constructConfig() *config.Config {
cfg := &config.Config{
DryRun: viper.GetBool(constants.DryRun),
TraceLogging: viper.GetBool(constants.TraceLogging),
RestoreFormat: viper.GetBool(constants.RestoreFormat),
ProxyPort: viper.GetString(constants.EnvoyPort),
InboundCapturePort: viper.GetString(constants.InboundCapturePort),
InboundTunnelPort: viper.GetString(constants.InboundTunnelPort),
ProxyUID: viper.GetString(constants.ProxyUID),
ProxyGID: viper.GetString(constants.ProxyGID),
InboundInterceptionMode: viper.GetString(constants.InboundInterceptionMode),
InboundTProxyMark: viper.GetString(constants.InboundTProxyMark),
InboundTProxyRouteTable: viper.GetString(constants.InboundTProxyRouteTable),
InboundPortsInclude: viper.GetString(constants.InboundPorts),
InboundPortsExclude: viper.GetString(constants.LocalExcludePorts),
OwnerGroupsInclude: viper.GetString(constants.OwnerGroupsInclude.Name),
OwnerGroupsExclude: viper.GetString(constants.OwnerGroupsExclude.Name),
OutboundPortsInclude: viper.GetString(constants.OutboundPorts),
OutboundPortsExclude: viper.GetString(constants.LocalOutboundPortsExclude),
OutboundIPRangesInclude: viper.GetString(constants.ServiceCidr),
OutboundIPRangesExclude: viper.GetString(constants.ServiceExcludeCidr),
KubeVirtInterfaces: viper.GetString(constants.KubeVirtInterfaces),
ExcludeInterfaces: viper.GetString(constants.ExcludeInterfaces),
IptablesProbePort: uint16(viper.GetUint(constants.IptablesProbePort)),
ProbeTimeout: viper.GetDuration(constants.ProbeTimeout),
SkipRuleApply: viper.GetBool(constants.SkipRuleApply),
RunValidation: viper.GetBool(constants.RunValidation),
RedirectDNS: viper.GetBool(constants.RedirectDNS),
DropInvalid: viper.GetBool(constants.DropInvalid),
CaptureAllDNS: viper.GetBool(constants.CaptureAllDNS),
OutputPath: viper.GetString(constants.OutputPath),
NetworkNamespace: viper.GetString(constants.NetworkNamespace),
CNIMode: viper.GetBool(constants.CNIMode),
}
// TODO: Make this more configurable, maybe with an allowlist of users to be captured for output instead of a denylist.
if cfg.ProxyUID == "" {
usr, err := user.Lookup(envoyUserVar.Get())
var userID string
// Default to the UID of ENVOY_USER
if err != nil {
userID = constants.DefaultProxyUID
} else {
userID = usr.Uid
}
cfg.ProxyUID = userID
}
// For TPROXY as its uid and gid are same.
if cfg.ProxyGID == "" {
cfg.ProxyGID = cfg.ProxyUID
}
// Detect whether IPv6 is enabled by checking if the pod's IP address is IPv4 or IPv6.
podIP, err := getLocalIP()
if err != nil {
panic(err)
}
cfg.EnableInboundIPv6 = podIP.To4() == nil
// Lookup DNS nameservers. We only do this if DNS is enabled in case of some obscure theoretical
// case where reading /etc/resolv.conf could fail.
// If capture all DNS option is enabled, we don't need to read from the dns resolve conf. All
// traffic to port 53 will be captured.
if cfg.RedirectDNS && !cfg.CaptureAllDNS {
dnsConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
panic(fmt.Sprintf("failed to load /etc/resolv.conf: %v", err))
}
cfg.DNSServersV4, cfg.DNSServersV6 = capture.SplitV4V6(dnsConfig.Servers)
}
return cfg
}
// getLocalIP returns the local IP address
func getLocalIP() (net.IP, error) {
addrs, err := LocalIPAddrs()
if err != nil {
return nil, err
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsLinkLocalUnicast() && !ipnet.IP.IsLinkLocalMulticast() {
return ipnet.IP, nil
}
}
return nil, fmt.Errorf("no valid local IP address found")
}
func handleError(err error) {
handleErrorWithCode(err, 1)
}
func handleErrorWithCode(err error, code int) {
log.Error(err)
os.Exit(code)
}
// https://github.com/spf13/viper/issues/233.
// Any viper mutation and binding should be placed in `PreRun` since they should be dynamically bound to the subcommand being executed.
func bindFlags(cmd *cobra.Command, args []string) {
// Read in all environment variables
viper.AutomaticEnv()
// Replace - with _; so that environment variables are looked up correctly.
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
envoyPort := "15001"
inboundPort := "15006"
inboundTunnelPort := "15008"
if err := viper.BindPFlag(constants.EnvoyPort, cmd.Flags().Lookup(constants.EnvoyPort)); err != nil {
handleError(err)
}
viper.SetDefault(constants.EnvoyPort, envoyPort)
if err := viper.BindPFlag(constants.InboundCapturePort, cmd.Flags().Lookup(constants.InboundCapturePort)); err != nil {
handleError(err)
}
viper.SetDefault(constants.InboundCapturePort, inboundPort)
if err := viper.BindPFlag(constants.InboundTunnelPort, cmd.Flags().Lookup(constants.InboundTunnelPort)); err != nil {
handleError(err)
}
viper.SetDefault(constants.InboundTunnelPort, inboundTunnelPort)
if err := viper.BindPFlag(constants.ProxyUID, cmd.Flags().Lookup(constants.ProxyUID)); err != nil {
handleError(err)
}
viper.SetDefault(constants.ProxyUID, "")
if err := viper.BindPFlag(constants.ProxyGID, cmd.Flags().Lookup(constants.ProxyGID)); err != nil {
handleError(err)
}
viper.SetDefault(constants.ProxyGID, "")
if err := viper.BindPFlag(constants.InboundInterceptionMode, cmd.Flags().Lookup(constants.InboundInterceptionMode)); err != nil {
handleError(err)
}
viper.SetDefault(constants.InboundInterceptionMode, "")
if err := viper.BindPFlag(constants.InboundPorts, cmd.Flags().Lookup(constants.InboundPorts)); err != nil {
handleError(err)
}
viper.SetDefault(constants.InboundPorts, "")
if err := viper.BindPFlag(constants.LocalExcludePorts, cmd.Flags().Lookup(constants.LocalExcludePorts)); err != nil {
handleError(err)
}
viper.SetDefault(constants.LocalExcludePorts, "")
if err := viper.BindPFlag(constants.ExcludeInterfaces, cmd.Flags().Lookup(constants.ExcludeInterfaces)); err != nil {
handleError(err)
}
viper.SetDefault(constants.ExcludeInterfaces, "")
if err := viper.BindPFlag(constants.ServiceCidr, cmd.Flags().Lookup(constants.ServiceCidr)); err != nil {
handleError(err)
}
viper.SetDefault(constants.ServiceCidr, "")
if err := viper.BindPFlag(constants.ServiceExcludeCidr, cmd.Flags().Lookup(constants.ServiceExcludeCidr)); err != nil {
handleError(err)
}
viper.SetDefault(constants.ServiceExcludeCidr, "")
if err := viper.BindEnv(constants.OwnerGroupsInclude.Name); err != nil {
handleError(err)
}
viper.SetDefault(constants.OwnerGroupsInclude.Name, constants.OwnerGroupsInclude.DefaultValue)
if err := viper.BindEnv(constants.OwnerGroupsExclude.Name); err != nil {
handleError(err)
}
viper.SetDefault(constants.OwnerGroupsExclude.Name, constants.OwnerGroupsExclude.DefaultValue)
if err := viper.BindPFlag(constants.OutboundPorts, cmd.Flags().Lookup(constants.OutboundPorts)); err != nil {
handleError(err)
}
viper.SetDefault(constants.OutboundPorts, "")
if err := viper.BindPFlag(constants.LocalOutboundPortsExclude, cmd.Flags().Lookup(constants.LocalOutboundPortsExclude)); err != nil {
handleError(err)
}
viper.SetDefault(constants.LocalOutboundPortsExclude, "")
if err := viper.BindPFlag(constants.KubeVirtInterfaces, cmd.Flags().Lookup(constants.KubeVirtInterfaces)); err != nil {
handleError(err)
}
viper.SetDefault(constants.KubeVirtInterfaces, "")
if err := viper.BindPFlag(constants.InboundTProxyMark, cmd.Flags().Lookup(constants.InboundTProxyMark)); err != nil {
handleError(err)
}
viper.SetDefault(constants.InboundTProxyMark, "1337")
if err := viper.BindPFlag(constants.InboundTProxyRouteTable, cmd.Flags().Lookup(constants.InboundTProxyRouteTable)); err != nil {
handleError(err)
}
viper.SetDefault(constants.InboundTProxyRouteTable, "133")
if err := viper.BindPFlag(constants.DryRun, cmd.Flags().Lookup(constants.DryRun)); err != nil {
handleError(err)
}
viper.SetDefault(constants.DryRun, false)
if err := viper.BindPFlag(constants.TraceLogging, cmd.Flags().Lookup(constants.TraceLogging)); err != nil {
handleError(err)
}
viper.SetDefault(constants.TraceLogging, false)
if err := viper.BindPFlag(constants.RestoreFormat, cmd.Flags().Lookup(constants.RestoreFormat)); err != nil {
handleError(err)
}
viper.SetDefault(constants.RestoreFormat, true)
if err := viper.BindPFlag(constants.IptablesProbePort, cmd.Flags().Lookup(constants.IptablesProbePort)); err != nil {
handleError(err)
}
viper.SetDefault(constants.IptablesProbePort, constants.DefaultIptablesProbePort)
if err := viper.BindPFlag(constants.ProbeTimeout, cmd.Flags().Lookup(constants.ProbeTimeout)); err != nil {
handleError(err)
}
viper.SetDefault(constants.ProbeTimeout, constants.DefaultProbeTimeout)
if err := viper.BindPFlag(constants.SkipRuleApply, cmd.Flags().Lookup(constants.SkipRuleApply)); err != nil {
handleError(err)
}
viper.SetDefault(constants.SkipRuleApply, false)
if err := viper.BindPFlag(constants.RunValidation, cmd.Flags().Lookup(constants.RunValidation)); err != nil {
handleError(err)
}
viper.SetDefault(constants.RunValidation, false)
if err := viper.BindPFlag(constants.RedirectDNS, cmd.Flags().Lookup(constants.RedirectDNS)); err != nil {
handleError(err)
}
viper.SetDefault(constants.RedirectDNS, dnsCaptureByAgent)
if err := viper.BindPFlag(constants.DropInvalid, cmd.Flags().Lookup(constants.DropInvalid)); err != nil {
handleError(err)
}
viper.SetDefault(constants.DropInvalid, InvalidDropByIptables)
if err := viper.BindPFlag(constants.CaptureAllDNS, cmd.Flags().Lookup(constants.CaptureAllDNS)); err != nil {
handleError(err)
}
viper.SetDefault(constants.CaptureAllDNS, false)
if err := viper.BindPFlag(constants.OutputPath, cmd.Flags().Lookup(constants.OutputPath)); err != nil {
handleError(err)
}
viper.SetDefault(constants.OutputPath, "")
if err := viper.BindPFlag(constants.NetworkNamespace, cmd.Flags().Lookup(constants.NetworkNamespace)); err != nil {
handleError(err)
}
viper.SetDefault(constants.NetworkNamespace, "")
if err := viper.BindPFlag(constants.CNIMode, cmd.Flags().Lookup(constants.CNIMode)); err != nil {
handleError(err)
}
viper.SetDefault(constants.CNIMode, false)
}
// https://github.com/spf13/viper/issues/233.
// Only adding flags in `init()` while moving its binding to Viper and value defaulting as part of the command execution.
// Otherwise, the flag with the same name shared across subcommands will be overwritten by the last.
func init() {
bindCmdlineFlags(rootCmd)
bindCmdlineFlags(configureRoutesCommand)
}
func bindCmdlineFlags(rootCmd *cobra.Command) {
rootCmd.Flags().StringP(constants.EnvoyPort, "p", "", "Specify the envoy port to which redirect all TCP traffic (default $ENVOY_PORT = 15001)")
rootCmd.Flags().StringP(constants.InboundCapturePort, "z", "",
"Port to which all inbound TCP traffic to the pod/VM should be redirected to (default $INBOUND_CAPTURE_PORT = 15006)")
rootCmd.Flags().StringP(constants.InboundTunnelPort, "e", "",
"Specify the istio tunnel port for inbound tcp traffic (default $INBOUND_TUNNEL_PORT = 15008)")
rootCmd.Flags().StringP(constants.ProxyUID, "u", "",
"Specify the UID of the user for which the redirection is not applied. Typically, this is the UID of the proxy container")
rootCmd.Flags().StringP(constants.ProxyGID, "g", "",
"Specify the GID of the user for which the redirection is not applied. (same default value as -u param)")
rootCmd.Flags().StringP(constants.InboundInterceptionMode, "m", "",
"The mode used to redirect inbound connections to Envoy, either \"REDIRECT\" or \"TPROXY\"")
rootCmd.Flags().StringP(constants.InboundPorts, "b", "",
"Comma separated list of inbound ports for which traffic is to be redirected to Envoy (optional). "+
"The wildcard character \"*\" can be used to configure redirection for all ports. An empty list will disable")
rootCmd.Flags().StringP(constants.LocalExcludePorts, "d", "",
"Comma separated list of inbound ports to be excluded from redirection to Envoy (optional). "+
"Only applies when all inbound traffic (i.e. \"*\") is being redirected (default to $ISTIO_LOCAL_EXCLUDE_PORTS)")
rootCmd.Flags().StringP(constants.ExcludeInterfaces, "", "",
"Comma separated list of NIC (optional). Neither inbound nor outbound traffic will be captured")
rootCmd.Flags().StringP(constants.ServiceCidr, "i", "",
"Comma separated list of IP ranges in CIDR form to redirect to envoy (optional). "+
"The wildcard character \"*\" can be used to redirect all outbound traffic. An empty list will disable all outbound")
rootCmd.Flags().StringP(constants.ServiceExcludeCidr, "x", "",
"Comma separated list of IP ranges in CIDR form to be excluded from redirection. "+
"Only applies when all outbound traffic (i.e. \"*\") is being redirected (default to $ISTIO_SERVICE_EXCLUDE_CIDR)")
rootCmd.Flags().StringP(constants.OutboundPorts, "q", "",
"Comma separated list of outbound ports to be explicitly included for redirection to Envoy")
rootCmd.Flags().StringP(constants.LocalOutboundPortsExclude, "o", "",
"Comma separated list of outbound ports to be excluded from redirection to Envoy")
rootCmd.Flags().StringP(constants.KubeVirtInterfaces, "k", "",
"Comma separated list of virtual interfaces whose inbound traffic (from VM) will be treated as outbound")
rootCmd.Flags().StringP(constants.InboundTProxyMark, "t", "", "")
rootCmd.Flags().StringP(constants.InboundTProxyRouteTable, "r", "", "")
rootCmd.Flags().BoolP(constants.DryRun, "n", false, "Do not call any external dependencies like iptables")
rootCmd.Flags().Bool(constants.TraceLogging, false, "Insert tracing logs for each iptables rules, using the LOG chain.")
rootCmd.Flags().BoolP(constants.RestoreFormat, "f", true, "Print iptables rules in iptables-restore interpretable format")
rootCmd.Flags().String(constants.IptablesProbePort, constants.DefaultIptablesProbePort, "set listen port for failure detection")
rootCmd.Flags().Duration(constants.ProbeTimeout, constants.DefaultProbeTimeout, "failure detection timeout")
rootCmd.Flags().Bool(constants.SkipRuleApply, false, "Skip iptables apply")
rootCmd.Flags().Bool(constants.RunValidation, false, "Validate iptables")
rootCmd.Flags().Bool(constants.RedirectDNS, dnsCaptureByAgent, "Enable capture of dns traffic by istio-agent")
rootCmd.Flags().Bool(constants.DropInvalid, InvalidDropByIptables.Get(), "Enable invalid drop in the iptables rules")
rootCmd.Flags().Bool(constants.CaptureAllDNS, false,
"Instead of only capturing DNS traffic to DNS server IP, capture all DNS traffic at port 53. This setting is only effective when redirect dns is enabled.")
rootCmd.Flags().String(constants.OutputPath, "", "A file path to write the applied iptables rules to.")
rootCmd.Flags().String(constants.NetworkNamespace, "", "The network namespace that iptables rules should be applied to.")
rootCmd.Flags().Bool(constants.CNIMode, false, "Whether to run as CNI plugin.")
}
func GetCommand() *cobra.Command {
return rootCmd
}
func GetRouteCommand() *cobra.Command {
return configureRoutesCommand
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
handleError(err)
}
}