// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package xds import ( "strings" "time" envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" "google.golang.org/protobuf/types/known/durationpb" "github.com/mitchellh/mapstructure" "google.golang.org/protobuf/types/known/wrapperspb" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib/decode" ) func parseConfig[T any](m map[string]any, cfg *T) error { d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc( decode.HookWeakDecodeFromSlice, decode.HookTranslateKeys, ), Result: cfg, WeaklyTypedInput: true, }) if err != nil { return err } return d.Decode(m) } // XDSCommonConfig contains the configuration from the opaque map that is common to both gateways and sidecar proxies. type XDSCommonConfig struct { // XDSFetchTimeoutMs specifies the amount of milliseconds to wait for dynamically configured Envoy data (EDS, RDS). // Uses the Envoy default value if not specified or negative. A value of zero disables the timeout. XDSFetchTimeoutMs *int `mapstructure:"xds_fetch_timeout_ms"` } // ParseXDSCommonConfig returns the XDSCommonConfig parsed from an opaque map. If an // error occurs during parsing, it is returned along with the default config. This // allows the caller to choose whether and how to report the error func ParseXDSCommonConfig(m map[string]interface{}) (XDSCommonConfig, error) { var cfg XDSCommonConfig err := parseConfig(m, &cfg) return cfg, err } func (c *XDSCommonConfig) GetXDSFetchTimeout() *durationpb.Duration { if c == nil || c.XDSFetchTimeoutMs == nil { return nil } if *c.XDSFetchTimeoutMs >= 0 { return durationpb.New(time.Duration(*c.XDSFetchTimeoutMs) * time.Millisecond) } return nil } // ProxyConfig describes the keys we understand from Connect.Proxy.Config. Note // that this only includes config keys that affects runtime config delivered by // xDS. For Envoy config keys that affect bootstrap generation see // command/connect/envoy/bootstrap_config.go. type ProxyConfig struct { // PublicListenerJSON is a complete override ("escape hatch") for the // upstream's public listener. The Connect server TLS certificate and // validation context will be injected overriding any TLS settings present. An // AuthZ filter will also be prepended to each filterChain provided to enforce // Connect's access control. // // Note: This escape hatch is compatible with the discovery chain. PublicListenerJSON string `mapstructure:"envoy_public_listener_json"` // ListenerTracingJSON is a complete override ("escape hatch") for the // listeners tracing configuration. // // Note: This escape hatch is compatible with the discovery chain. ListenerTracingJSON string `mapstructure:"envoy_listener_tracing_json"` // LocalClusterJSON is a complete override ("escape hatch") for the // local application cluster. // // Note: This escape hatch is compatible with the discovery chain. LocalClusterJSON string `mapstructure:"envoy_local_cluster_json"` // LocalConnectTimeoutMs is the number of milliseconds to timeout making a new // connection to the local app instance. Defaults to 5000 (5 seconds) if not // set. LocalConnectTimeoutMs int `mapstructure:"local_connect_timeout_ms"` // LocalRequestTimeoutMs is the number of milliseconds to timeout HTTP requests // to the local app instance. If not set, no value is set, Envoy defaults are // respected (15s) LocalRequestTimeoutMs *int `mapstructure:"local_request_timeout_ms"` // LocalIdleTimeoutMs is the number of milliseconds to timeout HTTP streams // to the local app instance. If not set, no value is set, Envoy defaults are // respected (300s) LocalIdleTimeoutMs *int `mapstructure:"local_idle_timeout_ms"` // Protocol describes the service's protocol. Valid values are "tcp", // "http" and "grpc". Anything else is treated as tcp. This enables // protocol aware features like per-request metrics and connection // pooling, tracing, routing etc. Protocol string `mapstructure:"protocol"` // BindAddress overrides the address the proxy's listener binds to. This // enables proxies in network namespaces to bind to a different address // than the host address. BindAddress string `mapstructure:"bind_address"` // BindPort overrides the port the proxy's listener binds to. This // enable proxies in network namespaces to bind to a different port // than the host port being advertised. BindPort int `mapstructure:"bind_port"` // MaxInboundConnections is the maximum number of inbound connections to // the proxy. If not set, the default is 0 (no limit). MaxInboundConnections int `mapstructure:"max_inbound_connections"` // BalanceInboundConnections indicates how the proxy should attempt to distribute // connections across worker threads. Only used by envoy proxies. BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"` } // ParseProxyConfig returns the ProxyConfig parsed from the an opaque map. If an // error occurs during parsing it is returned along with the default config this // allows caller to choose whether and how to report the error. func ParseProxyConfig(m map[string]interface{}) (ProxyConfig, error) { var cfg ProxyConfig if err := parseConfig(m, &cfg); err != nil { return cfg, err } if cfg.Protocol == "" { cfg.Protocol = "tcp" } else { cfg.Protocol = strings.ToLower(cfg.Protocol) } if cfg.LocalConnectTimeoutMs < 1 { cfg.LocalConnectTimeoutMs = 5000 } return cfg, nil } type GatewayConfig struct { // BindTaggedAddresses when set will cause all of the services tagged // addresses to have listeners bound to them in addition to the main service // address listener. This is only suitable when the tagged addresses are IP // addresses of network interfaces Envoy can see. i.e. When using DNS names // for those addresses or where an external entity maps that IP to the Envoy // (like AWS EC2 mapping a public IP to the private interface) then this // cannot be used. See the BindAddresses config instead BindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses" alias:"envoy_mesh_gateway_bind_tagged_addresses"` // BindAddresses additional bind addresses to configure listeners for BindAddresses map[string]structs.ServiceAddress `mapstructure:"envoy_gateway_bind_addresses" alias:"envoy_mesh_gateway_bind_addresses"` // NoDefaultBind indicates that we should not bind to the default address of the // gateway service NoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind" alias:"envoy_mesh_gateway_no_default_bind"` // DNSDiscoveryType indicates the DNS service discovery type. // See: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/service_discovery#arch-overview-service-discovery-types DNSDiscoveryType string `mapstructure:"envoy_dns_discovery_type"` // ConnectTimeoutMs is the number of milliseconds to timeout making a new // connection to this upstream. Defaults to 5000 (5 seconds) if not set. ConnectTimeoutMs int `mapstructure:"connect_timeout_ms"` // TCP keepalive settings for remote gateway upstreams (mesh gateways and terminating gateway upstreams). // See: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#envoy-v3-api-msg-config-core-v3-tcpkeepalive TcpKeepaliveEnable bool `mapstructure:"envoy_gateway_remote_tcp_enable_keepalive"` TcpKeepaliveTime int `mapstructure:"envoy_gateway_remote_tcp_keepalive_time"` TcpKeepaliveInterval int `mapstructure:"envoy_gateway_remote_tcp_keepalive_interval"` TcpKeepaliveProbes int `mapstructure:"envoy_gateway_remote_tcp_keepalive_probes"` } // ParseGatewayConfig returns the GatewayConfig parsed from an opaque map. If an // error occurs during parsing, it is returned along with the default config. This // allows the caller to choose whether and how to report the error func ParseGatewayConfig(m map[string]interface{}) (GatewayConfig, error) { var cfg GatewayConfig if err := parseConfig(m, &cfg); err != nil { return cfg, err } if cfg.ConnectTimeoutMs < 1 { cfg.ConnectTimeoutMs = 5000 } cfg.DNSDiscoveryType = strings.ToLower(cfg.DNSDiscoveryType) return cfg, nil } // Return an envoy.OutlierDetection populated by the values from structs.PassiveHealthCheck. // If all values are zero a default empty OutlierDetection will be returned to // enable outlier detection with default values. // - If override is not nil, it will overwrite the values from p, e.g., ingress gateway defaults // - allowZero is added to handle the legacy case where connect-proxy and mesh gateway can set 0 // for EnforcingConsecutive5xx. Due to the definition of proto of PassiveHealthCheck, ingress // gateway's EnforcingConsecutive5xx must be > 0. func ToOutlierDetection(p *structs.PassiveHealthCheck, override *structs.PassiveHealthCheck, allowZero bool) *envoy_cluster_v3.OutlierDetection { od := &envoy_cluster_v3.OutlierDetection{} if p != nil { if p.Interval != 0 { od.Interval = durationpb.New(p.Interval) } if p.MaxFailures != 0 { od.Consecutive_5Xx = &wrapperspb.UInt32Value{Value: p.MaxFailures} } if p.EnforcingConsecutive5xx != nil { // NOTE: EnforcingConsecutive5xx must be greater than 0 for ingress-gateway if *p.EnforcingConsecutive5xx != 0 { od.EnforcingConsecutive_5Xx = &wrapperspb.UInt32Value{Value: *p.EnforcingConsecutive5xx} } else if allowZero { od.EnforcingConsecutive_5Xx = &wrapperspb.UInt32Value{Value: *p.EnforcingConsecutive5xx} } } if p.MaxEjectionPercent != nil { od.MaxEjectionPercent = &wrapperspb.UInt32Value{Value: *p.MaxEjectionPercent} } if p.BaseEjectionTime != nil { od.BaseEjectionTime = durationpb.New(*p.BaseEjectionTime) } } if override == nil { return od } // override the default outlier detection value if override.Interval != 0 { od.Interval = durationpb.New(override.Interval) } if override.MaxFailures != 0 { od.Consecutive_5Xx = &wrapperspb.UInt32Value{Value: override.MaxFailures} } if override.EnforcingConsecutive5xx != nil { // NOTE: EnforcingConsecutive5xx must be great than 0 for ingress-gateway if *override.EnforcingConsecutive5xx != 0 { od.EnforcingConsecutive_5Xx = &wrapperspb.UInt32Value{Value: *override.EnforcingConsecutive5xx} } else if allowZero { od.EnforcingConsecutive_5Xx = &wrapperspb.UInt32Value{Value: *override.EnforcingConsecutive5xx} } } if override.MaxEjectionPercent != nil { od.MaxEjectionPercent = &wrapperspb.UInt32Value{Value: *override.MaxEjectionPercent} } if override.BaseEjectionTime != nil { od.BaseEjectionTime = durationpb.New(*override.BaseEjectionTime) } return od }