Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: Add new consul connect redirect-traffic command for applying traffic redirection rules when Transparent Proxy is enabled. #9910

Merged
merged 22 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 != 0) {
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)
freddygv marked this conversation as resolved.
Show resolved Hide resolved
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
ishustava marked this conversation as resolved.
Show resolved Hide resolved
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
freddygv marked this conversation as resolved.
Show resolved Hide resolved
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