2018-10-03 19:37:53 +00:00
package envoy
import (
2018-10-05 20:08:01 +00:00
"errors"
2018-10-03 19:37:53 +00:00
"flag"
"fmt"
"net"
"os"
"os/exec"
"strconv"
"strings"
2019-04-29 16:27:57 +00:00
"github.com/mitchellh/mapstructure"
2018-10-03 19:37:53 +00:00
"github.com/hashicorp/consul/agent/xds"
"github.com/hashicorp/consul/api"
proxyCmd "github.com/hashicorp/consul/command/connect/proxy"
"github.com/hashicorp/consul/command/flags"
2019-06-18 00:52:01 +00:00
"github.com/hashicorp/consul/ipaddr"
"github.com/hashicorp/go-sockaddr/template"
2018-10-03 19:37:53 +00:00
"github.com/mitchellh/cli"
)
func New ( ui cli . Ui ) * cmd {
ui = & cli . PrefixedUi {
OutputPrefix : "==> " ,
InfoPrefix : " " ,
ErrorPrefix : "==> " ,
Ui : ui ,
}
c := & cmd { UI : ui }
c . init ( )
return c
}
2019-06-07 09:26:43 +00:00
const DefaultAdminAccessLogPath = "/dev/null"
2018-10-03 19:37:53 +00:00
type cmd struct {
UI cli . Ui
flags * flag . FlagSet
http * flags . HTTPFlags
help string
client * api . Client
// flags
2019-06-18 00:52:01 +00:00
meshGateway bool
2019-04-29 16:27:57 +00:00
proxyID string
sidecarFor string
2019-06-07 09:26:43 +00:00
adminAccessLogPath string
2019-04-29 16:27:57 +00:00
adminBind string
envoyBin string
bootstrap bool
disableCentralConfig bool
grpcAddr string
2019-06-18 00:52:01 +00:00
// mesh gateway registration information
register bool
address string
wanAddress string
deregAfterCritical string
2019-07-12 16:57:31 +00:00
bindAddresses map [ string ] string
2019-06-18 00:52:01 +00:00
meshGatewaySvcName string
2018-10-03 19:37:53 +00:00
}
func ( c * cmd ) init ( ) {
c . flags = flag . NewFlagSet ( "" , flag . ContinueOnError )
c . flags . StringVar ( & c . proxyID , "proxy-id" , "" ,
"The proxy's ID on the local agent." )
2019-06-18 00:52:01 +00:00
c . flags . BoolVar ( & c . meshGateway , "mesh-gateway" , false ,
"Generate the bootstrap.json but don't exec envoy" )
2018-10-03 19:37:53 +00:00
c . flags . StringVar ( & c . sidecarFor , "sidecar-for" , "" ,
"The ID of a service instance on the local agent that this proxy should " +
"become a sidecar for. It requires that the proxy service is registered " +
"with the agent as a connect-proxy with Proxy.DestinationServiceID set " +
"to this value. If more than one such proxy is registered it will fail." )
c . flags . StringVar ( & c . envoyBin , "envoy-binary" , "" ,
"The full path to the envoy binary to run. By default will just search " +
"$PATH. Ignored if -bootstrap is used." )
2019-06-07 09:26:43 +00:00
c . flags . StringVar ( & c . adminAccessLogPath , "admin-access-log-path" , DefaultAdminAccessLogPath ,
fmt . Sprintf ( "The path to write the access log for the administration server. If no access " +
"log is desired specify %q. By default it will use %q." ,
DefaultAdminAccessLogPath , DefaultAdminAccessLogPath ) )
2018-10-03 19:37:53 +00:00
c . flags . StringVar ( & c . adminBind , "admin-bind" , "localhost:19000" ,
"The address:port to start envoy's admin server on. Envoy requires this " +
2019-04-29 16:27:57 +00:00
"but care must be taken to ensure it's not exposed to an untrusted network " +
2018-10-03 19:37:53 +00:00
"as it has full control over the secrets and config of the proxy." )
c . flags . BoolVar ( & c . bootstrap , "bootstrap" , false ,
2018-10-05 20:08:01 +00:00
"Generate the bootstrap.json but don't exec envoy" )
2018-10-03 19:37:53 +00:00
2019-04-29 16:27:57 +00:00
c . flags . BoolVar ( & c . disableCentralConfig , "no-central-config" , false ,
"By default the proxy's bootstrap configuration can be customized " +
"centrally. This requires that the command run on the same agent as the " +
"proxy will and that the agent is reachable when the command is run. In " +
"cases where either assumption is violated this flag will prevent the " +
"command attempting to resolve config from the local agent." )
2018-10-03 19:37:53 +00:00
c . flags . StringVar ( & c . grpcAddr , "grpc-addr" , "" ,
"Set the agent's gRPC address and port (in http(s)://host:port format). " +
"Alternatively, you can specify CONSUL_GRPC_ADDR in ENV." )
2019-06-18 00:52:01 +00:00
c . flags . BoolVar ( & c . register , "register" , false ,
"Register a new Mesh Gateway service before configuring and starting Envoy" )
c . flags . StringVar ( & c . address , "address" , "" ,
"LAN address to advertise in the Mesh Gateway service registration" )
c . flags . StringVar ( & c . wanAddress , "wan-address" , "" ,
"WAN address to advertise in the Mesh Gateway service registration" )
2019-07-12 16:57:31 +00:00
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." )
2019-06-18 00:52:01 +00:00
c . flags . StringVar ( & c . meshGatewaySvcName , "service" , "mesh-gateway" ,
"Service name to use for the registration" )
c . flags . StringVar ( & c . deregAfterCritical , "deregister-after-critical" , "6h" ,
"The amount of time the gateway services health check can be failing before being deregistered" )
2018-10-03 19:37:53 +00:00
c . http = & flags . HTTPFlags { }
flags . Merge ( c . flags , c . http . ClientFlags ( ) )
c . help = flags . Usage ( help , c . flags )
}
2019-06-18 00:52:01 +00:00
const (
DefaultMeshGatewayPort int = 443
)
func parseAddress ( addrStr string ) ( string , int , error ) {
if addrStr == "" {
// defaulting the port to 443
return "" , DefaultMeshGatewayPort , nil
}
x , err := template . Parse ( addrStr )
if err != nil {
return "" , DefaultMeshGatewayPort , fmt . Errorf ( "Error parsing address %q: %v" , addrStr , err )
}
addr , portStr , err := net . SplitHostPort ( x )
if err != nil {
return "" , DefaultMeshGatewayPort , fmt . Errorf ( "Error parsing address %q: %v" , x , err )
}
port := DefaultMeshGatewayPort
if portStr != "" {
port , err = strconv . Atoi ( portStr )
if err != nil {
return "" , DefaultMeshGatewayPort , fmt . Errorf ( "Error parsing port %q: %v" , portStr , err )
}
}
return addr , port , nil
}
2019-07-30 13:56:56 +00:00
// canBindInternal is here mainly so we can unit test this with a constant net.Addr list
func canBindInternal ( addr string , ifAddrs [ ] net . Addr ) bool {
2019-07-12 16:57:31 +00:00
if addr == "" {
return false
}
ip := net . ParseIP ( addr )
if ip == nil {
return false
}
2019-07-30 13:56:56 +00:00
ipStr := ip . String ( )
2019-07-12 16:57:31 +00:00
for _ , addr := range ifAddrs {
2019-07-30 13:56:56 +00:00
switch v := addr . ( type ) {
case * net . IPNet :
if v . IP . String ( ) == ipStr {
return true
}
default :
if addr . String ( ) == ipStr {
return true
}
2019-07-12 16:57:31 +00:00
}
}
return false
}
2019-07-30 13:56:56 +00:00
func canBind ( addr string ) bool {
ifAddrs , err := net . InterfaceAddrs ( )
if err != nil {
return false
}
return canBindInternal ( addr , ifAddrs )
}
2018-10-03 19:37:53 +00:00
func ( c * cmd ) Run ( args [ ] string ) int {
if err := c . flags . Parse ( args ) ; err != nil {
return 1
}
passThroughArgs := c . flags . Args ( )
// Load the proxy ID and token from env vars if they're set
if c . proxyID == "" {
2019-08-09 19:19:30 +00:00
c . proxyID = os . Getenv ( "CONNECT_PROXY_ID" )
2018-10-03 19:37:53 +00:00
}
if c . sidecarFor == "" {
2019-08-09 19:19:30 +00:00
c . sidecarFor = os . Getenv ( "CONNECT_SIDECAR_FOR" )
2018-10-03 19:37:53 +00:00
}
if c . grpcAddr == "" {
c . grpcAddr = os . Getenv ( api . GRPCAddrEnvName )
}
// Setup Consul client
client , err := c . http . APIClient ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error connecting to Consul agent: %s" , err ) )
return 1
}
c . client = client
2019-06-18 00:52:01 +00:00
if c . register {
if ! c . meshGateway {
c . UI . Error ( "Auto-Registration can only be used for mesh gateways" )
return 1
}
lanAddr , lanPort , err := parseAddress ( c . address )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Failed to parse the -address parameter: %v" , err ) )
return 1
}
taggedAddrs := make ( map [ string ] api . ServiceAddress )
if lanAddr != "" {
taggedAddrs [ "lan" ] = api . ServiceAddress { Address : lanAddr , Port : lanPort }
}
2019-07-12 16:57:31 +00:00
wanAddr := ""
wanPort := lanPort
2019-06-18 00:52:01 +00:00
if c . wanAddress != "" {
2019-07-12 16:57:31 +00:00
wanAddr , wanPort , err = parseAddress ( c . wanAddress )
2019-06-18 00:52:01 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Failed to parse the -wan-address parameter: %v" , err ) )
return 1
}
taggedAddrs [ "wan" ] = api . ServiceAddress { Address : wanAddr , Port : wanPort }
}
tcpCheckAddr := lanAddr
if tcpCheckAddr == "" {
// fallback to localhost as the gateway has to reside in the same network namespace
// as the agent
tcpCheckAddr = "127.0.0.1"
}
var proxyConf * api . AgentServiceConnectProxyConfig
2019-07-12 16:57:31 +00:00
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
2019-06-18 00:52:01 +00:00
proxyConf = & api . AgentServiceConnectProxyConfig {
Config : map [ string ] interface { } {
"envoy_mesh_gateway_no_default_bind" : true ,
"envoy_mesh_gateway_bind_tagged_addresses" : true ,
} ,
}
2019-07-12 16:57:31 +00:00
} 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
2019-06-18 00:52:01 +00:00
}
svc := api . AgentServiceRegistration {
Kind : api . ServiceKindMeshGateway ,
Name : c . meshGatewaySvcName ,
Address : lanAddr ,
Port : lanPort ,
TaggedAddresses : taggedAddrs ,
Proxy : proxyConf ,
Check : & api . AgentServiceCheck {
Name : "Mesh Gateway Listening" ,
TCP : ipaddr . FormatAddressPort ( tcpCheckAddr , lanPort ) ,
Interval : "10s" ,
DeregisterCriticalServiceAfter : c . deregAfterCritical ,
} ,
}
if err := client . Agent ( ) . ServiceRegister ( & svc ) ; err != nil {
c . UI . Error ( fmt . Sprintf ( "Error registering service %q: %s" , svc . Name , err ) )
return 1
}
c . UI . Output ( fmt . Sprintf ( "Registered service: %s" , svc . Name ) )
}
2018-10-03 19:37:53 +00:00
// See if we need to lookup proxyID
if c . proxyID == "" && c . sidecarFor != "" {
proxyID , err := c . lookupProxyIDForSidecar ( )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
c . proxyID = proxyID
2019-06-18 00:52:01 +00:00
} else if c . proxyID == "" && c . meshGateway {
gatewaySvc , err := c . lookupGatewayProxy ( )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
c . proxyID = gatewaySvc . ID
c . meshGatewaySvcName = gatewaySvc . Service
2018-10-03 19:37:53 +00:00
}
2019-06-18 00:52:01 +00:00
2018-10-03 19:37:53 +00:00
if c . proxyID == "" {
2019-06-18 00:52:01 +00:00
c . UI . Error ( "No proxy ID specified. One of -proxy-id or -sidecar-for/-mesh-gateway is " +
2018-10-03 19:37:53 +00:00
"required" )
return 1
}
2019-08-01 16:53:34 +00:00
// See if we need to lookup grpcAddr
if c . grpcAddr == "" {
port , err := c . lookupGRPCPort ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error connecting to Consul agent: %s" , err ) )
}
if port <= 0 {
// This is the dev mode default and recommended production setting if
// enabled.
port = 8502
c . UI . Info ( fmt . Sprintf ( "Defaulting to grpc port = %d" , port ) )
}
c . grpcAddr = fmt . Sprintf ( "localhost:%v" , port )
}
2018-10-03 19:37:53 +00:00
// Generate config
2018-10-05 20:08:01 +00:00
bootstrapJson , err := c . generateConfig ( )
2018-10-03 19:37:53 +00:00
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
if c . bootstrap {
// Just output it and we are done
2018-10-05 20:08:01 +00:00
os . Stdout . Write ( bootstrapJson )
2018-10-03 19:37:53 +00:00
return 0
}
// Find Envoy binary
binary , err := c . findBinary ( )
if err != nil {
c . UI . Error ( "Couldn't find envoy binary: " + err . Error ( ) )
return 1
}
2018-10-05 20:08:01 +00:00
err = execEnvoy ( binary , nil , passThroughArgs , bootstrapJson )
if err == errUnsupportedOS {
c . UI . Error ( "Directly running Envoy is only supported on linux and macOS " +
"since envoy itself doesn't build on other platforms currently." )
c . UI . Error ( "Use the -bootstrap option to generate the JSON to use when running envoy " +
"on a supported OS or via a container or VM." )
return 1
} else if err != nil {
c . UI . Error ( err . Error ( ) )
2018-10-03 19:37:53 +00:00
return 1
}
2018-10-05 20:08:01 +00:00
2018-10-03 19:37:53 +00:00
return 0
}
2018-10-05 20:08:01 +00:00
var errUnsupportedOS = errors . New ( "envoy: not implemented on this operating system" )
2018-10-03 19:37:53 +00:00
func ( c * cmd ) findBinary ( ) ( string , error ) {
if c . envoyBin != "" {
return c . envoyBin , nil
}
return exec . LookPath ( "envoy" )
}
2019-04-29 16:27:57 +00:00
func ( c * cmd ) templateArgs ( ) ( * BootstrapTplArgs , error ) {
2018-10-03 19:37:53 +00:00
httpCfg := api . DefaultConfig ( )
c . http . MergeOntoConfig ( httpCfg )
2019-04-30 14:59:00 +00:00
// Trigger the Client init to do any last-minute updates to the Config.
if _ , err := api . NewClient ( httpCfg ) ; err != nil {
return nil , err
}
2018-10-03 19:37:53 +00:00
// Decide on TLS if the scheme is provided and indicates it, if the HTTP env
// suggests TLS is supported explicitly (CONSUL_HTTP_SSL) or implicitly
// (CONSUL_HTTP_ADDR) is https://
useTLS := false
if strings . HasPrefix ( strings . ToLower ( c . grpcAddr ) , "https://" ) {
useTLS = true
} else if useSSLEnv := os . Getenv ( api . HTTPSSLEnvName ) ; useSSLEnv != "" {
if enabled , err := strconv . ParseBool ( useSSLEnv ) ; err != nil {
useTLS = enabled
}
} else if strings . HasPrefix ( strings . ToLower ( httpCfg . Address ) , "https://" ) {
useTLS = true
}
// We want to allow grpcAddr set as host:port with no scheme but if the host
// is an IP this will fail to parse as a URL with "parse 127.0.0.1:8500: first
// path segment in URL cannot contain colon". On the other hand we also
// support both http(s)://host:port and unix:///path/to/file.
2019-07-05 15:06:47 +00:00
var agentAddr , agentPort , agentSock string
if grpcAddr := strings . TrimPrefix ( c . grpcAddr , "unix://" ) ; grpcAddr != c . grpcAddr {
// Path to unix socket
agentSock = grpcAddr
} else {
// Parse as host:port with option http prefix
grpcAddr = strings . TrimPrefix ( c . grpcAddr , "http://" )
grpcAddr = strings . TrimPrefix ( c . grpcAddr , "https://" )
var err error
agentAddr , agentPort , err = net . SplitHostPort ( grpcAddr )
if err != nil {
return nil , fmt . Errorf ( "Invalid Consul HTTP address: %s" , err )
}
if agentAddr == "" {
agentAddr = "127.0.0.1"
}
2018-10-03 19:37:53 +00:00
2019-07-05 15:06:47 +00:00
// We use STATIC for agent which means we need to resolve DNS names like
// `localhost` ourselves. We could use STRICT_DNS or LOGICAL_DNS with envoy
// but Envoy resolves `localhost` differently to go on macOS at least which
// causes paper cuts like default dev agent (which binds specifically to
// 127.0.0.1) isn't reachable since Envoy resolves localhost to `[::]` and
// can't connect.
agentIP , err := net . ResolveIPAddr ( "ip" , agentAddr )
if err != nil {
return nil , fmt . Errorf ( "Failed to resolve agent address: %s" , err )
}
agentAddr = agentIP . String ( )
2018-10-03 19:37:53 +00:00
}
adminAddr , adminPort , err := net . SplitHostPort ( c . adminBind )
if err != nil {
2018-10-05 20:08:01 +00:00
return nil , fmt . Errorf ( "Invalid Consul HTTP address: %s" , err )
2018-10-03 19:37:53 +00:00
}
// Envoy requires IP addresses to bind too when using static so resolve DNS or
// localhost here.
adminBindIP , err := net . ResolveIPAddr ( "ip" , adminAddr )
if err != nil {
2018-10-05 20:08:01 +00:00
return nil , fmt . Errorf ( "Failed to resolve admin bind address: %s" , err )
2018-10-03 19:37:53 +00:00
}
2019-04-29 16:27:57 +00:00
// Ideally the cluster should be the service name. We may or may not have that
// yet depending on the arguments used so make a best effort here. In the
// common case, even if the command was invoked with proxy-id and we don't
// know service name yet, we will after we resolve the proxy's config in a bit
// and will update this then.
cluster := c . proxyID
if c . sidecarFor != "" {
cluster = c . sidecarFor
2019-06-18 00:52:01 +00:00
} else if c . meshGateway && c . meshGatewaySvcName != "" {
cluster = c . meshGatewaySvcName
2019-04-29 16:27:57 +00:00
}
2019-06-07 09:26:43 +00:00
adminAccessLogPath := c . adminAccessLogPath
if adminAccessLogPath == "" {
adminAccessLogPath = DefaultAdminAccessLogPath
}
2019-04-29 16:27:57 +00:00
return & BootstrapTplArgs {
ProxyCluster : cluster ,
2018-10-03 19:37:53 +00:00
ProxyID : c . proxyID ,
2019-07-05 15:06:47 +00:00
AgentAddress : agentAddr ,
2018-10-09 09:57:26 +00:00
AgentPort : agentPort ,
2019-07-05 15:06:47 +00:00
AgentSocket : agentSock ,
2018-10-03 19:37:53 +00:00
AgentTLS : useTLS ,
AgentCAFile : httpCfg . TLSConfig . CAFile ,
2019-06-07 09:26:43 +00:00
AdminAccessLogPath : adminAccessLogPath ,
2018-10-03 19:37:53 +00:00
AdminBindAddress : adminBindIP . String ( ) ,
AdminBindPort : adminPort ,
Token : httpCfg . Token ,
LocalAgentClusterName : xds . LocalAgentClusterName ,
2018-10-09 09:57:26 +00:00
} , nil
}
2018-10-03 19:37:53 +00:00
2018-10-09 09:57:26 +00:00
func ( c * cmd ) generateConfig ( ) ( [ ] byte , error ) {
args , err := c . templateArgs ( )
if err != nil {
return nil , err
}
2019-04-29 16:27:57 +00:00
var bsCfg BootstrapConfig
if ! c . disableCentralConfig {
// Fetch any customization from the registration
svc , _ , err := c . client . Agent ( ) . Service ( c . proxyID , nil )
if err != nil {
return nil , fmt . Errorf ( "failed fetch proxy config from local agent: %s" , err )
}
if svc . Proxy == nil {
2019-06-18 00:52:01 +00:00
return nil , errors . New ( "service is not a Connect proxy or mesh gateway" )
2019-04-29 16:27:57 +00:00
}
// Parse the bootstrap config
if err := mapstructure . WeakDecode ( svc . Proxy . Config , & bsCfg ) ; err != nil {
return nil , fmt . Errorf ( "failed parsing Proxy.Config: %s" , err )
}
2019-06-18 00:52:01 +00:00
if svc . Proxy . DestinationServiceName != "" {
// Override cluster now we know the actual service name
args . ProxyCluster = svc . Proxy . DestinationServiceName
}
2018-10-03 19:37:53 +00:00
}
2019-04-29 16:27:57 +00:00
return bsCfg . GenerateJSON ( args )
2018-10-03 19:37:53 +00:00
}
func ( c * cmd ) lookupProxyIDForSidecar ( ) ( string , error ) {
return proxyCmd . LookupProxyIDForSidecar ( c . client , c . sidecarFor )
}
2019-06-18 00:52:01 +00:00
func ( c * cmd ) lookupGatewayProxy ( ) ( * api . AgentService , error ) {
return proxyCmd . LookupGatewayProxy ( c . client )
}
2019-08-01 16:53:34 +00:00
func ( c * cmd ) lookupGRPCPort ( ) ( int , error ) {
self , err := c . client . Agent ( ) . Self ( )
if err != nil {
return 0 , err
}
cfg , ok := self [ "DebugConfig" ]
if ! ok {
return 0 , fmt . Errorf ( "unexpected agent response: no debug config" )
}
port , ok := cfg [ "GRPCPort" ]
if ! ok {
return 0 , fmt . Errorf ( "agent does not have grpc port enabled" )
}
portN , ok := port . ( float64 )
if ! ok {
return 0 , fmt . Errorf ( "invalid grpc port in agent response" )
}
return int ( portN ) , nil
}
2018-10-03 19:37:53 +00:00
func ( c * cmd ) Synopsis ( ) string {
return synopsis
}
func ( c * cmd ) Help ( ) string {
return c . help
}
const synopsis = "Runs or Configures Envoy as a Connect proxy"
const help = `
Usage : consul connect envoy [ options ]
Generates the bootstrap configuration needed to start an Envoy proxy instance
for use as a Connect sidecar for a particular service instance . By default it
will generate the config and then exec Envoy directly until it exits normally .
It will search $ PATH for the envoy binary but this can be overridden with
- envoy - binary .
2018-10-05 20:08:01 +00:00
It can instead only generate the bootstrap . json based on the current ENV and
2018-10-03 19:37:53 +00:00
arguments using - bootstrap .
The proxy requires service : write permissions for the service it represents .
The token may be passed via the CLI or the CONSUL_TOKEN environment
variable .
The example below shows how to start a local proxy as a sidecar to a "web"
service instance . It assumes that the proxy was already registered with it ' s
Config for example via a sidecar_service block .
$ consul connect envoy - sidecar - for web
`