2018-09-12 16:07:47 +00:00
|
|
|
package structs
|
|
|
|
|
|
|
|
import (
|
2019-07-12 16:57:31 +00:00
|
|
|
"encoding/json"
|
2018-09-12 16:07:47 +00:00
|
|
|
"fmt"
|
2021-03-26 19:43:57 +00:00
|
|
|
"net"
|
2019-10-29 18:13:36 +00:00
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2019-07-12 16:57:31 +00:00
|
|
|
"github.com/hashicorp/consul/lib"
|
2019-09-26 02:55:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultExposeProtocol = "http"
|
2018-09-12 16:07:47 +00:00
|
|
|
)
|
|
|
|
|
2019-09-26 02:55:52 +00:00
|
|
|
var allowedExposeProtocols = map[string]bool{"http": true, "http2": true}
|
|
|
|
|
2019-06-18 00:52:01 +00:00
|
|
|
type MeshGatewayMode string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// MeshGatewayModeDefault represents no specific mode and should
|
|
|
|
// be used to indicate that a different layer of the configuration
|
|
|
|
// chain should take precedence
|
|
|
|
MeshGatewayModeDefault MeshGatewayMode = ""
|
|
|
|
|
|
|
|
// MeshGatewayModeNone represents that the Upstream Connect connections
|
|
|
|
// should be direct and not flow through a mesh gateway.
|
|
|
|
MeshGatewayModeNone MeshGatewayMode = "none"
|
|
|
|
|
2021-04-11 21:48:04 +00:00
|
|
|
// MeshGatewayModeLocal represents that the Upstream Connect connections
|
|
|
|
// should be made to a mesh gateway in the local datacenter.
|
2019-06-18 00:52:01 +00:00
|
|
|
MeshGatewayModeLocal MeshGatewayMode = "local"
|
|
|
|
|
|
|
|
// MeshGatewayModeRemote represents that the Upstream Connect connections
|
|
|
|
// should be made to a mesh gateway in a remote datacenter.
|
|
|
|
MeshGatewayModeRemote MeshGatewayMode = "remote"
|
|
|
|
)
|
|
|
|
|
2021-04-13 16:12:13 +00:00
|
|
|
const (
|
|
|
|
// TODO (freddy) Should we have a TopologySourceMixed when there is a mix of proxy reg and tproxy?
|
|
|
|
// Currently we label as proxy-registration if ANY instance has the explicit upstream definition.
|
|
|
|
// TopologySourceRegistration is used to label upstreams or downstreams from explicit upstream definitions
|
|
|
|
TopologySourceRegistration = "proxy-registration"
|
|
|
|
|
|
|
|
// TopologySourceSpecificIntention is used to label upstreams or downstreams from specific intentions
|
|
|
|
TopologySourceSpecificIntention = "specific-intention"
|
|
|
|
|
|
|
|
// TopologySourceWildcardIntention is used to label upstreams or downstreams from wildcard intentions
|
|
|
|
TopologySourceWildcardIntention = "wildcard-intention"
|
|
|
|
|
|
|
|
// TopologySourceDefaultAllow is used to label upstreams or downstreams from default allow ACL policy
|
|
|
|
TopologySourceDefaultAllow = "default-allow"
|
|
|
|
)
|
|
|
|
|
2019-06-18 00:52:01 +00:00
|
|
|
// MeshGatewayConfig controls how Mesh Gateways are configured and used
|
|
|
|
// This is a struct to allow for future additions without having more free-hanging
|
|
|
|
// configuration items all over the place
|
|
|
|
type MeshGatewayConfig struct {
|
|
|
|
// The Mesh Gateway routing mode
|
|
|
|
Mode MeshGatewayMode `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
2019-08-02 03:03:34 +00:00
|
|
|
func (c *MeshGatewayConfig) IsZero() bool {
|
|
|
|
zeroVal := MeshGatewayConfig{}
|
|
|
|
return *c == zeroVal
|
|
|
|
}
|
|
|
|
|
|
|
|
func (base *MeshGatewayConfig) OverlayWith(overlay MeshGatewayConfig) MeshGatewayConfig {
|
|
|
|
out := *base
|
|
|
|
if overlay.Mode != MeshGatewayModeDefault {
|
|
|
|
out.Mode = overlay.Mode
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-07-24 21:01:42 +00:00
|
|
|
func ValidateMeshGatewayMode(mode string) (MeshGatewayMode, error) {
|
|
|
|
switch MeshGatewayMode(mode) {
|
|
|
|
case MeshGatewayModeNone:
|
|
|
|
return MeshGatewayModeNone, nil
|
|
|
|
case MeshGatewayModeDefault:
|
|
|
|
return MeshGatewayModeDefault, nil
|
|
|
|
case MeshGatewayModeLocal:
|
|
|
|
return MeshGatewayModeLocal, nil
|
|
|
|
case MeshGatewayModeRemote:
|
|
|
|
return MeshGatewayModeRemote, nil
|
|
|
|
default:
|
|
|
|
return MeshGatewayModeDefault, fmt.Errorf("Invalid Mesh Gateway Mode: %q", mode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-01 23:47:58 +00:00
|
|
|
func (c *MeshGatewayConfig) ToAPI() api.MeshGatewayConfig {
|
|
|
|
return api.MeshGatewayConfig{Mode: api.MeshGatewayMode(c.Mode)}
|
|
|
|
}
|
|
|
|
|
2021-04-12 15:35:14 +00:00
|
|
|
type ProxyMode string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// ProxyModeDefault represents no specific mode and should
|
|
|
|
// be used to indicate that a different layer of the configuration
|
|
|
|
// chain should take precedence
|
|
|
|
ProxyModeDefault ProxyMode = ""
|
|
|
|
|
|
|
|
// ProxyModeTransparent represents that inbound and outbound application
|
|
|
|
// traffic is being captured and redirected through the proxy.
|
|
|
|
ProxyModeTransparent ProxyMode = "transparent"
|
|
|
|
|
|
|
|
// ProxyModeDirect represents that the proxy's listeners must be dialed directly
|
|
|
|
// by the local application and other proxies.
|
|
|
|
ProxyModeDirect ProxyMode = "direct"
|
|
|
|
)
|
|
|
|
|
|
|
|
func ValidateProxyMode(mode string) (ProxyMode, error) {
|
|
|
|
switch ProxyMode(mode) {
|
|
|
|
case ProxyModeDefault:
|
|
|
|
return ProxyModeDefault, nil
|
|
|
|
case ProxyModeDirect:
|
|
|
|
return ProxyModeDirect, nil
|
|
|
|
case ProxyModeTransparent:
|
|
|
|
return ProxyModeTransparent, nil
|
|
|
|
default:
|
|
|
|
return ProxyModeDefault, fmt.Errorf("Invalid Proxy Mode: %q", mode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type TransparentProxyConfig struct {
|
|
|
|
// The port of the listener where outbound application traffic is being redirected to.
|
|
|
|
OutboundListenerPort int `json:",omitempty" alias:"outbound_listener_port"`
|
2021-06-09 20:34:17 +00:00
|
|
|
|
|
|
|
// DialedDirectly indicates whether transparent proxies can dial this proxy instance directly.
|
|
|
|
// The discovery chain is not considered when dialing a service instance directly.
|
|
|
|
// This setting is useful when addressing stateful services, such as a database cluster with a leader node.
|
|
|
|
DialedDirectly bool `json:",omitempty" alias:"dialed_directly"`
|
2021-04-12 15:35:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 17:54:56 +00:00
|
|
|
func (c TransparentProxyConfig) ToAPI() *api.TransparentProxyConfig {
|
2021-06-15 19:53:35 +00:00
|
|
|
if c.IsZero() {
|
|
|
|
return nil
|
|
|
|
}
|
2021-06-09 20:34:17 +00:00
|
|
|
return &api.TransparentProxyConfig{
|
|
|
|
OutboundListenerPort: c.OutboundListenerPort,
|
|
|
|
DialedDirectly: c.DialedDirectly,
|
|
|
|
}
|
2021-04-12 15:35:14 +00:00
|
|
|
}
|
|
|
|
|
2021-06-15 19:53:35 +00:00
|
|
|
func (c *TransparentProxyConfig) IsZero() bool {
|
|
|
|
zeroVal := TransparentProxyConfig{}
|
|
|
|
return *c == zeroVal
|
|
|
|
}
|
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
// ConnectProxyConfig describes the configuration needed for any proxy managed
|
|
|
|
// or unmanaged. It describes a single logical service's listener and optionally
|
|
|
|
// upstreams and sidecar-related config for a single instance. To describe a
|
2019-03-06 17:13:28 +00:00
|
|
|
// centralized proxy that routed traffic for multiple services, a different one
|
2018-09-12 16:07:47 +00:00
|
|
|
// of these would be needed for each, sharing the same LogicalProxyID.
|
|
|
|
type ConnectProxyConfig struct {
|
|
|
|
// DestinationServiceName is required and is the name of the service to accept
|
|
|
|
// traffic for.
|
2020-09-24 18:58:52 +00:00
|
|
|
DestinationServiceName string `json:",omitempty" alias:"destination_service_name"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// DestinationServiceID is optional and should only be specified for
|
|
|
|
// "side-car" style proxies where the proxy is in front of just a single
|
|
|
|
// instance of the service. It should be set to the service ID of the instance
|
|
|
|
// being represented which must be registered to the same agent. It's valid to
|
|
|
|
// provide a service ID that does not yet exist to avoid timing issues when
|
|
|
|
// bootstrapping a service with a proxy.
|
2020-09-24 18:58:52 +00:00
|
|
|
DestinationServiceID string `json:",omitempty" alias:"destination_service_id"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// LocalServiceAddress is the address of the local service instance. It is
|
|
|
|
// optional and should only be specified for "side-car" style proxies. It will
|
|
|
|
// default to 127.0.0.1 if the proxy is a "side-car" (DestinationServiceID is
|
|
|
|
// set) but otherwise will be ignored.
|
2020-09-24 18:58:52 +00:00
|
|
|
LocalServiceAddress string `json:",omitempty" alias:"local_service_address"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// LocalServicePort is the port of the local service instance. It is optional
|
|
|
|
// and should only be specified for "side-car" style proxies. It will default
|
|
|
|
// to the registered port for the instance if the proxy is a "side-car"
|
|
|
|
// (DestinationServiceID is set) but otherwise will be ignored.
|
2020-09-24 18:58:52 +00:00
|
|
|
LocalServicePort int `json:",omitempty" alias:"local_service_port"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
2021-05-04 04:43:55 +00:00
|
|
|
// LocalServiceSocketPath is the socket of the local service instance. It is optional
|
|
|
|
// and should only be specified for "side-car" style proxies.
|
|
|
|
LocalServiceSocketPath string `json:",omitempty" alias:"local_service_socket_path"`
|
|
|
|
|
2021-04-12 15:35:14 +00:00
|
|
|
// Mode represents how the proxy's inbound and upstream listeners are dialed.
|
|
|
|
Mode ProxyMode
|
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
// Config is the arbitrary configuration data provided with the proxy
|
|
|
|
// registration.
|
2019-04-16 16:00:15 +00:00
|
|
|
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// Upstreams describes any upstream dependencies the proxy instance should
|
|
|
|
// setup.
|
2018-09-27 13:33:12 +00:00
|
|
|
Upstreams Upstreams `json:",omitempty"`
|
2019-06-18 00:52:01 +00:00
|
|
|
|
|
|
|
// MeshGateway defines the mesh gateway configuration for this upstream
|
2020-05-27 18:28:28 +00:00
|
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
2019-09-26 02:55:52 +00:00
|
|
|
|
|
|
|
// Expose defines whether checks or paths are exposed through the proxy
|
|
|
|
Expose ExposeConfig `json:",omitempty"`
|
2021-03-11 06:08:41 +00:00
|
|
|
|
2021-04-12 15:35:14 +00:00
|
|
|
// TransparentProxy defines configuration for when the proxy is in
|
|
|
|
// transparent mode.
|
|
|
|
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
2018-09-12 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 18:13:36 +00:00
|
|
|
func (t *ConnectProxyConfig) UnmarshalJSON(data []byte) (err error) {
|
|
|
|
type Alias ConnectProxyConfig
|
|
|
|
aux := &struct {
|
2021-04-12 15:35:14 +00:00
|
|
|
DestinationServiceNameSnake string `json:"destination_service_name"`
|
|
|
|
DestinationServiceIDSnake string `json:"destination_service_id"`
|
|
|
|
LocalServiceAddressSnake string `json:"local_service_address"`
|
|
|
|
LocalServicePortSnake int `json:"local_service_port"`
|
2021-05-04 04:43:55 +00:00
|
|
|
LocalServiceSocketPathSnake string `json:"local_service_socket_path"`
|
2021-04-12 15:35:14 +00:00
|
|
|
MeshGatewaySnake MeshGatewayConfig `json:"mesh_gateway"`
|
|
|
|
TransparentProxySnake TransparentProxyConfig `json:"transparent_proxy"`
|
2019-10-29 18:13:36 +00:00
|
|
|
*Alias
|
|
|
|
}{
|
|
|
|
Alias: (*Alias)(t),
|
|
|
|
}
|
2019-12-06 16:14:56 +00:00
|
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
2019-10-29 18:13:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if t.DestinationServiceName == "" {
|
|
|
|
t.DestinationServiceName = aux.DestinationServiceNameSnake
|
|
|
|
}
|
|
|
|
if t.DestinationServiceID == "" {
|
|
|
|
t.DestinationServiceID = aux.DestinationServiceIDSnake
|
|
|
|
}
|
|
|
|
if t.LocalServiceAddress == "" {
|
|
|
|
t.LocalServiceAddress = aux.LocalServiceAddressSnake
|
|
|
|
}
|
|
|
|
if t.LocalServicePort == 0 {
|
|
|
|
t.LocalServicePort = aux.LocalServicePortSnake
|
|
|
|
}
|
2021-05-04 04:43:55 +00:00
|
|
|
if t.LocalServiceSocketPath == "" {
|
|
|
|
t.LocalServiceSocketPath = aux.LocalServiceSocketPathSnake
|
|
|
|
}
|
2020-09-24 18:58:52 +00:00
|
|
|
if t.MeshGateway.Mode == "" {
|
|
|
|
t.MeshGateway.Mode = aux.MeshGatewaySnake.Mode
|
|
|
|
}
|
2021-04-12 15:35:14 +00:00
|
|
|
if t.TransparentProxy.OutboundListenerPort == 0 {
|
|
|
|
t.TransparentProxy.OutboundListenerPort = aux.TransparentProxySnake.OutboundListenerPort
|
2021-03-11 06:08:41 +00:00
|
|
|
}
|
2021-06-09 20:34:17 +00:00
|
|
|
if !t.TransparentProxy.DialedDirectly {
|
|
|
|
t.TransparentProxy.DialedDirectly = aux.TransparentProxySnake.DialedDirectly
|
|
|
|
}
|
2019-10-29 18:13:36 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-12 16:57:31 +00:00
|
|
|
func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) {
|
2021-06-15 19:53:35 +00:00
|
|
|
type Alias ConnectProxyConfig
|
|
|
|
out := struct {
|
|
|
|
TransparentProxy *TransparentProxyConfig `json:",omitempty"`
|
|
|
|
Alias
|
|
|
|
}{
|
|
|
|
Alias: (Alias)(*c),
|
|
|
|
}
|
2019-07-12 16:57:31 +00:00
|
|
|
|
2021-06-15 19:53:35 +00:00
|
|
|
proxyConfig, err := lib.MapWalk(c.Config)
|
2019-07-12 16:57:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-06-15 19:53:35 +00:00
|
|
|
out.Alias.Config = proxyConfig
|
|
|
|
|
|
|
|
if !c.TransparentProxy.IsZero() {
|
|
|
|
out.TransparentProxy = &out.Alias.TransparentProxy
|
|
|
|
}
|
2019-07-12 16:57:31 +00:00
|
|
|
|
2021-06-15 19:53:35 +00:00
|
|
|
return json.Marshal(&out)
|
2019-07-12 16:57:31 +00:00
|
|
|
}
|
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
// ToAPI returns the api struct with the same fields. We have duplicates to
|
|
|
|
// avoid the api package depending on this one which imports a ton of Consul's
|
|
|
|
// core which you don't want if you are just trying to use our client in your
|
|
|
|
// app.
|
|
|
|
func (c *ConnectProxyConfig) ToAPI() *api.AgentServiceConnectProxyConfig {
|
|
|
|
return &api.AgentServiceConnectProxyConfig{
|
|
|
|
DestinationServiceName: c.DestinationServiceName,
|
|
|
|
DestinationServiceID: c.DestinationServiceID,
|
|
|
|
LocalServiceAddress: c.LocalServiceAddress,
|
|
|
|
LocalServicePort: c.LocalServicePort,
|
2021-05-04 04:43:55 +00:00
|
|
|
LocalServiceSocketPath: c.LocalServiceSocketPath,
|
2021-04-12 15:35:14 +00:00
|
|
|
Mode: api.ProxyMode(c.Mode),
|
|
|
|
TransparentProxy: c.TransparentProxy.ToAPI(),
|
2018-09-12 16:07:47 +00:00
|
|
|
Config: c.Config,
|
|
|
|
Upstreams: c.Upstreams.ToAPI(),
|
2019-07-01 23:47:58 +00:00
|
|
|
MeshGateway: c.MeshGateway.ToAPI(),
|
2019-09-26 02:55:52 +00:00
|
|
|
Expose: c.Expose.ToAPI(),
|
2018-09-12 16:07:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
UpstreamDestTypeService = "service"
|
|
|
|
UpstreamDestTypePreparedQuery = "prepared_query"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Upstreams is a list of upstreams. Aliased to allow ToAPI method.
|
|
|
|
type Upstreams []Upstream
|
|
|
|
|
|
|
|
// ToAPI returns the api structs with the same fields. We have duplicates to
|
|
|
|
// avoid the api package depending on this one which imports a ton of Consul's
|
|
|
|
// core which you don't want if you are just trying to use our client in your
|
|
|
|
// app.
|
|
|
|
func (us Upstreams) ToAPI() []api.Upstream {
|
|
|
|
a := make([]api.Upstream, len(us))
|
|
|
|
for i, u := range us {
|
|
|
|
a[i] = u.ToAPI()
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2021-03-17 19:40:39 +00:00
|
|
|
func (us Upstreams) ToMap() map[string]*Upstream {
|
|
|
|
upstreamMap := make(map[string]*Upstream)
|
|
|
|
|
|
|
|
for i := range us {
|
|
|
|
upstreamMap[us[i].Identifier()] = &us[i]
|
|
|
|
}
|
|
|
|
return upstreamMap
|
|
|
|
}
|
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
// UpstreamsFromAPI is a helper for converting api.Upstream to Upstream.
|
|
|
|
func UpstreamsFromAPI(us []api.Upstream) Upstreams {
|
|
|
|
a := make([]Upstream, len(us))
|
|
|
|
for i, u := range us {
|
|
|
|
a[i] = UpstreamFromAPI(u)
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upstream represents a single upstream dependency for a service or proxy. It
|
|
|
|
// describes the mechanism used to discover instances to communicate with (the
|
|
|
|
// Target) as well as any potential client configuration that may be useful such
|
|
|
|
// as load balancer options, timeouts etc.
|
|
|
|
type Upstream struct {
|
|
|
|
// Destination fields are the required ones for determining what this upstream
|
|
|
|
// points to. Depending on DestinationType some other fields below might
|
|
|
|
// further restrict the set of instances allowable.
|
|
|
|
//
|
|
|
|
// DestinationType would be better as an int constant but even with custom
|
|
|
|
// JSON marshallers it causes havoc with all the mapstructure mangling we do
|
|
|
|
// on service definitions in various places.
|
2020-09-24 18:58:52 +00:00
|
|
|
DestinationType string `alias:"destination_type"`
|
|
|
|
DestinationNamespace string `json:",omitempty" alias:"destination_namespace"`
|
2021-08-20 16:57:45 +00:00
|
|
|
DestinationPartition string `json:",omitempty" alias:"destination_partition"`
|
2020-09-24 18:58:52 +00:00
|
|
|
DestinationName string `alias:"destination_name"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// Datacenter that the service discovery request should be run against. Note
|
|
|
|
// for prepared queries, the actual results might be from a different
|
|
|
|
// datacenter.
|
|
|
|
Datacenter string
|
|
|
|
|
|
|
|
// LocalBindAddress is the ip address a side-car proxy should listen on for
|
|
|
|
// traffic destined for this upstream service. Default if empty is 127.0.0.1.
|
2020-09-24 18:58:52 +00:00
|
|
|
LocalBindAddress string `json:",omitempty" alias:"local_bind_address"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// LocalBindPort is the ip address a side-car proxy should listen on for traffic
|
|
|
|
// destined for this upstream service. Required.
|
2021-03-26 19:43:57 +00:00
|
|
|
LocalBindPort int `json:",omitempty" alias:"local_bind_port"`
|
|
|
|
|
|
|
|
// These are exclusive with LocalBindAddress/LocalBindPort
|
|
|
|
LocalBindSocketPath string `json:",omitempty" alias:"local_bind_socket_path"`
|
2021-04-14 01:01:30 +00:00
|
|
|
// This might be represented as an int, but because it's octal outputs can be a bit strange.
|
|
|
|
LocalBindSocketMode string `json:",omitempty" alias:"local_bind_socket_mode"`
|
2018-09-12 16:07:47 +00:00
|
|
|
|
|
|
|
// Config is an opaque config that is specific to the proxy process being run.
|
2019-03-06 17:13:28 +00:00
|
|
|
// It can be used to pass arbitrary configuration for this specific upstream
|
2018-09-12 16:07:47 +00:00
|
|
|
// to the proxy.
|
2020-09-24 18:58:52 +00:00
|
|
|
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
|
2019-06-18 00:52:01 +00:00
|
|
|
|
|
|
|
// MeshGateway is the configuration for mesh gateway usage of this upstream
|
2020-09-24 18:58:52 +00:00
|
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
2020-04-23 15:06:19 +00:00
|
|
|
|
|
|
|
// IngressHosts are a list of hosts that should route to this upstream from
|
2020-05-01 00:51:41 +00:00
|
|
|
// an ingress gateway. This cannot and should not be set by a user, it is
|
|
|
|
// used internally to store the association of hosts to an upstream service.
|
2020-04-23 15:06:19 +00:00
|
|
|
IngressHosts []string `json:"-" bexpr:"-"`
|
2021-03-11 17:49:43 +00:00
|
|
|
|
|
|
|
// CentrallyConfigured indicates whether the upstream was defined in a proxy
|
|
|
|
// instance registration or whether it was generated from a config entry.
|
2021-03-15 22:02:03 +00:00
|
|
|
CentrallyConfigured bool `json:",omitempty" bexpr:"-"`
|
2018-09-12 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 18:13:36 +00:00
|
|
|
func (t *Upstream) UnmarshalJSON(data []byte) (err error) {
|
|
|
|
type Alias Upstream
|
|
|
|
aux := &struct {
|
|
|
|
DestinationTypeSnake string `json:"destination_type"`
|
2021-08-20 16:57:45 +00:00
|
|
|
DestinationPartitionSnake string `json:"destination_partition"`
|
2019-10-29 18:13:36 +00:00
|
|
|
DestinationNamespaceSnake string `json:"destination_namespace"`
|
|
|
|
DestinationNameSnake string `json:"destination_name"`
|
2020-09-24 18:58:52 +00:00
|
|
|
|
|
|
|
LocalBindAddressSnake string `json:"local_bind_address"`
|
|
|
|
LocalBindPortSnake int `json:"local_bind_port"`
|
|
|
|
|
2021-03-26 19:43:57 +00:00
|
|
|
LocalBindSocketPathSnake string `json:"local_bind_socket_path"`
|
2021-04-14 01:01:30 +00:00
|
|
|
LocalBindSocketModeSnake string `json:"local_bind_socket_mode"`
|
2021-03-26 19:43:57 +00:00
|
|
|
|
2020-09-24 18:58:52 +00:00
|
|
|
MeshGatewaySnake MeshGatewayConfig `json:"mesh_gateway"`
|
2019-10-29 18:13:36 +00:00
|
|
|
|
|
|
|
*Alias
|
|
|
|
}{
|
|
|
|
Alias: (*Alias)(t),
|
|
|
|
}
|
2019-12-06 16:14:56 +00:00
|
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
2019-10-29 18:13:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if t.DestinationType == "" {
|
|
|
|
t.DestinationType = aux.DestinationTypeSnake
|
|
|
|
}
|
|
|
|
if t.DestinationNamespace == "" {
|
|
|
|
t.DestinationNamespace = aux.DestinationNamespaceSnake
|
|
|
|
}
|
2021-08-20 16:57:45 +00:00
|
|
|
if t.DestinationPartition == "" {
|
|
|
|
t.DestinationPartition = aux.DestinationPartitionSnake
|
|
|
|
}
|
2019-10-29 18:13:36 +00:00
|
|
|
if t.DestinationName == "" {
|
|
|
|
t.DestinationName = aux.DestinationNameSnake
|
|
|
|
}
|
2020-09-24 18:58:52 +00:00
|
|
|
if t.LocalBindAddress == "" {
|
|
|
|
t.LocalBindAddress = aux.LocalBindAddressSnake
|
|
|
|
}
|
2019-10-29 18:13:36 +00:00
|
|
|
if t.LocalBindPort == 0 {
|
|
|
|
t.LocalBindPort = aux.LocalBindPortSnake
|
|
|
|
}
|
2021-03-26 19:43:57 +00:00
|
|
|
if t.LocalBindSocketPath == "" {
|
|
|
|
t.LocalBindSocketPath = aux.LocalBindSocketPathSnake
|
|
|
|
}
|
2021-04-14 01:01:30 +00:00
|
|
|
if t.LocalBindSocketMode == "" {
|
2021-03-26 19:43:57 +00:00
|
|
|
t.LocalBindSocketMode = aux.LocalBindSocketModeSnake
|
|
|
|
}
|
2020-09-24 18:58:52 +00:00
|
|
|
if t.MeshGateway.Mode == "" {
|
|
|
|
t.MeshGateway.Mode = aux.MeshGatewaySnake.Mode
|
2019-10-29 18:13:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
// Validate sanity checks the struct is valid
|
|
|
|
func (u *Upstream) Validate() error {
|
2019-08-01 18:26:02 +00:00
|
|
|
switch u.DestinationType {
|
|
|
|
case UpstreamDestTypePreparedQuery:
|
|
|
|
case UpstreamDestTypeService, "":
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unknown upstream destination type: %q", u.DestinationType)
|
2018-09-12 16:07:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if u.DestinationName == "" {
|
|
|
|
return fmt.Errorf("upstream destination name cannot be empty")
|
|
|
|
}
|
2021-03-20 02:56:02 +00:00
|
|
|
if u.DestinationName == WildcardSpecifier && !u.CentrallyConfigured {
|
|
|
|
return fmt.Errorf("upstream destination name cannot be a wildcard")
|
|
|
|
}
|
2018-09-12 16:07:47 +00:00
|
|
|
|
2021-03-26 20:00:44 +00:00
|
|
|
if u.LocalBindPort == 0 && u.LocalBindSocketPath == "" && !u.CentrallyConfigured {
|
|
|
|
return fmt.Errorf("upstream local bind port or local socket path must be defined and nonzero")
|
|
|
|
}
|
|
|
|
if u.LocalBindPort != 0 && u.LocalBindSocketPath != "" && !u.CentrallyConfigured {
|
|
|
|
return fmt.Errorf("only one of upstream local bind port or local socket path can be defined and nonzero")
|
2018-09-12 16:07:47 +00:00
|
|
|
}
|
2021-04-15 19:21:44 +00:00
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToAPI returns the api structs with the same fields. We have duplicates to
|
|
|
|
// avoid the api package depending on this one which imports a ton of Consul's
|
|
|
|
// core which you don't want if you are just trying to use our client in your
|
|
|
|
// app.
|
|
|
|
func (u *Upstream) ToAPI() api.Upstream {
|
|
|
|
return api.Upstream{
|
|
|
|
DestinationType: api.UpstreamDestType(u.DestinationType),
|
|
|
|
DestinationNamespace: u.DestinationNamespace,
|
2021-08-20 16:57:45 +00:00
|
|
|
DestinationPartition: u.DestinationPartition,
|
2018-09-12 16:07:47 +00:00
|
|
|
DestinationName: u.DestinationName,
|
|
|
|
Datacenter: u.Datacenter,
|
|
|
|
LocalBindAddress: u.LocalBindAddress,
|
|
|
|
LocalBindPort: u.LocalBindPort,
|
2021-03-26 19:43:57 +00:00
|
|
|
LocalBindSocketPath: u.LocalBindSocketPath,
|
|
|
|
LocalBindSocketMode: u.LocalBindSocketMode,
|
2018-09-12 16:07:47 +00:00
|
|
|
Config: u.Config,
|
2019-07-12 21:19:37 +00:00
|
|
|
MeshGateway: u.MeshGateway.ToAPI(),
|
2018-09-12 16:07:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-01 18:26:02 +00:00
|
|
|
// ToKey returns a value-type representation that uniquely identifies the
|
|
|
|
// upstream in a canonical way. Set and unset values are deliberately handled
|
|
|
|
// differently.
|
|
|
|
//
|
|
|
|
// These fields should be user-specificed explicit values and not inferred
|
|
|
|
// values.
|
|
|
|
func (u *Upstream) ToKey() UpstreamKey {
|
|
|
|
return UpstreamKey{
|
|
|
|
DestinationType: u.DestinationType,
|
2021-08-20 16:57:45 +00:00
|
|
|
DestinationPartition: u.DestinationPartition,
|
2019-08-01 18:26:02 +00:00
|
|
|
DestinationNamespace: u.DestinationNamespace,
|
|
|
|
DestinationName: u.DestinationName,
|
|
|
|
Datacenter: u.Datacenter,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-26 19:43:57 +00:00
|
|
|
func (u Upstream) HasLocalPortOrSocket() bool {
|
|
|
|
return (u.LocalBindPort != 0 || u.LocalBindSocketPath != "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u Upstream) UpstreamIsUnixSocket() bool {
|
|
|
|
return (u.LocalBindPort == 0 && u.LocalBindAddress == "" && u.LocalBindSocketPath != "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u Upstream) UpstreamAddressToString() string {
|
|
|
|
if u.UpstreamIsUnixSocket() {
|
|
|
|
return u.LocalBindSocketPath
|
|
|
|
}
|
|
|
|
|
|
|
|
addr := u.LocalBindAddress
|
|
|
|
if addr == "" {
|
|
|
|
addr = "127.0.0.1"
|
|
|
|
}
|
|
|
|
return net.JoinHostPort(addr, fmt.Sprintf("%d", u.LocalBindPort))
|
|
|
|
}
|
|
|
|
|
2019-08-01 18:26:02 +00:00
|
|
|
type UpstreamKey struct {
|
|
|
|
DestinationType string
|
|
|
|
DestinationName string
|
2021-08-20 16:57:45 +00:00
|
|
|
DestinationPartition string
|
2019-08-01 18:26:02 +00:00
|
|
|
DestinationNamespace string
|
|
|
|
Datacenter string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k UpstreamKey) String() string {
|
|
|
|
return fmt.Sprintf(
|
2021-08-20 16:57:45 +00:00
|
|
|
"[type=%q, name=%q, partition=%q, namespace=%q, datacenter=%q]",
|
2019-08-01 18:26:02 +00:00
|
|
|
k.DestinationType,
|
|
|
|
k.DestinationName,
|
2021-08-20 16:57:45 +00:00
|
|
|
k.DestinationPartition,
|
2019-08-01 18:26:02 +00:00
|
|
|
k.DestinationNamespace,
|
|
|
|
k.Datacenter,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-10-03 12:36:38 +00:00
|
|
|
// String implements Stringer by returning the Identifier.
|
|
|
|
func (u *Upstream) String() string {
|
|
|
|
return u.Identifier()
|
|
|
|
}
|
|
|
|
|
2018-09-12 16:07:47 +00:00
|
|
|
// UpstreamFromAPI is a helper for converting api.Upstream to Upstream.
|
|
|
|
func UpstreamFromAPI(u api.Upstream) Upstream {
|
|
|
|
return Upstream{
|
|
|
|
DestinationType: string(u.DestinationType),
|
2021-08-20 16:57:45 +00:00
|
|
|
DestinationPartition: u.DestinationPartition,
|
2018-09-12 16:07:47 +00:00
|
|
|
DestinationNamespace: u.DestinationNamespace,
|
|
|
|
DestinationName: u.DestinationName,
|
|
|
|
Datacenter: u.Datacenter,
|
|
|
|
LocalBindAddress: u.LocalBindAddress,
|
|
|
|
LocalBindPort: u.LocalBindPort,
|
2021-03-26 19:43:57 +00:00
|
|
|
LocalBindSocketPath: u.LocalBindSocketPath,
|
|
|
|
LocalBindSocketMode: u.LocalBindSocketMode,
|
2018-09-12 16:07:47 +00:00
|
|
|
Config: u.Config,
|
|
|
|
}
|
|
|
|
}
|
2019-09-26 02:55:52 +00:00
|
|
|
|
|
|
|
// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
|
|
|
|
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
|
|
|
|
type ExposeConfig struct {
|
|
|
|
// Checks defines whether paths associated with Consul checks will be exposed.
|
|
|
|
// This flag triggers exposing all HTTP and GRPC check paths registered for the service.
|
|
|
|
Checks bool `json:",omitempty"`
|
|
|
|
|
|
|
|
// Paths is the list of paths exposed through the proxy.
|
|
|
|
Paths []ExposePath `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
server: config entry replication now correctly uses namespaces in comparisons (#9024)
Previously config entries sharing a kind & name but in different
namespaces could occasionally cause "stuck states" in replication
because the namespace fields were ignored during the differential
comparison phase.
Example:
Two config entries written to the primary:
kind=A,name=web,namespace=bar
kind=A,name=web,namespace=foo
Under the covers these both get saved to memdb, so they are sorted by
all 3 components (kind,name,namespace) during natural iteration. This
means that before the replication code does it's own incomplete sort,
the underlying data IS sorted by namespace ascending (bar comes before
foo).
After one pass of replication the primary and secondary datacenters have
the same set of config entries present. If
"kind=A,name=web,namespace=bar" were to be deleted, then things get
weird. Before replication the two sides look like:
primary: [
kind=A,name=web,namespace=foo
]
secondary: [
kind=A,name=web,namespace=bar
kind=A,name=web,namespace=foo
]
The differential comparison phase walks these two lists in sorted order
and first compares "kind=A,name=web,namespace=foo" vs
"kind=A,name=web,namespace=bar" and falsely determines they are the SAME
and are thus cause an update of "kind=A,name=web,namespace=foo". Then it
compares "<nothing>" with "kind=A,name=web,namespace=foo" and falsely
determines that the latter should be DELETED.
During reconciliation the deletes are processed before updates, and so
for a brief moment in the secondary "kind=A,name=web,namespace=foo" is
erroneously deleted and then immediately restored.
Unfortunately after this replication phase the final state is identical
to the initial state, so when it loops around again (rate limited) it
repeats the same set of operations indefinitely.
2020-10-23 18:41:54 +00:00
|
|
|
func (e ExposeConfig) Clone() ExposeConfig {
|
|
|
|
e2 := e
|
|
|
|
if len(e.Paths) > 0 {
|
|
|
|
e2.Paths = make([]ExposePath, 0, len(e.Paths))
|
|
|
|
for _, p := range e.Paths {
|
|
|
|
e2.Paths = append(e2.Paths, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return e2
|
|
|
|
}
|
|
|
|
|
2019-09-26 02:55:52 +00:00
|
|
|
type ExposePath struct {
|
|
|
|
// ListenerPort defines the port of the proxy's listener for exposed paths.
|
2020-05-27 18:28:28 +00:00
|
|
|
ListenerPort int `json:",omitempty" alias:"listener_port"`
|
2019-09-26 02:55:52 +00:00
|
|
|
|
2020-09-24 18:58:52 +00:00
|
|
|
// Path is the path to expose through the proxy, ie. "/metrics."
|
2019-09-26 02:55:52 +00:00
|
|
|
Path string `json:",omitempty"`
|
|
|
|
|
|
|
|
// LocalPathPort is the port that the service is listening on for the given path.
|
2020-05-27 18:28:28 +00:00
|
|
|
LocalPathPort int `json:",omitempty" alias:"local_path_port"`
|
2019-09-26 02:55:52 +00:00
|
|
|
|
|
|
|
// Protocol describes the upstream's service protocol.
|
|
|
|
// Valid values are "http" and "http2", defaults to "http"
|
|
|
|
Protocol string `json:",omitempty"`
|
|
|
|
|
|
|
|
// ParsedFromCheck is set if this path was parsed from a registered check
|
2020-09-24 18:58:52 +00:00
|
|
|
ParsedFromCheck bool `json:",omitempty" alias:"parsed_from_check"`
|
2019-09-26 02:55:52 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 18:13:36 +00:00
|
|
|
func (t *ExposePath) UnmarshalJSON(data []byte) (err error) {
|
|
|
|
type Alias ExposePath
|
|
|
|
aux := &struct {
|
2020-09-24 18:58:52 +00:00
|
|
|
ListenerPortSnake int `json:"listener_port"`
|
|
|
|
LocalPathPortSnake int `json:"local_path_port"`
|
|
|
|
ParsedFromCheckSnake bool `json:"parsed_from_check"`
|
2019-10-29 18:13:36 +00:00
|
|
|
|
|
|
|
*Alias
|
|
|
|
}{
|
|
|
|
Alias: (*Alias)(t),
|
|
|
|
}
|
2019-12-06 16:14:56 +00:00
|
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
2019-10-29 18:13:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if t.LocalPathPort == 0 {
|
|
|
|
t.LocalPathPort = aux.LocalPathPortSnake
|
|
|
|
}
|
|
|
|
if t.ListenerPort == 0 {
|
|
|
|
t.ListenerPort = aux.ListenerPortSnake
|
|
|
|
}
|
2020-09-24 18:58:52 +00:00
|
|
|
if aux.ParsedFromCheckSnake {
|
|
|
|
t.ParsedFromCheck = true
|
|
|
|
}
|
2019-10-29 18:13:36 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-26 02:55:52 +00:00
|
|
|
func (e *ExposeConfig) ToAPI() api.ExposeConfig {
|
|
|
|
paths := make([]api.ExposePath, 0)
|
|
|
|
for _, p := range e.Paths {
|
|
|
|
paths = append(paths, p.ToAPI())
|
|
|
|
}
|
|
|
|
if e.Paths == nil {
|
|
|
|
paths = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return api.ExposeConfig{
|
|
|
|
Checks: e.Checks,
|
|
|
|
Paths: paths,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *ExposePath) ToAPI() api.ExposePath {
|
|
|
|
return api.ExposePath{
|
|
|
|
ListenerPort: p.ListenerPort,
|
|
|
|
Path: p.Path,
|
|
|
|
LocalPathPort: p.LocalPathPort,
|
|
|
|
Protocol: p.Protocol,
|
|
|
|
ParsedFromCheck: p.ParsedFromCheck,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finalize validates ExposeConfig and sets default values
|
2020-01-28 23:50:41 +00:00
|
|
|
func (e *ExposeConfig) Finalize() {
|
2019-09-26 02:55:52 +00:00
|
|
|
for i := 0; i < len(e.Paths); i++ {
|
|
|
|
path := &e.Paths[i]
|
|
|
|
|
|
|
|
if path.Protocol == "" {
|
|
|
|
path.Protocol = defaultExposeProtocol
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|