mirror of https://github.com/status-im/consul.git
[NET-4799] [OSS] xdsv2: listeners L4 support for connect proxies (#18436)
* refactor to avoid future import cycles
This commit is contained in:
parent
0e94f48ce0
commit
6b7ccd06cf
|
@ -19,6 +19,8 @@ import (
|
|||
envoy_upstreams_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
|
||||
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
||||
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||
"github.com/hashicorp/consul/agent/xds/config"
|
||||
"github.com/hashicorp/consul/agent/xds/naming"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
@ -366,7 +368,7 @@ func makePassthroughClusters(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message,
|
|||
!meshConf.TransparentProxy.MeshDestinationsOnly {
|
||||
|
||||
clusters = append(clusters, &envoy_cluster_v3.Cluster{
|
||||
Name: OriginalDestinationClusterName,
|
||||
Name: naming.OriginalDestinationClusterName,
|
||||
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
||||
Type: envoy_cluster_v3.Cluster_ORIGINAL_DST,
|
||||
},
|
||||
|
@ -1041,7 +1043,7 @@ func (s *ResourceGenerator) configIngressUpstreamCluster(c *envoy_cluster_v3.Clu
|
|||
if svc != nil {
|
||||
override = svc.PassiveHealthCheck
|
||||
}
|
||||
outlierDetection := ToOutlierDetection(cfgSnap.IngressGateway.Defaults.PassiveHealthCheck, override, false)
|
||||
outlierDetection := config.ToOutlierDetection(cfgSnap.IngressGateway.Defaults.PassiveHealthCheck, override, false)
|
||||
|
||||
c.OutlierDetection = outlierDetection
|
||||
}
|
||||
|
@ -1050,7 +1052,7 @@ func (s *ResourceGenerator) makeAppCluster(cfgSnap *proxycfg.ConfigSnapshot, nam
|
|||
var c *envoy_cluster_v3.Cluster
|
||||
var err error
|
||||
|
||||
cfg, err := ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
cfg, err := config.ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1144,7 +1146,7 @@ func (s *ResourceGenerator) makeUpstreamClusterForPeerService(
|
|||
|
||||
clusterName := generatePeeredClusterName(uid, tbs)
|
||||
|
||||
outlierDetection := ToOutlierDetection(upstreamConfig.PassiveHealthCheck, nil, true)
|
||||
outlierDetection := config.ToOutlierDetection(upstreamConfig.PassiveHealthCheck, nil, true)
|
||||
// We can't rely on health checks for services on cluster peers because they
|
||||
// don't take into account service resolvers, splitters and routers. Setting
|
||||
// MaxEjectionPercent too 100% gives outlier detection the power to eject the
|
||||
|
@ -1279,7 +1281,7 @@ func (s *ResourceGenerator) makeUpstreamClusterForPreparedQuery(upstream structs
|
|||
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{
|
||||
Thresholds: makeThresholdsIfNeeded(cfg.Limits),
|
||||
},
|
||||
OutlierDetection: ToOutlierDetection(cfg.PassiveHealthCheck, nil, true),
|
||||
OutlierDetection: config.ToOutlierDetection(cfg.PassiveHealthCheck, nil, true),
|
||||
}
|
||||
if cfg.Protocol == "http2" || cfg.Protocol == "grpc" {
|
||||
if err := s.setHttp2ProtocolOptions(c); err != nil {
|
||||
|
@ -1499,7 +1501,7 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
|
|||
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{
|
||||
Thresholds: makeThresholdsIfNeeded(upstreamConfig.Limits),
|
||||
},
|
||||
OutlierDetection: ToOutlierDetection(upstreamConfig.PassiveHealthCheck, nil, true),
|
||||
OutlierDetection: config.ToOutlierDetection(upstreamConfig.PassiveHealthCheck, nil, true),
|
||||
}
|
||||
|
||||
var lb *structs.LoadBalancer
|
||||
|
@ -1676,7 +1678,7 @@ type clusterOpts struct {
|
|||
|
||||
// makeGatewayCluster creates an Envoy cluster for a mesh or terminating gateway
|
||||
func (s *ResourceGenerator) makeGatewayCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster {
|
||||
cfg, err := ParseGatewayConfig(snap.Proxy.Config)
|
||||
cfg, err := config.ParseGatewayConfig(snap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1819,7 +1821,7 @@ func configureClusterWithHostnames(
|
|||
// makeExternalIPCluster creates an Envoy cluster for routing to IP addresses outside of Consul
|
||||
// This is used by terminating gateways for Destinations
|
||||
func (s *ResourceGenerator) makeExternalIPCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster {
|
||||
cfg, err := ParseGatewayConfig(snap.Proxy.Config)
|
||||
cfg, err := config.ParseGatewayConfig(snap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1858,7 +1860,7 @@ func (s *ResourceGenerator) makeExternalIPCluster(snap *proxycfg.ConfigSnapshot,
|
|||
// makeExternalHostnameCluster creates an Envoy cluster for hostname endpoints that will be resolved with DNS
|
||||
// This is used by both terminating gateways for Destinations, and Mesh Gateways for peering control plane traffice
|
||||
func (s *ResourceGenerator) makeExternalHostnameCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster {
|
||||
cfg, err := ParseGatewayConfig(snap.Proxy.Config)
|
||||
cfg, err := config.ParseGatewayConfig(snap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -2044,7 +2046,7 @@ func (s *ResourceGenerator) getTargetClusterName(upstreamsSnapshot *proxycfg.Con
|
|||
|
||||
clusterName = generatePeeredClusterName(targetUID, tbs)
|
||||
}
|
||||
clusterName = CustomizeClusterName(clusterName, chain)
|
||||
clusterName = naming.CustomizeClusterName(clusterName, chain)
|
||||
if forMeshGateway {
|
||||
clusterName = meshGatewayExportedClusterNamePrefix + clusterName
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package xds
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package xds
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -0,0 +1,7 @@
|
|||
package configfetcher
|
||||
|
||||
// ConfigFetcher is the interface the agent needs to expose
|
||||
// for the xDS server to fetch agent config, currently only one field is fetched
|
||||
type ConfigFetcher interface {
|
||||
AdvertiseAddrLAN() string
|
||||
}
|
|
@ -29,6 +29,9 @@ import (
|
|||
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||
"github.com/hashicorp/consul/agent/xds/config"
|
||||
"github.com/hashicorp/consul/agent/xds/naming"
|
||||
"github.com/hashicorp/consul/agent/xds/platform"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
@ -50,8 +53,6 @@ import (
|
|||
"github.com/hashicorp/consul/types"
|
||||
)
|
||||
|
||||
const virtualIPTag = "virtual"
|
||||
|
||||
// listenersFromSnapshot returns the xDS API representation of the "listeners" in the snapshot.
|
||||
func (s *ResourceGenerator) listenersFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
|
||||
if cfgSnap == nil {
|
||||
|
@ -118,7 +119,7 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg.
|
|||
}
|
||||
}
|
||||
|
||||
proxyCfg, err := ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
proxyCfg, err := config.ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -258,7 +259,7 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg.
|
|||
// We only match on this virtual IP if the upstream is in the proxy's partition.
|
||||
// This is because the IP is not guaranteed to be unique across k8s clusters.
|
||||
if acl.EqualPartitions(e.Node.PartitionOrDefault(), cfgSnap.ProxyID.PartitionOrDefault()) {
|
||||
if vip := e.Service.TaggedAddresses[virtualIPTag]; vip.Address != "" {
|
||||
if vip := e.Service.TaggedAddresses[naming.VirtualIPTag]; vip.Address != "" {
|
||||
uniqueAddrs[vip.Address] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
@ -462,7 +463,7 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg.
|
|||
// The virtualIPTag is used by consul-k8s to store the ClusterIP for a service.
|
||||
// For services imported from a peer,the partition will be equal in all cases.
|
||||
if acl.EqualPartitions(e.Node.PartitionOrDefault(), cfgSnap.ProxyID.PartitionOrDefault()) {
|
||||
if vip := e.Service.TaggedAddresses[virtualIPTag]; vip.Address != "" {
|
||||
if vip := e.Service.TaggedAddresses[naming.VirtualIPTag]; vip.Address != "" {
|
||||
uniqueAddrs[vip.Address] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
@ -552,8 +553,8 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg.
|
|||
|
||||
filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{
|
||||
accessLogs: &cfgSnap.Proxy.AccessLogs,
|
||||
clusterName: OriginalDestinationClusterName,
|
||||
filterName: OriginalDestinationClusterName,
|
||||
clusterName: naming.OriginalDestinationClusterName,
|
||||
filterName: naming.OriginalDestinationClusterName,
|
||||
protocol: "tcp",
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -787,7 +788,7 @@ func parseCheckPath(check structs.CheckType) (structs.ExposePath, error) {
|
|||
|
||||
// listenersFromSnapshotGateway returns the "listener" for a terminating-gateway or mesh-gateway service
|
||||
func (s *ResourceGenerator) listenersFromSnapshotGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
|
||||
cfg, err := ParseGatewayConfig(cfgSnap.Proxy.Config)
|
||||
cfg, err := config.ParseGatewayConfig(cfgSnap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1171,7 +1172,7 @@ func createDownstreamTransportSocketForConnectTLS(cfgSnap *proxycfg.ConfigSnapsh
|
|||
|
||||
// Determine listener protocol type from configured service protocol. Don't hard fail on a config typo,
|
||||
//The parse func returns default config if there is an error, so it's safe to continue.
|
||||
cfg, _ := ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
cfg, _ := config.ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
|
||||
// Create TLS validation context for mTLS with leaf certificate and root certs.
|
||||
tlsContext := makeCommonTLSContext(
|
||||
|
@ -1263,7 +1264,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
|||
var l *envoy_listener_v3.Listener
|
||||
var err error
|
||||
|
||||
cfg, err := ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
cfg, err := config.ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1513,7 +1514,7 @@ func (s *ResourceGenerator) finalizePublicListenerFromConfig(l *envoy_listener_v
|
|||
}
|
||||
|
||||
func (s *ResourceGenerator) makeExposedCheckListener(cfgSnap *proxycfg.ConfigSnapshot, cluster string, path structs.ExposePath) (proto.Message, error) {
|
||||
cfg, err := ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
cfg, err := config.ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1588,7 +1589,7 @@ func (s *ResourceGenerator) makeExposedCheckListener(cfgSnap *proxycfg.ConfigSna
|
|||
&envoy_core_v3.CidrRange{AddressPrefix: advertise, PrefixLen: &wrapperspb.UInt32Value{Value: uint32(advertiseLen)}},
|
||||
)
|
||||
|
||||
if ok, err := kernelSupportsIPv6(); err != nil {
|
||||
if ok, err := platform.SupportsIPv6(); err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
ranges = append(ranges,
|
||||
|
@ -1639,7 +1640,7 @@ func (s *ResourceGenerator) makeTerminatingGatewayListener(
|
|||
intentions := cfgSnap.TerminatingGateway.Intentions[svc]
|
||||
svcConfig := cfgSnap.TerminatingGateway.ServiceConfigs[svc]
|
||||
|
||||
cfg, err := ParseProxyConfig(svcConfig.ProxyConfig)
|
||||
cfg, err := config.ParseProxyConfig(svcConfig.ProxyConfig)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1683,7 +1684,7 @@ func (s *ResourceGenerator) makeTerminatingGatewayListener(
|
|||
intentions := cfgSnap.TerminatingGateway.Intentions[svc]
|
||||
svcConfig := cfgSnap.TerminatingGateway.ServiceConfigs[svc]
|
||||
|
||||
cfg, err := ParseProxyConfig(svcConfig.ProxyConfig)
|
||||
cfg, err := config.ParseProxyConfig(svcConfig.ProxyConfig)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -1807,7 +1808,7 @@ func (s *ResourceGenerator) makeFilterChainTerminatingGateway(cfgSnap *proxycfg.
|
|||
filterChain.Filters = append(filterChain.Filters, authFilter)
|
||||
}
|
||||
|
||||
proxyCfg, err := ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
proxyCfg, err := config.ParseProxyConfig(cfgSnap.Proxy.Config)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -2128,7 +2129,7 @@ func (s *ResourceGenerator) makeMeshGatewayPeerFilterChain(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusterName = meshGatewayExportedClusterNamePrefix + CustomizeClusterName(target.Name, chain)
|
||||
clusterName = meshGatewayExportedClusterNamePrefix + naming.CustomizeClusterName(target.Name, chain)
|
||||
}
|
||||
|
||||
uid := proxycfg.NewUpstreamIDFromServiceName(svc)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
"github.com/hashicorp/consul/agent/xds/naming"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
|
@ -70,7 +71,7 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusterName = CustomizeClusterName(target.Name, chain)
|
||||
clusterName = naming.CustomizeClusterName(target.Name, chain)
|
||||
}
|
||||
|
||||
filterName := fmt.Sprintf("%s.%s.%s.%s", chain.ServiceName, chain.Namespace, chain.Partition, chain.Datacenter)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
"github.com/hashicorp/consul/agent/xds/naming"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
@ -62,7 +63,7 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusterName = CustomizeClusterName(target.Name, chain)
|
||||
clusterName = naming.CustomizeClusterName(target.Name, chain)
|
||||
}
|
||||
|
||||
filterName := fmt.Sprintf("%s.%s.%s.%s", chain.ServiceName, chain.Namespace, chain.Partition, chain.Datacenter)
|
||||
|
|
|
@ -11,8 +11,7 @@ import (
|
|||
"text/template"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/hashicorp/consul/agent/xds/testcommon"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
testinf "github.com/mitchellh/go-testing-interface"
|
||||
|
@ -20,6 +19,10 @@ import (
|
|||
|
||||
"github.com/hashicorp/consul/agent/proxycfg"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/agent/xds/configfetcher"
|
||||
"github.com/hashicorp/consul/agent/xds/proxystateconverter"
|
||||
"github.com/hashicorp/consul/agent/xds/testcommon"
|
||||
"github.com/hashicorp/consul/agent/xdsv2"
|
||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/types"
|
||||
|
@ -33,6 +36,7 @@ type listenerTestCase struct {
|
|||
// test input.
|
||||
overrideGoldenName string
|
||||
generatorSetup func(*ResourceGenerator)
|
||||
alsoRunTestForV2 bool
|
||||
}
|
||||
|
||||
func makeListenerDiscoChainTests(enterprise bool) []listenerTestCase {
|
||||
|
@ -70,6 +74,7 @@ func makeListenerDiscoChainTests(enterprise bool) []listenerTestCase {
|
|||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "simple", enterprise, nil, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-http-chain",
|
||||
|
@ -118,6 +123,7 @@ func makeListenerDiscoChainTests(enterprise bool) []listenerTestCase {
|
|||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "external-sni", enterprise, nil, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-chain-and-overrides",
|
||||
|
@ -130,12 +136,14 @@ func makeListenerDiscoChainTests(enterprise bool) []listenerTestCase {
|
|||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-remote-gateway", enterprise, nil, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-tcp-chain-failover-through-local-gateway",
|
||||
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
|
||||
return proxycfg.TestConfigSnapshotDiscoveryChain(t, "failover-through-local-gateway", enterprise, nil, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-jwt-config-entry-with-local",
|
||||
|
@ -226,6 +234,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
})
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-tls-incoming-min-version",
|
||||
|
@ -245,6 +254,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
})
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-tls-incoming-max-version",
|
||||
|
@ -264,6 +274,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
})
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-with-tls-incoming-cipher-suites",
|
||||
|
@ -286,6 +297,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
})
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "grpc-public-listener",
|
||||
|
@ -302,6 +314,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Config["bind_address"] = "127.0.0.2"
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "listener-bind-port",
|
||||
|
@ -310,6 +323,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Config["bind_port"] = 8888
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "listener-bind-address-port",
|
||||
|
@ -319,6 +333,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Config["bind_port"] = 8888
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "listener-unix-domain-socket",
|
||||
|
@ -330,6 +345,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Upstreams[0].LocalBindSocketMode = "0640"
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "listener-max-inbound-connections",
|
||||
|
@ -338,6 +354,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Config["max_inbound_connections"] = 222
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "http2-public-listener",
|
||||
|
@ -354,6 +371,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Config["balance_inbound_connections"] = "exact_balance"
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "listener-balance-outbound-connections-bind-port",
|
||||
|
@ -362,6 +380,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
ns.Proxy.Upstreams[0].Config["balance_outbound_connections"] = "exact_balance"
|
||||
}, nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "http-public-listener",
|
||||
|
@ -559,7 +578,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
{
|
||||
// NOTE: if IPv6 is not supported in the kernel per
|
||||
// kernelSupportsIPv6() then this test will fail because the golden
|
||||
// platform.SupportsIPv6() then this test will fail because the golden
|
||||
// files were generated assuming ipv6 support was present
|
||||
name: "expose-checks-http",
|
||||
create: proxycfg.TestConfigSnapshotExposeChecks,
|
||||
|
@ -571,7 +590,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
{
|
||||
// NOTE: if IPv6 is not supported in the kernel per
|
||||
// kernelSupportsIPv6() then this test will fail because the golden
|
||||
// platform.SupportsIPv6() then this test will fail because the golden
|
||||
// files were generated assuming ipv6 support was present
|
||||
name: "expose-checks-http-with-bind-override",
|
||||
create: proxycfg.TestConfigSnapshotExposeChecksWithBindOverride,
|
||||
|
@ -583,7 +602,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
{
|
||||
// NOTE: if IPv6 is not supported in the kernel per
|
||||
// kernelSupportsIPv6() then this test will fail because the golden
|
||||
// platform.SupportsIPv6() then this test will fail because the golden
|
||||
// files were generated assuming ipv6 support was present
|
||||
name: "expose-checks-grpc",
|
||||
create: proxycfg.TestConfigSnapshotExposeChecksGRPC,
|
||||
|
@ -1172,14 +1191,17 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
{
|
||||
name: "transparent-proxy-catalog-destinations-only",
|
||||
create: proxycfg.TestConfigSnapshotTransparentProxyCatalogDestinationsOnly,
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "transparent-proxy-dial-instances-directly",
|
||||
create: proxycfg.TestConfigSnapshotTransparentProxyDialDirectly,
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "transparent-proxy-terminating-gateway",
|
||||
create: proxycfg.TestConfigSnapshotTransparentProxyTerminatingGatewayCatalogDestinationsOnly,
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "custom-trace-listener",
|
||||
|
@ -1242,6 +1264,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
{
|
||||
name: "connect-proxy-without-tproxy-and-permissive-mtls",
|
||||
|
@ -1251,6 +1274,7 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
},
|
||||
nil)
|
||||
},
|
||||
alsoRunTestForV2: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1275,16 +1299,17 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
// golder files for every test case and so not be any use!
|
||||
testcommon.SetupTLSRootsAndLeaf(t, snap)
|
||||
|
||||
var listeners []proto.Message
|
||||
|
||||
t.Run("current-xdsv1", func(t *testing.T) {
|
||||
// Need server just for logger dependency
|
||||
g := NewResourceGenerator(testutil.Logger(t), nil, false)
|
||||
g.ProxyFeatures = sf
|
||||
if tt.generatorSetup != nil {
|
||||
tt.generatorSetup(g)
|
||||
}
|
||||
|
||||
listeners, err := g.listenersFromSnapshot(snap)
|
||||
listeners, err = g.listenersFromSnapshot(snap)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The order of listeners returned via LDS isn't relevant, so it's safe
|
||||
// to sort these for the purposes of test comparisons.
|
||||
sort.Slice(listeners, func(i, j int) bool {
|
||||
|
@ -1294,7 +1319,6 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
r, err := createResponse(xdscommon.ListenerType, "00000001", "00000001", listeners)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("current", func(t *testing.T) {
|
||||
gotJSON := protoToJSON(t, r)
|
||||
|
||||
gName := tt.name
|
||||
|
@ -1305,6 +1329,39 @@ func TestListenersFromSnapshot(t *testing.T) {
|
|||
expectedJSON := goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion, gotJSON)
|
||||
require.JSONEq(t, expectedJSON, gotJSON)
|
||||
})
|
||||
|
||||
if tt.alsoRunTestForV2 {
|
||||
t.Run("current-xdsv2", func(t *testing.T) {
|
||||
generator := xdsv2.NewResourceGenerator(testutil.Logger(t))
|
||||
converter := proxystateconverter.NewConverter(testutil.Logger(t), nil)
|
||||
proxyState, err := converter.ProxyStateFromSnapshot(snap)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := generator.AllResourcesFromIR(proxyState)
|
||||
require.NoError(t, err)
|
||||
|
||||
listeners = res[xdscommon.ListenerType]
|
||||
// The order of listeners returned via LDS isn't relevant, so it's safe
|
||||
// to sort these for the purposes of test comparisons.
|
||||
sort.Slice(listeners, func(i, j int) bool {
|
||||
return listeners[i].(*envoy_listener_v3.Listener).Name < listeners[j].(*envoy_listener_v3.Listener).Name
|
||||
})
|
||||
|
||||
r, err := createResponse(xdscommon.ListenerType, "00000001", "00000001", listeners)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotJSON := protoToJSON(t, r)
|
||||
|
||||
gName := tt.name
|
||||
if tt.overrideGoldenName != "" {
|
||||
gName = tt.overrideGoldenName
|
||||
}
|
||||
|
||||
expectedJSON := goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion, gotJSON)
|
||||
require.JSONEq(t, expectedJSON, gotJSON)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -1462,7 +1519,7 @@ func customTraceJSON(t testinf.T) string {
|
|||
|
||||
type configFetcherFunc func() string
|
||||
|
||||
var _ ConfigFetcher = (configFetcherFunc)(nil)
|
||||
var _ configfetcher.ConfigFetcher = (configFetcherFunc)(nil)
|
||||
|
||||
func (f configFetcherFunc) AdvertiseAddrLAN() string {
|
||||
return f()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package xds
|
||||
package naming
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -9,6 +9,16 @@ import (
|
|||
"github.com/hashicorp/consul/agent/structs"
|
||||
)
|
||||
|
||||
const (
|
||||
// OriginalDestinationClusterName is the name we give to the passthrough
|
||||
// cluster which redirects transparently-proxied requests to their original
|
||||
// destination outside the mesh. This cluster prevents Consul from blocking
|
||||
// connections to destinations outside of the catalog when in transparent
|
||||
// proxy mode.
|
||||
OriginalDestinationClusterName = "original-destination"
|
||||
VirtualIPTag = "virtual"
|
||||
)
|
||||
|
||||
func CustomizeClusterName(clusterName string, chain *structs.CompiledDiscoveryChain) string {
|
||||
if chain == nil || chain.CustomizationHash == "" {
|
||||
return clusterName
|
|
@ -4,8 +4,8 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package xds
|
||||
package platform
|
||||
|
||||
func kernelSupportsIPv6() (bool, error) {
|
||||
func SupportsIPv6() (bool, error) {
|
||||
return true, nil
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package xds
|
||||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -20,7 +20,7 @@ var (
|
|||
ipv6SupportedErr error
|
||||
)
|
||||
|
||||
func kernelSupportsIPv6() (bool, error) {
|
||||
func SupportsIPv6() (bool, error) {
|
||||
ipv6SupportOnce.Do(func() {
|
||||
ipv6Supported, ipv6SupportedErr = checkIfKernelSupportsIPv6()
|
||||
})
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package proxystateconverter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/proxycfg"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/agent/xds/naming"
|
||||
"github.com/hashicorp/consul/proto/private/pbpeering"
|
||||
)
|
||||
|
||||
const (
|
||||
meshGatewayExportedClusterNamePrefix = "exported~"
|
||||
)
|
||||
|
||||
func makeExposeClusterName(destinationPort int) string {
|
||||
return fmt.Sprintf("exposed_cluster_%d", destinationPort)
|
||||
}
|
||||
|
||||
func clusterNameForDestination(cfgSnap *proxycfg.ConfigSnapshot, name string, address string, namespace string, partition string) string {
|
||||
name = destinationSpecificServiceName(name, address)
|
||||
sni := connect.ServiceSNI(name, "", namespace, partition, cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
|
||||
|
||||
// Prefixed with destination to distinguish from non-passthrough clusters for the same upstream.
|
||||
return "destination." + sni
|
||||
}
|
||||
|
||||
func destinationSpecificServiceName(name string, address string) string {
|
||||
address = strings.ReplaceAll(address, ":", "-")
|
||||
address = strings.ReplaceAll(address, ".", "-")
|
||||
return fmt.Sprintf("%s.%s", address, name)
|
||||
}
|
||||
|
||||
// generatePeeredClusterName returns an SNI-like cluster name which mimics PeeredServiceSNI
|
||||
// but excludes partition information which could be ambiguous (local vs remote partition).
|
||||
func generatePeeredClusterName(uid proxycfg.UpstreamID, tb *pbpeering.PeeringTrustBundle) string {
|
||||
return strings.Join([]string{
|
||||
uid.Name,
|
||||
uid.NamespaceOrDefault(),
|
||||
uid.Peer,
|
||||
"external",
|
||||
tb.TrustDomain,
|
||||
}, ".")
|
||||
}
|
||||
|
||||
func (s *Converter) getTargetClusterName(upstreamsSnapshot *proxycfg.ConfigSnapshotUpstreams, chain *structs.CompiledDiscoveryChain, tid string, forMeshGateway bool) string {
|
||||
target := chain.Targets[tid]
|
||||
clusterName := target.Name
|
||||
targetUID := proxycfg.NewUpstreamIDFromTargetID(tid)
|
||||
if targetUID.Peer != "" {
|
||||
tbs, ok := upstreamsSnapshot.UpstreamPeerTrustBundles.Get(targetUID.Peer)
|
||||
// We can't generate cluster on peers without the trust bundle. The
|
||||
// trust bundle should be ready soon.
|
||||
if !ok {
|
||||
s.Logger.Debug("peer trust bundle not ready for discovery chain target",
|
||||
"peer", targetUID.Peer,
|
||||
"target", tid,
|
||||
)
|
||||
return ""
|
||||
}
|
||||
|
||||
clusterName = generatePeeredClusterName(targetUID, tbs)
|
||||
}
|
||||
clusterName = naming.CustomizeClusterName(clusterName, chain)
|
||||
if forMeshGateway {
|
||||
clusterName = meshGatewayExportedClusterNamePrefix + clusterName
|
||||
}
|
||||
return clusterName
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package proxystateconverter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
|
||||
"github.com/hashicorp/consul/agent/proxycfg"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/agent/xds/configfetcher"
|
||||
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1"
|
||||
"github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1/pbproxystate"
|
||||
)
|
||||
|
||||
// Converter converts a single snapshot into a ProxyState.
|
||||
type Converter struct {
|
||||
Logger hclog.Logger
|
||||
CfgFetcher configfetcher.ConfigFetcher
|
||||
proxyState *pbmesh.ProxyState
|
||||
}
|
||||
|
||||
func NewConverter(
|
||||
logger hclog.Logger,
|
||||
cfgFetcher configfetcher.ConfigFetcher,
|
||||
) *Converter {
|
||||
return &Converter{
|
||||
Logger: logger,
|
||||
CfgFetcher: cfgFetcher,
|
||||
proxyState: &pbmesh.ProxyState{
|
||||
Listeners: make([]*pbproxystate.Listener, 0),
|
||||
Clusters: make(map[string]*pbproxystate.Cluster),
|
||||
Routes: make(map[string]*pbproxystate.Route),
|
||||
Endpoints: make(map[string]*pbproxystate.Endpoints),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Converter) ProxyStateFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot) (*pbmesh.ProxyState, error) {
|
||||
err := g.resourcesFromSnapshot(cfgSnap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate FullProxyState: %v", err)
|
||||
}
|
||||
|
||||
return g.proxyState, nil
|
||||
}
|
||||
|
||||
func (g *Converter) resourcesFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot) error {
|
||||
err := g.tlsConfigFromSnapshot(cfgSnap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = g.listenersFromSnapshot(cfgSnap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const localPeerKey = "local"
|
||||
|
||||
func (g *Converter) tlsConfigFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot) error {
|
||||
proxyStateTLS := &pbproxystate.TLS{}
|
||||
g.proxyState.TrustBundles = make(map[string]*pbproxystate.TrustBundle)
|
||||
g.proxyState.LeafCertificates = make(map[string]*pbproxystate.LeafCertificate)
|
||||
|
||||
// Set the TLS in the top level proxyState
|
||||
g.proxyState.Tls = proxyStateTLS
|
||||
|
||||
// Add local trust bundle
|
||||
g.proxyState.TrustBundles[localPeerKey] = &pbproxystate.TrustBundle{
|
||||
TrustDomain: cfgSnap.Roots.TrustDomain,
|
||||
Roots: []string{cfgSnap.RootPEMs()},
|
||||
}
|
||||
|
||||
// Add peered trust bundles for remote peers that will dial this proxy.
|
||||
for _, peeringTrustBundle := range cfgSnap.PeeringTrustBundles() {
|
||||
g.proxyState.TrustBundles[peeringTrustBundle.PeerName] = &pbproxystate.TrustBundle{
|
||||
TrustDomain: peeringTrustBundle.GetTrustDomain(),
|
||||
Roots: peeringTrustBundle.RootPEMs,
|
||||
}
|
||||
}
|
||||
|
||||
// Add upstream peer trust bundles for dialing upstreams in remote peers.
|
||||
upstreamsSnapshot, err := cfgSnap.ToConfigSnapshotUpstreams()
|
||||
if err != nil {
|
||||
if !(cfgSnap.Kind == structs.ServiceKindMeshGateway || cfgSnap.Kind == structs.ServiceKindTerminatingGateway) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if upstreamsSnapshot != nil {
|
||||
upstreamsSnapshot.UpstreamPeerTrustBundles.ForEachKeyE(func(k proxycfg.PeerName) error {
|
||||
tbs, ok := upstreamsSnapshot.UpstreamPeerTrustBundles.Get(k)
|
||||
if ok {
|
||||
g.proxyState.TrustBundles[k] = &pbproxystate.TrustBundle{
|
||||
TrustDomain: tbs.TrustDomain,
|
||||
Roots: tbs.RootPEMs,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if cfgSnap.MeshConfigTLSOutgoing() != nil {
|
||||
proxyStateTLS.OutboundTlsParameters = makeTLSParametersFromTLSConfig(cfgSnap.MeshConfigTLSOutgoing().TLSMinVersion,
|
||||
cfgSnap.MeshConfigTLSOutgoing().TLSMaxVersion, cfgSnap.MeshConfigTLSOutgoing().CipherSuites)
|
||||
}
|
||||
|
||||
if cfgSnap.MeshConfigTLSIncoming() != nil {
|
||||
proxyStateTLS.InboundTlsParameters = makeTLSParametersFromTLSConfig(cfgSnap.MeshConfigTLSIncoming().TLSMinVersion,
|
||||
cfgSnap.MeshConfigTLSIncoming().TLSMaxVersion, cfgSnap.MeshConfigTLSIncoming().CipherSuites)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -6,6 +6,7 @@ package xds
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/agent/xds/configfetcher"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
// resources for a single client.
|
||||
type ResourceGenerator struct {
|
||||
Logger hclog.Logger
|
||||
CfgFetcher ConfigFetcher
|
||||
CfgFetcher configfetcher.ConfigFetcher
|
||||
IncrementalXDS bool
|
||||
|
||||
ProxyFeatures xdscommon.SupportedProxyFeatures
|
||||
|
@ -26,7 +27,7 @@ type ResourceGenerator struct {
|
|||
|
||||
func NewResourceGenerator(
|
||||
logger hclog.Logger,
|
||||
cfgFetcher ConfigFetcher,
|
||||
cfgFetcher configfetcher.ConfigFetcher,
|
||||
incrementalXDS bool,
|
||||
) *ResourceGenerator {
|
||||
return &ResourceGenerator{
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
||||
"github.com/hashicorp/consul/agent/proxycfg"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/agent/xds/config"
|
||||
)
|
||||
|
||||
// routesFromSnapshot returns the xDS API representation of the "routes" in the
|
||||
|
@ -140,7 +141,7 @@ func (s *ResourceGenerator) routesForTerminatingGateway(cfgSnap *proxycfg.Config
|
|||
var resources []proto.Message
|
||||
for _, svc := range cfgSnap.TerminatingGateway.ValidServices() {
|
||||
clusterName := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), svc.PartitionOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
|
||||
cfg, err := ParseProxyConfig(cfgSnap.TerminatingGateway.ServiceConfigs[svc].ProxyConfig)
|
||||
cfg, err := config.ParseProxyConfig(cfgSnap.TerminatingGateway.ServiceConfigs[svc].ProxyConfig)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
@ -168,7 +169,7 @@ func (s *ResourceGenerator) routesForTerminatingGateway(cfgSnap *proxycfg.Config
|
|||
|
||||
for _, address := range svcConfig.Destination.Addresses {
|
||||
clusterName := clusterNameForDestination(cfgSnap, svc.Name, address, svc.NamespaceOrDefault(), svc.PartitionOrDefault())
|
||||
cfg, err := ParseProxyConfig(cfgSnap.TerminatingGateway.ServiceConfigs[svc].ProxyConfig)
|
||||
cfg, err := config.ParseProxyConfig(cfgSnap.TerminatingGateway.ServiceConfigs[svc].ProxyConfig)
|
||||
if err != nil {
|
||||
// Don't hard fail on a config typo, just warn. The parse func returns
|
||||
// default config if there is an error so it's safe to continue.
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
envoy_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
||||
"github.com/hashicorp/consul/agent/xds/configfetcher"
|
||||
|
||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||
|
||||
|
@ -70,13 +71,6 @@ const (
|
|||
// services named "local_agent" in the future.
|
||||
LocalAgentClusterName = "local_agent"
|
||||
|
||||
// OriginalDestinationClusterName is the name we give to the passthrough
|
||||
// cluster which redirects transparently-proxied requests to their original
|
||||
// destination outside the mesh. This cluster prevents Consul from blocking
|
||||
// connections to destinations outside of the catalog when in transparent
|
||||
// proxy mode.
|
||||
OriginalDestinationClusterName = "original-destination"
|
||||
|
||||
// DefaultAuthCheckFrequency is the default value for
|
||||
// Server.AuthCheckFrequency to use when the zero value is provided.
|
||||
DefaultAuthCheckFrequency = 5 * time.Minute
|
||||
|
@ -88,12 +82,6 @@ const (
|
|||
// coupling this to the agent.
|
||||
type ACLResolverFunc func(id string) (acl.Authorizer, error)
|
||||
|
||||
// ConfigFetcher is the interface the agent needs to expose
|
||||
// for the xDS server to fetch agent config, currently only one field is fetched
|
||||
type ConfigFetcher interface {
|
||||
AdvertiseAddrLAN() string
|
||||
}
|
||||
|
||||
// ProxyConfigSource is the interface xds.Server requires to consume proxy
|
||||
// config updates.
|
||||
type ProxyConfigSource interface {
|
||||
|
@ -110,7 +98,7 @@ type Server struct {
|
|||
Logger hclog.Logger
|
||||
CfgSrc ProxyConfigSource
|
||||
ResolveToken ACLResolverFunc
|
||||
CfgFetcher ConfigFetcher
|
||||
CfgFetcher configfetcher.ConfigFetcher
|
||||
|
||||
// AuthCheckFrequency is how often we should re-check the credentials used
|
||||
// during a long-lived gRPC Stream after it has been initially established.
|
||||
|
@ -161,7 +149,7 @@ func NewServer(
|
|||
logger hclog.Logger,
|
||||
cfgMgr ProxyConfigSource,
|
||||
resolveTokenSecret ACLResolverFunc,
|
||||
cfgFetcher ConfigFetcher,
|
||||
cfgFetcher configfetcher.ConfigFetcher,
|
||||
) *Server {
|
||||
return &Server{
|
||||
NodeName: nodeName,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package xdsv2
|
||||
|
||||
// TODO(proxystate): In a future PR this will create clusters and add it to ProxyResources.proxyState
|
||||
func (pr *ProxyResources) makeCluster(name string) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,967 @@
|
|||
package xdsv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||
envoy_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
|
||||
envoy_grpc_http1_bridge_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_http1_bridge/v3"
|
||||
envoy_grpc_stats_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_stats/v3"
|
||||
envoy_http_router_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
|
||||
envoy_extensions_filters_listener_http_inspector_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/http_inspector/v3"
|
||||
envoy_original_dst_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/original_dst/v3"
|
||||
envoy_tls_inspector_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3"
|
||||
envoy_connection_limit_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/connection_limit/v3"
|
||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||
envoy_network_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
|
||||
envoy_sni_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/sni_cluster/v3"
|
||||
envoy_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
|
||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
||||
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
|
||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1/pbproxystate"
|
||||
)
|
||||
|
||||
const (
|
||||
envoyNetworkFilterName = "envoy.filters.network.tcp_proxy"
|
||||
envoyOriginalDestinationListenerFilterName = "envoy.filters.listener.original_dst"
|
||||
envoyTLSInspectorListenerFilterName = "envoy.filters.listener.tls_inspector"
|
||||
envoyHttpInspectorListenerFilterName = "envoy.filters.listener.http_inspector"
|
||||
envoyHttpConnectionManagerFilterName = "envoy.filters.network.http_connection_manager"
|
||||
)
|
||||
|
||||
func (pr *ProxyResources) makeListener(listener *pbproxystate.Listener) (*envoy_listener_v3.Listener, error) {
|
||||
envoyListener := &envoy_listener_v3.Listener{}
|
||||
|
||||
// Listener Address
|
||||
var address *envoy_core_v3.Address
|
||||
switch listener.BindAddress.(type) {
|
||||
case *pbproxystate.Listener_HostPort:
|
||||
address = makeIpPortEnvoyAddress(listener.BindAddress.(*pbproxystate.Listener_HostPort))
|
||||
case *pbproxystate.Listener_UnixSocket:
|
||||
address = makeUnixSocketEnvoyAddress(listener.BindAddress.(*pbproxystate.Listener_UnixSocket))
|
||||
default:
|
||||
// This should be impossible to reach because we're using protobufs.
|
||||
return nil, fmt.Errorf("invalid listener bind address type: %t", listener.BindAddress)
|
||||
}
|
||||
envoyListener.Address = address
|
||||
|
||||
// Listener Direction
|
||||
var direction envoy_core_v3.TrafficDirection
|
||||
switch listener.Direction {
|
||||
case pbproxystate.Direction_DIRECTION_OUTBOUND:
|
||||
direction = envoy_core_v3.TrafficDirection_OUTBOUND
|
||||
case pbproxystate.Direction_DIRECTION_INBOUND:
|
||||
direction = envoy_core_v3.TrafficDirection_INBOUND
|
||||
case pbproxystate.Direction_DIRECTION_UNSPECIFIED:
|
||||
direction = envoy_core_v3.TrafficDirection_UNSPECIFIED
|
||||
default:
|
||||
return nil, fmt.Errorf("no direction for listener %+v", listener.Name)
|
||||
}
|
||||
envoyListener.TrafficDirection = direction
|
||||
|
||||
// Before creating the filter chains, sort routers by match to avoid draining if the list is provided out of order.
|
||||
sortRouters(listener.Routers)
|
||||
|
||||
// Listener filter chains
|
||||
for _, r := range listener.Routers {
|
||||
filterChain, err := pr.makeEnvoyListenerFilterChain(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not make filter chain: %w", err)
|
||||
}
|
||||
envoyListener.FilterChains = append(envoyListener.FilterChains, filterChain)
|
||||
}
|
||||
|
||||
if listener.DefaultRouter != nil {
|
||||
defaultFilterChain, err := pr.makeEnvoyListenerFilterChain(listener.DefaultRouter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not make filter chain: %w", err)
|
||||
}
|
||||
envoyListener.DefaultFilterChain = defaultFilterChain
|
||||
}
|
||||
|
||||
// Envoy builtin listener filters
|
||||
for _, c := range listener.Capabilities {
|
||||
listenerFilter, err := makeEnvoyListenerFilter(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not make listener filter: %w", err)
|
||||
}
|
||||
envoyListener.ListenerFilters = append(envoyListener.ListenerFilters, listenerFilter)
|
||||
}
|
||||
|
||||
err := addEnvoyListenerConnectionBalanceConfig(listener.BalanceConnections, envoyListener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envoyListener.Name = listener.Name
|
||||
envoyListener.Address = address
|
||||
envoyListener.TrafficDirection = direction
|
||||
|
||||
return envoyListener, nil
|
||||
}
|
||||
|
||||
func makeEnvoyConnectionLimitFilter(maxInboundConns uint64) (*envoy_listener_v3.Filter, error) {
|
||||
cfg := &envoy_connection_limit_v3.ConnectionLimit{
|
||||
StatPrefix: "inbound_connection_limit",
|
||||
MaxConnections: wrapperspb.UInt64(maxInboundConns),
|
||||
}
|
||||
|
||||
return makeEnvoyFilter("envoy.filters.network.connection_limit", cfg)
|
||||
}
|
||||
|
||||
func addEnvoyListenerConnectionBalanceConfig(balanceType pbproxystate.BalanceConnections, listener *envoy_listener_v3.Listener) error {
|
||||
switch balanceType {
|
||||
case pbproxystate.BalanceConnections_BALANCE_CONNECTIONS_DEFAULT:
|
||||
// Default with no balancing.
|
||||
return nil
|
||||
case pbproxystate.BalanceConnections_BALANCE_CONNECTIONS_EXACT:
|
||||
listener.ConnectionBalanceConfig = &envoy_listener_v3.Listener_ConnectionBalanceConfig{
|
||||
BalanceType: &envoy_listener_v3.Listener_ConnectionBalanceConfig_ExactBalance_{},
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// This should be impossible using protobufs.
|
||||
return fmt.Errorf("unsupported connection balance option: %+v", balanceType)
|
||||
}
|
||||
}
|
||||
|
||||
func makeIpPortEnvoyAddress(address *pbproxystate.Listener_HostPort) *envoy_core_v3.Address {
|
||||
return &envoy_core_v3.Address{
|
||||
Address: &envoy_core_v3.Address_SocketAddress{
|
||||
SocketAddress: &envoy_core_v3.SocketAddress{
|
||||
Address: address.HostPort.Host,
|
||||
PortSpecifier: &envoy_core_v3.SocketAddress_PortValue{
|
||||
PortValue: address.HostPort.Port,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeUnixSocketEnvoyAddress(address *pbproxystate.Listener_UnixSocket) *envoy_core_v3.Address {
|
||||
modeInt, err := strconv.ParseUint(address.UnixSocket.Mode, 0, 32)
|
||||
if err != nil {
|
||||
modeInt = 0
|
||||
}
|
||||
return &envoy_core_v3.Address{
|
||||
Address: &envoy_core_v3.Address_Pipe{
|
||||
Pipe: &envoy_core_v3.Pipe{
|
||||
Path: address.UnixSocket.Path,
|
||||
Mode: uint32(modeInt),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyListenerFilterChain(router *pbproxystate.Router) (*envoy_listener_v3.FilterChain, error) {
|
||||
envoyFilterChain := &envoy_listener_v3.FilterChain{}
|
||||
|
||||
if router == nil {
|
||||
return nil, fmt.Errorf("no router to create filter chain")
|
||||
}
|
||||
|
||||
// Router Match
|
||||
match := makeEnvoyFilterChainMatch(router.Match)
|
||||
if match != nil {
|
||||
envoyFilterChain.FilterChainMatch = match
|
||||
}
|
||||
|
||||
// Router Destination
|
||||
var envoyFilters []*envoy_listener_v3.Filter
|
||||
switch router.Destination.(type) {
|
||||
case *pbproxystate.Router_L4:
|
||||
l4Filters, err := pr.makeEnvoyResourcesForL4Destination(router.Destination.(*pbproxystate.Router_L4))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, l4Filters...)
|
||||
case *pbproxystate.Router_L7:
|
||||
l7 := router.Destination.(*pbproxystate.Router_L7)
|
||||
l7Filters, err := pr.makeEnvoyResourcesForL7Destination(l7)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Inject ALPN protocols to router's TLS if destination is L7
|
||||
if router.InboundTls != nil {
|
||||
router.InboundTls.AlpnProtocols = getAlpnProtocols(l7.L7.Protocol)
|
||||
}
|
||||
envoyFilters = append(envoyFilters, l7Filters...)
|
||||
case *pbproxystate.Router_Sni:
|
||||
sniFilters, err := pr.makeEnvoyResourcesForSNIDestination(router.Destination.(*pbproxystate.Router_Sni))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, sniFilters...)
|
||||
default:
|
||||
// This should be impossible using protobufs.
|
||||
return nil, fmt.Errorf("unsupported destination type: %t", router.Destination)
|
||||
|
||||
}
|
||||
|
||||
// Router TLS
|
||||
ts, err := pr.makeEnvoyTransportSocket(router.InboundTls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilterChain.TransportSocket = ts
|
||||
|
||||
envoyFilterChain.Filters = envoyFilters
|
||||
return envoyFilterChain, err
|
||||
}
|
||||
|
||||
func makeEnvoyFilterChainMatch(routerMatch *pbproxystate.Match) *envoy_listener_v3.FilterChainMatch {
|
||||
var envoyFilterChainMatch *envoy_listener_v3.FilterChainMatch
|
||||
if routerMatch != nil {
|
||||
envoyFilterChainMatch = &envoy_listener_v3.FilterChainMatch{}
|
||||
|
||||
envoyFilterChainMatch.DestinationPort = routerMatch.DestinationPort
|
||||
|
||||
if len(routerMatch.ServerNames) > 0 {
|
||||
var serverNames []string
|
||||
for _, n := range routerMatch.ServerNames {
|
||||
serverNames = append(serverNames, n)
|
||||
}
|
||||
envoyFilterChainMatch.ServerNames = serverNames
|
||||
}
|
||||
if len(routerMatch.PrefixRanges) > 0 {
|
||||
sortPrefixRanges(routerMatch.PrefixRanges)
|
||||
var ranges []*envoy_core_v3.CidrRange
|
||||
for _, r := range routerMatch.PrefixRanges {
|
||||
cidrRange := &envoy_core_v3.CidrRange{
|
||||
PrefixLen: r.PrefixLen,
|
||||
AddressPrefix: r.AddressPrefix,
|
||||
}
|
||||
ranges = append(ranges, cidrRange)
|
||||
}
|
||||
envoyFilterChainMatch.PrefixRanges = ranges
|
||||
}
|
||||
if len(routerMatch.SourcePrefixRanges) > 0 {
|
||||
var ranges []*envoy_core_v3.CidrRange
|
||||
for _, r := range routerMatch.SourcePrefixRanges {
|
||||
cidrRange := &envoy_core_v3.CidrRange{
|
||||
PrefixLen: r.PrefixLen,
|
||||
AddressPrefix: r.AddressPrefix,
|
||||
}
|
||||
ranges = append(ranges, cidrRange)
|
||||
}
|
||||
envoyFilterChainMatch.SourcePrefixRanges = ranges
|
||||
}
|
||||
}
|
||||
return envoyFilterChainMatch
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyResourcesForSNIDestination(sni *pbproxystate.Router_Sni) ([]*envoy_listener_v3.Filter, error) {
|
||||
var envoyFilters []*envoy_listener_v3.Filter
|
||||
sniFilter, err := makeEnvoyFilter("envoy.filters.network.sni_cluster", &envoy_sni_cluster_v3.SniCluster{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcp := &envoy_tcp_proxy_v3.TcpProxy{
|
||||
StatPrefix: sni.Sni.StatPrefix,
|
||||
ClusterSpecifier: &envoy_tcp_proxy_v3.TcpProxy_Cluster{Cluster: ""},
|
||||
}
|
||||
tcpFilter, err := makeEnvoyFilter(envoyNetworkFilterName, tcp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, sniFilter, tcpFilter)
|
||||
return envoyFilters, err
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyResourcesForL4Destination(l4 *pbproxystate.Router_L4) ([]*envoy_listener_v3.Filter, error) {
|
||||
err := pr.makeCluster(l4.L4.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters, err := makeL4Filters(l4.L4)
|
||||
return envoyFilters, err
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyResourcesForL7Destination(l7 *pbproxystate.Router_L7) ([]*envoy_listener_v3.Filter, error) {
|
||||
envoyFilters, err := pr.makeL7Filters(l7.L7)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return envoyFilters, err
|
||||
}
|
||||
|
||||
func getAlpnProtocols(protocol pbproxystate.L7Protocol) []string {
|
||||
var alpnProtocols []string
|
||||
|
||||
switch protocol {
|
||||
case pbproxystate.L7Protocol_L7_PROTOCOL_GRPC, pbproxystate.L7Protocol_L7_PROTOCOL_HTTP2:
|
||||
alpnProtocols = append(alpnProtocols, "h2", "http/1.1")
|
||||
case pbproxystate.L7Protocol_L7_PROTOCOL_HTTP:
|
||||
alpnProtocols = append(alpnProtocols, "http/1.1")
|
||||
}
|
||||
|
||||
return alpnProtocols
|
||||
}
|
||||
|
||||
func makeL4Filters(l4 *pbproxystate.L4Destination) ([]*envoy_listener_v3.Filter, error) {
|
||||
var envoyFilters []*envoy_listener_v3.Filter
|
||||
if l4 != nil {
|
||||
// Add rbac filter. RBAC filter needs to be added first so any
|
||||
// unauthorized connections will get rejected.
|
||||
// TODO(proxystate): Intentions will be added in the future.
|
||||
if l4.AddEmptyIntention {
|
||||
rbacFilter, err := makeEmptyRBACNetworkFilter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, rbacFilter)
|
||||
}
|
||||
|
||||
if l4.MaxInboundConnections > 0 {
|
||||
connectionLimitFilter, err := makeEnvoyConnectionLimitFilter(l4.MaxInboundConnections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, connectionLimitFilter)
|
||||
}
|
||||
|
||||
// Add tcp proxy filter
|
||||
tcp := &envoy_tcp_proxy_v3.TcpProxy{
|
||||
ClusterSpecifier: &envoy_tcp_proxy_v3.TcpProxy_Cluster{Cluster: l4.Name},
|
||||
StatPrefix: l4.StatPrefix,
|
||||
}
|
||||
tcpFilter, err := makeEnvoyFilter(envoyNetworkFilterName, tcp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, tcpFilter)
|
||||
}
|
||||
return envoyFilters, nil
|
||||
|
||||
}
|
||||
|
||||
func makeEmptyRBACNetworkFilter() (*envoy_listener_v3.Filter, error) {
|
||||
cfg := &envoy_network_rbac_v3.RBAC{
|
||||
StatPrefix: "connect_authz",
|
||||
Rules: &envoy_rbac_v3.RBAC{},
|
||||
}
|
||||
filter, err := makeEnvoyFilter("envoy.filters.network.rbac", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
// TODO: Forward client cert details will be added as part of L7 listeners task.
|
||||
func (pr *ProxyResources) makeL7Filters(l7 *pbproxystate.L7Destination) ([]*envoy_listener_v3.Filter, error) {
|
||||
var envoyFilters []*envoy_listener_v3.Filter
|
||||
var httpConnMgr *envoy_http_v3.HttpConnectionManager
|
||||
|
||||
if l7 != nil {
|
||||
// TODO: Intentions will be added in the future.
|
||||
if l7.MaxInboundConnections > 0 {
|
||||
connLimitFilter, err := makeEnvoyConnectionLimitFilter(l7.MaxInboundConnections)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, connLimitFilter)
|
||||
}
|
||||
envoyHttpRouter, err := makeEnvoyHTTPFilter("envoy.filters.http.router", &envoy_http_router_v3.Router{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpConnMgr = &envoy_http_v3.HttpConnectionManager{
|
||||
StatPrefix: l7.StatPrefix,
|
||||
CodecType: envoy_http_v3.HttpConnectionManager_AUTO,
|
||||
HttpFilters: []*envoy_http_v3.HttpFilter{
|
||||
envoyHttpRouter,
|
||||
},
|
||||
Tracing: &envoy_http_v3.HttpConnectionManager_Tracing{
|
||||
// Don't trace any requests by default unless the client application
|
||||
// explicitly propagates trace headers that indicate this should be
|
||||
// sampled.
|
||||
RandomSampling: &envoy_type_v3.Percent{Value: 0.0},
|
||||
},
|
||||
// Explicitly enable WebSocket upgrades for all HTTP listeners
|
||||
UpgradeConfigs: []*envoy_http_v3.HttpConnectionManager_UpgradeConfig{
|
||||
{UpgradeType: "websocket"},
|
||||
},
|
||||
}
|
||||
|
||||
routeConfig, err := pr.makeRoute(l7.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l7.StaticRoute {
|
||||
httpConnMgr.RouteSpecifier = &envoy_http_v3.HttpConnectionManager_RouteConfig{
|
||||
RouteConfig: routeConfig,
|
||||
}
|
||||
} else {
|
||||
// Add Envoy route under the route resource since it's not inlined.
|
||||
pr.envoyResources[xdscommon.RouteType] = append(pr.envoyResources[xdscommon.RouteType], routeConfig)
|
||||
|
||||
httpConnMgr.RouteSpecifier = &envoy_http_v3.HttpConnectionManager_Rds{
|
||||
Rds: &envoy_http_v3.Rds{
|
||||
RouteConfigName: l7.Name,
|
||||
ConfigSource: &envoy_core_v3.ConfigSource{
|
||||
ResourceApiVersion: envoy_core_v3.ApiVersion_V3,
|
||||
ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_Ads{
|
||||
Ads: &envoy_core_v3.AggregatedConfigSource{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Add http2 protocol options
|
||||
if l7.Protocol == pbproxystate.L7Protocol_L7_PROTOCOL_HTTP2 || l7.Protocol == pbproxystate.L7Protocol_L7_PROTOCOL_GRPC {
|
||||
httpConnMgr.Http2ProtocolOptions = &envoy_core_v3.Http2ProtocolOptions{}
|
||||
}
|
||||
|
||||
// Add grpc envoy http filters.
|
||||
if l7.Protocol == pbproxystate.L7Protocol_L7_PROTOCOL_GRPC {
|
||||
grpcHttp1Bridge, err := makeEnvoyHTTPFilter(
|
||||
"envoy.filters.http.grpc_http1_bridge",
|
||||
&envoy_grpc_http1_bridge_v3.Config{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// In envoy 1.14.x the default value "stats_for_all_methods=true" was
|
||||
// deprecated, and was changed to "false" in 1.18.x. Avoid using the
|
||||
// default. TODO: we may want to expose this to users somehow easily.
|
||||
grpcStatsFilter, err := makeEnvoyHTTPFilter(
|
||||
"envoy.filters.http.grpc_stats",
|
||||
&envoy_grpc_stats_v3.FilterConfig{
|
||||
PerMethodStatSpecifier: &envoy_grpc_stats_v3.FilterConfig_StatsForAllMethods{
|
||||
StatsForAllMethods: &wrapperspb.BoolValue{Value: true},
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add grpc bridge before envoyRouter and authz, and the stats in front of that.
|
||||
httpConnMgr.HttpFilters = append([]*envoy_http_v3.HttpFilter{
|
||||
grpcStatsFilter,
|
||||
grpcHttp1Bridge,
|
||||
}, httpConnMgr.HttpFilters...)
|
||||
}
|
||||
|
||||
httpFilter, err := makeEnvoyFilter(envoyHttpConnectionManagerFilterName, httpConnMgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyFilters = append(envoyFilters, httpFilter)
|
||||
}
|
||||
return envoyFilters, nil
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyTLSParameters(defaultParams *pbproxystate.TLSParameters, overrideParams *pbproxystate.TLSParameters) *envoy_tls_v3.TlsParameters {
|
||||
tlsParams := &envoy_tls_v3.TlsParameters{}
|
||||
|
||||
if overrideParams != nil {
|
||||
if overrideParams.MinVersion != pbproxystate.TLSVersion_TLS_VERSION_UNSPECIFIED {
|
||||
if minVersion, ok := envoyTLSVersions[overrideParams.MinVersion]; ok {
|
||||
tlsParams.TlsMinimumProtocolVersion = minVersion
|
||||
}
|
||||
}
|
||||
if overrideParams.MaxVersion != pbproxystate.TLSVersion_TLS_VERSION_UNSPECIFIED {
|
||||
if maxVersion, ok := envoyTLSVersions[overrideParams.MaxVersion]; ok {
|
||||
tlsParams.TlsMaximumProtocolVersion = maxVersion
|
||||
}
|
||||
}
|
||||
if len(overrideParams.CipherSuites) != 0 {
|
||||
tlsParams.CipherSuites = marshalEnvoyTLSCipherSuiteStrings(overrideParams.CipherSuites)
|
||||
}
|
||||
return tlsParams
|
||||
}
|
||||
|
||||
if defaultParams != nil {
|
||||
if defaultParams.MinVersion != pbproxystate.TLSVersion_TLS_VERSION_UNSPECIFIED {
|
||||
if minVersion, ok := envoyTLSVersions[defaultParams.MinVersion]; ok {
|
||||
tlsParams.TlsMinimumProtocolVersion = minVersion
|
||||
}
|
||||
}
|
||||
if defaultParams.MaxVersion != pbproxystate.TLSVersion_TLS_VERSION_UNSPECIFIED {
|
||||
if maxVersion, ok := envoyTLSVersions[defaultParams.MaxVersion]; ok {
|
||||
tlsParams.TlsMaximumProtocolVersion = maxVersion
|
||||
}
|
||||
}
|
||||
if len(defaultParams.CipherSuites) != 0 {
|
||||
tlsParams.CipherSuites = marshalEnvoyTLSCipherSuiteStrings(defaultParams.CipherSuites)
|
||||
}
|
||||
return tlsParams
|
||||
}
|
||||
|
||||
return tlsParams
|
||||
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyTransportSocket(ts *pbproxystate.TransportSocket) (*envoy_core_v3.TransportSocket, error) {
|
||||
if ts == nil {
|
||||
return nil, nil
|
||||
}
|
||||
commonTLSContext := &envoy_tls_v3.CommonTlsContext{}
|
||||
|
||||
// Create connection TLS. Listeners should only look at inbound TLS.
|
||||
switch ts.ConnectionTls.(type) {
|
||||
case *pbproxystate.TransportSocket_InboundMesh:
|
||||
downstreamContext := &envoy_tls_v3.DownstreamTlsContext{}
|
||||
downstreamContext.CommonTlsContext = commonTLSContext
|
||||
// Set TLS Parameters.
|
||||
tlsParams := pr.makeEnvoyTLSParameters(pr.proxyState.Tls.InboundTlsParameters, ts.TlsParameters)
|
||||
commonTLSContext.TlsParams = tlsParams
|
||||
|
||||
// Set the certificate config on the tls context.
|
||||
// For inbound mesh, we need to add the identity certificate
|
||||
// and the validation context for the mesh depending on the provided trust bundle names.
|
||||
if pr.proxyState.Tls == nil {
|
||||
// if tls is nil but connection tls is provided, then the proxy state is misconfigured
|
||||
return nil, fmt.Errorf("proxyState.Tls is required to generate router's transport socket")
|
||||
}
|
||||
im := ts.ConnectionTls.(*pbproxystate.TransportSocket_InboundMesh).InboundMesh
|
||||
leaf, ok := pr.proxyState.LeafCertificates[im.IdentityKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to create transport socket: leaf certificate %q not found", im.IdentityKey)
|
||||
}
|
||||
err := pr.makeEnvoyCertConfig(commonTLSContext, leaf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create transport socket: %w", err)
|
||||
}
|
||||
|
||||
// Create validation context.
|
||||
// When there's only one trust bundle name, we create a simple validation context
|
||||
if len(im.ValidationContext.TrustBundlePeerNameKeys) == 1 {
|
||||
peerName := im.ValidationContext.TrustBundlePeerNameKeys[0]
|
||||
tb, ok := pr.proxyState.TrustBundles[peerName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to create transport socket: provided trust bundle name does not exist in proxystate trust bundle map: %s", peerName)
|
||||
}
|
||||
commonTLSContext.ValidationContextType = &envoy_tls_v3.CommonTlsContext_ValidationContext{
|
||||
ValidationContext: &envoy_tls_v3.CertificateValidationContext{
|
||||
// TODO(banks): later for L7 support we may need to configure ALPN here.
|
||||
TrustedCa: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: RootPEMsAsString(tb.Roots),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else if len(im.ValidationContext.TrustBundlePeerNameKeys) > 1 {
|
||||
cfg := &envoy_tls_v3.SPIFFECertValidatorConfig{
|
||||
TrustDomains: make([]*envoy_tls_v3.SPIFFECertValidatorConfig_TrustDomain, 0, len(im.ValidationContext.TrustBundlePeerNameKeys)),
|
||||
}
|
||||
|
||||
for _, peerName := range im.ValidationContext.TrustBundlePeerNameKeys {
|
||||
// Look up the trust bundle ca in the map.
|
||||
tb, ok := pr.proxyState.TrustBundles[peerName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to create transport socket: provided bundle name does not exist in trust bundle map: %s", peerName)
|
||||
}
|
||||
cfg.TrustDomains = append(cfg.TrustDomains, &envoy_tls_v3.SPIFFECertValidatorConfig_TrustDomain{
|
||||
Name: tb.TrustDomain,
|
||||
TrustBundle: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: RootPEMsAsString(tb.Roots),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
// Sort the trust domains so the output is stable.
|
||||
sortTrustDomains(cfg.TrustDomains)
|
||||
|
||||
spiffeConfig, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commonTLSContext.ValidationContextType = &envoy_tls_v3.CommonTlsContext_ValidationContext{
|
||||
ValidationContext: &envoy_tls_v3.CertificateValidationContext{
|
||||
CustomValidatorConfig: &envoy_core_v3.TypedExtensionConfig{
|
||||
// The typed config name is hard-coded because it is not available as a wellknown var in the control plane lib.
|
||||
Name: "envoy.tls.cert_validator.spiffe",
|
||||
TypedConfig: spiffeConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
// Always require client certificate
|
||||
downstreamContext.RequireClientCertificate = &wrapperspb.BoolValue{Value: true}
|
||||
transportSocket, err := makeTransportSocket("tls", downstreamContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transportSocket, nil
|
||||
case *pbproxystate.TransportSocket_InboundNonMesh:
|
||||
downstreamContext := &envoy_tls_v3.DownstreamTlsContext{}
|
||||
downstreamContext.CommonTlsContext = commonTLSContext
|
||||
// Set TLS Parameters
|
||||
tlsParams := pr.makeEnvoyTLSParameters(pr.proxyState.Tls.InboundTlsParameters, ts.TlsParameters)
|
||||
commonTLSContext.TlsParams = tlsParams
|
||||
// For non-mesh, we don't care about validation context as currently we don't support mTLS for non-mesh connections.
|
||||
nonMeshTLS := ts.ConnectionTls.(*pbproxystate.TransportSocket_InboundNonMesh).InboundNonMesh
|
||||
err := pr.addNonMeshCertConfig(commonTLSContext, nonMeshTLS)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create transport socket: %w", err)
|
||||
}
|
||||
transportSocket, err := makeTransportSocket("tls", downstreamContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transportSocket, nil
|
||||
case *pbproxystate.TransportSocket_OutboundMesh:
|
||||
upstreamContext := &envoy_tls_v3.UpstreamTlsContext{}
|
||||
upstreamContext.CommonTlsContext = commonTLSContext
|
||||
// Set TLS Parameters
|
||||
tlsParams := pr.makeEnvoyTLSParameters(pr.proxyState.Tls.OutboundTlsParameters, ts.TlsParameters)
|
||||
commonTLSContext.TlsParams = tlsParams
|
||||
// For outbound mesh, we need to insert the mesh identity certificate
|
||||
// and the validation context for the mesh depending on the provided trust bundle names.
|
||||
if pr.proxyState.Tls == nil {
|
||||
// if tls is nil but connection tls is provided, then the proxy state is misconfigured
|
||||
return nil, fmt.Errorf("proxyState.Tls is required to generate router's transport socket")
|
||||
}
|
||||
om := ts.ConnectionTls.(*pbproxystate.TransportSocket_OutboundMesh).OutboundMesh
|
||||
leaf, ok := pr.proxyState.LeafCertificates[om.IdentityKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("leaf %s not found in proxyState", om.IdentityKey)
|
||||
}
|
||||
err := pr.makeEnvoyCertConfig(commonTLSContext, leaf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create transport socket: %w", err)
|
||||
}
|
||||
|
||||
// Create validation context
|
||||
peerName := om.ValidationContext.TrustBundlePeerNameKey
|
||||
tb, ok := pr.proxyState.TrustBundles[peerName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to create transport socket: provided peer name does not exist in trust bundle map: %s", peerName)
|
||||
}
|
||||
|
||||
var matchers []*envoy_matcher_v3.StringMatcher
|
||||
if len(om.ValidationContext.SpiffeIds) > 0 {
|
||||
matchers = make([]*envoy_matcher_v3.StringMatcher, 0)
|
||||
for _, m := range om.ValidationContext.SpiffeIds {
|
||||
matchers = append(matchers, &envoy_matcher_v3.StringMatcher{
|
||||
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||
Exact: m,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
commonTLSContext.ValidationContextType = &envoy_tls_v3.CommonTlsContext_ValidationContext{
|
||||
ValidationContext: &envoy_tls_v3.CertificateValidationContext{
|
||||
// TODO(banks): later for L7 support we may need to configure ALPN here.
|
||||
TrustedCa: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: RootPEMsAsString(tb.Roots),
|
||||
},
|
||||
},
|
||||
MatchSubjectAltNames: matchers,
|
||||
},
|
||||
}
|
||||
|
||||
upstreamContext.Sni = om.Sni
|
||||
transportSocket, err := makeTransportSocket("tls", upstreamContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transportSocket, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoyCertConfig(common *envoy_tls_v3.CommonTlsContext, certificate *pbproxystate.LeafCertificate) error {
|
||||
if certificate == nil {
|
||||
return fmt.Errorf("no leaf certificate provided")
|
||||
}
|
||||
common.TlsCertificates = []*envoy_tls_v3.TlsCertificate{
|
||||
{
|
||||
CertificateChain: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: lib.EnsureTrailingNewline(certificate.Cert),
|
||||
},
|
||||
},
|
||||
PrivateKey: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: lib.EnsureTrailingNewline(certificate.Key),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) makeEnvoySDSCertConfig(common *envoy_tls_v3.CommonTlsContext, certificate *pbproxystate.SDSCertificate) error {
|
||||
if certificate == nil {
|
||||
return fmt.Errorf("no SDS certificate provided")
|
||||
}
|
||||
common.TlsCertificateSdsSecretConfigs = []*envoy_tls_v3.SdsSecretConfig{
|
||||
{
|
||||
Name: certificate.CertResource,
|
||||
SdsConfig: &envoy_core_v3.ConfigSource{
|
||||
ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_ApiConfigSource{
|
||||
ApiConfigSource: &envoy_core_v3.ApiConfigSource{
|
||||
ApiType: envoy_core_v3.ApiConfigSource_GRPC,
|
||||
TransportApiVersion: envoy_core_v3.ApiVersion_V3,
|
||||
// Note ClusterNames can't be set here - that's only for REST type
|
||||
// we need a full GRPC config instead.
|
||||
GrpcServices: []*envoy_core_v3.GrpcService{
|
||||
{
|
||||
TargetSpecifier: &envoy_core_v3.GrpcService_EnvoyGrpc_{
|
||||
EnvoyGrpc: &envoy_core_v3.GrpcService_EnvoyGrpc{
|
||||
ClusterName: certificate.ClusterName,
|
||||
},
|
||||
},
|
||||
Timeout: &durationpb.Duration{Seconds: 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ResourceApiVersion: envoy_core_v3.ApiVersion_V3,
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) addNonMeshCertConfig(common *envoy_tls_v3.CommonTlsContext, tls *pbproxystate.InboundNonMeshTLS) error {
|
||||
if tls == nil {
|
||||
return fmt.Errorf("no inbound non-mesh TLS provided")
|
||||
}
|
||||
|
||||
switch tls.Identity.(type) {
|
||||
case *pbproxystate.InboundNonMeshTLS_LeafKey:
|
||||
leafKey := tls.Identity.(*pbproxystate.InboundNonMeshTLS_LeafKey).LeafKey
|
||||
leaf, ok := pr.proxyState.LeafCertificates[leafKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("leaf key %s not found in leaf certificate map", leafKey)
|
||||
}
|
||||
common.TlsCertificates = []*envoy_tls_v3.TlsCertificate{
|
||||
{
|
||||
CertificateChain: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: lib.EnsureTrailingNewline(leaf.Cert),
|
||||
},
|
||||
},
|
||||
PrivateKey: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: lib.EnsureTrailingNewline(leaf.Key),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
case *pbproxystate.InboundNonMeshTLS_Sds:
|
||||
c := tls.Identity.(*pbproxystate.InboundNonMeshTLS_Sds).Sds
|
||||
common.TlsCertificateSdsSecretConfigs = []*envoy_tls_v3.SdsSecretConfig{
|
||||
{
|
||||
Name: c.CertResource,
|
||||
SdsConfig: &envoy_core_v3.ConfigSource{
|
||||
ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_ApiConfigSource{
|
||||
ApiConfigSource: &envoy_core_v3.ApiConfigSource{
|
||||
ApiType: envoy_core_v3.ApiConfigSource_GRPC,
|
||||
TransportApiVersion: envoy_core_v3.ApiVersion_V3,
|
||||
// Note ClusterNames can't be set here - that's only for REST type
|
||||
// we need a full GRPC config instead.
|
||||
GrpcServices: []*envoy_core_v3.GrpcService{
|
||||
{
|
||||
TargetSpecifier: &envoy_core_v3.GrpcService_EnvoyGrpc_{
|
||||
EnvoyGrpc: &envoy_core_v3.GrpcService_EnvoyGrpc{
|
||||
ClusterName: c.ClusterName,
|
||||
},
|
||||
},
|
||||
Timeout: &durationpb.Duration{Seconds: 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ResourceApiVersion: envoy_core_v3.ApiVersion_V3,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeTransportSocket(name string, config proto.Message) (*envoy_core_v3.TransportSocket, error) {
|
||||
any, err := anypb.New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &envoy_core_v3.TransportSocket{
|
||||
Name: name,
|
||||
ConfigType: &envoy_core_v3.TransportSocket_TypedConfig{
|
||||
TypedConfig: any,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeEnvoyListenerFilter(c pbproxystate.Capability) (*envoy_listener_v3.ListenerFilter, error) {
|
||||
var lf proto.Message
|
||||
var name string
|
||||
|
||||
switch c {
|
||||
case pbproxystate.Capability_CAPABILITY_TRANSPARENT:
|
||||
lf = &envoy_original_dst_v3.OriginalDst{}
|
||||
name = envoyOriginalDestinationListenerFilterName
|
||||
case pbproxystate.Capability_CAPABILITY_L4_TLS_INSPECTION:
|
||||
name = envoyTLSInspectorListenerFilterName
|
||||
lf = &envoy_tls_inspector_v3.TlsInspector{}
|
||||
case pbproxystate.Capability_CAPABILITY_L7_PROTOCOL_INSPECTION:
|
||||
name = envoyHttpInspectorListenerFilterName
|
||||
lf = &envoy_extensions_filters_listener_http_inspector_v3.HttpInspector{}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported listener captability: %s", c)
|
||||
}
|
||||
lfAsAny, err := anypb.New(lf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_listener_v3.ListenerFilter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_listener_v3.ListenerFilter_TypedConfig{TypedConfig: lfAsAny},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeEnvoyFilter(name string, cfg proto.Message) (*envoy_listener_v3.Filter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_listener_v3.Filter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_listener_v3.Filter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFilter, error) {
|
||||
any, err := anypb.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &envoy_http_v3.HttpFilter{
|
||||
Name: name,
|
||||
ConfigType: &envoy_http_v3.HttpFilter_TypedConfig{TypedConfig: any},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RootPEMsAsString(rootPEMs []string) string {
|
||||
var rootPEMsString string
|
||||
for _, root := range rootPEMs {
|
||||
rootPEMsString += lib.EnsureTrailingNewline(root)
|
||||
}
|
||||
return rootPEMsString
|
||||
}
|
||||
|
||||
func marshalEnvoyTLSCipherSuiteStrings(cipherSuites []pbproxystate.TLSCipherSuite) []string {
|
||||
envoyTLSCipherSuiteStrings := map[pbproxystate.TLSCipherSuite]string{
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_GCM_SHA256: "ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_ECDSA_CHACHA20_POLY1305: "ECDHE-ECDSA-CHACHA20-POLY1305",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_RSA_AES128_GCM_SHA256: "ECDHE-RSA-AES128-GCM-SHA256",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_RSA_CHACHA20_POLY1305: "ECDHE-RSA-CHACHA20-POLY1305",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_ECDSA_AES128_SHA: "ECDHE-ECDSA-AES128-SHA",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_RSA_AES128_SHA: "ECDHE-RSA-AES128-SHA",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_AES128_GCM_SHA256: "AES128-GCM-SHA256",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_AES128_SHA: "AES128-SHA",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_GCM_SHA384: "ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_RSA_AES256_GCM_SHA384: "ECDHE-RSA-AES256-GCM-SHA384",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_ECDSA_AES256_SHA: "ECDHE-ECDSA-AES256-SHA",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_ECDHE_RSA_AES256_SHA: "ECDHE-RSA-AES256-SHA",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_AES256_GCM_SHA384: "AES256-GCM-SHA384",
|
||||
pbproxystate.TLSCipherSuite_TLS_CIPHER_SUITE_AES256_SHA: "AES256-SHA",
|
||||
}
|
||||
|
||||
var cipherSuiteStrings []string
|
||||
|
||||
for _, c := range cipherSuites {
|
||||
if s, ok := envoyTLSCipherSuiteStrings[c]; ok {
|
||||
cipherSuiteStrings = append(cipherSuiteStrings, s)
|
||||
}
|
||||
}
|
||||
|
||||
return cipherSuiteStrings
|
||||
}
|
||||
|
||||
var envoyTLSVersions = map[pbproxystate.TLSVersion]envoy_tls_v3.TlsParameters_TlsProtocol{
|
||||
pbproxystate.TLSVersion_TLS_VERSION_AUTO: envoy_tls_v3.TlsParameters_TLS_AUTO,
|
||||
pbproxystate.TLSVersion_TLS_VERSION_1_0: envoy_tls_v3.TlsParameters_TLSv1_0,
|
||||
pbproxystate.TLSVersion_TLS_VERSION_1_1: envoy_tls_v3.TlsParameters_TLSv1_1,
|
||||
pbproxystate.TLSVersion_TLS_VERSION_1_2: envoy_tls_v3.TlsParameters_TLSv1_2,
|
||||
pbproxystate.TLSVersion_TLS_VERSION_1_3: envoy_tls_v3.TlsParameters_TLSv1_3,
|
||||
}
|
||||
|
||||
// Sort the trust domains so that the output is stable.
|
||||
// This benefits tests but also prevents Envoy from mistakenly thinking the listener
|
||||
// changed and needs to be drained only because this ordering is different.
|
||||
func sortTrustDomains(trustDomains []*envoy_tls_v3.SPIFFECertValidatorConfig_TrustDomain) {
|
||||
sort.Slice(trustDomains, func(i int, j int) bool {
|
||||
return trustDomains[i].Name < trustDomains[j].Name
|
||||
})
|
||||
}
|
||||
|
||||
// sortRouters stable sorts routers with a Match to avoid draining if the list is provided out of order.
|
||||
// xdsv1 used to sort the filter chains on outbound listeners, so this adds that functionality by sorting routers with matches.
|
||||
func sortRouters(routers []*pbproxystate.Router) {
|
||||
if routers == nil {
|
||||
return
|
||||
}
|
||||
sort.SliceStable(routers, func(i, j int) bool {
|
||||
si := ""
|
||||
sj := ""
|
||||
if routers[i].Match != nil {
|
||||
if len(routers[i].Match.PrefixRanges) > 0 {
|
||||
si += routers[i].Match.PrefixRanges[0].AddressPrefix +
|
||||
"/" + routers[i].Match.PrefixRanges[0].PrefixLen.String() +
|
||||
":" + routers[i].Match.DestinationPort.String()
|
||||
}
|
||||
if len(routers[i].Match.ServerNames) > 0 {
|
||||
si += routers[i].Match.ServerNames[0] +
|
||||
":" + routers[i].Match.DestinationPort.String()
|
||||
} else {
|
||||
si += routers[i].Match.DestinationPort.String()
|
||||
}
|
||||
}
|
||||
|
||||
if routers[j].Match != nil {
|
||||
if len(routers[j].Match.PrefixRanges) > 0 {
|
||||
sj += routers[j].Match.PrefixRanges[0].AddressPrefix +
|
||||
"/" + routers[j].Match.PrefixRanges[0].PrefixLen.String() +
|
||||
":" + routers[j].Match.DestinationPort.String()
|
||||
}
|
||||
if len(routers[j].Match.ServerNames) > 0 {
|
||||
sj += routers[j].Match.ServerNames[0] +
|
||||
":" + routers[j].Match.DestinationPort.String()
|
||||
} else {
|
||||
sj += routers[j].Match.DestinationPort.String()
|
||||
}
|
||||
}
|
||||
|
||||
return si < sj
|
||||
})
|
||||
}
|
||||
|
||||
func sortPrefixRanges(prefixRanges []*pbproxystate.CidrRange) {
|
||||
if prefixRanges == nil {
|
||||
return
|
||||
}
|
||||
sort.SliceStable(prefixRanges, func(i, j int) bool {
|
||||
return prefixRanges[i].AddressPrefix < prefixRanges[j].AddressPrefix
|
||||
})
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package xdsv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/hashicorp/consul/envoyextensions/xdscommon"
|
||||
)
|
||||
|
||||
// ResourceGenerator is associated with a single gRPC stream and creates xDS
|
||||
// resources for a single client.
|
||||
type ResourceGenerator struct {
|
||||
Logger hclog.Logger
|
||||
ProxyFeatures xdscommon.SupportedProxyFeatures
|
||||
}
|
||||
|
||||
func NewResourceGenerator(
|
||||
logger hclog.Logger,
|
||||
) *ResourceGenerator {
|
||||
return &ResourceGenerator{
|
||||
Logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type ProxyResources struct {
|
||||
proxyState *pbmesh.ProxyState
|
||||
envoyResources map[string][]proto.Message
|
||||
}
|
||||
|
||||
func (g *ResourceGenerator) AllResourcesFromIR(proxyState *pbmesh.ProxyState) (map[string][]proto.Message, error) {
|
||||
pr := &ProxyResources{
|
||||
proxyState: proxyState,
|
||||
envoyResources: make(map[string][]proto.Message),
|
||||
}
|
||||
err := pr.generateXDSResources()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate xDS resources for ProxyState: %v", err)
|
||||
}
|
||||
return pr.envoyResources, nil
|
||||
}
|
||||
|
||||
func (pr *ProxyResources) generateXDSResources() error {
|
||||
listeners := make([]proto.Message, 0)
|
||||
clusters := make([]proto.Message, 0)
|
||||
routes := make([]proto.Message, 0)
|
||||
endpoints := make([]proto.Message, 0)
|
||||
|
||||
for _, l := range pr.proxyState.Listeners {
|
||||
protoListener, err := pr.makeListener(l)
|
||||
// TODO: aggregate errors for listeners and still return any properly formed listeners.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listeners = append(listeners, protoListener)
|
||||
}
|
||||
|
||||
pr.envoyResources[xdscommon.ListenerType] = listeners
|
||||
pr.envoyResources[xdscommon.ClusterType] = clusters
|
||||
pr.envoyResources[xdscommon.RouteType] = routes
|
||||
pr.envoyResources[xdscommon.EndpointType] = endpoints
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package xdsv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
)
|
||||
|
||||
func (pr *ProxyResources) makeRoute(name string) (*envoy_route_v3.RouteConfiguration, error) {
|
||||
var route *envoy_route_v3.RouteConfiguration
|
||||
// TODO(proxystate): This will make routes in the future. This function should distinguish between static routes
|
||||
// inlined into listeners and non-static routes that should be added as top level Envoy resources.
|
||||
_, ok := pr.proxyState.Routes[name]
|
||||
if !ok {
|
||||
// This should not happen with a valid proxy state.
|
||||
return nil, fmt.Errorf("could not find route in ProxyState: %s", name)
|
||||
|
||||
}
|
||||
return route, nil
|
||||
}
|
|
@ -105,8 +105,7 @@ type ProxyState struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// id is this proxy's identity. This should correspond to the workload identity that this proxy of
|
||||
// the workload this proxy represents.
|
||||
// identity is a reference to the WorkloadIdentity associated with this proxy.
|
||||
Identity *pbresource.Reference `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
|
||||
// listeners is a list of listeners for this proxy.
|
||||
Listeners []*pbproxystate.Listener `protobuf:"bytes,2,rep,name=listeners,proto3" json:"listeners,omitempty"`
|
||||
|
|
|
@ -30,8 +30,7 @@ message ProxyStateTemplate {
|
|||
}
|
||||
|
||||
message ProxyState {
|
||||
// id is this proxy's identity. This should correspond to the workload identity that this proxy of
|
||||
// the workload this proxy represents.
|
||||
// identity is a reference to the WorkloadIdentity associated with this proxy.
|
||||
hashicorp.consul.resource.Reference identity = 1;
|
||||
// listeners is a list of listeners for this proxy.
|
||||
repeated pbproxystate.Listener listeners = 2;
|
||||
|
|
|
@ -210,7 +210,7 @@ function assert_envoy_expose_checks_listener_count {
|
|||
RANGES=$(echo "$BODY" | jq '.active_state.listener.filter_chains[0].filter_chain_match.source_prefix_ranges | length')
|
||||
echo "RANGES = $RANGES (expect 3)"
|
||||
# note: if IPv6 is not supported in the kernel per
|
||||
# agent/xds:kernelSupportsIPv6() then this will only be 2
|
||||
# agent/xds/platform:SupportsIPv6() then this will only be 2
|
||||
[ "${RANGES:-0}" -eq 3 ]
|
||||
|
||||
HCM=$(echo "$BODY" | jq '.active_state.listener.filter_chains[0].filters[0]')
|
||||
|
|
|
@ -255,7 +255,7 @@ function assert_envoy_expose_checks_listener_count {
|
|||
RANGES=$(echo "$BODY" | jq '.active_state.listener.filter_chains[0].filter_chain_match.source_prefix_ranges | length')
|
||||
echo "RANGES = $RANGES (expect 3)"
|
||||
# note: if IPv6 is not supported in the kernel per
|
||||
# agent/xds:kernelSupportsIPv6() then this will only be 2
|
||||
# agent/xds/platform:SupportsIPv6() then this will only be 2
|
||||
[ "${RANGES:-0}" -eq 3 ]
|
||||
|
||||
HCM=$(echo "$BODY" | jq '.active_state.listener.filter_chains[0].filters[0]')
|
||||
|
|
Loading…
Reference in New Issue