diff --git a/agent/structs/connect_proxy_config.go b/agent/structs/connect_proxy_config.go index e2152e0ef..f83eb5774 100644 --- a/agent/structs/connect_proxy_config.go +++ b/agent/structs/connect_proxy_config.go @@ -1,9 +1,11 @@ package structs import ( + "encoding/json" "fmt" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/lib" ) type MeshGatewayMode string @@ -81,6 +83,19 @@ type ConnectProxyConfig struct { MeshGateway MeshGatewayConfig `json:",omitempty"` } +func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) { + type typeCopy ConnectProxyConfig + copy := typeCopy(*c) + + proxyConfig, err := lib.MapWalk(copy.Config) + if err != nil { + return nil, err + } + copy.Config = proxyConfig + + return json.Marshal(©) +} + // ToAPI returns the api struct with the same fields. We have duplicates to // avoid the api package depending on this one which imports a ton of Consul's // core which you don't want if you are just trying to use our client in your diff --git a/agent/structs/structs_test.go b/agent/structs/structs_test.go index 0a46ffbe3..37c04737e 100644 --- a/agent/structs/structs_test.go +++ b/agent/structs/structs_test.go @@ -1535,3 +1535,48 @@ func TestCheckServiceNode_BestAddress(t *testing.T) { }) } } + +func TestNodeService_JSON_Marshal(t *testing.T) { + ns := &NodeService{ + Service: "foo", + Proxy: ConnectProxyConfig{ + Config: map[string]interface{}{ + "bind_addresses": map[string]interface{}{ + "default": map[string]interface{}{ + "Address": "0.0.0.0", + "Port": "443", + }, + }, + }, + }, + } + buf, err := json.Marshal(ns) + require.NoError(t, err) + + var out NodeService + require.NoError(t, json.Unmarshal(buf, &out)) + require.Equal(t, *ns, out) +} + +func TestServiceNode_JSON_Marshal(t *testing.T) { + sn := &ServiceNode{ + Node: "foo", + ServiceName: "foo", + ServiceProxy: ConnectProxyConfig{ + Config: map[string]interface{}{ + "bind_addresses": map[string]interface{}{ + "default": map[string]interface{}{ + "Address": "0.0.0.0", + "Port": "443", + }, + }, + }, + }, + } + buf, err := json.Marshal(sn) + require.NoError(t, err) + + var out ServiceNode + require.NoError(t, json.Unmarshal(buf, &out)) + require.Equal(t, *sn, out) +} diff --git a/command/connect/envoy/envoy.go b/command/connect/envoy/envoy.go index 7021eee46..89406ecd5 100644 --- a/command/connect/envoy/envoy.go +++ b/command/connect/envoy/envoy.go @@ -61,6 +61,7 @@ type cmd struct { address string wanAddress string deregAfterCritical string + bindAddresses map[string]string meshGatewaySvcName string } @@ -117,6 +118,10 @@ func (c *cmd) init() { c.flags.StringVar(&c.wanAddress, "wan-address", "", "WAN address to advertise in the Mesh Gateway service registration") + c.flags.Var((*flags.FlagMapValue)(&c.bindAddresses), "bind-address", "Bind "+ + "address to use instead of the default binding rules given as `=:` "+ + "pairs. This flag may be specified multiple times to add multiple bind addresses.") + c.flags.StringVar(&c.meshGatewaySvcName, "service", "mesh-gateway", "Service name to use for the registration") @@ -160,6 +165,31 @@ func parseAddress(addrStr string) (string, int, error) { return addr, port, nil } +func canBind(addr string) bool { + if addr == "" { + return false + } + + ip := net.ParseIP(addr) + if ip == nil { + return false + } + + ifAddrs, err := net.InterfaceAddrs() + + if err != nil { + return false + } + + for _, addr := range ifAddrs { + if addr.String() == ip.String() { + return true + } + } + + return false +} + func (c *cmd) Run(args []string) int { if err := c.flags.Parse(args); err != nil { return 1 @@ -215,8 +245,10 @@ func (c *cmd) Run(args []string) int { taggedAddrs["lan"] = api.ServiceAddress{Address: lanAddr, Port: lanPort} } + wanAddr := "" + wanPort := lanPort if c.wanAddress != "" { - wanAddr, wanPort, err := parseAddress(c.wanAddress) + wanAddr, wanPort, err = parseAddress(c.wanAddress) if err != nil { c.UI.Error(fmt.Sprintf("Failed to parse the -wan-address parameter: %v", err)) return 1 @@ -233,13 +265,38 @@ func (c *cmd) Run(args []string) int { var proxyConf *api.AgentServiceConnectProxyConfig - if lanAddr != "" { + if len(c.bindAddresses) > 0 { + // override all default binding rules and just bind to the user-supplied addresses + bindAddresses := make(map[string]api.ServiceAddress) + + for addrName, addrStr := range c.bindAddresses { + addr, port, err := parseAddress(addrStr) + if err != nil { + c.UI.Error(fmt.Sprintf("Failed to parse the bind address: %s=%s: %v", addrName, addrStr, err)) + return 1 + } + + bindAddresses[addrName] = api.ServiceAddress{Address: addr, Port: port} + } + + proxyConf = &api.AgentServiceConnectProxyConfig{ + Config: map[string]interface{}{ + "envoy_mesh_gateway_no_default_bind": true, + "envoy_mesh_gateway_bind_addresses": bindAddresses, + }, + } + } else if canBind(lanAddr) && canBind(wanAddr) { + // when both addresses are bindable then we bind to the tagged addresses + // for creating the envoy listeners proxyConf = &api.AgentServiceConnectProxyConfig{ Config: map[string]interface{}{ "envoy_mesh_gateway_no_default_bind": true, "envoy_mesh_gateway_bind_tagged_addresses": true, }, } + } else if !canBind(lanAddr) && lanAddr != "" { + c.UI.Error(fmt.Sprintf("The LAN address %q will not be bindable. Either set a bindable address or override the bind addresses with -bind-address", lanAddr)) + return 1 } svc := api.AgentServiceRegistration{