Skip to content

Commit

Permalink
cli: Add new consul connect redirect-traffic command for applying t…
Browse files Browse the repository at this point in the history
…raffic redirection rules when Transparent Proxy is enabled. (#9910)

* Add new consul connect redirect-traffic command for applying traffic redirection rules when Transparent Proxy is enabled.
* Add new iptables package for applying traffic redirection rules with iptables.
  • Loading branch information
ishustava authored Apr 9, 2021
1 parent a02245b commit 5755c97
Show file tree
Hide file tree
Showing 14 changed files with 852 additions and 16 deletions.
6 changes: 6 additions & 0 deletions .changelog/9910.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:feature
cli: Add new `consul connect redirect-traffic` command for applying traffic redirection rules when Transparent Proxy is enabled.
```
```release-note:feature
sdk: Add new `iptables` package for applying traffic redirection rules with iptables.
```
9 changes: 3 additions & 6 deletions agent/xds/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/hashicorp/consul/sdk/iptables"

"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
Expand All @@ -32,11 +33,6 @@ import (
"github.com/hashicorp/consul/logging"
)

const (
// TODO (freddy) Make this configurable
TProxyOutboundPort = 15001
)

// listenersFromSnapshot returns the xDS API representation of the "listeners" in the snapshot.
func (s *Server) listenersFromSnapshot(cInfo connectionInfo, cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
if cfgSnap == nil {
Expand Down Expand Up @@ -75,7 +71,8 @@ func (s *Server) listenersFromSnapshotConnectProxy(cInfo connectionInfo, cfgSnap
var outboundListener *envoy_listener_v3.Listener

if cfgSnap.Proxy.TransparentProxy {
outboundListener = makeListener(OutboundListenerName, "127.0.0.1", TProxyOutboundPort, envoy_core_v3.TrafficDirection_OUTBOUND)
// TODO (freddy) Make DefaultTProxyOutboundPort configurable
outboundListener = makeListener(OutboundListenerName, "127.0.0.1", iptables.DefaultTProxyOutboundPort, envoy_core_v3.TrafficDirection_OUTBOUND)
outboundListener.FilterChains = make([]*envoy_listener_v3.FilterChain, 0)
outboundListener.ListenerFilters = []*envoy_listener_v3.ListenerFilter{
{
Expand Down
1 change: 1 addition & 0 deletions build-support/docker/Consul-Dev.dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ARG CONSUL_IMAGE_VERSION=latest
FROM consul:${CONSUL_IMAGE_VERSION}
RUN apk update && apk add iptables
COPY consul /bin/consul
6 changes: 4 additions & 2 deletions command/commands_oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
pipebootstrap "github.com/hashicorp/consul/command/connect/envoy/pipe-bootstrap"
"github.com/hashicorp/consul/command/connect/expose"
"github.com/hashicorp/consul/command/connect/proxy"
"github.com/hashicorp/consul/command/connect/redirecttraffic"
"github.com/hashicorp/consul/command/debug"
"github.com/hashicorp/consul/command/event"
"github.com/hashicorp/consul/command/exec"
Expand All @@ -77,8 +78,8 @@ import (
kvput "github.com/hashicorp/consul/command/kv/put"
"github.com/hashicorp/consul/command/leave"
"github.com/hashicorp/consul/command/lock"
login "github.com/hashicorp/consul/command/login"
logout "github.com/hashicorp/consul/command/logout"
"github.com/hashicorp/consul/command/login"
"github.com/hashicorp/consul/command/logout"
"github.com/hashicorp/consul/command/maint"
"github.com/hashicorp/consul/command/members"
"github.com/hashicorp/consul/command/monitor"
Expand Down Expand Up @@ -173,6 +174,7 @@ func init() {
Register("connect envoy", func(ui cli.Ui) (cli.Command, error) { return envoy.New(ui), nil })
Register("connect envoy pipe-bootstrap", func(ui cli.Ui) (cli.Command, error) { return pipebootstrap.New(ui), nil })
Register("connect expose", func(ui cli.Ui) (cli.Command, error) { return expose.New(ui), nil })
Register("connect redirect-traffic", func(ui cli.Ui) (cli.Command, error) { return redirecttraffic.New(ui), nil })
Register("debug", func(ui cli.Ui) (cli.Command, error) { return debug.New(ui, MakeShutdownCh()), nil })
Register("event", func(ui cli.Ui) (cli.Command, error) { return event.New(ui), nil })
Register("exec", func(ui cli.Ui) (cli.Command, error) { return exec.New(ui, MakeShutdownCh()), nil })
Expand Down
168 changes: 168 additions & 0 deletions command/connect/redirecttraffic/redirect_traffic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package redirecttraffic

import (
"flag"
"fmt"

"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/consul/sdk/iptables"
"github.com/mitchellh/cli"
"github.com/mitchellh/mapstructure"
)

func New(ui cli.Ui) *cmd {
ui = &cli.PrefixedUi{
OutputPrefix: "==> ",
InfoPrefix: " ",
ErrorPrefix: "==> ",
Ui: ui,
}

c := &cmd{
UI: ui,
}
c.init()
return c
}

type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
client *api.Client

// Flags.
proxyUID string
proxyID string
proxyInboundPort int
proxyOutboundPort int
}

func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)

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.")
c.flags.IntVar(&c.proxyOutboundPort, "proxy-outbound-port", iptables.DefaultTProxyOutboundPort,
"The outbound port that the proxy is listening on. When not provided, 15001 is used by default.")

c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.NamespaceFlags())
c.help = flags.Usage(help, c.flags)
}

func (c *cmd) Run(args []string) int {
if err := c.flags.Parse(args); err != nil {
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
return 1
}

if c.proxyUID == "" {
c.UI.Error("-proxy-uid is required")
return 1
}

if c.proxyID == "" && c.proxyInboundPort == 0 {
c.UI.Error("either -proxy-id or -proxy-inbound-port are required")
return 1
}

if c.proxyID != "" && (c.proxyInboundPort != 0 || c.proxyOutboundPort != iptables.DefaultTProxyOutboundPort) {
c.UI.Error("-proxy-inbound-port or -proxy-outbound-port cannot be provided together with -proxy-id. " +
"Proxy's inbound and outbound ports are retrieved from the proxy's configuration instead.")
return 1
}

cfg, err := c.generateConfigFromFlags()
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to create configuration to apply traffic redirection rules: %s", err))
return 1
}

err = iptables.Setup(cfg)
if err != nil {
c.UI.Error(fmt.Sprintf("Error setting up traffic redirection rules: %s", err.Error()))
return 1
}

c.UI.Info("Successfully applied traffic redirection rules")
return 0
}

func (c *cmd) Synopsis() string {
return synopsis
}

func (c *cmd) Help() string {
return c.help
}

// trafficRedirectProxyConfig is a snippet of xds/config.go
// with only the configuration values that we need to parse from Proxy.Config
// to apply traffic redirection rules.
type trafficRedirectProxyConfig struct {
BindPort int `mapstructure:"bind_port"`
}

// generateConfigFromFlags generates iptables.Config based on command flags.
func (c *cmd) generateConfigFromFlags() (iptables.Config, error) {
cfg := iptables.Config{ProxyUserID: c.proxyUID}

// When proxyID is provided, we set up cfg with values
// from proxy's service registration in Consul.
if c.proxyID != "" {
var err error
if c.client == nil {
c.client, err = c.http.APIClient()
if err != nil {
return iptables.Config{}, fmt.Errorf("error creating Consul API client: %s", err)
}
}

svc, _, err := c.client.Agent().Service(c.proxyID, nil)
if err != nil {
return iptables.Config{}, fmt.Errorf("failed to fetch proxy service from Consul Agent: %s", err)
}

if svc.Proxy == nil {
return iptables.Config{}, fmt.Errorf("service %s is not a proxy service", c.proxyID)
}

cfg.ProxyInboundPort = svc.Port
var trCfg trafficRedirectProxyConfig
if err := mapstructure.WeakDecode(svc.Proxy.Config, &trCfg); err != nil {
return iptables.Config{}, fmt.Errorf("failed parsing Proxy.Config: %s", err)
}

if trCfg.BindPort != 0 {
cfg.ProxyInboundPort = trCfg.BindPort
}

// todo: Change once it's configurable
cfg.ProxyOutboundPort = iptables.DefaultTProxyOutboundPort
} else {
cfg.ProxyInboundPort = c.proxyInboundPort
cfg.ProxyOutboundPort = c.proxyOutboundPort
}

return cfg, nil
}

const synopsis = "Applies iptables rules for traffic redirection"
const help = `
Usage: consul connect redirect-traffic [options]
Applies iptables rules for inbound and outbound traffic redirection.
Requires that the iptables command line utility is installed.
Examples:
$ consul connect redirect-traffic -proxy-uid 1234 -proxy-id web
$ consul connect redirect-traffic -proxy-uid 1234 -proxy-inbound-port 20000
`
Loading

0 comments on commit 5755c97

Please sign in to comment.