1package envoy 2 3import ( 4 "errors" 5 "flag" 6 "fmt" 7 "net" 8 "os" 9 "os/exec" 10 "strings" 11 12 "github.com/mitchellh/cli" 13 "github.com/mitchellh/mapstructure" 14 15 "github.com/hashicorp/consul/agent/structs" 16 "github.com/hashicorp/consul/agent/xds" 17 "github.com/hashicorp/consul/agent/xds/proxysupport" 18 "github.com/hashicorp/consul/api" 19 proxyCmd "github.com/hashicorp/consul/command/connect/proxy" 20 "github.com/hashicorp/consul/command/flags" 21 "github.com/hashicorp/consul/ipaddr" 22 "github.com/hashicorp/consul/tlsutil" 23) 24 25func New(ui cli.Ui) *cmd { 26 c := &cmd{UI: ui} 27 c.init() 28 return c 29} 30 31const DefaultAdminAccessLogPath = "/dev/null" 32 33type cmd struct { 34 UI cli.Ui 35 flags *flag.FlagSet 36 http *flags.HTTPFlags 37 help string 38 client *api.Client 39 40 // flags 41 meshGateway bool 42 gateway string 43 proxyID string 44 sidecarFor string 45 adminAccessLogPath string 46 adminBind string 47 envoyBin string 48 bootstrap bool 49 disableCentralConfig bool 50 grpcAddr string 51 envoyVersion string 52 prometheusBackendPort string 53 prometheusScrapePath string 54 55 // mesh gateway registration information 56 register bool 57 lanAddress ServiceAddressValue 58 wanAddress ServiceAddressValue 59 deregAfterCritical string 60 bindAddresses ServiceAddressMapValue 61 exposeServers bool 62 omitDeprecatedTags bool 63 64 gatewaySvcName string 65 gatewayKind api.ServiceKind 66} 67 68const meshGatewayVal = "mesh" 69 70var defaultEnvoyVersion = proxysupport.EnvoyVersions[0] 71 72var supportedGateways = map[string]api.ServiceKind{ 73 "mesh": api.ServiceKindMeshGateway, 74 "terminating": api.ServiceKindTerminatingGateway, 75 "ingress": api.ServiceKindIngressGateway, 76} 77 78func (c *cmd) init() { 79 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 80 81 c.flags.StringVar(&c.proxyID, "proxy-id", os.Getenv("CONNECT_PROXY_ID"), 82 "The proxy's ID on the local agent.") 83 84 // Deprecated in favor of `gateway` 85 c.flags.BoolVar(&c.meshGateway, "mesh-gateway", false, 86 "Configure Envoy as a Mesh Gateway.") 87 88 c.flags.StringVar(&c.gateway, "gateway", "", 89 "The type of gateway to register. One of: terminating, ingress, or mesh") 90 91 c.flags.StringVar(&c.sidecarFor, "sidecar-for", os.Getenv("CONNECT_SIDECAR_FOR"), 92 "The ID of a service instance on the local agent that this proxy should "+ 93 "become a sidecar for. It requires that the proxy service is registered "+ 94 "with the agent as a connect-proxy with Proxy.DestinationServiceID set "+ 95 "to this value. If more than one such proxy is registered it will fail.") 96 97 c.flags.StringVar(&c.envoyBin, "envoy-binary", "", 98 "The full path to the envoy binary to run. By default will just search "+ 99 "$PATH. Ignored if -bootstrap is used.") 100 101 c.flags.StringVar(&c.adminAccessLogPath, "admin-access-log-path", DefaultAdminAccessLogPath, 102 fmt.Sprintf("The path to write the access log for the administration server. If no access "+ 103 "log is desired specify %q. By default it will use %q.", 104 DefaultAdminAccessLogPath, DefaultAdminAccessLogPath)) 105 106 c.flags.StringVar(&c.adminBind, "admin-bind", "localhost:19000", 107 "The address:port to start envoy's admin server on. Envoy requires this "+ 108 "but care must be taken to ensure it's not exposed to an untrusted network "+ 109 "as it has full control over the secrets and config of the proxy.") 110 111 c.flags.BoolVar(&c.bootstrap, "bootstrap", false, 112 "Generate the bootstrap.json but don't exec envoy") 113 114 c.flags.BoolVar(&c.disableCentralConfig, "no-central-config", false, 115 "By default the proxy's bootstrap configuration can be customized "+ 116 "centrally. This requires that the command run on the same agent as the "+ 117 "proxy will and that the agent is reachable when the command is run. In "+ 118 "cases where either assumption is violated this flag will prevent the "+ 119 "command attempting to resolve config from the local agent.") 120 121 c.flags.StringVar(&c.grpcAddr, "grpc-addr", os.Getenv(api.GRPCAddrEnvName), 122 "Set the agent's gRPC address and port (in http(s)://host:port format). "+ 123 "Alternatively, you can specify CONSUL_GRPC_ADDR in ENV.") 124 125 c.flags.StringVar(&c.envoyVersion, "envoy-version", defaultEnvoyVersion, 126 "Sets the envoy-version that the envoy binary has.") 127 128 c.flags.BoolVar(&c.register, "register", false, 129 "Register a new gateway service before configuring and starting Envoy") 130 131 c.flags.Var(&c.lanAddress, "address", 132 "LAN address to advertise in the gateway service registration") 133 134 c.flags.Var(&c.wanAddress, "wan-address", 135 "WAN address to advertise in the gateway service registration. For ingress gateways, "+ 136 "only an IP address (without a port) is required.") 137 138 c.flags.Var(&c.bindAddresses, "bind-address", "Bind "+ 139 "address to use instead of the default binding rules given as `<name>=<ip>:<port>` "+ 140 "pairs. This flag may be specified multiple times to add multiple bind addresses.") 141 142 c.flags.StringVar(&c.gatewaySvcName, "service", "", 143 "Service name to use for the registration") 144 145 c.flags.BoolVar(&c.exposeServers, "expose-servers", false, 146 "Expose the servers for WAN federation via this mesh gateway") 147 148 c.flags.StringVar(&c.deregAfterCritical, "deregister-after-critical", "6h", 149 "The amount of time the gateway services health check can be failing before being deregistered") 150 151 c.flags.BoolVar(&c.omitDeprecatedTags, "omit-deprecated-tags", false, 152 "In Consul 1.9.0 the format of metric tags for Envoy clusters was updated from consul.[service|dc|...] to "+ 153 "consul.destination.[service|dc|...]. The old tags were preserved for backward compatibility,"+ 154 "but can be disabled with this flag.") 155 156 c.flags.StringVar(&c.prometheusBackendPort, "prometheus-backend-port", "", 157 "Sets the backend port for the 'prometheus_backend' cluster that envoy_prometheus_bind_addr will point to. "+ 158 "Without this flag, envoy_prometheus_bind_addr would point to the 'self_admin' cluster where Envoy metrics are exposed. "+ 159 "The metrics merging feature in consul-k8s uses this to point to the merged metrics endpoint combining Envoy and service metrics. "+ 160 "Only applicable when envoy_prometheus_bind_addr is set in proxy config.") 161 162 c.flags.StringVar(&c.prometheusScrapePath, "prometheus-scrape-path", "/metrics", 163 "Sets the path where Envoy will expose metrics on envoy_prometheus_bind_addr listener. "+ 164 "For example, if envoy_prometheus_bind_addr is 0.0.0.0:20200, and this flag is "+ 165 "set to /scrape-metrics, prometheus metrics would be scrapeable at "+ 166 "0.0.0.0:20200/scrape-metrics. "+ 167 "Only applicable when envoy_prometheus_bind_addr is set in proxy config.") 168 169 c.http = &flags.HTTPFlags{} 170 flags.Merge(c.flags, c.http.ClientFlags()) 171 flags.Merge(c.flags, c.http.NamespaceFlags()) 172 c.help = flags.Usage(help, c.flags) 173} 174 175// canBindInternal is here mainly so we can unit test this with a constant net.Addr list 176func canBindInternal(addr string, ifAddrs []net.Addr) bool { 177 if addr == "" { 178 return false 179 } 180 181 ip := net.ParseIP(addr) 182 if ip == nil { 183 return false 184 } 185 186 ipStr := ip.String() 187 188 for _, addr := range ifAddrs { 189 switch v := addr.(type) { 190 case *net.IPNet: 191 if v.IP.String() == ipStr { 192 return true 193 } 194 default: 195 if addr.String() == ipStr { 196 return true 197 } 198 } 199 } 200 201 return false 202} 203 204func canBind(addr api.ServiceAddress) bool { 205 ifAddrs, err := net.InterfaceAddrs() 206 if err != nil { 207 return false 208 } 209 210 return canBindInternal(addr.Address, ifAddrs) 211} 212 213func (c *cmd) Run(args []string) int { 214 if err := c.flags.Parse(args); err != nil { 215 return 1 216 } 217 218 // Setup Consul client 219 var err error 220 c.client, err = c.http.APIClient() 221 if err != nil { 222 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 223 return 1 224 } 225 // TODO: refactor 226 return c.run(c.flags.Args()) 227} 228 229func (c *cmd) run(args []string) int { 230 // Fixup for deprecated mesh-gateway flag 231 if c.meshGateway && c.gateway != "" { 232 c.UI.Error("The mesh-gateway flag is deprecated and cannot be used alongside the gateway flag") 233 return 1 234 } 235 236 if c.meshGateway { 237 c.gateway = meshGatewayVal 238 } 239 240 if c.exposeServers { 241 if c.gateway != meshGatewayVal { 242 c.UI.Error("'-expose-servers' can only be used for mesh gateways") 243 return 1 244 } 245 if !c.register { 246 c.UI.Error("'-expose-servers' requires '-register'") 247 return 1 248 } 249 } 250 251 // Gateway kind is set so that it is available even if not auto-registering the gateway 252 if c.gateway != "" { 253 kind, ok := supportedGateways[c.gateway] 254 if !ok { 255 c.UI.Error("Gateway must be one of: terminating, mesh, or ingress") 256 return 1 257 } 258 c.gatewayKind = kind 259 260 if c.gatewaySvcName == "" { 261 c.gatewaySvcName = string(c.gatewayKind) 262 } 263 } 264 265 if c.proxyID == "" { 266 switch { 267 case c.sidecarFor != "": 268 proxyID, err := proxyCmd.LookupProxyIDForSidecar(c.client, c.sidecarFor) 269 if err != nil { 270 c.UI.Error(err.Error()) 271 return 1 272 } 273 c.proxyID = proxyID 274 275 case c.gateway != "" && !c.register: 276 gatewaySvc, err := proxyCmd.LookupGatewayProxy(c.client, c.gatewayKind) 277 if err != nil { 278 c.UI.Error(err.Error()) 279 return 1 280 } 281 c.proxyID = gatewaySvc.ID 282 c.gatewaySvcName = gatewaySvc.Service 283 284 case c.gateway != "" && c.register: 285 c.proxyID = c.gatewaySvcName 286 287 } 288 } 289 if c.proxyID == "" { 290 c.UI.Error("No proxy ID specified. One of -proxy-id, -sidecar-for, or -gateway is " + 291 "required") 292 return 1 293 } 294 295 if c.register { 296 if c.gateway == "" { 297 c.UI.Error("Auto-Registration can only be used for gateways") 298 return 1 299 } 300 301 taggedAddrs := make(map[string]api.ServiceAddress) 302 lanAddr := c.lanAddress.Value() 303 if lanAddr.Address != "" { 304 taggedAddrs[structs.TaggedAddressLAN] = lanAddr 305 } 306 307 wanAddr := c.wanAddress.Value() 308 if wanAddr.Address != "" { 309 taggedAddrs[structs.TaggedAddressWAN] = wanAddr 310 } 311 312 tcpCheckAddr := lanAddr.Address 313 if tcpCheckAddr == "" { 314 // fallback to localhost as the gateway has to reside in the same network namespace 315 // as the agent 316 tcpCheckAddr = "127.0.0.1" 317 } 318 319 var proxyConf *api.AgentServiceConnectProxyConfig 320 if len(c.bindAddresses.value) > 0 { 321 // override all default binding rules and just bind to the user-supplied addresses 322 proxyConf = &api.AgentServiceConnectProxyConfig{ 323 Config: map[string]interface{}{ 324 "envoy_gateway_no_default_bind": true, 325 "envoy_gateway_bind_addresses": c.bindAddresses.value, 326 }, 327 } 328 } else if canBind(lanAddr) && canBind(wanAddr) { 329 // when both addresses are bindable then we bind to the tagged addresses 330 // for creating the envoy listeners 331 proxyConf = &api.AgentServiceConnectProxyConfig{ 332 Config: map[string]interface{}{ 333 "envoy_gateway_no_default_bind": true, 334 "envoy_gateway_bind_tagged_addresses": true, 335 }, 336 } 337 } else if !canBind(lanAddr) && lanAddr.Address != "" { 338 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)) 339 return 1 340 } 341 342 var meta map[string]string 343 if c.exposeServers { 344 meta = map[string]string{structs.MetaWANFederationKey: "1"} 345 } 346 347 svc := api.AgentServiceRegistration{ 348 Kind: c.gatewayKind, 349 Name: c.gatewaySvcName, 350 ID: c.proxyID, 351 Address: lanAddr.Address, 352 Port: lanAddr.Port, 353 Meta: meta, 354 TaggedAddresses: taggedAddrs, 355 Proxy: proxyConf, 356 Check: &api.AgentServiceCheck{ 357 Name: fmt.Sprintf("%s listening", c.gatewayKind), 358 TCP: ipaddr.FormatAddressPort(tcpCheckAddr, lanAddr.Port), 359 Interval: "10s", 360 DeregisterCriticalServiceAfter: c.deregAfterCritical, 361 }, 362 } 363 364 if err := c.client.Agent().ServiceRegister(&svc); err != nil { 365 c.UI.Error(fmt.Sprintf("Error registering service %q: %s", svc.Name, err)) 366 return 1 367 } 368 369 if !c.bootstrap { 370 // We need stdout to be reserved exclusively for the JSON blob, so 371 // we omit logging this to Info which also writes to stdout. 372 c.UI.Info(fmt.Sprintf("Registered service: %s", svc.Name)) 373 } 374 } 375 376 // Generate config 377 bootstrapJson, err := c.generateConfig() 378 if err != nil { 379 c.UI.Error(err.Error()) 380 return 1 381 } 382 383 if c.bootstrap { 384 // Just output it and we are done 385 c.UI.Output(string(bootstrapJson)) 386 return 0 387 } 388 389 // Find Envoy binary 390 binary, err := c.findBinary() 391 if err != nil { 392 c.UI.Error("Couldn't find envoy binary: " + err.Error()) 393 return 1 394 } 395 396 err = execEnvoy(binary, nil, args, bootstrapJson) 397 if err == errUnsupportedOS { 398 c.UI.Error("Directly running Envoy is only supported on linux and macOS " + 399 "since envoy itself doesn't build on other platforms currently.") 400 c.UI.Error("Use the -bootstrap option to generate the JSON to use when running envoy " + 401 "on a supported OS or via a container or VM.") 402 return 1 403 } else if err != nil { 404 c.UI.Error(err.Error()) 405 return 1 406 } 407 408 return 0 409} 410 411var errUnsupportedOS = errors.New("envoy: not implemented on this operating system") 412 413func (c *cmd) findBinary() (string, error) { 414 if c.envoyBin != "" { 415 return c.envoyBin, nil 416 } 417 return exec.LookPath("envoy") 418} 419 420func (c *cmd) templateArgs() (*BootstrapTplArgs, error) { 421 httpCfg := api.DefaultConfig() 422 c.http.MergeOntoConfig(httpCfg) 423 424 // api.NewClient normalizes some values (Token, Scheme) on the Config. 425 if _, err := api.NewClient(httpCfg); err != nil { 426 return nil, err 427 } 428 429 grpcAddr, err := c.grpcAddress(httpCfg) 430 if err != nil { 431 return nil, err 432 } 433 434 adminAddr, adminPort, err := net.SplitHostPort(c.adminBind) 435 if err != nil { 436 return nil, fmt.Errorf("Invalid Consul HTTP address: %s", err) 437 } 438 439 // Envoy requires IP addresses to bind too when using static so resolve DNS or 440 // localhost here. 441 adminBindIP, err := net.ResolveIPAddr("ip", adminAddr) 442 if err != nil { 443 return nil, fmt.Errorf("Failed to resolve admin bind address: %s", err) 444 } 445 446 // Ideally the cluster should be the service name. We may or may not have that 447 // yet depending on the arguments used so make a best effort here. In the 448 // common case, even if the command was invoked with proxy-id and we don't 449 // know service name yet, we will after we resolve the proxy's config in a bit 450 // and will update this then. 451 cluster := c.proxyID 452 proxySourceService := "" 453 if c.sidecarFor != "" { 454 cluster = c.sidecarFor 455 proxySourceService = c.sidecarFor 456 } else if c.gateway != "" && c.gatewaySvcName != "" { 457 cluster = c.gatewaySvcName 458 proxySourceService = c.gatewaySvcName 459 } 460 461 adminAccessLogPath := c.adminAccessLogPath 462 if adminAccessLogPath == "" { 463 adminAccessLogPath = DefaultAdminAccessLogPath 464 } 465 466 var caPEM string 467 pems, err := tlsutil.LoadCAs(httpCfg.TLSConfig.CAFile, httpCfg.TLSConfig.CAPath) 468 if err != nil { 469 return nil, err 470 } 471 caPEM = strings.Replace(strings.Join(pems, ""), "\n", "\\n", -1) 472 473 return &BootstrapTplArgs{ 474 GRPC: grpcAddr, 475 ProxyCluster: cluster, 476 ProxyID: c.proxyID, 477 ProxySourceService: proxySourceService, 478 AgentCAPEM: caPEM, 479 AdminAccessLogPath: adminAccessLogPath, 480 AdminBindAddress: adminBindIP.String(), 481 AdminBindPort: adminPort, 482 Token: httpCfg.Token, 483 LocalAgentClusterName: xds.LocalAgentClusterName, 484 Namespace: httpCfg.Namespace, 485 EnvoyVersion: c.envoyVersion, 486 Datacenter: httpCfg.Datacenter, 487 PrometheusBackendPort: c.prometheusBackendPort, 488 PrometheusScrapePath: c.prometheusScrapePath, 489 }, nil 490} 491 492func (c *cmd) generateConfig() ([]byte, error) { 493 args, err := c.templateArgs() 494 if err != nil { 495 return nil, err 496 } 497 498 var bsCfg BootstrapConfig 499 500 // Setup ready listener for ingress gateway to pass healthcheck 501 if c.gatewayKind == api.ServiceKindIngressGateway { 502 lanAddr := c.lanAddress.String() 503 // Deal with possibility of address not being specified and defaulting to 504 // ":443" 505 if strings.HasPrefix(lanAddr, ":") { 506 lanAddr = "127.0.0.1" + lanAddr 507 } 508 bsCfg.ReadyBindAddr = lanAddr 509 } 510 511 // Fetch any customization from the registration 512 svc, _, err := c.client.Agent().Service(c.proxyID, nil) 513 if err != nil { 514 return nil, fmt.Errorf("failed fetch proxy config from local agent: %s", err) 515 } 516 if svc.Proxy == nil { 517 return nil, errors.New("service is not a Connect proxy or gateway") 518 } 519 520 if svc.Proxy.DestinationServiceName != "" { 521 // Override cluster now we know the actual service name 522 args.ProxyCluster = svc.Proxy.DestinationServiceName 523 args.ProxySourceService = svc.Proxy.DestinationServiceName 524 } else { 525 // Set the source service name from the proxy's own registration 526 args.ProxySourceService = svc.Service 527 } 528 if svc.Namespace != "" { 529 // In most cases where namespaces are enabled this will already be set 530 // correctly because the http client that fetched this will need to have 531 // had the namespace set on it which is also how we initially populate 532 // this. However in the case of "default" namespace being accessed because 533 // there was no namespace argument, args.Namespace will be empty even 534 // though Namespaces are actually being used and the namespace of the request was 535 // inferred from the ACL token or defaulted to the "default" namespace. 536 // Overriding it here ensures that we always set the Namespace arg if the 537 // cluster is using namespaces regardless. 538 args.Namespace = svc.Namespace 539 } 540 541 if svc.Datacenter != "" { 542 // The agent will definitely have the definitive answer here. 543 args.Datacenter = svc.Datacenter 544 } 545 546 if !c.disableCentralConfig { 547 // Parse the bootstrap config 548 if err := mapstructure.WeakDecode(svc.Proxy.Config, &bsCfg); err != nil { 549 return nil, fmt.Errorf("failed parsing Proxy.Config: %s", err) 550 } 551 } 552 553 return bsCfg.GenerateJSON(args, c.omitDeprecatedTags) 554} 555 556// TODO: make method a function 557func (c *cmd) grpcAddress(httpCfg *api.Config) (GRPC, error) { 558 g := GRPC{} 559 560 addr := c.grpcAddr 561 // See if we need to lookup grpcAddr 562 if addr == "" { 563 port, err := c.lookupGRPCPort() 564 if err != nil { 565 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 566 } 567 if port <= 0 { 568 // This is the dev mode default and recommended production setting if 569 // enabled. 570 port = 8502 571 } 572 addr = fmt.Sprintf("localhost:%v", port) 573 } 574 575 // TODO: parse addr as a url instead of strings.HasPrefix/TrimPrefix 576 577 // Decide on TLS if the scheme is provided and indicates it, if the HTTP env 578 // suggests TLS is supported explicitly (CONSUL_HTTP_SSL) or implicitly 579 // (CONSUL_HTTP_ADDR) is https:// 580 switch { 581 case strings.HasPrefix(strings.ToLower(addr), "https://"): 582 g.AgentTLS = true 583 case httpCfg.Scheme == "https": 584 g.AgentTLS = true 585 } 586 587 // We want to allow grpcAddr set as host:port with no scheme but if the host 588 // is an IP this will fail to parse as a URL with "parse 127.0.0.1:8500: first 589 // path segment in URL cannot contain colon". On the other hand we also 590 // support both http(s)://host:port and unix:///path/to/file. 591 if grpcAddr := strings.TrimPrefix(addr, "unix://"); grpcAddr != addr { 592 // Path to unix socket 593 g.AgentSocket = grpcAddr 594 } else { 595 // Parse as host:port with option http prefix 596 grpcAddr = strings.TrimPrefix(addr, "http://") 597 grpcAddr = strings.TrimPrefix(grpcAddr, "https://") 598 599 var err error 600 var host string 601 host, g.AgentPort, err = net.SplitHostPort(grpcAddr) 602 if err != nil { 603 return g, fmt.Errorf("Invalid Consul HTTP address: %s", err) 604 } 605 606 // We use STATIC for agent which means we need to resolve DNS names like 607 // `localhost` ourselves. We could use STRICT_DNS or LOGICAL_DNS with envoy 608 // but Envoy resolves `localhost` differently to go on macOS at least which 609 // causes paper cuts like default dev agent (which binds specifically to 610 // 127.0.0.1) isn't reachable since Envoy resolves localhost to `[::]` and 611 // can't connect. 612 agentIP, err := net.ResolveIPAddr("ip", host) 613 if err != nil { 614 return g, fmt.Errorf("Failed to resolve agent address: %s", err) 615 } 616 g.AgentAddress = agentIP.String() 617 } 618 return g, nil 619} 620 621func (c *cmd) lookupGRPCPort() (int, error) { 622 self, err := c.client.Agent().Self() 623 if err != nil { 624 return 0, err 625 } 626 cfg, ok := self["DebugConfig"] 627 if !ok { 628 return 0, fmt.Errorf("unexpected agent response: no debug config") 629 } 630 port, ok := cfg["GRPCPort"] 631 if !ok { 632 return 0, fmt.Errorf("agent does not have grpc port enabled") 633 } 634 portN, ok := port.(float64) 635 if !ok { 636 return 0, fmt.Errorf("invalid grpc port in agent response") 637 } 638 639 return int(portN), nil 640} 641 642func (c *cmd) Synopsis() string { 643 return synopsis 644} 645 646func (c *cmd) Help() string { 647 return c.help 648} 649 650const synopsis = "Runs or Configures Envoy as a Connect proxy" 651const help = ` 652Usage: consul connect envoy [options] 653 654 Generates the bootstrap configuration needed to start an Envoy proxy instance 655 for use as a Connect sidecar for a particular service instance. By default it 656 will generate the config and then exec Envoy directly until it exits normally. 657 658 It will search $PATH for the envoy binary but this can be overridden with 659 -envoy-binary. 660 661 It can instead only generate the bootstrap.json based on the current ENV and 662 arguments using -bootstrap. 663 664 The proxy requires service:write permissions for the service it represents. 665 The token may be passed via the CLI or the CONSUL_HTTP_TOKEN environment 666 variable. 667 668 The example below shows how to start a local proxy as a sidecar to a "web" 669 service instance. It assumes that the proxy was already registered with it's 670 Config for example via a sidecar_service block. 671 672 $ consul connect envoy -sidecar-for web 673 674` 675