Envoy CLI bind addresses (#6107)

* Ensure we MapWalk the proxy config in the NodeService and ServiceNode structs

This gets rid of some json encoder errors in the catalog endpoints

* Allow passing explicit bind addresses to envoy

* Move map walking to the ConnectProxyConfig struct

Any place where this struct gets JSON encoded will benefit as opposed to having to implement it everywhere.

* Fail when a non-empty address is provided and not bindable

* camel case

* Update command/connect/envoy/envoy.go

Co-Authored-By: Paul Banks <banks@banksco.de>
This commit is contained in:
Matt Keeler 2019-07-12 12:57:31 -04:00 committed by GitHub
parent 911ed76e5b
commit 6e65811db2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 2 deletions

View File

@ -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(&copy)
}
// 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

View File

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

View File

@ -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 `<name>=<ip>:<port>` "+
"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{