From 1b1a97e8f99658c4c3dceada5b146a4e8ff02966 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 11 Jan 2022 11:46:42 -0500 Subject: [PATCH] ingress: allow setting TLS min version and cipher suites in ingress gateway config entries (#11576) * xds: refactor ingress listener SDS configuration * xds: update resolveListenerSDS call args in listeners_test * ingress: add TLS min, max and cipher suites to GatewayTLSConfig * xds: implement envoyTLSVersions and envoyTLSCipherSuites * xds: merge TLS config * xds: configure TLS parameters with ingress TLS context from leaf * xds: nil check in resolveListenerTLSConfig validation * xds: nil check in makeTLSParameters* functions * changelog: add entry for TLS params on ingress config entries * xds: remove indirection for TLS params in TLSConfig structs * xds: return tlsContext, nil instead of ambiguous err Co-authored-by: Chris S. Kim * xds: switch zero checks to types.TLSVersionUnspecified * ingress: add validation for ingress config entry TLS params * ingress: validate listener TLS config * xds: add basic ingress with TLS params tests * xds: add ingress listeners mixed TLS min version defaults precedence test * xds: add more explicit tests for ingress listeners inheriting gateway defaults * xds: add test for single TLS listener on gateway without TLS defaults * xds: regen golden files for TLSVersionInvalid zero value, add TLSVersionAuto listener test * types/tls: change TLSVersion to string * types/tls: update TLSCipherSuite to string type * types/tls: implement validation functions for TLSVersion and TLSCipherSuites, make some maps private * api: add TLS params to GatewayTLSConfig, add tests * api: add TLSMinVersion to ingress gateway config entry test JSON * xds: switch to Envoy TLS cipher suite encoding from types package * xds: fixup validation for TLSv1_3 min version with cipher suites * add some kitchen sink tests and add a missing struct tag * xds: check if mergedCfg.TLSVersion is in TLSVersionsWithConfigurableCipherSuites * xds: update connectTLSEnabled comment * xds: remove unsued resolveGatewayServiceTLSConfig function * xds: add makeCommonTLSContextFromLeafWithoutParams * types/tls: add LessThan comparator function for concrete values * types/tls: change tlsVersions validation map from string to TLSVersion keys * types/tls: remove unused envoyTLSCipherSuites * types/tls: enable chacha20 cipher suites for Consul agent * types/tls: remove insecure cipher suites from allowed config TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 are both explicitly listed as insecure and disabled in the Go source. Refs https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/crypto/tls/cipher_suites.go;l=329-330 * types/tls: add ValidateConsulAgentCipherSuites function, make direct lookup map private * types/tls: return all unmatched cipher suites in validation errors * xds: check that Envoy API value matching TLS version is found when building TlsParameters * types/tls: check that value is found in map before appending to slice in MarshalEnvoyTLSCipherSuiteStrings * types/tls: cast to string rather than fmt.Printf in TLSCihperSuite.String() * xds: add TLSVersionUnspecified to list of configurable cipher suites * structs: update note about config entry warning * xds: remove TLS min version cipher suite unconfigurable test placeholder * types/tls: update tests to remove assumption about private map values Co-authored-by: R.B. Boyer --- .changelog/11576.txt | 3 + agent/structs/config_entry_gateways.go | 52 +++ agent/structs/config_entry_test.go | 22 +- agent/xds/clusters.go | 6 +- agent/xds/listeners.go | 16 +- agent/xds/listeners_ingress.go | 224 ++++++++--- agent/xds/listeners_test.go | 235 +++++++++++- ...th-single-tls-listener.envoy-1-20-x.golden | 120 ++++++ ...listener-cipher-suites.envoy-1-20-x.golden | 62 +++ ...s-listener-max-version.envoy-1-20-x.golden | 59 +++ ...s-listener-min-version.envoy-1-20-x.golden | 59 +++ ...eners-gateway-defaults.envoy-1-20-x.golden | 357 ++++++++++++++++++ ...-min-version-listeners.envoy-1-20-x.golden | 217 +++++++++++ api/config_entry_gateways.go | 7 + api/config_entry_gateways_test.go | 3 +- api/config_entry_test.go | 17 +- command/config/write/config_write_test.go | 39 +- types/tls.go | 240 +++++++----- types/tls_test.go | 27 +- 19 files changed, 1578 insertions(+), 187 deletions(-) create mode 100644 .changelog/11576.txt create mode 100644 agent/xds/testdata/listeners/ingress-with-single-tls-listener.envoy-1-20-x.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-tls-listener-cipher-suites.envoy-1-20-x.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-tls-listener-max-version.envoy-1-20-x.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-tls-listener-min-version.envoy-1-20-x.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.envoy-1-20-x.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.envoy-1-20-x.golden diff --git a/.changelog/11576.txt b/.changelog/11576.txt new file mode 100644 index 0000000000..94b4221473 --- /dev/null +++ b/.changelog/11576.txt @@ -0,0 +1,3 @@ +```release-note:feature +ingress: allow setting TLS min version and cipher suites in ingress gateway config entries +``` diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index bad63294a7..eb46df0d71 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/lib/stringslice" + "github.com/hashicorp/consul/types" ) // IngressGatewayConfigEntry manages the configuration for an ingress service @@ -99,6 +100,13 @@ type GatewayTLSConfig struct { // SDS allows configuring TLS certificate from an SDS service. SDS *GatewayTLSSDSConfig `json:",omitempty"` + + TLSMinVersion types.TLSVersion `json:",omitempty" alias:"tls_min_version"` + TLSMaxVersion types.TLSVersion `json:",omitempty" alias:"tls_max_version"` + + // Define a subset of cipher suites to restrict + // Only applicable to connections negotiated via TLS 1.2 or earlier + CipherSuites []types.TLSCipherSuite `json:",omitempty" alias:"cipher_suites"` } type GatewayServiceTLSConfig struct { @@ -231,11 +239,49 @@ func (e *IngressGatewayConfigEntry) validateServiceSDS(lis IngressListener, svc return nil } +func validateGatewayTLSConfig(tlsCfg GatewayTLSConfig) error { + if tlsCfg.TLSMinVersion != types.TLSVersionUnspecified { + if err := types.ValidateTLSVersion(tlsCfg.TLSMinVersion); err != nil { + return err + } + } + + if tlsCfg.TLSMaxVersion != types.TLSVersionUnspecified { + if err := types.ValidateTLSVersion(tlsCfg.TLSMaxVersion); err != nil { + return err + } + + if tlsCfg.TLSMinVersion != types.TLSVersionUnspecified { + if err, maxLessThanMin := tlsCfg.TLSMaxVersion.LessThan(tlsCfg.TLSMinVersion); err == nil && maxLessThanMin { + return fmt.Errorf("configuring max version %s less than the configured min version %s is invalid", tlsCfg.TLSMaxVersion, tlsCfg.TLSMinVersion) + } + } + } + + if len(tlsCfg.CipherSuites) != 0 { + if _, ok := types.TLSVersionsWithConfigurableCipherSuites[tlsCfg.TLSMinVersion]; !ok { + return fmt.Errorf("configuring CipherSuites is only applicable to conncetions negotiated with TLS 1.2 or earlier, TLSMinVersion is set to %s", tlsCfg.TLSMinVersion) + } + + // NOTE: it would be nice to emit a warning but not return an error from + // here if TLSMaxVersion is unspecified, TLS_AUTO or TLSv1_3 + if err := types.ValidateEnvoyCipherSuites(tlsCfg.CipherSuites); err != nil { + return err + } + } + + return nil +} + func (e *IngressGatewayConfigEntry) Validate() error { if err := validateConfigEntryMeta(e.Meta); err != nil { return err } + if err := validateGatewayTLSConfig(e.TLS); err != nil { + return err + } + validProtocols := map[string]bool{ "tcp": true, "http": true, @@ -264,6 +310,12 @@ func (e *IngressGatewayConfigEntry) Validate() error { listener.Port) } + if listener.TLS != nil { + if err := validateGatewayTLSConfig(*listener.TLS); err != nil { + return err + } + } + declaredHosts := make(map[string]bool) serviceNames := make(map[ServiceID]struct{}) for _, s := range listener.Services { diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index febe750124..54c7989fc2 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/types" ) func TestConfigEntries_ACLs(t *testing.T) { @@ -1107,6 +1108,7 @@ func TestDecodeConfigEntry(t *testing.T) { }, }, { + // TODO(rb): test SDS stuff here in both places (global/service) name: "ingress-gateway: kitchen sink", snake: ` kind = "ingress-gateway" @@ -1118,6 +1120,12 @@ func TestDecodeConfigEntry(t *testing.T) { tls { enabled = true + tls_min_version = "TLSv1_1" + tls_max_version = "TLSv1_2" + cipher_suites = [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } listeners = [ @@ -1181,6 +1189,12 @@ func TestDecodeConfigEntry(t *testing.T) { } TLS { Enabled = true + TLSMinVersion = "TLSv1_1" + TLSMaxVersion = "TLSv1_2" + CipherSuites = [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } Listeners = [ { @@ -1242,7 +1256,13 @@ func TestDecodeConfigEntry(t *testing.T) { "gir": "zim", }, TLS: GatewayTLSConfig{ - Enabled: true, + Enabled: true, + TLSMinVersion: types.TLSv1_1, + TLSMaxVersion: types.TLSv1_2, + CipherSuites: []types.TLSCipherSuite{ + types.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + types.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, }, Listeners: []IngressListener{ { diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index fe978c2d98..d534e9e9a4 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -186,7 +186,7 @@ func makePassthroughClusters(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, ConnectTimeout: ptypes.DurationProto(5 * time.Second), } - commonTLSContext := makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()) + commonTLSContext := makeCommonTLSContextFromLeafWithoutParams(cfgSnap, cfgSnap.Leaf()) err := injectSANMatcher(commonTLSContext, passthrough.SpiffeID) if err != nil { return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", passthrough.SNI, err) @@ -550,7 +550,7 @@ func (s *ResourceGenerator) makeUpstreamClusterForPreparedQuery(upstream structs } // Enable TLS upstream with the configured client certificate. - commonTLSContext := makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()) + commonTLSContext := makeCommonTLSContextFromLeafWithoutParams(cfgSnap, cfgSnap.Leaf()) err = injectSANMatcher(commonTLSContext, spiffeIDs...) if err != nil { return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err) @@ -728,7 +728,7 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain( c.Http2ProtocolOptions = &envoy_core_v3.Http2ProtocolOptions{} } - commonTLSContext := makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()) + commonTLSContext := makeCommonTLSContextFromLeafWithoutParams(cfgSnap, cfgSnap.Leaf()) err = injectSANMatcher(commonTLSContext, spiffeIDs...) if err != nil { return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err) diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index d4de6feb56..2dd9c2ca70 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -740,7 +740,7 @@ func injectHTTPFilterOnFilterChains( func (s *ResourceGenerator) injectConnectTLSOnFilterChains(cfgSnap *proxycfg.ConfigSnapshot, listener *envoy_listener_v3.Listener) error { for idx := range listener.FilterChains { tlsContext := &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()), + CommonTlsContext: makeCommonTLSContextFromLeafWithoutParams(cfgSnap, cfgSnap.Leaf()), RequireClientCertificate: &wrappers.BoolValue{Value: true}, } transportSocket, err := makeDownstreamTLSTransportSocket(tlsContext) @@ -1062,7 +1062,7 @@ func (s *ResourceGenerator) makeFilterChainTerminatingGateway( protocol string, ) (*envoy_listener_v3.FilterChain, error) { tlsContext := &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.TerminatingGateway.ServiceLeaves[service]), + CommonTlsContext: makeCommonTLSContextFromLeafWithoutParams(cfgSnap, cfgSnap.TerminatingGateway.ServiceLeaves[service]), RequireClientCertificate: &wrappers.BoolValue{Value: true}, } transportSocket, err := makeDownstreamTLSTransportSocket(tlsContext) @@ -1536,7 +1536,11 @@ func makeEnvoyHTTPFilter(name string, cfg proto.Message) (*envoy_http_v3.HttpFil }, nil } -func makeCommonTLSContextFromLeaf(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.IssuedCert) *envoy_tls_v3.CommonTlsContext { +func makeCommonTLSContextFromLeafWithoutParams(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.IssuedCert) *envoy_tls_v3.CommonTlsContext { + return makeCommonTLSContextFromLeaf(cfgSnap, leaf, nil) +} + +func makeCommonTLSContextFromLeaf(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.IssuedCert, tlsParams *envoy_tls_v3.TlsParameters) *envoy_tls_v3.CommonTlsContext { // Concatenate all the root PEMs into one. if cfgSnap.Roots == nil { return nil @@ -1547,8 +1551,12 @@ func makeCommonTLSContextFromLeaf(cfgSnap *proxycfg.ConfigSnapshot, leaf *struct rootPEMS += ca.EnsureTrailingNewline(root.RootCert) } + if tlsParams == nil { + tlsParams = &envoy_tls_v3.TlsParameters{} + } + return &envoy_tls_v3.CommonTlsContext{ - TlsParams: &envoy_tls_v3.TlsParameters{}, + TlsParams: tlsParams, TlsCertificates: []*envoy_tls_v3.TlsCertificate{ { CertificateChain: &envoy_core_v3.DataSource{ diff --git a/agent/xds/listeners_ingress.go b/agent/xds/listeners_ingress.go index 7668bd02ea..8f8f741d1c 100644 --- a/agent/xds/listeners_ingress.go +++ b/agent/xds/listeners_ingress.go @@ -8,43 +8,26 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/duration" "github.com/golang/protobuf/ptypes/wrappers" + "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/types" ) func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { var resources []proto.Message for listenerKey, upstreams := range cfgSnap.IngressGateway.Upstreams { - var tlsContext *envoy_tls_v3.DownstreamTlsContext - listenerCfg, ok := cfgSnap.IngressGateway.Listeners[listenerKey] if !ok { return nil, fmt.Errorf("no listener config found for listener on port %d", listenerKey.Port) } - // Enable connect TLS if it is enabled at the Gateway or specific listener - // level. - connectTLSEnabled := cfgSnap.IngressGateway.TLSConfig.Enabled || - (listenerCfg.TLS != nil && listenerCfg.TLS.Enabled) - sdsCfg, err := resolveListenerSDSConfig(cfgSnap, listenerCfg) + tlsContext, err := makeDownstreamTLSContextFromSnapshotListenerConfig(cfgSnap, listenerCfg) if err != nil { return nil, err } - if sdsCfg != nil { - // Set up listener TLS from SDS - tlsContext = &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromSDS(*sdsCfg), - RequireClientCertificate: &wrappers.BoolValue{Value: false}, - } - } else if connectTLSEnabled { - tlsContext = &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()), - RequireClientCertificate: &wrappers.BoolValue{Value: false}, - } - } - if listenerKey.Protocol == "tcp" { // We rely on the invariant of upstreams slice always having at least 1 // member, because this key/value pair is created only when a @@ -154,21 +137,116 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap return resources, nil } -func resolveListenerSDSConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerCfg structs.IngressListener) (*structs.GatewayTLSSDSConfig, error) { - var mergedCfg structs.GatewayTLSSDSConfig +func makeDownstreamTLSContextFromSnapshotListenerConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerCfg structs.IngressListener) (*envoy_tls_v3.DownstreamTlsContext, error) { + var downstreamContext *envoy_tls_v3.DownstreamTlsContext - gwSDS := cfgSnap.IngressGateway.TLSConfig.SDS - if gwSDS != nil { - mergedCfg.ClusterName = gwSDS.ClusterName - mergedCfg.CertResource = gwSDS.CertResource + tlsContext, err := makeCommonTLSContextFromSnapshotListenerConfig(cfgSnap, listenerCfg) + if err != nil { + return nil, err } - if listenerCfg.TLS != nil && listenerCfg.TLS.SDS != nil { - if listenerCfg.TLS.SDS.ClusterName != "" { - mergedCfg.ClusterName = listenerCfg.TLS.SDS.ClusterName + if tlsContext != nil { + downstreamContext = &envoy_tls_v3.DownstreamTlsContext{ + CommonTlsContext: tlsContext, + RequireClientCertificate: &wrappers.BoolValue{Value: false}, } - if listenerCfg.TLS.SDS.CertResource != "" { - mergedCfg.CertResource = listenerCfg.TLS.SDS.CertResource + } + + return downstreamContext, nil +} + +func makeCommonTLSContextFromSnapshotListenerConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerCfg structs.IngressListener) (*envoy_tls_v3.CommonTlsContext, error) { + var tlsContext *envoy_tls_v3.CommonTlsContext + + // Enable connect TLS if it is enabled at the Gateway or specific listener + // level. + gatewayTLSCfg := cfgSnap.IngressGateway.TLSConfig + + // It is not possible to explicitly _disable_ TLS on a listener if it's + // enabled on the gateway, because false is the zero-value of the struct field + // and therefore indistinguishable from it being unspecified. + connectTLSEnabled := gatewayTLSCfg.Enabled || + (listenerCfg.TLS != nil && listenerCfg.TLS.Enabled) + + tlsCfg, err := resolveListenerTLSConfig(&gatewayTLSCfg, listenerCfg) + if err != nil { + return nil, err + } + + if tlsCfg.SDS != nil { + // Set up listener TLS from SDS + tlsContext = makeCommonTLSContextFromGatewayTLSConfig(*tlsCfg) + } else if connectTLSEnabled { + tlsContext = makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf(), makeTLSParametersFromGatewayTLSConfig(*tlsCfg)) + } + + return tlsContext, nil +} + +func resolveListenerTLSConfig(gatewayTLSCfg *structs.GatewayTLSConfig, listenerCfg structs.IngressListener) (*structs.GatewayTLSConfig, error) { + var mergedCfg structs.GatewayTLSConfig + + resolvedSDSCfg, err := resolveListenerSDSConfig(gatewayTLSCfg.SDS, listenerCfg.TLS, listenerCfg.Port) + if err != nil { + return nil, err + } + + mergedCfg.SDS = resolvedSDSCfg + + if gatewayTLSCfg != nil { + mergedCfg.TLSMinVersion = gatewayTLSCfg.TLSMinVersion + mergedCfg.TLSMaxVersion = gatewayTLSCfg.TLSMaxVersion + mergedCfg.CipherSuites = gatewayTLSCfg.CipherSuites + } + + if listenerCfg.TLS != nil { + if listenerCfg.TLS.TLSMinVersion != types.TLSVersionUnspecified { + mergedCfg.TLSMinVersion = listenerCfg.TLS.TLSMinVersion + } + if listenerCfg.TLS.TLSMaxVersion != types.TLSVersionUnspecified { + mergedCfg.TLSMaxVersion = listenerCfg.TLS.TLSMaxVersion + } + if len(listenerCfg.TLS.CipherSuites) != 0 { + mergedCfg.CipherSuites = listenerCfg.TLS.CipherSuites + } + } + + var TLSVersionsWithConfigurableCipherSuites = map[types.TLSVersion]struct{}{ + // Remove these two if Envoy ever sets TLS 1.3 as default minimum + types.TLSVersionUnspecified: {}, + types.TLSVersionAuto: {}, + + types.TLSv1_0: {}, + types.TLSv1_1: {}, + types.TLSv1_2: {}, + } + + // Validate. Configuring cipher suites is only applicable to connections negotiated + // via TLS 1.2 or earlier. Other cases shouldn't be possible as we validate them at + // input but be resilient to bugs later. + if len(mergedCfg.CipherSuites) != 0 { + if _, ok := TLSVersionsWithConfigurableCipherSuites[mergedCfg.TLSMinVersion]; !ok { + return nil, fmt.Errorf("configuring CipherSuites is only applicable to connections negotiated with TLS 1.2 or earlier, TLSMinVersion is set to %s in listener or gateway config", mergedCfg.TLSMinVersion) + } + } + + return &mergedCfg, nil +} + +func resolveListenerSDSConfig(gatewaySDSCfg *structs.GatewayTLSSDSConfig, listenerTLSCfg *structs.GatewayTLSConfig, listenerPort int) (*structs.GatewayTLSSDSConfig, error) { + var mergedCfg structs.GatewayTLSSDSConfig + + if gatewaySDSCfg != nil { + mergedCfg.ClusterName = gatewaySDSCfg.ClusterName + mergedCfg.CertResource = gatewaySDSCfg.CertResource + } + + if listenerTLSCfg != nil && listenerTLSCfg.SDS != nil { + if listenerTLSCfg.SDS.ClusterName != "" { + mergedCfg.ClusterName = listenerTLSCfg.SDS.ClusterName + } + if listenerTLSCfg.SDS.CertResource != "" { + mergedCfg.CertResource = listenerTLSCfg.SDS.CertResource } } @@ -183,10 +261,10 @@ func resolveListenerSDSConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerCfg stru return &mergedCfg, nil case mergedCfg.ClusterName == "" && mergedCfg.CertResource != "": - return nil, fmt.Errorf("missing SDS cluster name for listener on port %d", listenerCfg.Port) + return nil, fmt.Errorf("missing SDS cluster name for listener on port %d", listenerPort) case mergedCfg.ClusterName != "" && mergedCfg.CertResource == "": - return nil, fmt.Errorf("missing SDS cert resource for listener on port %d", listenerCfg.Port) + return nil, fmt.Errorf("missing SDS cert resource for listener on port %d", listenerPort) } return &mergedCfg, nil @@ -260,7 +338,7 @@ func makeSDSOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot, } tlsContext := &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromSDS(*svc.TLS.SDS), + CommonTlsContext: makeCommonTLSContextFromGatewayServiceTLSConfig(*svc.TLS), RequireClientCertificate: &wrappers.BoolValue{Value: false}, } @@ -284,33 +362,71 @@ func makeSDSOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot, return chains, nil } -func makeCommonTLSContextFromSDS(sdsCfg structs.GatewayTLSSDSConfig) *envoy_tls_v3.CommonTlsContext { +var envoyTLSVersions = map[types.TLSVersion]envoy_tls_v3.TlsParameters_TlsProtocol{ + types.TLSVersionAuto: envoy_tls_v3.TlsParameters_TLS_AUTO, + types.TLSv1_0: envoy_tls_v3.TlsParameters_TLSv1_0, + types.TLSv1_1: envoy_tls_v3.TlsParameters_TLSv1_1, + types.TLSv1_2: envoy_tls_v3.TlsParameters_TLSv1_2, + types.TLSv1_3: envoy_tls_v3.TlsParameters_TLSv1_3, +} + +func makeTLSParametersFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *envoy_tls_v3.TlsParameters { + tlsParams := envoy_tls_v3.TlsParameters{} + + if tlsCfg.TLSMinVersion != types.TLSVersionUnspecified { + if minVersion, ok := envoyTLSVersions[tlsCfg.TLSMinVersion]; ok { + tlsParams.TlsMinimumProtocolVersion = minVersion + } + } + if tlsCfg.TLSMaxVersion != types.TLSVersionUnspecified { + if maxVersion, ok := envoyTLSVersions[tlsCfg.TLSMaxVersion]; ok { + tlsParams.TlsMaximumProtocolVersion = maxVersion + } + } + if len(tlsCfg.CipherSuites) != 0 { + tlsParams.CipherSuites = types.MarshalEnvoyTLSCipherSuiteStrings(tlsCfg.CipherSuites) + } + + return &tlsParams +} + +func makeCommonTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *envoy_tls_v3.CommonTlsContext { return &envoy_tls_v3.CommonTlsContext{ - TlsParams: &envoy_tls_v3.TlsParameters{}, - TlsCertificateSdsSecretConfigs: []*envoy_tls_v3.SdsSecretConfig{ - { - Name: sdsCfg.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: sdsCfg.ClusterName, - }, + TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg), + TlsCertificateSdsSecretConfigs: makeTLSCertificateSdsSecretConfigsFromSDS(*tlsCfg.SDS), + } +} + +func makeCommonTLSContextFromGatewayServiceTLSConfig(tlsCfg structs.GatewayServiceTLSConfig) *envoy_tls_v3.CommonTlsContext { + return &envoy_tls_v3.CommonTlsContext{ + TlsParams: &envoy_tls_v3.TlsParameters{}, + TlsCertificateSdsSecretConfigs: makeTLSCertificateSdsSecretConfigsFromSDS(*tlsCfg.SDS), + } +} +func makeTLSCertificateSdsSecretConfigsFromSDS(sdsCfg structs.GatewayTLSSDSConfig) []*envoy_tls_v3.SdsSecretConfig { + return []*envoy_tls_v3.SdsSecretConfig{ + { + Name: sdsCfg.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: sdsCfg.ClusterName, }, - Timeout: &duration.Duration{Seconds: 5}, }, + Timeout: &duration.Duration{Seconds: 5}, }, }, }, - ResourceApiVersion: envoy_core_v3.ApiVersion_V3, }, + ResourceApiVersion: envoy_core_v3.ApiVersion_V3, }, }, } diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index cbfe6c7285..7a3419a247 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -519,6 +519,30 @@ func TestListenersFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotIngressWithTLSListener, setup: nil, }, + { + name: "ingress-with-tls-listener-min-version", + create: proxycfg.TestConfigSnapshotIngressWithTLSListener, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.TLSConfig.TLSMinVersion = types.TLSv1_3 + }, + }, + { + name: "ingress-with-tls-listener-max-version", + create: proxycfg.TestConfigSnapshotIngressWithTLSListener, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.TLSConfig.TLSMaxVersion = types.TLSv1_2 + }, + }, + { + name: "ingress-with-tls-listener-cipher-suites", + create: proxycfg.TestConfigSnapshotIngressWithTLSListener, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.TLSConfig.CipherSuites = []types.TLSCipherSuite{ + types.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + types.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + } + }, + }, { name: "ingress-with-tls-mixed-listeners", // Use SDS helper even though we aren't testing SDS since it already sets @@ -572,6 +596,215 @@ func TestListenersFromSnapshot(t *testing.T) { } }, }, + { + name: "ingress-with-tls-min-version-listeners-gateway-defaults", + create: proxycfg.TestConfigSnapshotIngressWithTLSListener, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.TLSConfig.TLSMinVersion = types.TLSv1_2 + + // One listener disables TLS, one inherits TLS minimum version from the gateway + // config, two others set different versions + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "s1", + LocalBindPort: 8080, + }, + }, + {Protocol: "http", Port: 8081}: { + { + DestinationName: "s2", + LocalBindPort: 8081, + }, + }, + {Protocol: "http", Port: 8082}: { + { + DestinationName: "s3", + LocalBindPort: 8082, + }, + }, + {Protocol: "http", Port: 8083}: { + { + DestinationName: "s4", + LocalBindPort: 8083, + }, + }, + {Protocol: "http", Port: 8084}: { + { + DestinationName: "s4", + LocalBindPort: 8084, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + // Omits listener TLS config, should default to gateway TLS config + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "s1", + }, + }, + }, + // Explicitly sets listener TLS config to nil, should default to gateway TLS config + {Protocol: "http", Port: 8081}: { + Port: 8081, + Services: []structs.IngressService{ + { + Name: "s2", + }, + }, + TLS: nil, + }, + // Explicitly enables TLS config, but with no listener default TLS params, + // should default to gateway TLS config + {Protocol: "http", Port: 8082}: { + Port: 8082, + Services: []structs.IngressService{ + { + Name: "s3", + }, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + }, + }, + // Explicitly unset gateway default TLS min version in favor of proxy default + {Protocol: "http", Port: 8083}: { + Port: 8083, + Services: []structs.IngressService{ + { + Name: "s3", + }, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSVersionAuto, + }, + }, + // Disables listener TLS + {Protocol: "http", Port: 8084}: { + Port: 8084, + Services: []structs.IngressService{ + { + Name: "s4", + }, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: false, + }, + }, + } + }, + }, + { + name: "ingress-with-single-tls-listener", + create: proxycfg.TestConfigSnapshotIngress, + setup: func(snap *proxycfg.ConfigSnapshot) { + // One listener should inherit non-TLS gateway config, another + // listener configures TLS with an explicit minimum version + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "s1", + LocalBindPort: 8080, + }, + }, + {Protocol: "http", Port: 8081}: { + { + DestinationName: "s2", + LocalBindPort: 8081, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "s1", + }, + }, + }, + {Protocol: "http", Port: 8081}: { + Port: 8081, + Services: []structs.IngressService{ + { + Name: "s2", + }, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_2, + }, + }, + } + }, + }, + { + name: "ingress-with-tls-mixed-min-version-listeners", + create: proxycfg.TestConfigSnapshotIngressWithTLSListener, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.TLSConfig.TLSMinVersion = types.TLSv1_2 + + // One listener should inherit TLS minimum version from the gateway config, + // two others each set explicit TLS minimum versions + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "s1", + LocalBindPort: 8080, + }, + }, + {Protocol: "http", Port: 8081}: { + { + DestinationName: "s2", + LocalBindPort: 8081, + }, + }, + {Protocol: "http", Port: 8082}: { + { + DestinationName: "s3", + LocalBindPort: 8082, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "s1", + }, + }, + }, + {Protocol: "http", Port: 8081}: { + Port: 8081, + Services: []structs.IngressService{ + { + Name: "s2", + }, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_0, + }, + }, + {Protocol: "http", Port: 8082}: { + Port: 8082, + Services: []structs.IngressService{ + { + Name: "s3", + }, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_3, + }, + }, + } + }, + }, { name: "ingress-with-sds-listener-gw-level", create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, @@ -1208,7 +1441,7 @@ func TestResolveListenerSDSConfig(t *testing.T) { listenerCfg = lisCfg } - got, err := resolveListenerSDSConfig(snap, listenerCfg) + got, err := resolveListenerSDSConfig(snap.IngressGateway.TLSConfig.SDS, listenerCfg.TLS, listenerCfg.Port) if tc.wantErr != "" { require.Error(t, err) require.Contains(t, err.Error(), tc.wantErr) diff --git a/agent/xds/testdata/listeners/ingress-with-single-tls-listener.envoy-1-20-x.golden b/agent/xds/testdata/listeners/ingress-with-single-tls-listener.envoy-1-20-x.golden new file mode 100644 index 0000000000..3c0d3cbec8 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-single-tls-listener.envoy-1-20-x.golden @@ -0,0 +1,120 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-tls-listener-cipher-suites.envoy-1-20-x.golden b/agent/xds/testdata/listeners/ingress-with-tls-listener-cipher-suites.envoy-1-20-x.golden new file mode 100644 index 0000000000..0288278655 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-tls-listener-cipher-suites.envoy-1-20-x.golden @@ -0,0 +1,62 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:1.2.3.4:9191", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "cipherSuites": [ + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-CHACHA20-POLY1305" + ] + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-tls-listener-max-version.envoy-1-20-x.golden b/agent/xds/testdata/listeners/ingress-with-tls-listener-max-version.envoy-1-20-x.golden new file mode 100644 index 0000000000..6797b81497 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-tls-listener-max-version.envoy-1-20-x.golden @@ -0,0 +1,59 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:1.2.3.4:9191", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMaximumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-tls-listener-min-version.envoy-1-20-x.golden b/agent/xds/testdata/listeners/ingress-with-tls-listener-min-version.envoy-1-20-x.golden new file mode 100644 index 0000000000..e052df8dfd --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-tls-listener-min-version.envoy-1-20-x.golden @@ -0,0 +1,59 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:1.2.3.4:9191", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_3" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.envoy-1-20-x.golden b/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.envoy-1-20-x.golden new file mode 100644 index 0000000000..6cce4ea290 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.envoy-1-20-x.golden @@ -0,0 +1,357 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8082", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8082 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8082", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8082" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8083", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8083 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8083", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8083" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8084", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8084 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8084", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8084" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.envoy-1-20-x.golden b/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.envoy-1-20-x.golden new file mode 100644 index 0000000000..47b7046a92 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.envoy-1-20-x.golden @@ -0,0 +1,217 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_0" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http:1.2.3.4:8082", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8082 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8082", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8082" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_3" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 0792ad824e..56d949ea57 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -43,6 +43,13 @@ type GatewayTLSConfig struct { // SDS allows configuring TLS certificate from an SDS service. SDS *GatewayTLSSDSConfig `json:",omitempty"` + + TLSMinVersion string `json:",omitempty" alias:"tls_min_version"` + TLSMaxVersion string `json:",omitempty" alias:"tls_max_version"` + + // Define a subset of cipher suites to restrict + // Only applicable to connections negotiated via TLS 1.2 or earlier + CipherSuites []string `json:",omitempty" alias:"cipher_suites"` } type GatewayServiceTLSConfig struct { diff --git a/api/config_entry_gateways_test.go b/api/config_entry_gateways_test.go index f80a4d74e8..0e2acd728d 100644 --- a/api/config_entry_gateways_test.go +++ b/api/config_entry_gateways_test.go @@ -26,7 +26,8 @@ func TestAPI_ConfigEntries_IngressGateway(t *testing.T) { Kind: IngressGateway, Name: "bar", TLS: GatewayTLSConfig{ - Enabled: true, + Enabled: true, + TLSMinVersion: "TLSv1_2", }, } diff --git a/api/config_entry_test.go b/api/config_entry_test.go index 04419ea376..e029889f17 100644 --- a/api/config_entry_test.go +++ b/api/config_entry_test.go @@ -945,6 +945,7 @@ func TestDecodeConfigEntry(t *testing.T) { }, }, { + // TODO(rb): test SDS stuff here in both places (global/service) name: "ingress-gateway", body: ` { @@ -955,7 +956,13 @@ func TestDecodeConfigEntry(t *testing.T) { "gir": "zim" }, "Tls": { - "Enabled": true + "Enabled": true, + "TLSMinVersion": "TLSv1_1", + "TLSMaxVersion": "TLSv1_2", + "CipherSuites": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] }, "Listeners": [ { @@ -992,7 +999,13 @@ func TestDecodeConfigEntry(t *testing.T) { "gir": "zim", }, TLS: GatewayTLSConfig{ - Enabled: true, + Enabled: true, + TLSMinVersion: "TLSv1_1", + TLSMaxVersion: "TLSv1_2", + CipherSuites: []string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + }, }, Listeners: []IngressListener{ { diff --git a/command/config/write/config_write_test.go b/command/config/write/config_write_test.go index 0ff5152c90..3128233fed 100644 --- a/command/config/write/config_write_test.go +++ b/command/config/write/config_write_test.go @@ -2096,6 +2096,7 @@ func TestParseConfigEntry(t *testing.T) { }, }, { + // TODO(rb): test SDS stuff here in both places (global/service) name: "ingress-gateway: kitchen sink", snake: ` kind = "ingress-gateway" @@ -2106,6 +2107,12 @@ func TestParseConfigEntry(t *testing.T) { } tls { enabled = true + tls_min_version = "TLSv1_1" + tls_max_version = "TLSv1_2" + cipher_suites = [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } listeners = [ { @@ -2133,6 +2140,12 @@ func TestParseConfigEntry(t *testing.T) { } Tls { Enabled = true + TLSMinVersion = "TLSv1_1" + TLSMaxVersion = "TLSv1_2" + CipherSuites = [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } Listeners = [ { @@ -2160,7 +2173,13 @@ func TestParseConfigEntry(t *testing.T) { "gir": "zim" }, "tls": { - "enabled": true + "enabled": true, + "tls_min_version": "TLSv1_1", + "tls_max_version": "TLSv1_2", + "cipher_suites": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] }, "listeners": [ { @@ -2188,8 +2207,14 @@ func TestParseConfigEntry(t *testing.T) { "foo": "bar", "gir": "zim" }, - "Tls": { - "Enabled": true + "TLS": { + "Enabled": true, + "TLSMinVersion": "TLSv1_1", + "TLSMaxVersion": "TLSv1_2", + "CipherSuites": [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] }, "Listeners": [ { @@ -2217,7 +2242,13 @@ func TestParseConfigEntry(t *testing.T) { "gir": "zim", }, TLS: api.GatewayTLSConfig{ - Enabled: true, + Enabled: true, + TLSMinVersion: "TLSv1_1", + TLSMaxVersion: "TLSv1_2", + CipherSuites: []string{ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + }, }, Listeners: []api.IngressListener{ { diff --git a/types/tls.go b/types/tls.go index 66c10b19b1..9c50f498b5 100644 --- a/types/tls.go +++ b/types/tls.go @@ -1,43 +1,42 @@ package types import ( - "encoding/json" "fmt" + "strings" ) -// TLSVersion is a strongly-typed int used for relative comparison -// (minimum, maximum, greater than, less than) of TLS versions -type TLSVersion int +// TLSVersion is a strongly-typed string for TLS versions +type TLSVersion string const ( // Error value, excluded from lookup maps - TLSVersionInvalid TLSVersion = iota - 1 + TLSVersionInvalid TLSVersion = "TLS_INVALID" // Explicit unspecified zero-value to avoid overwriting parent defaults - TLSVersionUnspecified + TLSVersionUnspecified TLSVersion = "" // Explictly allow implementation to select TLS version // May be useful to supercede defaults specified at a higher layer - TLSVersionAuto + TLSVersionAuto TLSVersion = "TLS_AUTO" _ // Placeholder for SSLv3, hopefully we won't have to add this // TLS versions - TLSv1_0 - TLSv1_1 - TLSv1_2 - TLSv1_3 + TLSv1_0 TLSVersion = "TLSv1_0" + TLSv1_1 TLSVersion = "TLSv1_1" + TLSv1_2 TLSVersion = "TLSv1_2" + TLSv1_3 TLSVersion = "TLSv1_3" ) var ( - TLSVersions = map[string]TLSVersion{ - "TLS_AUTO": TLSVersionAuto, - "TLSv1_0": TLSv1_0, - "TLSv1_1": TLSv1_1, - "TLSv1_2": TLSv1_2, - "TLSv1_3": TLSv1_3, + tlsVersions = map[TLSVersion]struct{}{ + TLSVersionAuto: {}, + TLSv1_0: {}, + TLSv1_1: {}, + TLSv1_2: {}, + TLSv1_3: {}, } - // NOTE: This interface is deprecated in favor of TLSVersions + // NOTE: This interface is deprecated in favor of tlsVersions // and should be eventually removed in a future release. DeprecatedConsulAgentTLSVersions = map[string]TLSVersion{ "": TLSVersionAuto, @@ -46,24 +45,10 @@ var ( "tls12": TLSv1_2, "tls13": TLSv1_3, } - HumanTLSVersionStrings = map[TLSVersion]string{ - TLSVersionAuto: "Allow implementation to select TLS version", - TLSv1_0: "TLS 1.0", - TLSv1_1: "TLS 1.1", - TLSv1_2: "TLS 1.2", - TLSv1_3: "TLS 1.3", - } - ConsulConfigTLSVersionStrings = func() map[TLSVersion]string { - inverted := make(map[TLSVersion]string, len(TLSVersions)) - for k, v := range TLSVersions { - inverted[v] = k - } - return inverted - }() // NOTE: these currently map to the deprecated config strings to support the // deployment pattern of upgrading servers first. This map should eventually - // be removed and any lookups updated to use ConsulConfigTLSVersionStrings - // with newer config strings instead in a future release. + // be removed and any lookups updated to instead use the TLSVersion string + // values directly in a future release. ConsulAutoConfigTLSVersionStrings = map[TLSVersion]string{ TLSVersionAuto: "", TLSv1_0: "tls10", @@ -71,33 +56,51 @@ var ( TLSv1_2: "tls12", TLSv1_3: "tls13", } + TLSVersionsWithConfigurableCipherSuites = map[TLSVersion]struct{}{ + // NOTE: these two are implementation-dependent, but it is not expected that + // either Go or Envoy would default to TLS 1.3 as a minimum version in the + // near future + TLSVersionUnspecified: {}, + TLSVersionAuto: {}, + + TLSv1_0: {}, + TLSv1_1: {}, + TLSv1_2: {}, + } ) -func (v TLSVersion) String() string { - return ConsulConfigTLSVersionStrings[v] +func (v *TLSVersion) String() string { + return string(*v) } -func (v TLSVersion) MarshalJSON() ([]byte, error) { - return json.Marshal(v.String()) +var tlsVersionComparison = map[TLSVersion]uint{ + TLSv1_0: 1, + TLSv1_1: 2, + TLSv1_2: 3, + TLSv1_3: 4, } -func (v *TLSVersion) UnmarshalJSON(bytes []byte) error { - versionStr := string(bytes) - - if n := len(versionStr); n > 1 && versionStr[0] == '"' && versionStr[n-1] == '"' { - versionStr = versionStr[1 : n-1] // trim surrounding quotes +// Will only return true for concrete versions and won't catch +// implementation-dependent conflicts with TLSVersionAuto or unspecified values +func (a TLSVersion) LessThan(b TLSVersion) (error, bool) { + for _, v := range []TLSVersion{a, b} { + if _, ok := tlsVersionComparison[v]; !ok { + return fmt.Errorf("can't compare implementation-dependent values"), false + } } - if version, ok := TLSVersions[versionStr]; ok { - *v = version - return nil - } - - *v = TLSVersionInvalid - return fmt.Errorf("no matching TLS Version found for %s", versionStr) + return nil, tlsVersionComparison[a] < tlsVersionComparison[b] } -// IANA cipher suite constants and values as defined at +func ValidateTLSVersion(v TLSVersion) error { + if _, ok := tlsVersions[v]; !ok { + return fmt.Errorf("no matching TLS version found for %s", v.String()) + } + + return nil +} + +// IANA cipher suite string constants as defined at // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml // This is the total list of TLS 1.2-style cipher suites // which are currently supported by either Envoy 1.21 or the Consul agent @@ -106,67 +109,49 @@ func (v *TLSVersion) UnmarshalJSON(bytes []byte) error { // and as supported cipher suites in the Go runtime change. // // The naming convention for cipher suites changed in TLS 1.3 -// but constant values should still be globally unqiue -// Handling validation on a subset of TLSCipherSuite constants -// would be a future exercise if cipher suites for TLS 1.3 ever -// become configurable in BoringSSL, Envoy, or other implementation -type TLSCipherSuite uint16 +// but constant values should still be globally unqiue. +// +// Handling validation on distinct sets of TLS 1.3 and TLS 1.2 TLSCipherSuite +// constants would be a future exercise if cipher suites for TLS 1.3 ever +// become configurable in BoringSSL, Envoy, or other implementation. +type TLSCipherSuite string const ( - // Envoy cipher suites also used by Consul agent - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLSCipherSuite = 0xc02b - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca9 // Not used by Consul agent yet - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xc02f - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8 // Not used by Consul agent yet - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xc009 - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xc013 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xc02c - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xc030 - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc00a - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014 + // Cipher suites used by both Envoy and Consul agent + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" - // Older cipher suites not supported for Consul agent TLS, will eventually be removed from Envoy defaults - TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009c - TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f - TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009d - TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 - - // Additional cipher suites used by Consul agent but not Envoy - // TODO: these are both explicitly listed as insecure and disabled in the Go source, should they be removed? - // https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/crypto/tls/cipher_suites.go;l=329-330 - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0x0023 - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xc027 + // Older cipher suites not supported for Consul agent TLS, + // will eventually be removed from Envoy defaults + TLS_RSA_WITH_AES_128_GCM_SHA256 = "TLS_RSA_WITH_AES_128_GCM_SHA256" + TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA" + TLS_RSA_WITH_AES_256_GCM_SHA384 = "TLS_RSA_WITH_AES_256_GCM_SHA384" + TLS_RSA_WITH_AES_256_CBC_SHA = "TLS_RSA_WITH_AES_256_CBC_SHA" ) var ( - TLSCipherSuites = map[string]TLSCipherSuite{ - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + consulAgentTLSCipherSuites = map[TLSCipherSuite]struct{}{ + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: {}, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {}, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: {}, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {}, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: {}, - "TLS_RSA_WITH_AES_128_GCM_SHA256": TLS_RSA_WITH_AES_128_GCM_SHA256, - "TLS_RSA_WITH_AES_128_CBC_SHA": TLS_RSA_WITH_AES_128_CBC_SHA, - "TLS_RSA_WITH_AES_256_GCM_SHA384": TLS_RSA_WITH_AES_256_GCM_SHA384, - "TLS_RSA_WITH_AES_256_CBC_SHA": TLS_RSA_WITH_AES_256_CBC_SHA, - - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: {}, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {}, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: {}, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {}, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: {}, } - HumanTLSCipherSuiteStrings = func() map[TLSCipherSuite]string { - inverted := make(map[TLSCipherSuite]string, len(TLSCipherSuites)) - for k, v := range TLSCipherSuites { - inverted[v] = k - } - return inverted - }() - EnvoyTLSCipherSuiteStrings = map[TLSCipherSuite]string{ + envoyTLSCipherSuiteStrings = map[TLSCipherSuite]string{ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "ECDHE-ECDSA-AES128-GCM-SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: "ECDHE-ECDSA-CHACHA20-POLY1305", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "ECDHE-RSA-AES128-GCM-SHA256", @@ -183,3 +168,50 @@ var ( TLS_RSA_WITH_AES_256_CBC_SHA: "AES256-SHA", } ) + +func (c *TLSCipherSuite) String() string { + return string(*c) +} + +func ValidateConsulAgentCipherSuites(cipherSuites []TLSCipherSuite) error { + var unmatched []string + + for _, c := range cipherSuites { + if _, ok := consulAgentTLSCipherSuites[c]; !ok { + unmatched = append(unmatched, c.String()) + } + } + + if len(unmatched) > 0 { + return fmt.Errorf("no matching Consul Agent TLS cipher suite found for %s", strings.Join(unmatched, ",")) + } + return nil +} + +func ValidateEnvoyCipherSuites(cipherSuites []TLSCipherSuite) error { + var unmatched []string + + for _, c := range cipherSuites { + if _, ok := envoyTLSCipherSuiteStrings[c]; !ok { + unmatched = append(unmatched, c.String()) + } + } + + if len(unmatched) > 0 { + return fmt.Errorf("no matching Envoy TLS cipher suite found for %s", strings.Join(unmatched, ",")) + } + + return nil +} + +func MarshalEnvoyTLSCipherSuiteStrings(cipherSuites []TLSCipherSuite) []string { + cipherSuiteStrings := []string{} + + for _, c := range cipherSuites { + if s, ok := envoyTLSCipherSuiteStrings[c]; ok { + cipherSuiteStrings = append(cipherSuiteStrings, s) + } + } + + return cipherSuiteStrings +} diff --git a/types/tls_test.go b/types/tls_test.go index 0cf94e42f3..5c1c1f6457 100644 --- a/types/tls_test.go +++ b/types/tls_test.go @@ -7,14 +7,12 @@ import ( "github.com/stretchr/testify/require" ) -func TestTLSVersion_PartialEq(t *testing.T) { - require.Greater(t, TLSv1_3, TLSv1_2) - require.Greater(t, TLSv1_2, TLSv1_1) - require.Greater(t, TLSv1_1, TLSv1_0) - - require.Less(t, TLSv1_2, TLSv1_3) - require.Less(t, TLSv1_1, TLSv1_2) - require.Less(t, TLSv1_0, TLSv1_1) +func TestTLSVersion_Valid(t *testing.T) { + require.NoError(t, ValidateTLSVersion("TLS_AUTO")) + require.NoError(t, ValidateTLSVersion("TLSv1_0")) + require.NoError(t, ValidateTLSVersion("TLSv1_1")) + require.NoError(t, ValidateTLSVersion("TLSv1_2")) + require.NoError(t, ValidateTLSVersion("TLSv1_3")) } func TestTLSVersion_Invalid(t *testing.T) { @@ -33,16 +31,19 @@ func TestTLSVersion_Zero(t *testing.T) { func TestTLSVersion_ToJSON(t *testing.T) { var tlsVersion TLSVersion - err := tlsVersion.UnmarshalJSON([]byte(`"foo"`)) - require.Error(t, err) - require.Equal(t, tlsVersion, TLSVersionInvalid) - for str, version := range TLSVersions { + // Unmarshalling won't catch invalid version strings, + // must be checked in config or config entry validation + err := json.Unmarshal([]byte(`"foo"`), &tlsVersion) + require.NoError(t, err) + + for version := range tlsVersions { + str := version.String() versionJSON, err := json.Marshal(version) require.NoError(t, err) require.Equal(t, versionJSON, []byte(`"`+str+`"`)) - err = tlsVersion.UnmarshalJSON([]byte(`"` + str + `"`)) + err = json.Unmarshal([]byte(`"`+str+`"`), &tlsVersion) require.NoError(t, err) require.Equal(t, tlsVersion, version) }