diff --git a/.changelog/11480.txt b/.changelog/11480.txt new file mode 100644 index 0000000000..d5badc405c --- /dev/null +++ b/.changelog/11480.txt @@ -0,0 +1,3 @@ +```release-note:improvement +sdk: Add support for iptable rules that allow DNS lookup redirection to Consul DNS. +``` \ No newline at end of file diff --git a/command/connect/redirecttraffic/redirect_traffic.go b/command/connect/redirecttraffic/redirect_traffic.go index 8d79e75991..6436c98cca 100644 --- a/command/connect/redirecttraffic/redirect_traffic.go +++ b/command/connect/redirecttraffic/redirect_traffic.go @@ -36,6 +36,7 @@ type cmd struct { client *api.Client // Flags. + consulDNSIP string proxyUID string proxyID string proxyInboundPort int @@ -50,6 +51,7 @@ type cmd struct { func (c *cmd) init() { c.flags = flag.NewFlagSet("", flag.ContinueOnError) + c.flags.StringVar(&c.consulDNSIP, "consul-dns-ip", "", "IP used to reach Consul DNS. If provided, DNS queries will be redirected to Consul.") c.flags.StringVar(&c.proxyUID, "proxy-uid", "", "The user ID of the proxy to exclude from traffic redirection.") c.flags.StringVar(&c.proxyID, "proxy-id", "", "The service ID of the proxy service registered with Consul.") c.flags.IntVar(&c.proxyInboundPort, "proxy-inbound-port", 0, "The inbound port that the proxy is listening on.") @@ -130,6 +132,7 @@ type trafficRedirectProxyConfig struct { // generateConfigFromFlags generates iptables.Config based on command flags. func (c *cmd) generateConfigFromFlags() (iptables.Config, error) { cfg := iptables.Config{ + ConsulDNSIP: c.consulDNSIP, ProxyUserID: c.proxyUID, ProxyInboundPort: c.proxyInboundPort, ProxyOutboundPort: c.proxyOutboundPort, diff --git a/command/connect/redirecttraffic/redirect_traffic_test.go b/command/connect/redirecttraffic/redirect_traffic_test.go index 5c31af036c..e2ccf1c426 100644 --- a/command/connect/redirecttraffic/redirect_traffic_test.go +++ b/command/connect/redirecttraffic/redirect_traffic_test.go @@ -127,6 +127,35 @@ func TestGenerateConfigFromFlags(t *testing.T) { ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, }, }, + { + name: "proxyID with Consul DNS IP provided", + command: func() cmd { + var c cmd + c.init() + c.proxyUID = "1234" + c.proxyID = "test-proxy-id" + c.consulDNSIP = "10.0.34.16" + return c + }, + consulServices: []api.AgentServiceRegistration{ + { + Kind: api.ServiceKindConnectProxy, + ID: "test-proxy-id", + Name: "test-proxy", + Port: 20000, + Address: "1.1.1.1", + Proxy: &api.AgentServiceConnectProxyConfig{ + DestinationServiceName: "foo", + }, + }, + }, + expCfg: iptables.Config{ + ConsulDNSIP: "10.0.34.16", + ProxyUserID: "1234", + ProxyInboundPort: 20000, + ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, + }, + }, { name: "proxyID with bind_port(string) provided", command: func() cmd { diff --git a/sdk/iptables/iptables.go b/sdk/iptables/iptables.go index c80776833d..83b5c03a1b 100644 --- a/sdk/iptables/iptables.go +++ b/sdk/iptables/iptables.go @@ -18,12 +18,18 @@ const ( // ProxyOutputRedirectChain is the chain to redirect outbound traffic to the proxy ProxyOutputRedirectChain = "CONSUL_PROXY_REDIRECT" + // DNSChain is the chain to redirect outbound DNS traffic to Consul DNS. + DNSChain = "CONSUL_DNS_REDIRECT" + DefaultTProxyOutboundPort = 15001 ) // Config is used to configure which traffic interception and redirection // rules should be applied with the iptables commands. type Config struct { + // ConsulDNSIP is the IP for Consul DNS to direct DNS queries to. + ConsulDNSIP string + // ProxyUserID is the user ID of the proxy process. ProxyUserID string @@ -90,7 +96,7 @@ func Setup(cfg Config) error { } // Create chains we will use for redirection. - chains := []string{ProxyInboundChain, ProxyInboundRedirectChain, ProxyOutputChain, ProxyOutputRedirectChain} + chains := []string{ProxyInboundChain, ProxyInboundRedirectChain, ProxyOutputChain, ProxyOutputRedirectChain, DNSChain} for _, chain := range chains { cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-N", chain) } @@ -100,6 +106,17 @@ func Setup(cfg Config) error { // Redirects outbound TCP traffic hitting PROXY_REDIRECT chain to Envoy's outbound listener port. cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-A", ProxyOutputRedirectChain, "-p", "tcp", "-j", "REDIRECT", "--to-port", strconv.Itoa(cfg.ProxyOutboundPort)) + // The DNS rules are applied before the rules that directs all TCP traffic, so that the traffic going to port 53 goes through this rule first. + if cfg.ConsulDNSIP != "" { + // Traffic in the DNSChain is directed to the Consul DNS Service IP. + cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-A", DNSChain, "-p", "udp", "--dport", "53", "-j", "DNAT", "--to-destination", cfg.ConsulDNSIP) + cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-A", DNSChain, "-p", "tcp", "--dport", "53", "-j", "DNAT", "--to-destination", cfg.ConsulDNSIP) + + // For outbound TCP and UDP traffic going to port 53 (DNS), jump to the DNSChain. + cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "udp", "--dport", "53", "-j", DNSChain) + cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "tcp", "--dport", "53", "-j", DNSChain) + } + // For outbound TCP traffic jump from OUTPUT chain to PROXY_OUTPUT chain. cfg.IptablesProvider.AddRule("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "tcp", "-j", ProxyOutputChain) diff --git a/sdk/iptables/iptables_test.go b/sdk/iptables/iptables_test.go index 1421de7db9..1de3122e55 100644 --- a/sdk/iptables/iptables_test.go +++ b/sdk/iptables/iptables_test.go @@ -25,6 +25,7 @@ func TestSetup(t *testing.T) { "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", "iptables -t nat -N CONSUL_PROXY_OUTPUT", "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 15001", "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", @@ -35,6 +36,34 @@ func TestSetup(t *testing.T) { "iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT", }, }, + { + "Consul DNS IP provided", + Config{ + ProxyUserID: "123", + ProxyInboundPort: 20000, + ConsulDNSIP: "10.0.34.16", + IptablesProvider: &fakeIptablesProvider{}, + }, + []string{ + "iptables -t nat -N CONSUL_PROXY_INBOUND", + "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", + "iptables -t nat -N CONSUL_PROXY_OUTPUT", + "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", + "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 15001", + "iptables -t nat -A CONSUL_DNS_REDIRECT -p udp --dport 53 -j DNAT --to-destination 10.0.34.16", + "iptables -t nat -A CONSUL_DNS_REDIRECT -p tcp --dport 53 -j DNAT --to-destination 10.0.34.16", + "iptables -t nat -A OUTPUT -p udp --dport 53 -j CONSUL_DNS_REDIRECT", + "iptables -t nat -A OUTPUT -p tcp --dport 53 -j CONSUL_DNS_REDIRECT", + "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", + "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", + "iptables -t nat -A CONSUL_PROXY_OUTPUT -d 127.0.0.1/32 -j RETURN", + "iptables -t nat -A CONSUL_PROXY_OUTPUT -j CONSUL_PROXY_REDIRECT", + "iptables -t nat -A CONSUL_PROXY_IN_REDIRECT -p tcp -j REDIRECT --to-port 20000", + "iptables -t nat -A PREROUTING -p tcp -j CONSUL_PROXY_INBOUND", + "iptables -t nat -A CONSUL_PROXY_INBOUND -p tcp -j CONSUL_PROXY_IN_REDIRECT", + }, + }, { "proxy outbound port is provided", Config{ @@ -48,6 +77,7 @@ func TestSetup(t *testing.T) { "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", "iptables -t nat -N CONSUL_PROXY_OUTPUT", "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000", "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", @@ -72,6 +102,7 @@ func TestSetup(t *testing.T) { "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", "iptables -t nat -N CONSUL_PROXY_OUTPUT", "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000", "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", @@ -98,6 +129,7 @@ func TestSetup(t *testing.T) { "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", "iptables -t nat -N CONSUL_PROXY_OUTPUT", "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000", "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", @@ -124,6 +156,7 @@ func TestSetup(t *testing.T) { "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", "iptables -t nat -N CONSUL_PROXY_OUTPUT", "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000", "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", @@ -150,6 +183,7 @@ func TestSetup(t *testing.T) { "iptables -t nat -N CONSUL_PROXY_IN_REDIRECT", "iptables -t nat -N CONSUL_PROXY_OUTPUT", "iptables -t nat -N CONSUL_PROXY_REDIRECT", + "iptables -t nat -N CONSUL_DNS_REDIRECT", "iptables -t nat -A CONSUL_PROXY_REDIRECT -p tcp -j REDIRECT --to-port 21000", "iptables -t nat -A OUTPUT -p tcp -j CONSUL_PROXY_OUTPUT", "iptables -t nat -A CONSUL_PROXY_OUTPUT -m owner --uid-owner 123 -j RETURN", @@ -171,7 +205,6 @@ func TestSetup(t *testing.T) { require.Equal(t, c.expectedRules, c.cfg.IptablesProvider.Rules()) }) } - } func TestSetup_errors(t *testing.T) {