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  }