github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/command/connect/proxy/proxy.go (about) 1 package proxy 2 3 import ( 4 "flag" 5 "fmt" 6 "io" 7 "log" 8 "net" 9 "net/http" 10 _ "net/http/pprof" // Expose pprof if configured 11 "os" 12 "sort" 13 "strconv" 14 "strings" 15 16 proxyAgent "github.com/hashicorp/consul/agent/proxyprocess" 17 "github.com/hashicorp/consul/api" 18 "github.com/hashicorp/consul/command/flags" 19 proxyImpl "github.com/hashicorp/consul/connect/proxy" 20 21 "github.com/hashicorp/consul/logger" 22 "github.com/hashicorp/logutils" 23 "github.com/mitchellh/cli" 24 ) 25 26 func New(ui cli.Ui, shutdownCh <-chan struct{}) *cmd { 27 ui = &cli.PrefixedUi{ 28 OutputPrefix: "==> ", 29 InfoPrefix: " ", 30 ErrorPrefix: "==> ", 31 Ui: ui, 32 } 33 34 c := &cmd{UI: ui, shutdownCh: shutdownCh} 35 c.init() 36 return c 37 } 38 39 type cmd struct { 40 UI cli.Ui 41 flags *flag.FlagSet 42 http *flags.HTTPFlags 43 help string 44 45 shutdownCh <-chan struct{} 46 47 logFilter *logutils.LevelFilter 48 logOutput io.Writer 49 logger *log.Logger 50 51 // flags 52 logLevel string 53 cfgFile string 54 proxyID string 55 sidecarFor string 56 pprofAddr string 57 service string 58 serviceAddr string 59 upstreams map[string]proxyImpl.UpstreamConfig 60 listen string 61 register bool 62 registerId string 63 64 // test flags 65 testNoStart bool // don't start the proxy, just exit 0 66 } 67 68 func (c *cmd) init() { 69 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 70 71 c.flags.StringVar(&c.proxyID, "proxy-id", "", 72 "The proxy's ID on the local agent.") 73 74 c.flags.StringVar(&c.sidecarFor, "sidecar-for", "", 75 "The ID of a service instance on the local agent that this proxy should "+ 76 "become a sidecar for. It requires that the proxy service is registered "+ 77 "with the agent as a connect-proxy with Proxy.DestinationServiceID set "+ 78 "to this value. If more than one such proxy is registered it will fail.") 79 80 c.flags.StringVar(&c.logLevel, "log-level", "INFO", 81 "Specifies the log level.") 82 83 c.flags.StringVar(&c.pprofAddr, "pprof-addr", "", 84 "Enable debugging via pprof. Providing a host:port (or just ':port') "+ 85 "enables profiling HTTP endpoints on that address.") 86 87 c.flags.StringVar(&c.service, "service", "", 88 "Name of the service this proxy is representing.") 89 90 c.flags.Var((*FlagUpstreams)(&c.upstreams), "upstream", 91 "Upstream service to support connecting to. The format should be "+ 92 "'name:addr', such as 'db:8181'. This will make 'db' available "+ 93 "on port 8181. This can be repeated multiple times.") 94 95 c.flags.StringVar(&c.serviceAddr, "service-addr", "", 96 "Address of the local service to proxy. Only useful if -listen "+ 97 "and -service are both set.") 98 99 c.flags.StringVar(&c.listen, "listen", "", 100 "Address to listen for inbound connections to the proxied service. "+ 101 "Must be specified with -service and -service-addr.") 102 103 c.flags.BoolVar(&c.register, "register", false, 104 "Self-register with the local Consul agent. Only useful with "+ 105 "-listen.") 106 107 c.flags.StringVar(&c.registerId, "register-id", "", 108 "ID suffix for the service. Use this to disambiguate with other proxies.") 109 110 c.http = &flags.HTTPFlags{} 111 flags.Merge(c.flags, c.http.ClientFlags()) 112 flags.Merge(c.flags, c.http.ServerFlags()) 113 c.help = flags.Usage(help, c.flags) 114 } 115 116 func (c *cmd) Run(args []string) int { 117 if err := c.flags.Parse(args); err != nil { 118 return 1 119 } 120 if len(c.flags.Args()) > 0 { 121 c.UI.Error(fmt.Sprintf("Should have no non-flag arguments.")) 122 return 1 123 } 124 125 // Load the proxy ID and token from env vars if they're set 126 if c.proxyID == "" { 127 c.proxyID = os.Getenv(proxyAgent.EnvProxyID) 128 } 129 if c.sidecarFor == "" { 130 c.sidecarFor = os.Getenv(proxyAgent.EnvSidecarFor) 131 } 132 if c.http.Token() == "" { 133 c.http.SetToken(os.Getenv(proxyAgent.EnvProxyToken)) 134 } 135 136 // Setup the log outputs 137 logConfig := &logger.Config{ 138 LogLevel: c.logLevel, 139 } 140 logFilter, logGate, _, logOutput, ok := logger.Setup(logConfig, c.UI) 141 if !ok { 142 return 1 143 } 144 c.logFilter = logFilter 145 c.logOutput = logOutput 146 c.logger = log.New(logOutput, "", log.LstdFlags) 147 148 // Enable Pprof if needed 149 if c.pprofAddr != "" { 150 go func() { 151 c.UI.Output(fmt.Sprintf("Starting pprof HTTP endpoints on "+ 152 "http://%s/debug/pprof", c.pprofAddr)) 153 log.Fatal(http.ListenAndServe(c.pprofAddr, nil)) 154 }() 155 } 156 157 // Setup Consul client 158 client, err := c.http.APIClient() 159 if err != nil { 160 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 161 return 1 162 } 163 164 // Output this first since the config watcher below will output 165 // other information. 166 c.UI.Output("Consul Connect proxy starting...") 167 168 // Get the proper configuration watcher 169 cfgWatcher, err := c.configWatcher(client) 170 if err != nil { 171 c.UI.Error(fmt.Sprintf("Error preparing configuration: %s", err)) 172 return 1 173 } 174 175 p, err := proxyImpl.New(client, cfgWatcher, c.logger) 176 if err != nil { 177 c.UI.Error(fmt.Sprintf("Failed initializing proxy: %s", err)) 178 return 1 179 } 180 181 // Hook the shutdownCh up to close the proxy 182 go func() { 183 <-c.shutdownCh 184 p.Close() 185 }() 186 187 // Register the service if we requested it 188 if c.register { 189 monitor, err := c.registerMonitor(client) 190 if err != nil { 191 c.UI.Error(fmt.Sprintf("Failed initializing registration: %s", err)) 192 return 1 193 } 194 195 go monitor.Run() 196 defer monitor.Close() 197 } 198 199 c.UI.Info("") 200 c.UI.Output("Log data will now stream in as it occurs:\n") 201 logGate.Flush() 202 203 // Run the proxy unless our tests require we don't 204 if !c.testNoStart { 205 if err := p.Serve(); err != nil { 206 c.UI.Error(fmt.Sprintf("Failed running proxy: %s", err)) 207 } 208 } 209 210 c.UI.Output("Consul Connect proxy shutdown") 211 return 0 212 } 213 214 func (c *cmd) lookupProxyIDForSidecar(client *api.Client) (string, error) { 215 return LookupProxyIDForSidecar(client, c.sidecarFor) 216 } 217 218 // LookupProxyIDForSidecar finds candidate local proxy registrations that are a 219 // sidecar for the given service. It will return an ID if and only if there is 220 // exactly one registered connect proxy with `Proxy.DestinationServiceID` set to 221 // the specified service ID. 222 // 223 // This is exported to share it with the connect envoy command. 224 func LookupProxyIDForSidecar(client *api.Client, sidecarFor string) (string, error) { 225 svcs, err := client.Agent().Services() 226 if err != nil { 227 return "", fmt.Errorf("Failed looking up sidecar proxy info for %s: %s", 228 sidecarFor, err) 229 } 230 231 var proxyIDs []string 232 for _, svc := range svcs { 233 if svc.Kind == api.ServiceKindConnectProxy && svc.Proxy != nil && 234 strings.ToLower(svc.Proxy.DestinationServiceID) == sidecarFor { 235 proxyIDs = append(proxyIDs, svc.ID) 236 } 237 } 238 239 if len(proxyIDs) == 0 { 240 return "", fmt.Errorf("No sidecar proxy registered for %s", sidecarFor) 241 } 242 if len(proxyIDs) > 1 { 243 return "", fmt.Errorf("More than one sidecar proxy registered for %s.\n"+ 244 " Start proxy with -proxy-id and one of the following IDs: %s", 245 sidecarFor, strings.Join(proxyIDs, ", ")) 246 } 247 return proxyIDs[0], nil 248 } 249 250 func (c *cmd) configWatcher(client *api.Client) (proxyImpl.ConfigWatcher, error) { 251 // Use the configured proxy ID 252 if c.proxyID != "" { 253 c.UI.Info("Configuration mode: Agent API") 254 c.UI.Info(fmt.Sprintf(" Proxy ID: %s", c.proxyID)) 255 return proxyImpl.NewAgentConfigWatcher(client, c.proxyID, c.logger) 256 } 257 258 if c.sidecarFor != "" { 259 // Running as a sidecar, we need to find the proxy-id for the requested 260 // service 261 var err error 262 c.proxyID, err = c.lookupProxyIDForSidecar(client) 263 if err != nil { 264 return nil, err 265 } 266 267 c.UI.Info("Configuration mode: Agent API") 268 c.UI.Info(fmt.Sprintf(" Sidecar for ID: %s", c.sidecarFor)) 269 c.UI.Info(fmt.Sprintf(" Proxy ID: %s", c.proxyID)) 270 return proxyImpl.NewAgentConfigWatcher(client, c.proxyID, c.logger) 271 } 272 273 // Otherwise, we're representing a manually specified service. 274 if c.service == "" { 275 return nil, fmt.Errorf( 276 "-service or -proxy-id must be specified so that proxy can " + 277 "configure itself.") 278 } 279 280 c.UI.Info("Configuration mode: Flags") 281 c.UI.Info(fmt.Sprintf(" Service: %s", c.service)) 282 283 // Convert our upstreams to a slice of configurations. We do this 284 // deterministically by alphabetizing the upstream keys. We do this so 285 // that tests can compare the upstream values. 286 upstreamKeys := make([]string, 0, len(c.upstreams)) 287 for k := range c.upstreams { 288 upstreamKeys = append(upstreamKeys, k) 289 } 290 sort.Strings(upstreamKeys) 291 upstreams := make([]proxyImpl.UpstreamConfig, 0, len(c.upstreams)) 292 for _, k := range upstreamKeys { 293 config := c.upstreams[k] 294 295 c.UI.Info(fmt.Sprintf( 296 " Upstream: %s => %s:%d", 297 k, config.LocalBindAddress, config.LocalBindPort)) 298 upstreams = append(upstreams, config) 299 } 300 301 // Parse out our listener if we have one 302 var listener proxyImpl.PublicListenerConfig 303 if c.listen != "" { 304 host, port, err := c.listenParts() 305 if err != nil { 306 return nil, err 307 } 308 309 if c.serviceAddr == "" { 310 return nil, fmt.Errorf( 311 "-service-addr must be specified with -listen so the proxy " + 312 "knows the backend service address.") 313 } 314 315 c.UI.Info(fmt.Sprintf(" Public listener: %s:%d => %s", host, port, c.serviceAddr)) 316 listener.BindAddress = host 317 listener.BindPort = port 318 listener.LocalServiceAddress = c.serviceAddr 319 } else { 320 c.UI.Info(fmt.Sprintf(" Public listener: Disabled")) 321 } 322 323 return proxyImpl.NewStaticConfigWatcher(&proxyImpl.Config{ 324 ProxiedServiceName: c.service, 325 PublicListener: listener, 326 Upstreams: upstreams, 327 }), nil 328 } 329 330 // registerMonitor returns the registration monitor ready to be started. 331 func (c *cmd) registerMonitor(client *api.Client) (*RegisterMonitor, error) { 332 if c.service == "" || c.listen == "" { 333 return nil, fmt.Errorf("-register may only be specified with -service and -listen") 334 } 335 336 host, port, err := c.listenParts() 337 if err != nil { 338 return nil, err 339 } 340 341 m := NewRegisterMonitor() 342 m.Logger = c.logger 343 m.Client = client 344 m.Service = c.service 345 m.IDSuffix = c.registerId 346 m.LocalAddress = host 347 m.LocalPort = port 348 return m, nil 349 } 350 351 // listenParts returns the host and port parts of the -listen flag. The 352 // -listen flag must be non-empty prior to calling this. 353 func (c *cmd) listenParts() (string, int, error) { 354 host, portRaw, err := net.SplitHostPort(c.listen) 355 if err != nil { 356 return "", 0, err 357 } 358 359 port, err := strconv.ParseInt(portRaw, 0, 0) 360 if err != nil { 361 return "", 0, err 362 } 363 364 return host, int(port), nil 365 } 366 367 func (c *cmd) Synopsis() string { 368 return synopsis 369 } 370 371 func (c *cmd) Help() string { 372 return c.help 373 } 374 375 const synopsis = "Runs a Consul Connect proxy" 376 const help = ` 377 Usage: consul connect proxy [options] 378 379 Starts a Consul Connect proxy and runs until an interrupt is received. 380 The proxy can be used to accept inbound connections for a service, 381 wrap outbound connections to upstream services, or both. This enables 382 a non-Connect-aware application to use Connect. 383 384 The proxy requires service:write permissions for the service it represents. 385 The token may be passed via the CLI or the CONSUL_TOKEN environment 386 variable. 387 388 Consul can automatically start and manage this proxy by specifying the 389 "proxy" configuration within your service definition. 390 391 The example below shows how to start a local proxy for establishing outbound 392 connections to "db" representing the frontend service. Once running, any 393 process that creates a TCP connection to the specified port (8181) will 394 establish a mutual TLS connection to "db" identified as "frontend". 395 396 $ consul connect proxy -service frontend -upstream db:8181 397 398 The next example starts a local proxy that also accepts inbound connections 399 on port 8443, authorizes the connection, then proxies it to port 8080: 400 401 $ consul connect proxy \ 402 -service frontend \ 403 -service-addr 127.0.0.1:8080 \ 404 -listen ':8443' 405 406 A proxy can accept both inbound connections as well as proxy to upstream 407 services by specifying both the "-listen" and "-upstream" flags. 408 409 `