command/redirect_traffic: Redirect DNS requests to Consul if -consul-dns-ip is passed in (#11480)

* command/redirect_traffic: add rules to redirect DNS to Consul. Currently uses a hack to get the consul dns service ip, and this hack only works when the service is deployed in the same namespace as consul.
* command/redirect_traffic: redirect DNS to Consul when -consul-dns-ip is passed in
* Add unit tests to Consul DNS IP table redirect rules

Co-authored-by: Ashwin Venkatesh <ashwin@hashicorp.com>
Co-authored-by: Iryna Shustava <ishustava@users.noreply.github.com>
This commit is contained in:
Nitya Dhanushkodi 2021-11-10 06:10:48 -08:00 committed by GitHub
parent a34f81cc29
commit d396faf246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 2 deletions

3
.changelog/11480.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
sdk: Add support for iptable rules that allow DNS lookup redirection to Consul DNS.
```

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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) {