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"
"strings"
2020-03-26 14:19:21 +00:00
"github.com/mitchellh/cli"
2019-04-29 16:27:57 +00:00
"github.com/mitchellh/mapstructure"
2020-01-17 14:54:17 +00:00
"github.com/hashicorp/consul/agent/structs"
2018-10-03 19:37:53 +00:00
"github.com/hashicorp/consul/agent/xds"
2020-07-31 20:52:49 +00:00
"github.com/hashicorp/consul/agent/xds/proxysupport"
2018-10-03 19:37:53 +00:00
"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"
2020-09-08 10:16:16 +00:00
"github.com/hashicorp/consul/tlsutil"
2018-10-03 19:37:53 +00:00
)
func New ( ui cli . Ui ) * cmd {
2021-04-07 19:22:52 +00:00
c := & cmd { UI : ui }
2018-10-03 19:37:53 +00:00
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
2021-03-04 22:15:47 +00:00
meshGateway bool
gateway string
proxyID string
2022-06-06 16:23:08 +00:00
nodeName string
2021-03-04 22:15:47 +00:00
sidecarFor string
adminAccessLogPath string
adminBind string
envoyBin string
bootstrap bool
disableCentralConfig bool
grpcAddr string
2022-11-18 20:36:20 +00:00
grpcCAFile string
grpcCAPath string
2021-03-04 22:15:47 +00:00
envoyVersion string
prometheusBackendPort string
prometheusScrapePath string
2022-06-17 00:18:37 +00:00
prometheusCAFile string
prometheusCAPath string
prometheusCertFile string
prometheusKeyFile string
2019-06-18 00:52:01 +00:00
// mesh gateway registration information
register bool
2020-03-26 14:19:21 +00:00
lanAddress ServiceAddressValue
wanAddress ServiceAddressValue
2019-06-18 00:52:01 +00:00
deregAfterCritical string
2020-03-26 14:19:21 +00:00
bindAddresses ServiceAddressMapValue
2020-03-09 20:59:02 +00:00
exposeServers bool
2020-11-16 23:37:19 +00:00
omitDeprecatedTags bool
2019-06-18 00:52:01 +00:00
2020-03-26 16:20:56 +00:00
gatewaySvcName string
gatewayKind api . ServiceKind
2018-10-03 19:37:53 +00:00
}
2020-07-31 20:52:49 +00:00
const meshGatewayVal = "mesh"
var defaultEnvoyVersion = proxysupport . EnvoyVersions [ 0 ]
2020-03-26 16:20:56 +00:00
var supportedGateways = map [ string ] api . ServiceKind {
"mesh" : api . ServiceKindMeshGateway ,
"terminating" : api . ServiceKindTerminatingGateway ,
2020-04-14 14:48:02 +00:00
"ingress" : api . ServiceKindIngressGateway ,
2020-03-26 16:20:56 +00:00
}
2020-03-23 20:38:14 +00:00
2018-10-03 19:37:53 +00:00
func ( c * cmd ) init ( ) {
c . flags = flag . NewFlagSet ( "" , flag . ContinueOnError )
2020-03-23 14:49:50 +00:00
c . flags . StringVar ( & c . proxyID , "proxy-id" , os . Getenv ( "CONNECT_PROXY_ID" ) ,
2018-10-03 19:37:53 +00:00
"The proxy's ID on the local agent." )
2022-06-06 16:23:08 +00:00
c . flags . StringVar ( & c . nodeName , "node-name" , "" ,
"[Experimental] The node name where the proxy service is registered. It requires proxy-id to be specified. " )
2020-03-26 16:20:56 +00:00
// Deprecated in favor of `gateway`
2019-06-18 00:52:01 +00:00
c . flags . BoolVar ( & c . meshGateway , "mesh-gateway" , false ,
2020-02-11 20:48:58 +00:00
"Configure Envoy as a Mesh Gateway." )
2019-06-18 00:52:01 +00:00
2020-03-26 16:20:56 +00:00
c . flags . StringVar ( & c . gateway , "gateway" , "" ,
2020-05-13 17:56:53 +00:00
"The type of gateway to register. One of: terminating, ingress, or mesh" )
2020-03-26 16:20:56 +00:00
2020-03-23 14:49:50 +00:00
c . flags . StringVar ( & c . sidecarFor , "sidecar-for" , os . Getenv ( "CONNECT_SIDECAR_FOR" ) ,
2018-10-03 19:37:53 +00:00
"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." )
2020-03-23 14:49:50 +00:00
c . flags . StringVar ( & c . grpcAddr , "grpc-addr" , os . Getenv ( api . GRPCAddrEnvName ) ,
2018-10-03 19:37:53 +00:00
"Set the agent's gRPC address and port (in http(s)://host:port format). " +
"Alternatively, you can specify CONSUL_GRPC_ADDR in ENV." )
2022-11-18 20:36:20 +00:00
c . flags . StringVar ( & c . grpcCAFile , "grpc-ca-file" , os . Getenv ( api . GRPCCAFileEnvName ) ,
"Path to a CA file to use for TLS when communicating with the Consul agent through xDS. This " +
"can also be specified via the CONSUL_GRPC_CACERT environment variable." )
c . flags . StringVar ( & c . grpcCAPath , "grpc-ca-path" , os . Getenv ( api . GRPCCAPathEnvName ) ,
"Path to a directory of CA certificates to use for TLS when communicating " +
"with the Consul agent through xDS. This can also be specified via the " +
"CONSUL_GRPC_CAPATH environment variable." )
2021-10-12 02:18:56 +00:00
// Deprecated, no longer needed, keeping it around to not break back compat
2020-03-23 20:38:14 +00:00
c . flags . StringVar ( & c . envoyVersion , "envoy-version" , defaultEnvoyVersion ,
2021-10-12 02:18:56 +00:00
"This is a legacy flag that is currently not used but was formerly used to set the " +
"version for the envoy binary that gets invoked by Consul. This is no longer " +
"necessary as Consul will invoke the binary at a path set by -envoy-binary " +
"or whichever envoy binary it finds in $PATH" )
2020-02-10 19:53:04 +00:00
2019-06-18 00:52:01 +00:00
c . flags . BoolVar ( & c . register , "register" , false ,
2020-04-02 18:52:11 +00:00
"Register a new gateway service before configuring and starting Envoy" )
2019-06-18 00:52:01 +00:00
2020-03-26 14:19:21 +00:00
c . flags . Var ( & c . lanAddress , "address" ,
2020-04-02 18:52:11 +00:00
"LAN address to advertise in the gateway service registration" )
2019-06-18 00:52:01 +00:00
2020-03-26 14:19:21 +00:00
c . flags . Var ( & c . wanAddress , "wan-address" ,
2020-05-21 14:08:12 +00:00
"WAN address to advertise in the gateway service registration. For ingress gateways, " +
"only an IP address (without a port) is required." )
2019-06-18 00:52:01 +00:00
2020-03-26 14:19:21 +00:00
c . flags . Var ( & c . bindAddresses , "bind-address" , "Bind " +
2019-07-12 16:57:31 +00:00
"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." )
2020-03-26 16:20:56 +00:00
c . flags . StringVar ( & c . gatewaySvcName , "service" , "" ,
2019-06-18 00:52:01 +00:00
"Service name to use for the registration" )
2020-03-09 20:59:02 +00:00
c . flags . BoolVar ( & c . exposeServers , "expose-servers" , false ,
"Expose the servers for WAN federation via this mesh gateway" )
2019-06-18 00:52:01 +00:00
c . flags . StringVar ( & c . deregAfterCritical , "deregister-after-critical" , "6h" ,
"The amount of time the gateway services health check can be failing before being deregistered" )
2020-11-16 23:37:19 +00:00
c . flags . BoolVar ( & c . omitDeprecatedTags , "omit-deprecated-tags" , false ,
"In Consul 1.9.0 the format of metric tags for Envoy clusters was updated from consul.[service|dc|...] to " +
"consul.destination.[service|dc|...]. The old tags were preserved for backward compatibility," +
"but can be disabled with this flag." )
2021-03-04 22:15:47 +00:00
c . flags . StringVar ( & c . prometheusBackendPort , "prometheus-backend-port" , "" ,
"Sets the backend port for the 'prometheus_backend' cluster that envoy_prometheus_bind_addr will point to. " +
"Without this flag, envoy_prometheus_bind_addr would point to the 'self_admin' cluster where Envoy metrics are exposed. " +
"The metrics merging feature in consul-k8s uses this to point to the merged metrics endpoint combining Envoy and service metrics. " +
"Only applicable when envoy_prometheus_bind_addr is set in proxy config." )
c . flags . StringVar ( & c . prometheusScrapePath , "prometheus-scrape-path" , "/metrics" ,
"Sets the path where Envoy will expose metrics on envoy_prometheus_bind_addr listener. " +
"For example, if envoy_prometheus_bind_addr is 0.0.0.0:20200, and this flag is " +
"set to /scrape-metrics, prometheus metrics would be scrapeable at " +
"0.0.0.0:20200/scrape-metrics. " +
"Only applicable when envoy_prometheus_bind_addr is set in proxy config." )
2022-06-17 00:18:37 +00:00
c . flags . StringVar ( & c . prometheusCAFile , "prometheus-ca-file" , "" ,
"Path to a CA file for Envoy to use when serving TLS on the Prometheus metrics endpoint. " +
"Only applicable when envoy_prometheus_bind_addr is set in proxy config." )
c . flags . StringVar ( & c . prometheusCAPath , "prometheus-ca-path" , "" ,
"Path to a directory of CA certificates for Envoy to use when serving the Prometheus metrics endpoint. " +
"Only applicable when envoy_prometheus_bind_addr is set in proxy config." )
c . flags . StringVar ( & c . prometheusCertFile , "prometheus-cert-file" , "" ,
"Path to a certificate file for Envoy to use when serving TLS on the Prometheus metrics endpoint. " +
"Only applicable when envoy_prometheus_bind_addr is set in proxy config." )
c . flags . StringVar ( & c . prometheusKeyFile , "prometheus-key-file" , "" ,
"Path to a private key file for Envoy to use when serving TLS on the Prometheus metrics endpoint. " +
"Only applicable when envoy_prometheus_bind_addr is set in proxy config." )
2018-10-03 19:37:53 +00:00
c . http = & flags . HTTPFlags { }
flags . Merge ( c . flags , c . http . ClientFlags ( ) )
2021-07-21 19:45:24 +00:00
flags . Merge ( c . flags , c . http . MultiTenancyFlags ( ) )
2018-10-03 19:37:53 +00:00
c . help = flags . Usage ( help , c . flags )
}
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
}
2020-03-26 14:19:21 +00:00
func canBind ( addr api . ServiceAddress ) bool {
2019-07-30 13:56:56 +00:00
ifAddrs , err := net . InterfaceAddrs ( )
if err != nil {
return false
}
2020-03-26 14:19:21 +00:00
return canBindInternal ( addr . Address , ifAddrs )
2019-07-30 13:56:56 +00:00
}
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
}
// Setup Consul client
2020-04-07 22:02:56 +00:00
var err error
c . client , err = c . http . APIClient ( )
2018-10-03 19:37:53 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error connecting to Consul agent: %s" , err ) )
return 1
}
2020-04-07 22:02:56 +00:00
// TODO: refactor
return c . run ( c . flags . Args ( ) )
}
2018-10-03 19:37:53 +00:00
2020-04-07 22:02:56 +00:00
func ( c * cmd ) run ( args [ ] string ) int {
2022-06-06 16:23:08 +00:00
if c . nodeName != "" && c . proxyID == "" {
c . UI . Error ( "'-node-name' requires '-proxy-id'" )
return 1
}
2020-03-26 16:20:56 +00:00
// Fixup for deprecated mesh-gateway flag
if c . meshGateway && c . gateway != "" {
c . UI . Error ( "The mesh-gateway flag is deprecated and cannot be used alongside the gateway flag" )
return 1
}
2020-04-14 14:48:02 +00:00
2020-03-26 16:20:56 +00:00
if c . meshGateway {
c . gateway = meshGatewayVal
}
2020-03-09 20:59:02 +00:00
if c . exposeServers {
2020-03-26 16:20:56 +00:00
if c . gateway != meshGatewayVal {
2020-03-09 20:59:02 +00:00
c . UI . Error ( "'-expose-servers' can only be used for mesh gateways" )
return 1
}
if ! c . register {
c . UI . Error ( "'-expose-servers' requires '-register'" )
return 1
}
}
2020-04-02 18:52:11 +00:00
// Gateway kind is set so that it is available even if not auto-registering the gateway
if c . gateway != "" {
2020-03-26 16:20:56 +00:00
kind , ok := supportedGateways [ c . gateway ]
if ! ok {
2020-04-14 14:48:02 +00:00
c . UI . Error ( "Gateway must be one of: terminating, mesh, or ingress" )
2019-06-18 00:52:01 +00:00
return 1
}
2020-03-26 16:20:56 +00:00
c . gatewayKind = kind
2020-05-13 17:56:53 +00:00
if c . gatewaySvcName == "" {
c . gatewaySvcName = string ( c . gatewayKind )
}
}
if c . proxyID == "" {
switch {
case c . sidecarFor != "" :
proxyID , err := proxyCmd . LookupProxyIDForSidecar ( c . client , c . sidecarFor )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
c . proxyID = proxyID
case c . gateway != "" && ! c . register :
gatewaySvc , err := proxyCmd . LookupGatewayProxy ( c . client , c . gatewayKind )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
c . proxyID = gatewaySvc . ID
c . gatewaySvcName = gatewaySvc . Service
case c . gateway != "" && c . register :
c . proxyID = c . gatewaySvcName
}
}
if c . proxyID == "" {
c . UI . Error ( "No proxy ID specified. One of -proxy-id, -sidecar-for, or -gateway is " +
"required" )
return 1
2020-04-02 18:52:11 +00:00
}
2022-06-17 00:18:37 +00:00
// If any of CA/Cert/Key are specified, make sure they are all present.
if c . prometheusKeyFile != "" || c . prometheusCertFile != "" || ( c . prometheusCAFile != "" || c . prometheusCAPath != "" ) {
if c . prometheusKeyFile == "" || c . prometheusCertFile == "" || ( c . prometheusCAFile == "" && c . prometheusCAPath == "" ) {
c . UI . Error ( "Must provide a CA (-prometheus-ca-file or -prometheus-ca-path) as well as " +
"-prometheus-cert-file and -prometheus-key-file to enable TLS for prometheus metrics" )
return 1
}
}
2020-04-02 18:52:11 +00:00
if c . register {
2022-06-06 16:23:08 +00:00
if c . nodeName != "" {
c . UI . Error ( "'-register' cannot be used with '-node-name'" )
return 1
}
2020-04-02 18:52:11 +00:00
if c . gateway == "" {
c . UI . Error ( "Auto-Registration can only be used for gateways" )
return 1
}
2020-03-26 16:20:56 +00:00
2019-06-18 00:52:01 +00:00
taggedAddrs := make ( map [ string ] api . ServiceAddress )
2020-03-26 14:19:21 +00:00
lanAddr := c . lanAddress . Value ( )
if lanAddr . Address != "" {
taggedAddrs [ structs . TaggedAddressLAN ] = lanAddr
2019-06-18 00:52:01 +00:00
}
2020-03-26 14:19:21 +00:00
wanAddr := c . wanAddress . Value ( )
if wanAddr . Address != "" {
taggedAddrs [ structs . TaggedAddressWAN ] = wanAddr
2019-06-18 00:52:01 +00:00
}
2020-03-26 14:19:21 +00:00
tcpCheckAddr := lanAddr . Address
2019-06-18 00:52:01 +00:00
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
2020-03-26 14:19:21 +00:00
if len ( c . bindAddresses . value ) > 0 {
2019-07-12 16:57:31 +00:00
// override all default binding rules and just bind to the user-supplied addresses
proxyConf = & api . AgentServiceConnectProxyConfig {
Config : map [ string ] interface { } {
2020-03-26 16:20:56 +00:00
"envoy_gateway_no_default_bind" : true ,
"envoy_gateway_bind_addresses" : c . bindAddresses . value ,
2019-07-12 16:57:31 +00:00
} ,
}
} 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 { } {
2020-03-26 16:20:56 +00:00
"envoy_gateway_no_default_bind" : true ,
"envoy_gateway_bind_tagged_addresses" : true ,
2019-06-18 00:52:01 +00:00
} ,
}
2020-03-26 14:19:21 +00:00
} else if ! canBind ( lanAddr ) && lanAddr . Address != "" {
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 . Address ) )
2019-07-12 16:57:31 +00:00
return 1
2019-06-18 00:52:01 +00:00
}
2020-03-09 20:59:02 +00:00
var meta map [ string ] string
if c . exposeServers {
meta = map [ string ] string { structs . MetaWANFederationKey : "1" }
}
2019-06-18 00:52:01 +00:00
svc := api . AgentServiceRegistration {
2020-03-26 16:20:56 +00:00
Kind : c . gatewayKind ,
Name : c . gatewaySvcName ,
2020-05-13 17:56:53 +00:00
ID : c . proxyID ,
2020-03-26 14:19:21 +00:00
Address : lanAddr . Address ,
Port : lanAddr . Port ,
2020-03-09 20:59:02 +00:00
Meta : meta ,
2019-06-18 00:52:01 +00:00
TaggedAddresses : taggedAddrs ,
Proxy : proxyConf ,
Check : & api . AgentServiceCheck {
2020-03-26 16:20:56 +00:00
Name : fmt . Sprintf ( "%s listening" , c . gatewayKind ) ,
2020-03-26 14:19:21 +00:00
TCP : ipaddr . FormatAddressPort ( tcpCheckAddr , lanAddr . Port ) ,
2019-06-18 00:52:01 +00:00
Interval : "10s" ,
DeregisterCriticalServiceAfter : c . deregAfterCritical ,
} ,
}
2020-04-07 22:02:56 +00:00
if err := c . client . Agent ( ) . ServiceRegister ( & svc ) ; err != nil {
2019-06-18 00:52:01 +00:00
c . UI . Error ( fmt . Sprintf ( "Error registering service %q: %s" , svc . Name , err ) )
return 1
}
2021-04-07 19:22:52 +00:00
if ! c . bootstrap {
// We need stdout to be reserved exclusively for the JSON blob, so
// we omit logging this to Info which also writes to stdout.
c . UI . Info ( fmt . Sprintf ( "Registered service: %s" , svc . Name ) )
}
2019-06-18 00:52:01 +00:00
}
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
2021-04-07 19:22:52 +00:00
c . UI . Output ( string ( 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
}
2020-04-07 22:02:56 +00:00
err = execEnvoy ( binary , nil , args , bootstrapJson )
2018-10-05 20:08:01 +00:00
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 )
2020-04-07 22:16:34 +00:00
// api.NewClient normalizes some values (Token, Scheme) on the Config.
2019-04-30 14:59:00 +00:00
if _ , err := api . NewClient ( httpCfg ) ; err != nil {
return nil , err
}
2022-11-18 20:36:20 +00:00
xdsAddr , err := c . xdsAddress ( )
2020-04-07 20:33:22 +00:00
if err != nil {
return nil , err
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
2020-11-16 23:37:19 +00:00
proxySourceService := ""
2019-04-29 16:27:57 +00:00
if c . sidecarFor != "" {
cluster = c . sidecarFor
2020-11-16 23:37:19 +00:00
proxySourceService = c . sidecarFor
2020-03-26 16:20:56 +00:00
} else if c . gateway != "" && c . gatewaySvcName != "" {
cluster = c . gatewaySvcName
2020-11-16 23:37:19 +00:00
proxySourceService = c . gatewaySvcName
2019-04-29 16:27:57 +00:00
}
2019-06-07 09:26:43 +00:00
adminAccessLogPath := c . adminAccessLogPath
if adminAccessLogPath == "" {
adminAccessLogPath = DefaultAdminAccessLogPath
}
2022-11-18 20:36:20 +00:00
// Fallback to the old certificate configuration, if none was defined.
if xdsAddr . AgentTLS && c . grpcCAFile == "" {
c . grpcCAFile = httpCfg . TLSConfig . CAFile
}
if xdsAddr . AgentTLS && c . grpcCAPath == "" {
c . grpcCAPath = httpCfg . TLSConfig . CAPath
}
2020-01-10 14:57:54 +00:00
var caPEM string
2022-11-18 20:36:20 +00:00
pems , err := tlsutil . LoadCAs ( c . grpcCAFile , c . grpcCAPath )
2020-09-08 10:16:16 +00:00
if err != nil {
return nil , err
2019-12-13 16:44:48 +00:00
}
2020-09-08 10:16:16 +00:00
caPEM = strings . Replace ( strings . Join ( pems , "" ) , "\n" , "\\n" , - 1 )
2019-12-13 16:44:48 +00:00
2019-04-29 16:27:57 +00:00
return & BootstrapTplArgs {
2021-07-09 19:17:29 +00:00
GRPC : xdsAddr ,
2019-04-29 16:27:57 +00:00
ProxyCluster : cluster ,
2018-10-03 19:37:53 +00:00
ProxyID : c . proxyID ,
2022-06-06 16:23:08 +00:00
NodeName : c . nodeName ,
2020-11-16 23:37:19 +00:00
ProxySourceService : proxySourceService ,
2019-12-13 16:44:48 +00:00
AgentCAPEM : caPEM ,
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 ,
2020-01-24 15:04:58 +00:00
Namespace : httpCfg . Namespace ,
2021-09-15 01:37:11 +00:00
Partition : httpCfg . Partition ,
2020-11-19 21:27:31 +00:00
Datacenter : httpCfg . Datacenter ,
2021-03-04 22:15:47 +00:00
PrometheusBackendPort : c . prometheusBackendPort ,
PrometheusScrapePath : c . prometheusScrapePath ,
2022-06-17 00:18:37 +00:00
PrometheusCAFile : c . prometheusCAFile ,
PrometheusCAPath : c . prometheusCAPath ,
PrometheusCertFile : c . prometheusCertFile ,
PrometheusKeyFile : c . prometheusKeyFile ,
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
2020-11-16 23:37:19 +00:00
// Fetch any customization from the registration
2022-06-06 16:23:08 +00:00
var svcProxyConfig * api . AgentServiceConnectProxyConfig
var serviceName , ns , partition , datacenter string
if c . nodeName == "" {
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 )
}
svcProxyConfig = svc . Proxy
serviceName = svc . Service
ns = svc . Namespace
partition = svc . Partition
datacenter = svc . Datacenter
} else {
filter := fmt . Sprintf ( "ID == %q" , c . proxyID )
2022-06-15 15:30:31 +00:00
svcList , _ , err := c . client . Catalog ( ) . NodeServiceList ( c . nodeName ,
& api . QueryOptions { Filter : filter , MergeCentralConfig : true } )
2022-06-06 16:23:08 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to fetch proxy config from catalog for node %q: %w" , c . nodeName , err )
}
2022-06-15 15:30:31 +00:00
if len ( svcList . Services ) == 0 {
return nil , fmt . Errorf ( "Proxy service with ID %q not found" , c . proxyID )
2022-06-06 16:23:08 +00:00
}
2022-06-15 15:30:31 +00:00
if len ( svcList . Services ) > 1 {
return nil , fmt . Errorf ( "Expected to find only one proxy service with ID %q, but more were found" , c . proxyID )
}
2022-06-06 16:23:08 +00:00
svcProxyConfig = svcList . Services [ 0 ] . Proxy
serviceName = svcList . Services [ 0 ] . Service
ns = svcList . Services [ 0 ] . Namespace
partition = svcList . Services [ 0 ] . Partition
datacenter = svcList . Node . Datacenter
c . gatewayKind = svcList . Services [ 0 ] . Kind
}
if svcProxyConfig == nil {
2020-11-16 23:37:19 +00:00
return nil , errors . New ( "service is not a Connect proxy or gateway" )
}
2019-04-29 16:27:57 +00:00
2022-06-06 16:23:08 +00:00
if svcProxyConfig . DestinationServiceName != "" {
2020-11-16 23:37:19 +00:00
// Override cluster now we know the actual service name
2022-06-06 16:23:08 +00:00
args . ProxyCluster = svcProxyConfig . DestinationServiceName
args . ProxySourceService = svcProxyConfig . DestinationServiceName
2020-11-16 23:37:19 +00:00
} else {
// Set the source service name from the proxy's own registration
2022-06-06 16:23:08 +00:00
args . ProxySourceService = serviceName
2020-11-16 23:37:19 +00:00
}
2020-11-19 21:27:31 +00:00
2021-09-15 21:17:11 +00:00
// In most cases where namespaces and partitions are enabled they will already be set
// correctly because the http client that fetched this will provide them explicitly.
// However, if these arguments were not provided, they will be empty even
// though Namespaces and Partitions are actually being used.
// Overriding them ensures that we always set the Namespace and Partition args
// if the cluster is using them. This prevents us from defaulting to the "default"
// when a non-default partition or namespace was inferred from the ACL token.
2022-06-06 16:23:08 +00:00
if ns != "" {
args . Namespace = ns
2021-09-15 21:17:11 +00:00
}
2022-06-06 16:23:08 +00:00
if partition != "" {
args . Partition = partition
2021-09-15 21:17:11 +00:00
}
2022-06-06 16:23:08 +00:00
if datacenter != "" {
2020-11-19 21:27:31 +00:00
// The agent will definitely have the definitive answer here.
2022-06-06 16:23:08 +00:00
args . Datacenter = datacenter
}
// Setup ready listener for ingress gateway to pass healthcheck
if c . gatewayKind == api . ServiceKindIngressGateway {
lanAddr := c . lanAddress . String ( )
// Deal with possibility of address not being specified and defaulting to
// ":443"
if strings . HasPrefix ( lanAddr , ":" ) {
lanAddr = "127.0.0.1" + lanAddr
}
bsCfg . ReadyBindAddr = lanAddr
2020-11-16 23:37:19 +00:00
}
2019-04-29 16:27:57 +00:00
2020-11-16 23:37:19 +00:00
if ! c . disableCentralConfig {
2019-04-29 16:27:57 +00:00
// Parse the bootstrap config
2022-06-06 16:23:08 +00:00
if err := mapstructure . WeakDecode ( svcProxyConfig . Config , & bsCfg ) ; err != nil {
2019-04-29 16:27:57 +00:00
return nil , fmt . Errorf ( "failed parsing Proxy.Config: %s" , err )
}
2018-10-03 19:37:53 +00:00
}
2019-04-29 16:27:57 +00:00
2020-11-16 23:37:19 +00:00
return bsCfg . GenerateJSON ( args , c . omitDeprecatedTags )
2018-10-03 19:37:53 +00:00
}
2020-04-07 20:33:22 +00:00
// TODO: make method a function
2022-11-18 20:36:20 +00:00
func ( c * cmd ) xdsAddress ( ) ( GRPC , error ) {
2020-04-07 20:33:22 +00:00
g := GRPC { }
addr := c . grpcAddr
if addr == "" {
2022-08-19 17:07:22 +00:00
port , protocol , err := c . lookupXDSPort ( )
2020-04-07 20:33:22 +00:00
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
}
2022-08-19 17:07:22 +00:00
addr = fmt . Sprintf ( "%vlocalhost:%v" , protocol , port )
2020-04-07 20:33:22 +00:00
}
2020-04-07 22:16:34 +00:00
// TODO: parse addr as a url instead of strings.HasPrefix/TrimPrefix
2022-11-18 20:36:20 +00:00
if strings . HasPrefix ( strings . ToLower ( addr ) , "https://" ) {
2020-04-07 20:33:22 +00:00
g . AgentTLS = 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.
if grpcAddr := strings . TrimPrefix ( addr , "unix://" ) ; grpcAddr != addr {
// Path to unix socket
g . AgentSocket = grpcAddr
} else {
// Parse as host:port with option http prefix
grpcAddr = strings . TrimPrefix ( addr , "http://" )
2020-05-14 18:41:20 +00:00
grpcAddr = strings . TrimPrefix ( grpcAddr , "https://" )
2020-04-07 20:33:22 +00:00
var err error
2020-04-07 22:16:34 +00:00
var host string
host , g . AgentPort , err = net . SplitHostPort ( grpcAddr )
2020-04-07 20:33:22 +00:00
if err != nil {
return g , fmt . Errorf ( "Invalid Consul HTTP address: %s" , err )
}
// 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.
2020-04-07 22:16:34 +00:00
agentIP , err := net . ResolveIPAddr ( "ip" , host )
2020-04-07 20:33:22 +00:00
if err != nil {
return g , fmt . Errorf ( "Failed to resolve agent address: %s" , err )
}
g . AgentAddress = agentIP . String ( )
}
return g , nil
}
2022-08-19 17:07:22 +00:00
func ( c * cmd ) lookupXDSPort ( ) ( int , string , error ) {
2019-08-01 16:53:34 +00:00
self , err := c . client . Agent ( ) . Self ( )
if err != nil {
2022-08-19 17:07:22 +00:00
return 0 , "" , err
2019-08-01 16:53:34 +00:00
}
2021-07-09 19:17:29 +00:00
type response struct {
XDS struct {
2022-09-01 17:32:11 +00:00
Ports struct {
Plaintext int
TLS int
}
2021-07-09 19:17:29 +00:00
}
}
var resp response
2022-09-01 17:32:11 +00:00
if err := mapstructure . Decode ( self , & resp ) ; err == nil {
if resp . XDS . Ports . TLS > 0 {
return resp . XDS . Ports . TLS , "https://" , nil
}
if resp . XDS . Ports . Plaintext > 0 {
return resp . XDS . Ports . Plaintext , "http://" , nil
2022-08-19 17:07:22 +00:00
}
2021-07-09 19:17:29 +00:00
}
// Fallback to old API for the case where a new consul CLI is being used with
// an older API version.
2019-08-01 16:53:34 +00:00
cfg , ok := self [ "DebugConfig" ]
if ! ok {
2022-08-19 17:07:22 +00:00
return 0 , "" , fmt . Errorf ( "unexpected agent response: no debug config" )
2019-08-01 16:53:34 +00:00
}
2022-08-19 17:07:22 +00:00
// TODO what does this mean? What did the old API look like? How does this affect compatibility?
2019-08-01 16:53:34 +00:00
port , ok := cfg [ "GRPCPort" ]
if ! ok {
2022-08-19 17:07:22 +00:00
return 0 , "" , fmt . Errorf ( "agent does not have grpc port enabled" )
2019-08-01 16:53:34 +00:00
}
portN , ok := port . ( float64 )
if ! ok {
2022-08-19 17:07:22 +00:00
return 0 , "" , fmt . Errorf ( "invalid grpc port in agent response" )
2019-08-01 16:53:34 +00:00
}
2022-08-19 17:07:22 +00:00
return int ( portN ) , "" , nil
2019-08-01 16:53:34 +00:00
}
2018-10-03 19:37:53 +00:00
func ( c * cmd ) Synopsis ( ) string {
return synopsis
}
func ( c * cmd ) Help ( ) string {
return c . help
}
2021-07-21 19:45:24 +00:00
const (
synopsis = "Runs or Configures Envoy as a Connect proxy"
help = `
2021-07-21 17:43:10 +00:00
Usage : consul connect envoy [ options ] [ -- pass - through options ]
2018-10-03 19:37:53 +00:00
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 .
2019-12-04 20:01:03 +00:00
The token may be passed via the CLI or the CONSUL_HTTP_TOKEN environment
2018-10-03 19:37:53 +00:00
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
2021-07-21 17:43:10 +00:00
Additional arguments may be passed directly to Envoy by specifying a double
dash followed by a list of options .
$ consul connect envoy - sidecar - for web -- -- log - level debug
2018-10-03 19:37:53 +00:00
`
2021-07-21 19:45:24 +00:00
)