mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 10:21:05 +02:00
In split service mode, and during periods of inactivity, the gRPC connections to the databroker may fall idle. Some network firewalls may eventually time out an idle TCP connection and even start dropping subsequent packets once connection traffic resumes. Combined with Linux default TCP retransmission settings, this could cause a broken connection to persist for over 15 minutes. In an attempt to avoid this scenario, enable TCP keepalive for outbound gRPC connections, matching the Go standard library default settings for time & interval: 15 seconds for both. (The probe count does not appear to be set, so it will remain at the OS default.) Add a test case exercising the BuildClusters() method with the default configuration options, comparing the results with a reference "golden" file in the testdata directory. Also add an '-update' flag to make it easier to update the reference golden when needed: go test ./config/envoyconfig -update
567 lines
18 KiB
Go
567 lines
18 KiB
Go
package envoyconfig
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
envoy_config_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
|
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
|
|
"github.com/pomerium/pomerium/config"
|
|
"github.com/pomerium/pomerium/internal/log"
|
|
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
|
"github.com/pomerium/pomerium/internal/urlutil"
|
|
)
|
|
|
|
// BuildClusters builds envoy clusters from the given config.
|
|
func (b *Builder) BuildClusters(ctx context.Context, cfg *config.Config) ([]*envoy_config_cluster_v3.Cluster, error) {
|
|
ctx, span := trace.StartSpan(ctx, "envoyconfig.Builder.BuildClusters")
|
|
defer span.End()
|
|
|
|
grpcURLs := []*url.URL{{
|
|
Scheme: "http",
|
|
Host: b.localGRPCAddress,
|
|
}}
|
|
httpURL := &url.URL{
|
|
Scheme: "http",
|
|
Host: b.localHTTPAddress,
|
|
}
|
|
metricsURL := &url.URL{
|
|
Scheme: "http",
|
|
Host: b.localMetricsAddress,
|
|
}
|
|
|
|
authorizeURLs, databrokerURLs := grpcURLs, grpcURLs
|
|
if !config.IsAll(cfg.Options.Services) {
|
|
var err error
|
|
authorizeURLs, err = cfg.Options.GetInternalAuthorizeURLs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
databrokerURLs, err = cfg.Options.GetDataBrokerURLs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
controlGRPC, err := b.buildInternalCluster(ctx, cfg, "pomerium-control-plane-grpc", grpcURLs, upstreamProtocolHTTP2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
controlHTTP, err := b.buildInternalCluster(ctx, cfg, "pomerium-control-plane-http", []*url.URL{httpURL}, upstreamProtocolAuto)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
controlMetrics, err := b.buildInternalCluster(ctx, cfg, "pomerium-control-plane-metrics", []*url.URL{metricsURL}, upstreamProtocolAuto)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authorizeCluster, err := b.buildInternalCluster(ctx, cfg, "pomerium-authorize", authorizeURLs, upstreamProtocolHTTP2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(authorizeURLs) > 1 {
|
|
authorizeCluster.HealthChecks = grpcHealthChecks("pomerium-authorize")
|
|
authorizeCluster.OutlierDetection = grpcOutlierDetection()
|
|
}
|
|
|
|
databrokerCluster, err := b.buildInternalCluster(ctx, cfg, "pomerium-databroker", databrokerURLs, upstreamProtocolHTTP2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(databrokerURLs) > 1 {
|
|
databrokerCluster.HealthChecks = grpcHealthChecks("pomerium-databroker")
|
|
databrokerCluster.OutlierDetection = grpcOutlierDetection()
|
|
}
|
|
|
|
envoyAdminCluster, err := b.buildEnvoyAdminCluster(ctx, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clusters := []*envoy_config_cluster_v3.Cluster{
|
|
b.buildACMETLSALPNCluster(cfg),
|
|
controlGRPC,
|
|
controlHTTP,
|
|
controlMetrics,
|
|
authorizeCluster,
|
|
databrokerCluster,
|
|
envoyAdminCluster,
|
|
}
|
|
|
|
tracingCluster, err := buildTracingCluster(cfg.Options)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if tracingCluster != nil {
|
|
clusters = append(clusters, tracingCluster)
|
|
}
|
|
|
|
if config.IsProxy(cfg.Options.Services) {
|
|
for i, p := range cfg.Options.GetAllPolicies() {
|
|
policy := p
|
|
if policy.EnvoyOpts == nil {
|
|
policy.EnvoyOpts = newDefaultEnvoyClusterConfig()
|
|
}
|
|
if len(policy.To) > 0 {
|
|
cluster, err := b.buildPolicyCluster(ctx, cfg, &policy)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("policy #%d: %w", i, err)
|
|
}
|
|
clusters = append(clusters, cluster)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err = validateClusters(clusters); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return clusters, nil
|
|
}
|
|
|
|
func (b *Builder) buildInternalCluster(
|
|
ctx context.Context,
|
|
cfg *config.Config,
|
|
name string,
|
|
dsts []*url.URL,
|
|
upstreamProtocol upstreamProtocolConfig,
|
|
) (*envoy_config_cluster_v3.Cluster, error) {
|
|
cluster := newDefaultEnvoyClusterConfig()
|
|
cluster.DnsLookupFamily = config.GetEnvoyDNSLookupFamily(cfg.Options.DNSLookupFamily)
|
|
// Match the Go standard library default TCP keepalive settings.
|
|
const keepaliveTimeSeconds = 15
|
|
cluster.UpstreamConnectionOptions = &envoy_config_cluster_v3.UpstreamConnectionOptions{
|
|
TcpKeepalive: &envoy_config_core_v3.TcpKeepalive{
|
|
KeepaliveTime: wrapperspb.UInt32(keepaliveTimeSeconds),
|
|
KeepaliveInterval: wrapperspb.UInt32(keepaliveTimeSeconds),
|
|
},
|
|
}
|
|
var endpoints []Endpoint
|
|
for _, dst := range dsts {
|
|
ts, err := b.buildInternalTransportSocket(ctx, cfg, dst)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endpoints = append(endpoints, NewEndpoint(dst, ts, 1))
|
|
}
|
|
if err := b.buildCluster(cluster, name, endpoints, upstreamProtocol); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cluster, nil
|
|
}
|
|
|
|
func (b *Builder) buildPolicyCluster(ctx context.Context, cfg *config.Config, policy *config.Policy) (*envoy_config_cluster_v3.Cluster, error) {
|
|
cluster := new(envoy_config_cluster_v3.Cluster)
|
|
proto.Merge(cluster, policy.EnvoyOpts)
|
|
|
|
options := cfg.Options
|
|
|
|
if options.EnvoyBindConfigFreebind.IsSet() || options.EnvoyBindConfigSourceAddress != "" {
|
|
cluster.UpstreamBindConfig = new(envoy_config_core_v3.BindConfig)
|
|
if options.EnvoyBindConfigFreebind.IsSet() {
|
|
cluster.UpstreamBindConfig.Freebind = wrapperspb.Bool(options.EnvoyBindConfigFreebind.Bool)
|
|
}
|
|
if options.EnvoyBindConfigSourceAddress != "" {
|
|
cluster.UpstreamBindConfig.SourceAddress = &envoy_config_core_v3.SocketAddress{
|
|
Address: options.EnvoyBindConfigSourceAddress,
|
|
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{
|
|
PortValue: 0,
|
|
},
|
|
}
|
|
} else {
|
|
cluster.UpstreamBindConfig.SourceAddress = &envoy_config_core_v3.SocketAddress{
|
|
Address: "0.0.0.0",
|
|
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{
|
|
PortValue: 0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
cluster.AltStatName = getClusterStatsName(policy)
|
|
upstreamProtocol := getUpstreamProtocolForPolicy(ctx, policy)
|
|
|
|
name := getClusterID(policy)
|
|
endpoints, err := b.buildPolicyEndpoints(ctx, cfg, policy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cluster.DnsLookupFamily = config.GetEnvoyDNSLookupFamily(options.DNSLookupFamily)
|
|
if policy.EnableGoogleCloudServerlessAuthentication {
|
|
cluster.DnsLookupFamily = envoy_config_cluster_v3.Cluster_V4_ONLY
|
|
}
|
|
|
|
if err := b.buildCluster(cluster, name, endpoints, upstreamProtocol); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cluster, nil
|
|
}
|
|
|
|
func (b *Builder) buildPolicyEndpoints(
|
|
ctx context.Context,
|
|
cfg *config.Config,
|
|
policy *config.Policy,
|
|
) ([]Endpoint, error) {
|
|
var endpoints []Endpoint
|
|
for _, dst := range policy.To {
|
|
dst := dst
|
|
ts, err := b.buildPolicyTransportSocket(ctx, cfg, policy, dst.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endpoints = append(endpoints, NewEndpoint(&dst.URL, ts, dst.LbWeight))
|
|
}
|
|
return endpoints, nil
|
|
}
|
|
|
|
func (b *Builder) buildInternalTransportSocket(
|
|
ctx context.Context,
|
|
cfg *config.Config,
|
|
endpoint *url.URL,
|
|
) (*envoy_config_core_v3.TransportSocket, error) {
|
|
if endpoint.Scheme != "https" {
|
|
return nil, nil
|
|
}
|
|
|
|
validationContext := &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
|
|
MatchTypedSubjectAltNames: []*envoy_extensions_transport_sockets_tls_v3.SubjectAltNameMatcher{
|
|
b.buildSubjectAltNameMatcher(endpoint, cfg.Options.OverrideCertificateName),
|
|
},
|
|
}
|
|
bs, err := getCombinedCertificateAuthority(cfg)
|
|
if err != nil {
|
|
log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found")
|
|
} else {
|
|
validationContext.TrustedCa = b.filemgr.BytesDataSource("ca.pem", bs)
|
|
}
|
|
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
|
|
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
|
|
AlpnProtocols: []string{"h2", "http/1.1"},
|
|
ValidationContextType: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
|
ValidationContext: validationContext,
|
|
},
|
|
},
|
|
Sni: b.buildSubjectNameIndication(endpoint, cfg.Options.OverrideCertificateName),
|
|
}
|
|
tlsConfig := marshalAny(tlsContext)
|
|
return &envoy_config_core_v3.TransportSocket{
|
|
Name: "tls",
|
|
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
|
|
TypedConfig: tlsConfig,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (b *Builder) buildPolicyTransportSocket(
|
|
ctx context.Context,
|
|
cfg *config.Config,
|
|
policy *config.Policy,
|
|
dst url.URL,
|
|
) (*envoy_config_core_v3.TransportSocket, error) {
|
|
if dst.Scheme != "https" {
|
|
return nil, nil
|
|
}
|
|
|
|
upstreamProtocol := getUpstreamProtocolForPolicy(ctx, policy)
|
|
|
|
vc, err := b.buildPolicyValidationContext(ctx, cfg, policy, dst)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sni := dst.Hostname()
|
|
if policy.TLSServerName != "" {
|
|
sni = policy.TLSServerName
|
|
}
|
|
if policy.TLSUpstreamServerName != "" {
|
|
sni = policy.TLSUpstreamServerName
|
|
}
|
|
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
|
|
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
|
|
TlsParams: &envoy_extensions_transport_sockets_tls_v3.TlsParameters{
|
|
CipherSuites: []string{
|
|
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
|
"ECDHE-RSA-AES256-GCM-SHA384",
|
|
"ECDHE-ECDSA-AES128-GCM-SHA256",
|
|
"ECDHE-RSA-AES128-GCM-SHA256",
|
|
"ECDHE-ECDSA-CHACHA20-POLY1305",
|
|
"ECDHE-RSA-CHACHA20-POLY1305",
|
|
"ECDHE-ECDSA-AES128-SHA",
|
|
"ECDHE-RSA-AES128-SHA",
|
|
"AES128-GCM-SHA256",
|
|
"AES128-SHA",
|
|
"ECDHE-ECDSA-AES256-SHA",
|
|
"ECDHE-RSA-AES256-SHA",
|
|
"AES256-GCM-SHA384",
|
|
"AES256-SHA",
|
|
},
|
|
EcdhCurves: []string{
|
|
"X25519",
|
|
"P-256",
|
|
"P-384",
|
|
"P-521",
|
|
},
|
|
},
|
|
AlpnProtocols: buildUpstreamALPN(upstreamProtocol),
|
|
ValidationContextType: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
|
|
ValidationContext: vc,
|
|
},
|
|
},
|
|
Sni: sni,
|
|
AllowRenegotiation: policy.TLSUpstreamAllowRenegotiation,
|
|
}
|
|
if policy.ClientCertificate != nil {
|
|
tlsContext.CommonTlsContext.TlsCertificates = append(tlsContext.CommonTlsContext.TlsCertificates,
|
|
b.envoyTLSCertificateFromGoTLSCertificate(ctx, policy.ClientCertificate))
|
|
}
|
|
|
|
tlsConfig := marshalAny(tlsContext)
|
|
return &envoy_config_core_v3.TransportSocket{
|
|
Name: "tls",
|
|
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
|
|
TypedConfig: tlsConfig,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (b *Builder) buildPolicyValidationContext(
|
|
ctx context.Context,
|
|
cfg *config.Config,
|
|
policy *config.Policy,
|
|
dst url.URL,
|
|
) (*envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext, error) {
|
|
overrideName := ""
|
|
if policy.TLSServerName != "" {
|
|
overrideName = policy.TLSServerName
|
|
}
|
|
if policy.TLSUpstreamServerName != "" {
|
|
overrideName = policy.TLSUpstreamServerName
|
|
}
|
|
validationContext := &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
|
|
MatchTypedSubjectAltNames: []*envoy_extensions_transport_sockets_tls_v3.SubjectAltNameMatcher{
|
|
b.buildSubjectAltNameMatcher(&dst, overrideName),
|
|
},
|
|
}
|
|
if policy.TLSCustomCAFile != "" {
|
|
validationContext.TrustedCa = b.filemgr.FileDataSource(policy.TLSCustomCAFile)
|
|
} else if policy.TLSCustomCA != "" {
|
|
bs, err := base64.StdEncoding.DecodeString(policy.TLSCustomCA)
|
|
if err != nil {
|
|
log.Error(ctx).Err(err).Msg("invalid custom CA certificate")
|
|
}
|
|
validationContext.TrustedCa = b.filemgr.BytesDataSource("custom-ca.pem", bs)
|
|
} else {
|
|
bs, err := getCombinedCertificateAuthority(cfg)
|
|
if err != nil {
|
|
log.Error(ctx).Err(err).Msg("unable to enable certificate verification because no root CAs were found")
|
|
} else {
|
|
validationContext.TrustedCa = b.filemgr.BytesDataSource("ca.pem", bs)
|
|
}
|
|
}
|
|
|
|
if policy.TLSSkipVerify {
|
|
validationContext.TrustChainVerification = envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_ACCEPT_UNTRUSTED
|
|
}
|
|
|
|
return validationContext, nil
|
|
}
|
|
|
|
func (b *Builder) buildCluster(
|
|
cluster *envoy_config_cluster_v3.Cluster,
|
|
name string,
|
|
endpoints []Endpoint,
|
|
upstreamProtocol upstreamProtocolConfig,
|
|
) error {
|
|
if len(endpoints) == 0 {
|
|
return errNoEndpoints
|
|
}
|
|
|
|
if cluster.ConnectTimeout == nil {
|
|
cluster.ConnectTimeout = defaultConnectionTimeout
|
|
}
|
|
cluster.RespectDnsTtl = true
|
|
lbEndpoints, err := b.buildLbEndpoints(endpoints)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cluster.Name = name
|
|
cluster.LoadAssignment = &envoy_config_endpoint_v3.ClusterLoadAssignment{
|
|
ClusterName: name,
|
|
Endpoints: []*envoy_config_endpoint_v3.LocalityLbEndpoints{{
|
|
LbEndpoints: lbEndpoints,
|
|
}},
|
|
}
|
|
cluster.TransportSocketMatches, err = b.buildTransportSocketMatches(endpoints)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Set the default transport socket to the first socket match. This is necessary so that ALPN
|
|
// auto configuration works.
|
|
if len(cluster.TransportSocketMatches) > 0 {
|
|
cluster.TransportSocket = cluster.TransportSocketMatches[0].TransportSocket
|
|
}
|
|
|
|
cluster.TypedExtensionProtocolOptions = buildTypedExtensionProtocolOptions(endpoints, upstreamProtocol)
|
|
cluster.ClusterDiscoveryType = getClusterDiscoveryType(lbEndpoints)
|
|
|
|
return cluster.Validate()
|
|
}
|
|
|
|
// grpcOutlierDetection defines slightly more aggressive malfunction detection for grpc endpoints
|
|
func grpcOutlierDetection() *envoy_config_cluster_v3.OutlierDetection {
|
|
return &envoy_config_cluster_v3.OutlierDetection{
|
|
Consecutive_5Xx: wrapperspb.UInt32(5),
|
|
Interval: durationpb.New(time.Second * 10),
|
|
BaseEjectionTime: durationpb.New(time.Second * 30),
|
|
MaxEjectionPercent: wrapperspb.UInt32(100),
|
|
EnforcingConsecutive_5Xx: wrapperspb.UInt32(100),
|
|
EnforcingSuccessRate: wrapperspb.UInt32(100),
|
|
SuccessRateMinimumHosts: wrapperspb.UInt32(2),
|
|
SuccessRateRequestVolume: wrapperspb.UInt32(10),
|
|
SuccessRateStdevFactor: wrapperspb.UInt32(1900),
|
|
ConsecutiveGatewayFailure: wrapperspb.UInt32(5),
|
|
EnforcingConsecutiveGatewayFailure: wrapperspb.UInt32(0),
|
|
SplitExternalLocalOriginErrors: false,
|
|
FailurePercentageThreshold: wrapperspb.UInt32(85),
|
|
EnforcingFailurePercentage: wrapperspb.UInt32(100),
|
|
EnforcingFailurePercentageLocalOrigin: wrapperspb.UInt32(100),
|
|
FailurePercentageMinimumHosts: wrapperspb.UInt32(2),
|
|
FailurePercentageRequestVolume: wrapperspb.UInt32(10),
|
|
MaxEjectionTime: durationpb.New(time.Minute * 5),
|
|
}
|
|
}
|
|
|
|
func grpcHealthChecks(name string) []*envoy_config_core_v3.HealthCheck {
|
|
return []*envoy_config_core_v3.HealthCheck{{
|
|
Timeout: durationpb.New(time.Second * 10),
|
|
Interval: durationpb.New(time.Second * 10),
|
|
InitialJitter: durationpb.New(time.Millisecond * 100),
|
|
IntervalJitter: durationpb.New(time.Millisecond * 100),
|
|
IntervalJitterPercent: 10,
|
|
UnhealthyThreshold: wrapperspb.UInt32(1),
|
|
HealthyThreshold: wrapperspb.UInt32(1),
|
|
ReuseConnection: wrapperspb.Bool(true),
|
|
NoTrafficInterval: durationpb.New(time.Minute),
|
|
HealthChecker: &envoy_config_core_v3.HealthCheck_GrpcHealthCheck_{
|
|
GrpcHealthCheck: &envoy_config_core_v3.HealthCheck_GrpcHealthCheck{
|
|
ServiceName: name,
|
|
},
|
|
},
|
|
}}
|
|
}
|
|
|
|
func (b *Builder) buildLbEndpoints(endpoints []Endpoint) ([]*envoy_config_endpoint_v3.LbEndpoint, error) {
|
|
var lbes []*envoy_config_endpoint_v3.LbEndpoint
|
|
for _, e := range endpoints {
|
|
defaultPort := 80
|
|
if e.transportSocket != nil && e.transportSocket.Name == "tls" {
|
|
defaultPort = 443
|
|
}
|
|
|
|
u := e.url
|
|
if e.url.Hostname() == "localhost" {
|
|
u.Host = strings.Replace(e.url.Host, "localhost", "127.0.0.1", -1)
|
|
}
|
|
|
|
lbe := &envoy_config_endpoint_v3.LbEndpoint{
|
|
HostIdentifier: &envoy_config_endpoint_v3.LbEndpoint_Endpoint{
|
|
Endpoint: &envoy_config_endpoint_v3.Endpoint{
|
|
Address: buildAddress(u.Host, defaultPort),
|
|
},
|
|
},
|
|
LoadBalancingWeight: e.loadBalancerWeight,
|
|
}
|
|
|
|
if e.transportSocket != nil {
|
|
lbe.Metadata = &envoy_config_core_v3.Metadata{
|
|
FilterMetadata: map[string]*structpb.Struct{
|
|
"envoy.transport_socket_match": {
|
|
Fields: map[string]*structpb.Value{
|
|
e.TransportSocketName(): structpb.NewBoolValue(true),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
lbes = append(lbes, lbe)
|
|
}
|
|
return lbes, nil
|
|
}
|
|
|
|
func (b *Builder) buildTransportSocketMatches(endpoints []Endpoint) ([]*envoy_config_cluster_v3.Cluster_TransportSocketMatch, error) {
|
|
var tsms []*envoy_config_cluster_v3.Cluster_TransportSocketMatch
|
|
seen := map[string]struct{}{}
|
|
for _, e := range endpoints {
|
|
if e.transportSocket == nil {
|
|
continue
|
|
}
|
|
|
|
// only add unique transport sockets
|
|
nm := e.TransportSocketName()
|
|
if _, ok := seen[nm]; ok {
|
|
continue
|
|
}
|
|
seen[nm] = struct{}{}
|
|
|
|
tsms = append(tsms, &envoy_config_cluster_v3.Cluster_TransportSocketMatch{
|
|
Name: nm,
|
|
Match: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
nm: structpb.NewBoolValue(true),
|
|
},
|
|
},
|
|
TransportSocket: e.transportSocket,
|
|
})
|
|
}
|
|
return tsms, nil
|
|
}
|
|
|
|
// validateClusters contains certain rules that must match
|
|
func validateClusters(clusters []*envoy_config_cluster_v3.Cluster) error {
|
|
return validateClusterNamesUnique(clusters)
|
|
}
|
|
|
|
// validateClusterNamesUnique checks cluster names are unique, as they're effectively IDs
|
|
func validateClusterNamesUnique(clusters []*envoy_config_cluster_v3.Cluster) error {
|
|
names := make(map[string]bool, len(clusters))
|
|
|
|
for _, c := range clusters {
|
|
if _, there := names[c.Name]; there {
|
|
return fmt.Errorf("route %s is not unique", c.Name)
|
|
}
|
|
names[c.Name] = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getClusterDiscoveryType(lbEndpoints []*envoy_config_endpoint_v3.LbEndpoint) *envoy_config_cluster_v3.Cluster_Type {
|
|
// for IPs we use a static discovery type, otherwise we use DNS
|
|
allIP := true
|
|
for _, lbe := range lbEndpoints {
|
|
if net.ParseIP(urlutil.StripPort(lbe.GetEndpoint().GetAddress().GetSocketAddress().GetAddress())) == nil {
|
|
allIP = false
|
|
}
|
|
}
|
|
if allIP {
|
|
return &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_STATIC}
|
|
}
|
|
return &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_STRICT_DNS}
|
|
}
|