github.com/lmb/consul@v1.4.1/connect/proxy/config.go (about) 1 package proxy 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/mitchellh/mapstructure" 9 10 "github.com/hashicorp/consul/api" 11 "github.com/hashicorp/consul/connect" 12 "github.com/hashicorp/consul/lib" 13 "github.com/hashicorp/consul/watch" 14 ) 15 16 // Config is the publicly configurable state for an entire proxy instance. It's 17 // mostly used as the format for the local-file config mode which is mostly for 18 // dev/testing. In normal use, different parts of this config are pulled from 19 // different locations (e.g. command line, agent config endpoint, agent 20 // certificate endpoints). 21 type Config struct { 22 // Token is the authentication token provided for queries to the local agent. 23 Token string `json:"token" hcl:"token"` 24 25 // ProxiedServiceName is the name of the service this proxy is representing. 26 // This is the service _name_ and not the service _id_. This allows the 27 // proxy to represent services not present in the local catalog. 28 // 29 // ProxiedServiceNamespace is the namespace of the service this proxy is 30 // representing. 31 ProxiedServiceName string `json:"proxied_service_name" hcl:"proxied_service_name"` 32 ProxiedServiceNamespace string `json:"proxied_service_namespace" hcl:"proxied_service_namespace"` 33 34 // PublicListener configures the mTLS listener. 35 PublicListener PublicListenerConfig `json:"public_listener" hcl:"public_listener"` 36 37 // Upstreams configures outgoing proxies for remote connect services. 38 Upstreams []UpstreamConfig `json:"upstreams" hcl:"upstreams"` 39 40 // Telemetry stores configuration for go-metrics. It is typically populated 41 // from the agent's runtime config via the proxy config endpoint so that the 42 // proxy will log metrics to the same location(s) as the agent. 43 Telemetry lib.TelemetryConfig 44 } 45 46 // Service returns the *connect.Service structure represented by this config. 47 func (c *Config) Service(client *api.Client, logger *log.Logger) (*connect.Service, error) { 48 return connect.NewServiceWithLogger(c.ProxiedServiceName, client, logger) 49 } 50 51 // PublicListenerConfig contains the parameters needed for the incoming mTLS 52 // listener. 53 type PublicListenerConfig struct { 54 // BindAddress is the host/IP the public mTLS listener will bind to. 55 // 56 // BindPort is the port the public listener will bind to. 57 BindAddress string `json:"bind_address" hcl:"bind_address" mapstructure:"bind_address"` 58 BindPort int `json:"bind_port" hcl:"bind_port" mapstructure:"bind_port"` 59 60 // LocalServiceAddress is the host:port for the proxied application. This 61 // should be on loopback or otherwise protected as it's plain TCP. 62 LocalServiceAddress string `json:"local_service_address" hcl:"local_service_address" mapstructure:"local_service_address"` 63 64 // LocalConnectTimeout is the timeout for establishing connections with the 65 // local backend. Defaults to 1000 (1s). 66 LocalConnectTimeoutMs int `json:"local_connect_timeout_ms" hcl:"local_connect_timeout_ms" mapstructure:"local_connect_timeout_ms"` 67 68 // HandshakeTimeout is the timeout for incoming mTLS clients to complete a 69 // handshake. Setting this low avoids DOS by malicious clients holding 70 // resources open. Defaults to 10000 (10s). 71 HandshakeTimeoutMs int `json:"handshake_timeout_ms" hcl:"handshake_timeout_ms" mapstructure:"handshake_timeout_ms"` 72 } 73 74 // applyDefaults sets zero-valued params to a sane default. 75 func (plc *PublicListenerConfig) applyDefaults() { 76 if plc.LocalConnectTimeoutMs == 0 { 77 plc.LocalConnectTimeoutMs = 1000 78 } 79 if plc.HandshakeTimeoutMs == 0 { 80 plc.HandshakeTimeoutMs = 10000 81 } 82 if plc.BindAddress == "" { 83 plc.BindAddress = "0.0.0.0" 84 } 85 } 86 87 // UpstreamConfig is an alias for api.Upstream so we can parse in a compatible 88 // way but define custom methods for accessing the opaque config metadata. 89 type UpstreamConfig api.Upstream 90 91 // ConnectTimeout returns the connect timeout field of the nested config struct 92 // or the default value. 93 func (uc *UpstreamConfig) ConnectTimeout() time.Duration { 94 if ms, ok := uc.Config["connect_timeout_ms"].(int); ok { 95 return time.Duration(ms) * time.Millisecond 96 } 97 return 10000 * time.Millisecond 98 } 99 100 // applyDefaults sets zero-valued params to a sane default. 101 func (uc *UpstreamConfig) applyDefaults() { 102 if uc.DestinationType == "" { 103 uc.DestinationType = "service" 104 } 105 if uc.DestinationNamespace == "" { 106 uc.DestinationNamespace = "default" 107 } 108 if uc.LocalBindAddress == "" { 109 uc.LocalBindAddress = "127.0.0.1" 110 } 111 } 112 113 // String returns a string that uniquely identifies the Upstream. Used for 114 // identifying the upstream in log output and map keys. 115 func (uc *UpstreamConfig) String() string { 116 return fmt.Sprintf("%s:%d->%s:%s/%s", uc.LocalBindAddress, uc.LocalBindPort, 117 uc.DestinationType, uc.DestinationNamespace, uc.DestinationName) 118 } 119 120 // UpstreamResolverFuncFromClient returns a closure that captures a consul 121 // client and when called provides a ConsulResolver that can resolve the given 122 // UpstreamConfig using the provided api.Client dependency. 123 func UpstreamResolverFuncFromClient(client *api.Client) func(cfg UpstreamConfig) (connect.Resolver, error) { 124 return func(cfg UpstreamConfig) (connect.Resolver, error) { 125 // For now default to service as it has the most natural meaning and the error 126 // that the service doesn't exist is probably reasonable if misconfigured. We 127 // should probably handle actual configs that have invalid types at a higher 128 // level anyway (like when parsing). 129 typ := connect.ConsulResolverTypeService 130 if cfg.DestinationType == "prepared_query" { 131 typ = connect.ConsulResolverTypePreparedQuery 132 } 133 return &connect.ConsulResolver{ 134 Client: client, 135 Namespace: cfg.DestinationNamespace, 136 Name: cfg.DestinationName, 137 Type: typ, 138 Datacenter: cfg.Datacenter, 139 }, nil 140 } 141 } 142 143 // ConfigWatcher is a simple interface to allow dynamic configurations from 144 // pluggable sources. 145 type ConfigWatcher interface { 146 // Watch returns a channel that will deliver new Configs if something external 147 // provokes it. 148 Watch() <-chan *Config 149 } 150 151 // StaticConfigWatcher is a simple ConfigWatcher that delivers a static Config 152 // once and then never changes it. 153 type StaticConfigWatcher struct { 154 ch chan *Config 155 } 156 157 // NewStaticConfigWatcher returns a ConfigWatcher for a config that never 158 // changes. It assumes only one "watcher" will ever call Watch. The config is 159 // delivered on the first call but will never be delivered again to allow 160 // callers to call repeatedly (e.g. select in a loop). 161 func NewStaticConfigWatcher(cfg *Config) *StaticConfigWatcher { 162 sc := &StaticConfigWatcher{ 163 // Buffer it so we can queue up the config for first delivery. 164 ch: make(chan *Config, 1), 165 } 166 sc.ch <- cfg 167 return sc 168 } 169 170 // Watch implements ConfigWatcher on a static configuration for compatibility. 171 // It returns itself on the channel once and then leaves it open. 172 func (sc *StaticConfigWatcher) Watch() <-chan *Config { 173 return sc.ch 174 } 175 176 // AgentConfigWatcher watches the local Consul agent for proxy config changes. 177 type AgentConfigWatcher struct { 178 client *api.Client 179 proxyID string 180 logger *log.Logger 181 ch chan *Config 182 plan *watch.Plan 183 } 184 185 // NewAgentConfigWatcher creates an AgentConfigWatcher. 186 func NewAgentConfigWatcher(client *api.Client, proxyID string, 187 logger *log.Logger) (*AgentConfigWatcher, error) { 188 w := &AgentConfigWatcher{ 189 client: client, 190 proxyID: proxyID, 191 logger: logger, 192 ch: make(chan *Config), 193 } 194 195 // Setup watch plan for config 196 plan, err := watch.Parse(map[string]interface{}{ 197 "type": "agent_service", 198 "service_id": w.proxyID, 199 }) 200 if err != nil { 201 return nil, err 202 } 203 w.plan = plan 204 w.plan.HybridHandler = w.handler 205 go w.plan.RunWithClientAndLogger(w.client, w.logger) 206 return w, nil 207 } 208 209 func (w *AgentConfigWatcher) handler(blockVal watch.BlockingParamVal, 210 val interface{}) { 211 212 resp, ok := val.(*api.AgentService) 213 if !ok { 214 w.logger.Printf("[WARN] proxy config watch returned bad response: %v", val) 215 return 216 } 217 218 if resp.Kind != api.ServiceKindConnectProxy { 219 w.logger.Printf("[ERR] service with id %s is not a valid connect proxy", 220 w.proxyID) 221 return 222 } 223 224 // Create proxy config from the response 225 cfg := &Config{ 226 // Token should be already setup in the client 227 ProxiedServiceName: resp.Proxy.DestinationServiceName, 228 ProxiedServiceNamespace: "default", 229 } 230 231 if tRaw, ok := resp.Proxy.Config["telemetry"]; ok { 232 err := mapstructure.Decode(tRaw, &cfg.Telemetry) 233 if err != nil { 234 w.logger.Printf("[WARN] proxy telemetry config failed to parse: %s", err) 235 } 236 } 237 238 // Unmarshal configs 239 err := mapstructure.Decode(resp.Proxy.Config, &cfg.PublicListener) 240 if err != nil { 241 w.logger.Printf("[ERR] failed to parse public listener config: %s", err) 242 } 243 cfg.PublicListener.BindAddress = resp.Address 244 cfg.PublicListener.BindPort = resp.Port 245 cfg.PublicListener.LocalServiceAddress = fmt.Sprintf("%s:%d", 246 resp.Proxy.LocalServiceAddress, resp.Proxy.LocalServicePort) 247 248 cfg.PublicListener.applyDefaults() 249 250 for _, u := range resp.Proxy.Upstreams { 251 uc := UpstreamConfig(u) 252 uc.applyDefaults() 253 cfg.Upstreams = append(cfg.Upstreams, uc) 254 } 255 256 // Parsed config OK, deliver it! 257 w.ch <- cfg 258 } 259 260 // Watch implements ConfigWatcher. 261 func (w *AgentConfigWatcher) Watch() <-chan *Config { 262 return w.ch 263 } 264 265 // Close frees watcher resources and implements io.Closer 266 func (w *AgentConfigWatcher) Close() error { 267 if w.plan != nil { 268 w.plan.Stop() 269 } 270 return nil 271 }