diff --git a/.changelog/10903.txt b/.changelog/10903.txt new file mode 100644 index 0000000000..49f6d59319 --- /dev/null +++ b/.changelog/10903.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: Add low-level feature to allow an Ingress to retrieve TLS certificates from SDS. +``` diff --git a/agent/proxycfg/ingress_gateway.go b/agent/proxycfg/ingress_gateway.go index 3ee2c205ce..d8b5842864 100644 --- a/agent/proxycfg/ingress_gateway.go +++ b/agent/proxycfg/ingress_gateway.go @@ -80,13 +80,13 @@ func (s *handlerIngressGateway) handleUpdate(ctx context.Context, u cache.Update return fmt.Errorf("invalid type for config entry: %T", resp.Entry) } - snap.IngressGateway.TLSEnabled = gatewayConf.TLS.Enabled - snap.IngressGateway.TLSSet = true + snap.IngressGateway.GatewayConfigLoaded = true + snap.IngressGateway.TLSConfig = gatewayConf.TLS // Load each listener's config from the config entry so we don't have to // pass listener config through "upstreams" types as that grows. for _, l := range gatewayConf.Listeners { - key := IngressListenerKey{Protocol: l.Protocol, Port: l.Port} + key := IngressListenerKeyFromListener(l) snap.IngressGateway.Listeners[key] = l } @@ -123,7 +123,7 @@ func (s *handlerIngressGateway) handleUpdate(ctx context.Context, u cache.Update hosts = append(hosts, service.Hosts...) - id := IngressListenerKey{Protocol: service.Protocol, Port: service.Port} + id := IngressListenerKeyFromGWService(*service) upstreamsMap[id] = append(upstreamsMap[id], u) } @@ -169,7 +169,9 @@ func makeUpstream(g *structs.GatewayService) structs.Upstream { } func (s *handlerIngressGateway) watchIngressLeafCert(ctx context.Context, snap *ConfigSnapshot) error { - if !snap.IngressGateway.TLSSet || !snap.IngressGateway.HostsSet { + // Note that we DON'T test for TLS.Enabled because we need a leaf cert for the + // gateway even without TLS to use as a client cert. + if !snap.IngressGateway.GatewayConfigLoaded || !snap.IngressGateway.HostsSet { return nil } @@ -197,7 +199,7 @@ func (s *handlerIngressGateway) watchIngressLeafCert(ctx context.Context, snap * func (s *handlerIngressGateway) generateIngressDNSSANs(snap *ConfigSnapshot) []string { // Update our leaf cert watch with wildcard entries for our DNS domains as well as any // configured custom hostnames from the service. - if !snap.IngressGateway.TLSEnabled { + if !snap.IngressGateway.TLSConfig.Enabled { return nil } diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 171c04ae8f..a12e1f126f 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -3,11 +3,11 @@ package proxycfg import ( "context" "fmt" - "github.com/hashicorp/consul/agent/connect" "sort" "github.com/mitchellh/copystructure" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/structs" ) @@ -305,9 +305,13 @@ func (c *configSnapshotMeshGateway) IsEmpty() bool { type configSnapshotIngressGateway struct { ConfigSnapshotUpstreams - // TLSEnabled is whether this gateway's listeners should have TLS configured. - TLSEnabled bool - TLSSet bool + // TLSConfig is the gateway-level TLS configuration. Listener/service level + // config is preserved in the Listeners map below. + TLSConfig structs.GatewayTLSConfig + + // GatewayConfigLoaded is used to determine if we have received the initial + // ingress-gateway config entry yet. + GatewayConfigLoaded bool // Hosts is the list of extra host entries to add to our leaf cert's DNS SANs. Hosts []string @@ -346,6 +350,14 @@ func (k *IngressListenerKey) RouteName() string { return fmt.Sprintf("%d", k.Port) } +func IngressListenerKeyFromGWService(s structs.GatewayService) IngressListenerKey { + return IngressListenerKey{Protocol: s.Protocol, Port: s.Port} +} + +func IngressListenerKeyFromListener(l structs.IngressListener) IngressListenerKey { + return IngressListenerKey{Protocol: l.Protocol, Port: l.Port} +} + // ConfigSnapshot captures all the resulting config needed for a proxy instance. // It is meant to be point-in-time coherent and is used to deliver the current // config state to observers who need it to be pushed in (e.g. XDS server). @@ -403,7 +415,7 @@ func (s *ConfigSnapshot) Valid() bool { case structs.ServiceKindIngressGateway: return s.Roots != nil && s.IngressGateway.Leaf != nil && - s.IngressGateway.TLSSet && + s.IngressGateway.GatewayConfigLoaded && s.IngressGateway.HostsSet default: return false diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index ee55680bea..8ee1017e37 100644 --- a/agent/proxycfg/state_test.go +++ b/agent/proxycfg/state_test.go @@ -942,8 +942,8 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { require.False(t, snap.Valid(), "gateway without hosts set is not valid") - require.True(t, snap.IngressGateway.TLSSet) - require.False(t, snap.IngressGateway.TLSEnabled) + require.True(t, snap.IngressGateway.GatewayConfigLoaded) + require.False(t, snap.IngressGateway.TLSConfig.Enabled) }, }, { @@ -1111,8 +1111,8 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { require.True(t, snap.Valid()) - require.True(t, snap.IngressGateway.TLSSet) - require.True(t, snap.IngressGateway.TLSEnabled) + require.True(t, snap.IngressGateway.GatewayConfigLoaded) + require.True(t, snap.IngressGateway.TLSConfig.Enabled) require.True(t, snap.IngressGateway.HostsSet) require.Len(t, snap.IngressGateway.Hosts, 1) require.Len(t, snap.IngressGateway.Upstreams, 1) diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index e035bd04d4..f156179afe 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -1622,7 +1622,16 @@ func TestConfigSnapshotIngress(t testing.T) *ConfigSnapshot { func TestConfigSnapshotIngressWithTLSListener(t testing.T) *ConfigSnapshot { snap := testConfigSnapshotIngressGateway(t, true, "tcp", "default") - snap.IngressGateway.TLSEnabled = true + snap.IngressGateway.TLSConfig.Enabled = true + return snap +} + +func TestConfigSnapshotIngressWithGatewaySDS(t testing.T) *ConfigSnapshot { + snap := testConfigSnapshotIngressGateway(t, true, "tcp", "default") + snap.IngressGateway.TLSConfig.SDS = &structs.GatewayTLSSDSConfig{ + ClusterName: "sds-cluster", + CertResource: "cert-resource", + } return snap } diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index 48601eafe8..aebb0a0f3d 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -21,7 +21,9 @@ type IngressGatewayConfigEntry struct { // service. This should match the name provided in the service definition. Name string - // TLS holds the TLS configuration for this gateway. + // TLS holds the TLS configuration for this gateway. It would be nicer if it + // were a pointer so it could be omitempty when read back in JSON but that + // would be a breaking API change now as we currently always return it. TLS GatewayTLSConfig // Listeners declares what ports the ingress gateway should listen on, and @@ -43,6 +45,9 @@ type IngressListener struct { // current supported values are: (tcp | http | http2 | grpc). Protocol string + // TLS config for this listener. + TLS *GatewayTLSConfig `json:",omitempty"` + // Services declares the set of services to which the listener forwards // traffic. // @@ -75,6 +80,11 @@ type IngressService struct { // using a "tcp" listener. Hosts []string + // TLS configuration overrides for this service. At least one entry must exist + // in Hosts to use set and the Listener must also have a default Cert loaded + // from SDS. + TLS *GatewayServiceTLSConfig `json:",omitempty"` + // Allow HTTP header manipulation to be configured. RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"` ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"` @@ -84,8 +94,24 @@ type IngressService struct { } type GatewayTLSConfig struct { - // Indicates that TLS should be enabled for this gateway service + // Indicates that TLS should be enabled for this gateway or listener Enabled bool + + // SDS allows configuring TLS certificate from an SDS service. + SDS *GatewayTLSSDSConfig `json:",omitempty"` +} + +type GatewayServiceTLSConfig struct { + // Note no Enabled field here since it doesn't make sense to disable TLS on + // one host on a TLS-configured listener. + + // SDS allows configuring TLS certificate from an SDS service. + SDS *GatewayTLSSDSConfig `json:",omitempty"` +} + +type GatewayTLSSDSConfig struct { + ClusterName string `json:",omitempty" alias:"cluster_name"` + CertResource string `json:",omitempty" alias:"cert_resource"` } func (e *IngressGatewayConfigEntry) GetKind() string { @@ -134,6 +160,77 @@ func (e *IngressGatewayConfigEntry) Normalize() error { return nil } +// validateServiceSDS validates the SDS config for a specific service on a +// specific listener. It checks inherited config properties from listener and +// gateway level and ensures they are valid all the way down. If called on +// several services some of these checks will be duplicated but that isn't a big +// deal and it's significantly easier to reason about and read if this is in one +// place rather than threaded through the multi-level loop in Validate with +// other checks. +func (e *IngressGatewayConfigEntry) validateServiceSDS(lis IngressListener, svc IngressService) error { + // First work out if there is valid gateway-level SDS config + gwSDSClusterSet := false + gwSDSCertSet := false + if e.TLS.SDS != nil { + // Gateway level SDS config must set ClusterName if it specifies a default + // certificate. Just a clustername is OK though if certs are specified + // per-listener. + if e.TLS.SDS.ClusterName == "" && e.TLS.SDS.CertResource != "" { + return fmt.Errorf("TLS.SDS.ClusterName is required if CertResource is set") + } + // Note we rely on the fact that ClusterName must be non-empty if any SDS + // properties are defined at this level (as validated above) in validation + // below that uses this variable. If that changes we will need to change the + // code below too. + gwSDSClusterSet = (e.TLS.SDS.ClusterName != "") + gwSDSCertSet = (e.TLS.SDS.CertResource != "") + } + + // Validate listener-level SDS config. + lisSDSCertSet := false + lisSDSClusterSet := false + if lis.TLS != nil && lis.TLS.SDS != nil { + lisSDSCertSet = (lis.TLS.SDS.CertResource != "") + lisSDSClusterSet = (lis.TLS.SDS.ClusterName != "") + } + + // If SDS was setup at gw level but without a default CertResource, the + // listener MUST set a CertResource. + if gwSDSClusterSet && !gwSDSCertSet && !lisSDSCertSet { + return fmt.Errorf("TLS.SDS.CertResource is required if ClusterName is set for gateway (listener on port %d)", lis.Port) + } + + // If listener set a cluster name then it requires a cert resource too. + if lisSDSClusterSet && !lisSDSCertSet { + return fmt.Errorf("TLS.SDS.CertResource is required if ClusterName is set for listener (listener on port %d)", lis.Port) + } + + // If a listener-level cert is given, we need a cluster from at least one + // level. + if lisSDSCertSet && !lisSDSClusterSet && !gwSDSClusterSet { + return fmt.Errorf("TLS.SDS.ClusterName is required if CertResource is set (listener on port %d)", lis.Port) + } + + // Validate service-level SDS config + svcSDSSet := (svc.TLS != nil && svc.TLS.SDS != nil && svc.TLS.SDS.CertResource != "") + + // Service SDS is only supported with Host names because we need to bind + // specific service certs to one or more SNI hostnames. + if svcSDSSet && len(svc.Hosts) < 1 { + sid := NewServiceID(svc.Name, &svc.EnterpriseMeta) + return fmt.Errorf("A service specifying TLS.SDS.CertResource must have at least one item in Hosts (service %q on listener on port %d)", + sid.String(), lis.Port) + } + // If this service specified a certificate, there must be an SDS cluster set + // at one of the three levels. + if svcSDSSet && svc.TLS.SDS.ClusterName == "" && !lisSDSClusterSet && !gwSDSClusterSet { + sid := NewServiceID(svc.Name, &svc.EnterpriseMeta) + return fmt.Errorf("TLS.SDS.ClusterName is required if CertResource is set (service %q on listener on port %d)", + sid.String(), lis.Port) + } + return nil +} + func (e *IngressGatewayConfigEntry) Validate() error { if err := validateConfigEntryMeta(e.Meta); err != nil { return err @@ -204,6 +301,11 @@ func (e *IngressGatewayConfigEntry) Validate() error { } serviceNames[sid] = struct{}{} + // Validate SDS configuration for this service + if err := e.validateServiceSDS(listener, s); err != nil { + return err + } + for _, h := range s.Hosts { if declaredHosts[h] { return fmt.Errorf("Hosts must be unique within a specific listener (listener on port %d)", listener.Port) diff --git a/agent/structs/config_entry_gateways_test.go b/agent/structs/config_entry_gateways_test.go index 198bc4b0fb..3afc7cb03e 100644 --- a/agent/structs/config_entry_gateways_test.go +++ b/agent/structs/config_entry_gateways_test.go @@ -437,6 +437,7 @@ func TestIngressGatewayConfigEntry(t *testing.T) { }, }, }, + expectUnchanged: true, }, "request header manip not allowed for non-http protocol": { entry: &IngressGatewayConfigEntry{ @@ -503,6 +504,377 @@ func TestIngressGatewayConfigEntry(t *testing.T) { // differs between Ent and OSS default/default/web vs web validateErr: "cannot be added multiple times (listener on port 1111)", }, + "TLS.SDS kitchen sink": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + TLS: GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service1", + CertResource: "some-ns/ingress-default", + }, + }, + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "http", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service2", + CertResource: "some-ns/ingress-1111", + }, + }, + Services: []IngressService{ + { + Name: "web", + Hosts: []string{"*"}, + TLS: &GatewayServiceTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service3", + CertResource: "some-ns/web", + }, + }, + }, + }, + }, + }, + }, + }, + "TLS.SDS gateway-level": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + TLS: GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service1", + CertResource: "some-ns/ingress-default", + }, + }, + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + Services: []IngressService{ + { + Name: "db", + }, + }, + }, + }, + }, + expectUnchanged: true, + }, + "TLS.SDS listener-level": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service1", + CertResource: "some-ns/db1", + }, + }, + Services: []IngressService{ + { + Name: "db1", + }, + }, + }, + { + Port: 2222, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service2", + CertResource: "some-ns/db2", + }, + }, + Services: []IngressService{ + { + Name: "db2", + }, + }, + }, + }, + }, + expectUnchanged: true, + }, + "TLS.SDS gateway-level cluster only": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + TLS: GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "secret-service", + }, + }, + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + CertResource: "some-ns/db1", + }, + }, + Services: []IngressService{ + { + Name: "db1", + }, + }, + }, + { + Port: 2222, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + CertResource: "some-ns/db2", + }, + }, + Services: []IngressService{ + { + Name: "db2", + }, + }, + }, + }, + }, + expectUnchanged: true, + }, + "TLS.SDS mixed TLS and non-TLS listeners": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + // No Gateway level TLS.Enabled or SDS config + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "sds-cluster", + CertResource: "some-ns/db1", + }, + }, + Services: []IngressService{ + { + Name: "db1", + }, + }, + }, + { + Port: 2222, + Protocol: "tcp", + // No TLS config + Services: []IngressService{ + { + Name: "db2", + }, + }, + }, + }, + }, + expectUnchanged: true, + }, + "TLS.SDS only service-level mixed": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + // No Gateway level TLS.Enabled or SDS config + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "http", + // No TLS config + Services: []IngressService{ + { + Name: "web", + Hosts: []string{"www.example.com"}, + TLS: &GatewayServiceTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "sds-cluster", + CertResource: "web-cert", + }, + }, + }, + { + Name: "api", + Hosts: []string{"api.example.com"}, + TLS: &GatewayServiceTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "sds-cluster", + CertResource: "api-cert", + }, + }, + }, + }, + }, + { + Port: 2222, + Protocol: "http", + // No TLS config + Services: []IngressService{ + { + Name: "db2", + }, + }, + }, + }, + }, + expectUnchanged: true, + }, + "TLS.SDS requires cluster if gateway-level cert specified": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + TLS: GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + CertResource: "foo", + }, + }, + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + Services: []IngressService{ + { + Name: "db", + }, + }, + }, + }, + }, + validateErr: "TLS.SDS.ClusterName is required if CertResource is set", + }, + "TLS.SDS listener requires cluster if there is no gateway-level one": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + CertResource: "foo", + }, + }, + Services: []IngressService{ + { + Name: "db", + }, + }, + }, + }, + }, + validateErr: "TLS.SDS.ClusterName is required if CertResource is set", + }, + "TLS.SDS listener requires a cert resource if gw ClusterName set": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + TLS: GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "foo", + }, + }, + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + Services: []IngressService{ + { + Name: "db", + }, + }, + }, + }, + }, + validateErr: "TLS.SDS.CertResource is required if ClusterName is set for gateway (listener on port 1111)", + }, + "TLS.SDS listener requires a cert resource if listener ClusterName set": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "tcp", + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "foo", + }, + }, + Services: []IngressService{ + { + Name: "db", + }, + }, + }, + }, + }, + validateErr: "TLS.SDS.CertResource is required if ClusterName is set for listener (listener on port 1111)", + }, + "TLS.SDS at service level is not supported without Hosts set": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "http", + Services: []IngressService{ + { + Name: "*", + TLS: &GatewayServiceTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + CertResource: "foo", + ClusterName: "sds-cluster", + }, + }, + }, + }, + }, + }, + }, + // Note we don't assert the last part `(service \"*\" on listener on port 1111)` + // since the service name is normalized differently on OSS and Ent + validateErr: "A service specifying TLS.SDS.CertResource must have at least one item in Hosts", + }, + "TLS.SDS at service level needs a cluster from somewhere": { + entry: &IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress-web", + + Listeners: []IngressListener{ + { + Port: 1111, + Protocol: "http", + Services: []IngressService{ + { + Name: "foo", + Hosts: []string{"foo.example.com"}, + TLS: &GatewayServiceTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + CertResource: "foo", + }, + }, + }, + }, + }, + }, + }, + // Note we don't assert the last part `(service \"foo\" on listener on port 1111)` + // since the service name is normalized differently on OSS and Ent + validateErr: "TLS.SDS.ClusterName is required if CertResource is set", + }, } testConfigEntryNormalizeAndValidate(t, cases) diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index 4f08718540..7b82c2dd10 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -6,8 +6,10 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/hcl" + "github.com/mitchellh/copystructure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -2519,8 +2521,9 @@ type configEntryTestcase struct { normalizeErr string validateErr string - // Only one of either expected or check can be set. - expected ConfigEntry + // Only one of expected, expectUnchanged or check can be set. + expected ConfigEntry + expectUnchanged bool // check is called between normalize and validate check func(t *testing.T, entry ConfigEntry) } @@ -2531,21 +2534,50 @@ func testConfigEntryNormalizeAndValidate(t *testing.T, cases map[string]configEn for name, tc := range cases { tc := tc t.Run(name, func(t *testing.T) { - err := tc.entry.Normalize() + beforeNormalize, err := copystructure.Copy(tc.entry) + require.NoError(t, err) + + err = tc.entry.Normalize() if tc.normalizeErr != "" { testutil.RequireErrorContains(t, err, tc.normalizeErr) return } require.NoError(t, err) - if tc.expected != nil && tc.check != nil { - t.Fatal("cannot set both 'expected' and 'check' test case fields") + checkMethods := 0 + if tc.expected != nil { + checkMethods++ + } + if tc.expectUnchanged { + checkMethods++ + } + if tc.check != nil { + checkMethods++ + } + + if checkMethods > 1 { + t.Fatal("cannot set more than one of 'expected', 'expectUnchanged' and 'check' test case fields") } if tc.expected != nil { require.Equal(t, tc.expected, tc.entry) } + if tc.expectUnchanged { + // EnterpriseMeta.Normalize behaves differently in Ent and OSS which + // causes an exact comparison to fail. It's still useful to assert that + // nothing else changes though during Normalize. So we ignore + // EnterpriseMeta Defaults. + opts := cmp.Options{ + cmp.Comparer(func(a, b EnterpriseMeta) bool { + return a.IsSame(&b) + }), + } + if diff := cmp.Diff(beforeNormalize, tc.entry, opts); diff != "" { + t.Fatalf("expect unchanged after Normalize, got diff:\n%s", diff) + } + } + if tc.check != nil { tc.check(t, tc.entry) } diff --git a/agent/structs/structs_oss.go b/agent/structs/structs_oss.go index 13d2872bc4..045617b503 100644 --- a/agent/structs/structs_oss.go +++ b/agent/structs/structs_oss.go @@ -79,6 +79,10 @@ func (m *EnterpriseMeta) NamespaceOrEmpty() string { return "" } +func (m *EnterpriseMeta) InDefaultNamespace() bool { + return true +} + func (m *EnterpriseMeta) PartitionOrDefault() string { return "default" } @@ -91,6 +95,10 @@ func (m *EnterpriseMeta) PartitionOrEmpty() string { return "" } +func (m *EnterpriseMeta) InDefaultPartition() bool { + return true +} + // ReplicationEnterpriseMeta stub func ReplicationEnterpriseMeta() *EnterpriseMeta { return &emptyEnterpriseMeta diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index 43510b2040..c81d9f417a 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -511,75 +511,47 @@ func (s *ResourceGenerator) listenersFromSnapshotGateway(cfgSnap *proxycfg.Confi return resources, err } -func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { - var resources []proto.Message +func resolveListenerSDSConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerKey proxycfg.IngressListenerKey) (*structs.GatewayTLSSDSConfig, error) { + var mergedCfg structs.GatewayTLSSDSConfig - for listenerKey, upstreams := range cfgSnap.IngressGateway.Upstreams { - var tlsContext *envoy_tls_v3.DownstreamTlsContext - if cfgSnap.IngressGateway.TLSEnabled { - tlsContext = &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()), - RequireClientCertificate: &wrappers.BoolValue{Value: false}, - } + gwSDS := cfgSnap.IngressGateway.TLSConfig.SDS + if gwSDS != nil { + mergedCfg.ClusterName = gwSDS.ClusterName + mergedCfg.CertResource = gwSDS.CertResource + } + + listenerCfg, ok := cfgSnap.IngressGateway.Listeners[listenerKey] + if !ok { + return nil, fmt.Errorf("no listener config found for listener on port %d", listenerKey.Port) + } + + if listenerCfg.TLS != nil && listenerCfg.TLS.SDS != nil { + if listenerCfg.TLS.SDS.ClusterName != "" { + mergedCfg.ClusterName = listenerCfg.TLS.SDS.ClusterName } - - 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 - // GatewayService is returned in the RPC - u := upstreams[0] - id := u.Identifier() - - chain := cfgSnap.IngressGateway.DiscoveryChain[id] - - var upstreamListener proto.Message - upstreamListener, err := s.makeUpstreamListenerForDiscoveryChain( - &u, - address, - chain, - cfgSnap, - tlsContext, - ) - if err != nil { - return nil, err - } - resources = append(resources, upstreamListener) - } else { - // If multiple upstreams share this port, make a special listener for the protocol. - listener := makePortListener(listenerKey.Protocol, address, listenerKey.Port, envoy_core_v3.TrafficDirection_OUTBOUND) - opts := listenerFilterOpts{ - useRDS: true, - protocol: listenerKey.Protocol, - filterName: listenerKey.RouteName(), - routeName: listenerKey.RouteName(), - cluster: "", - statPrefix: "ingress_upstream_", - routePath: "", - httpAuthzFilter: nil, - } - filter, err := makeListenerFilter(opts) - if err != nil { - return nil, err - } - - transportSocket, err := makeDownstreamTLSTransportSocket(tlsContext) - if err != nil { - return nil, err - } - - listener.FilterChains = []*envoy_listener_v3.FilterChain{ - { - Filters: []*envoy_listener_v3.Filter{ - filter, - }, - TransportSocket: transportSocket, - }, - } - resources = append(resources, listener) + if listenerCfg.TLS.SDS.CertResource != "" { + mergedCfg.CertResource = listenerCfg.TLS.SDS.CertResource } } - return resources, nil + // Validate. Either merged should have both fields empty or both set. Other + // cases shouldn't be possible as we validate them at input but be robust to + // bugs later. + switch { + case mergedCfg.ClusterName == "" && mergedCfg.CertResource == "": + return nil, nil + + case mergedCfg.ClusterName != "" && mergedCfg.CertResource != "": + return &mergedCfg, nil + + case mergedCfg.ClusterName == "" && mergedCfg.CertResource != "": + return nil, fmt.Errorf("missing SDS cluster name for listener on port %d", listenerKey.Port) + + case mergedCfg.ClusterName != "" && mergedCfg.CertResource == "": + return nil, fmt.Errorf("missing SDS cert resource for listener on port %d", listenerKey.Port) + } + + return &mergedCfg, nil } // makeListener returns a listener with name and bind details set. Filters must @@ -1584,9 +1556,9 @@ func makeTLSInspectorListenerFilter() (*envoy_listener_v3.ListenerFilter, error) return &envoy_listener_v3.ListenerFilter{Name: "envoy.filters.listener.tls_inspector"}, nil } -func makeSNIFilterChainMatch(sniMatch string) *envoy_listener_v3.FilterChainMatch { +func makeSNIFilterChainMatch(sniMatches ...string) *envoy_listener_v3.FilterChainMatch { return &envoy_listener_v3.FilterChainMatch{ - ServerNames: []string{sniMatch}, + ServerNames: sniMatches, } } @@ -1763,7 +1735,6 @@ func makeCommonTLSContextFromLeaf(cfgSnap *proxycfg.ConfigSnapshot, leaf *struct return nil } - // TODO(banks): verify this actually works with Envoy (docs are not clear). rootPEMS := "" for _, root := range cfgSnap.Roots.Roots { rootPEMS += ca.EnsureTrailingNewline(root.RootCert) diff --git a/agent/xds/listeners_ingress.go b/agent/xds/listeners_ingress.go new file mode 100644 index 0000000000..df9010a3b8 --- /dev/null +++ b/agent/xds/listeners_ingress.go @@ -0,0 +1,243 @@ +package xds + +import ( + "fmt" + + envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + "github.com/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" +) + +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 + + sdsCfg, err := resolveListenerSDSConfig(cfgSnap, listenerKey) + 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 cfgSnap.IngressGateway.TLSConfig.Enabled { + 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 + // GatewayService is returned in the RPC + u := upstreams[0] + id := u.Identifier() + + chain := cfgSnap.IngressGateway.DiscoveryChain[id] + + var upstreamListener proto.Message + upstreamListener, err := s.makeUpstreamListenerForDiscoveryChain( + &u, + address, + chain, + cfgSnap, + tlsContext, + ) + if err != nil { + return nil, err + } + resources = append(resources, upstreamListener) + } else { + // If multiple upstreams share this port, make a special listener for the protocol. + listener := makePortListener(listenerKey.Protocol, address, listenerKey.Port, envoy_core_v3.TrafficDirection_OUTBOUND) + opts := listenerFilterOpts{ + useRDS: true, + protocol: listenerKey.Protocol, + filterName: listenerKey.RouteName(), + routeName: listenerKey.RouteName(), + cluster: "", + statPrefix: "ingress_upstream_", + routePath: "", + httpAuthzFilter: nil, + } + + // Generate any filter chains needed for services with custom TLS certs + // via SDS. + sniFilterChains, err := makeSDSOverrideFilterChains(cfgSnap, listenerKey, opts) + if err != nil { + return nil, err + } + + // If there are any sni filter chains, we need a TLS inspector filter! + if len(sniFilterChains) > 0 { + tlsInspector, err := makeTLSInspectorListenerFilter() + if err != nil { + return nil, err + } + listener.ListenerFilters = []*envoy_listener_v3.ListenerFilter{tlsInspector} + } + + listener.FilterChains = sniFilterChains + + // See if there are other services that didn't have specific SNI-matching + // filter chains. If so add a default filterchain to serve them. + if len(sniFilterChains) < len(upstreams) { + defaultFilter, err := makeListenerFilter(opts) + if err != nil { + return nil, err + } + + transportSocket, err := makeDownstreamTLSTransportSocket(tlsContext) + if err != nil { + return nil, err + } + listener.FilterChains = append(listener.FilterChains, + &envoy_listener_v3.FilterChain{ + Filters: []*envoy_listener_v3.Filter{ + defaultFilter, + }, + TransportSocket: transportSocket, + }) + } + + resources = append(resources, listener) + } + } + + return resources, nil +} + +func routeNameForUpstream(l structs.IngressListener, s structs.IngressService) string { + key := proxycfg.IngressListenerKeyFromListener(l) + + // If the upstream service doesn't have any TLS overrides then it can just use + // the combined filterchain with all the merged routes. + if !ingressServiceHasSDSOverrides(s) { + return key.RouteName() + } + + // Return a specific route for this service as it needs a custom FilterChain + // to serve its custom cert so we should attach its routes to a separate Route + // too. We need this to be consistent between OSS and Enterprise to avoid xDS + // config golden files in tests conflicting so we can't use ServiceID.String() + // which normalizes to included all identifiers in Enterprise. + sn := s.ToServiceName() + svcIdentifier := sn.Name + if !sn.InDefaultPartition() || !sn.InDefaultNamespace() { + // Non-default partition/namespace, use a full identifier + svcIdentifier = sn.String() + } + return fmt.Sprintf("%s_%s", key.RouteName(), svcIdentifier) +} + +func ingressServiceHasSDSOverrides(s structs.IngressService) bool { + return s.TLS != nil && + s.TLS.SDS != nil && + s.TLS.SDS.CertResource != "" +} + +// ingress services that specify custom TLS certs via SDS overrides need to get +// their own filter chain and routes. This will generate all the extra filter +// chains an ingress listener needs. It may be empty and expects the default +// catch-all chain and route to contain all the other services that share the +// default TLS config. +func makeSDSOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot, + listenerKey proxycfg.IngressListenerKey, + filterOpts listenerFilterOpts) ([]*envoy_listener_v3.FilterChain, error) { + + listenerCfg, ok := cfgSnap.IngressGateway.Listeners[listenerKey] + if !ok { + return nil, fmt.Errorf("no listener config found for listener on port %d", listenerKey.Port) + } + + var chains []*envoy_listener_v3.FilterChain + + for _, svc := range listenerCfg.Services { + if !ingressServiceHasSDSOverrides(svc) { + continue + } + + if len(svc.Hosts) < 1 { + // Shouldn't be possible with validation but be careful + return nil, fmt.Errorf("no hosts specified with SDS certificate (service %q on listener on port %d)", + svc.ToServiceName().ToServiceID().String(), listenerKey.Port) + } + + // Service has a certificate resource override. Return a new filter chain + // with the right TLS cert and a filter that will load only the routes for + // this service. + routeName := routeNameForUpstream(listenerCfg, svc) + filterOpts.filterName = routeName + filterOpts.routeName = routeName + filter, err := makeListenerFilter(filterOpts) + if err != nil { + return nil, err + } + + tlsContext := &envoy_tls_v3.DownstreamTlsContext{ + CommonTlsContext: makeCommonTLSContextFromSDS(*svc.TLS.SDS), + RequireClientCertificate: &wrappers.BoolValue{Value: false}, + } + + transportSocket, err := makeDownstreamTLSTransportSocket(tlsContext) + if err != nil { + return nil, err + } + + chain := &envoy_listener_v3.FilterChain{ + // Only match traffic for this service's hosts. + FilterChainMatch: makeSNIFilterChainMatch(svc.Hosts...), + Filters: []*envoy_listener_v3.Filter{ + filter, + }, + TransportSocket: transportSocket, + } + + chains = append(chains, chain) + } + + return chains, nil +} + +func makeCommonTLSContextFromSDS(sdsCfg structs.GatewayTLSSDSConfig) *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, + }, + }, + Timeout: &duration.Duration{Seconds: 5}, + }, + }, + }, + }, + ResourceApiVersion: envoy_core_v3.ApiVersion_V3, + }, + }, + }, + } +} diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index 9773435785..a3664db844 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -485,6 +485,10 @@ func TestListenersFromSnapshot(t *testing.T) { }, }, } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: {}, + {Protocol: "http", Port: 443}: {}, + } }, }, { @@ -499,6 +503,251 @@ func TestListenersFromSnapshot(t *testing.T) { create: proxycfg.TestConfigSnapshotIngressWithTLSListener, setup: nil, }, + { + name: "ingress-with-sds-listener-gw-level", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: nil, + }, + { + name: "ingress-with-sds-listener-listener-level", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "tcp", Port: 8080}: { + { + DestinationName: "foo", + LocalBindPort: 8080, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "tcp", Port: 8080}: { + Port: 8080, + TLS: &structs.GatewayTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + // Override the cert, fall back to the cluster at gw level. We + // don't test every possible valid combination here since we + // already did that in TestResolveListenerSDSConfig. This is + // just an extra check to make sure that data is plumbed through + // correctly. + CertResource: "listener-cert", + }, + }, + }, + } + }, + }, + { + name: "ingress-with-sds-listener-gw-level-http", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: func(snap *proxycfg.ConfigSnapshot) { + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "foo", + LocalBindPort: 8080, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + TLS: &structs.GatewayTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + // Override the cert, fall back to the cluster at gw level. We + // don't test every possible valid combination here since we + // already did that in TestResolveListenerSDSConfig. This is + // just an extra check to make sure that data is plumbed through + // correctly. + CertResource: "listener-cert", + }, + }, + }, + } + }, + }, + { + name: "ingress-with-sds-listener-gw-level-mixed-tls", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: func(snap *proxycfg.ConfigSnapshot) { + // Disable GW-level defaults so we can mix TLS and non-TLS listeners + snap.IngressGateway.TLSConfig.SDS = nil + + // Setup two TCP listeners, one with and one without SDS config + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "tcp", Port: 8080}: { + { + DestinationName: "secure", + LocalBindPort: 8080, + }, + }, + {Protocol: "tcp", Port: 9090}: { + { + DestinationName: "insecure", + LocalBindPort: 9090, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "tcp", Port: 8080}: { + Port: 8080, + TLS: &structs.GatewayTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "listener-sds-cluster", + CertResource: "listener-cert", + }, + }, + }, + {Protocol: "tcp", Port: 9090}: { + Port: 9090, + TLS: nil, + }, + } + }, + }, + { + name: "ingress-with-sds-service-level", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: func(snap *proxycfg.ConfigSnapshot) { + // Disable GW-level defaults so we can test only service-level + snap.IngressGateway.TLSConfig.SDS = nil + + // Setup http listeners, one multiple services with SDS + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "s1", + LocalBindPort: 8080, + }, + { + DestinationName: "s2", + LocalBindPort: 8080, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "s1", + Hosts: []string{"s1.example.com"}, + TLS: &structs.GatewayServiceTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "sds-cluster-1", + CertResource: "s1.example.com-cert", + }, + }, + }, + { + Name: "s2", + Hosts: []string{"s2.example.com"}, + TLS: &structs.GatewayServiceTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "sds-cluster-2", + CertResource: "s2.example.com-cert", + }, + }, + }, + }, + TLS: nil, // no listener-level SDS config + }, + } + }, + }, + { + name: "ingress-with-sds-listener+service-level", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: func(snap *proxycfg.ConfigSnapshot) { + // Disable GW-level defaults so we can test only service-level + snap.IngressGateway.TLSConfig.SDS = nil + + // Setup http listeners, one multiple services with SDS + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "s1", + LocalBindPort: 8080, + }, + { + DestinationName: "s2", + LocalBindPort: 8080, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "s1", + Hosts: []string{"s1.example.com"}, + TLS: &structs.GatewayServiceTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "sds-cluster-1", + CertResource: "s1.example.com-cert", + }, + }, + }, + { + Name: "s2", + // s2 uses the default listener cert + }, + }, + TLS: &structs.GatewayTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "sds-cluster-2", + CertResource: "*.example.com-cert", + }, + }, + }, + } + }, + }, + { + name: "ingress-with-sds-service-level-mixed-no-tls", + create: proxycfg.TestConfigSnapshotIngressWithGatewaySDS, + setup: func(snap *proxycfg.ConfigSnapshot) { + // Disable GW-level defaults so we can test only service-level + snap.IngressGateway.TLSConfig.SDS = nil + + // Setup http listeners, one multiple services with SDS + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 8080}: { + { + DestinationName: "s1", + LocalBindPort: 8080, + }, + { + DestinationName: "s2", + LocalBindPort: 8080, + }, + }, + } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "s1", + Hosts: []string{"s1.example.com"}, + TLS: &structs.GatewayServiceTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "sds-cluster-1", + CertResource: "s1.example.com-cert", + }, + }, + }, + { + Name: "s2", + // s2 has no SDS config so should be non-TLS + }, + }, + TLS: nil, // No listener level TLS setup either + }, + } + }, + }, { name: "transparent-proxy", create: proxycfg.TestConfigSnapshot, @@ -706,6 +955,17 @@ func TestListenersFromSnapshot(t *testing.T) { gName += ".v2compat" + // It's easy to miss a new type that encodes a version from just + // looking at the golden files so lets make it an error here. If + // there are ever false positives we can maybe include an allow list + // here as it seems safer to assume something was missed than to + // assume we'll notice the golden file being wrong. Note the first + // one matches both resourceApiVersion and transportApiVersion. I + // left it as a suffix in case there are other field names that + // follow that convention now or in the future. + require.NotContains(t, gotJSON, `ApiVersion": "V3"`) + require.NotContains(t, gotJSON, `type.googleapis.com/envoy.api.v3`) + require.JSONEq(t, goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion_v2, gotJSON), gotJSON) }) }) @@ -844,3 +1104,158 @@ var _ ConfigFetcher = (configFetcherFunc)(nil) func (f configFetcherFunc) AdvertiseAddrLAN() string { return f() } + +func TestResolveListenerSDSConfig(t *testing.T) { + type testCase struct { + name string + gwSDS *structs.GatewayTLSSDSConfig + lisSDS *structs.GatewayTLSSDSConfig + want *structs.GatewayTLSSDSConfig + wantErr string + } + + run := func(tc testCase) { + // fake a snapshot with just the data we care about + snap := proxycfg.TestConfigSnapshotIngressWithGatewaySDS(t) + // Override TLS configs + snap.IngressGateway.TLSConfig.SDS = tc.gwSDS + var key proxycfg.IngressListenerKey + for k, lisCfg := range snap.IngressGateway.Listeners { + if tc.lisSDS == nil { + lisCfg.TLS = nil + } else { + lisCfg.TLS = &structs.GatewayTLSConfig{ + SDS: tc.lisSDS, + } + } + // Override listener cfg in map + snap.IngressGateway.Listeners[k] = lisCfg + // Save the last key doesn't matter which as we set same listener config + // for all. + key = k + } + + got, err := resolveListenerSDSConfig(snap, key) + if tc.wantErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.want, got) + } + } + + cases := []testCase{ + { + name: "no SDS config", + gwSDS: nil, + lisSDS: nil, + want: nil, + }, + { + name: "all cluster-level SDS config", + gwSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "cert", + }, + lisSDS: nil, + want: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "cert", + }, + }, + { + name: "all listener-level SDS config", + gwSDS: nil, + lisSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "cert", + }, + want: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "cert", + }, + }, + { + name: "mixed level SDS config", + gwSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + }, + lisSDS: &structs.GatewayTLSSDSConfig{ + CertResource: "cert", + }, + want: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "cert", + }, + }, + { + name: "override cert", + gwSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "gw-cert", + }, + lisSDS: &structs.GatewayTLSSDSConfig{ + CertResource: "lis-cert", + }, + want: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + CertResource: "lis-cert", + }, + }, + { + name: "override both", + gwSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "gw-cluster", + CertResource: "gw-cert", + }, + lisSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "lis-cluster", + CertResource: "lis-cert", + }, + want: &structs.GatewayTLSSDSConfig{ + ClusterName: "lis-cluster", + CertResource: "lis-cert", + }, + }, + { + name: "missing cluster listener", + gwSDS: nil, + lisSDS: &structs.GatewayTLSSDSConfig{ + CertResource: "lis-cert", + }, + wantErr: "missing SDS cluster name", + }, + { + name: "missing cert listener", + gwSDS: nil, + lisSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + }, + wantErr: "missing SDS cert resource", + }, + { + name: "missing cluster gw", + gwSDS: &structs.GatewayTLSSDSConfig{ + CertResource: "lis-cert", + }, + lisSDS: nil, + wantErr: "missing SDS cluster name", + }, + { + name: "missing cert gw", + gwSDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "cluster", + }, + lisSDS: nil, + wantErr: "missing SDS cert resource", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + run(tc) + }) + } + +} diff --git a/agent/xds/routes.go b/agent/xds/routes.go index 1a5d6403b7..fd1a4ab9d4 100644 --- a/agent/xds/routes.go +++ b/agent/xds/routes.go @@ -177,13 +177,17 @@ func (s *ResourceGenerator) routesForIngressGateway( continue } - upstreamRoute := &envoy_route_v3.RouteConfiguration{ + // Depending on their TLS config, upstreams are either attached to the + // default route or have their own routes. We'll add any upstreams that + // don't have custom filter chains and routes to this. + defaultRoute := &envoy_route_v3.RouteConfiguration{ Name: listenerKey.RouteName(), // ValidateClusters defaults to true when defined statically and false // when done via RDS. Re-set the reasonable value of true to prevent // null-routing traffic. ValidateClusters: makeBoolValue(true), } + for _, u := range upstreams { upstreamID := u.Identifier() chain := chains[upstreamID] @@ -197,45 +201,42 @@ func (s *ResourceGenerator) routesForIngressGateway( return nil, err } - // See if we need to configure any special settings on this route config - if lCfg, ok := listeners[listenerKey]; ok { - if is := findIngressServiceMatchingUpstream(lCfg, u); is != nil { - // Set up any header manipulation we need - if is.RequestHeaders != nil { - virtualHost.RequestHeadersToAdd = append( - virtualHost.RequestHeadersToAdd, - makeHeadersValueOptions(is.RequestHeaders.Add, true)..., - ) - virtualHost.RequestHeadersToAdd = append( - virtualHost.RequestHeadersToAdd, - makeHeadersValueOptions(is.RequestHeaders.Set, false)..., - ) - virtualHost.RequestHeadersToRemove = append( - virtualHost.RequestHeadersToRemove, - is.RequestHeaders.Remove..., - ) - } - if is.ResponseHeaders != nil { - virtualHost.ResponseHeadersToAdd = append( - virtualHost.ResponseHeadersToAdd, - makeHeadersValueOptions(is.ResponseHeaders.Add, true)..., - ) - virtualHost.ResponseHeadersToAdd = append( - virtualHost.ResponseHeadersToAdd, - makeHeadersValueOptions(is.ResponseHeaders.Set, false)..., - ) - virtualHost.ResponseHeadersToRemove = append( - virtualHost.ResponseHeadersToRemove, - is.ResponseHeaders.Remove..., - ) - } - } + // Lookup listener and service config details from ingress gateway + // definition. + lCfg, ok := listeners[listenerKey] + if !ok { + return nil, fmt.Errorf("missing ingress listener config (listener on port %d)", listenerKey.Port) + } + svc := findIngressServiceMatchingUpstream(lCfg, u) + if svc == nil { + return nil, fmt.Errorf("missing service in listener config (service %q listener on port %d)", + u.DestinationID(), listenerKey.Port) } - upstreamRoute.VirtualHosts = append(upstreamRoute.VirtualHosts, virtualHost) + if err := injectHeaderManipToVirtualHost(svc, virtualHost); err != nil { + return nil, err + } + + // See if this upstream has its own route/filter chain + svcRouteName := routeNameForUpstream(lCfg, *svc) + + // If the routeName is the same as the default one, merge the virtual host + // to the default route + if svcRouteName == defaultRoute.Name { + defaultRoute.VirtualHosts = append(defaultRoute.VirtualHosts, virtualHost) + } else { + svcRoute := &envoy_route_v3.RouteConfiguration{ + Name: svcRouteName, + ValidateClusters: makeBoolValue(true), + VirtualHosts: []*envoy_route_v3.VirtualHost{virtualHost}, + } + result = append(result, svcRoute) + } } - result = append(result, upstreamRoute) + if len(defaultRoute.VirtualHosts) > 0 { + result = append(result, defaultRoute) + } } return result, nil @@ -262,13 +263,23 @@ func findIngressServiceMatchingUpstream(l structs.IngressListener, u structs.Ups // wasn't checked as it didn't matter. Assume there is only one now // though! wantSID := u.DestinationID() + var foundSameNSWildcard *structs.IngressService for _, s := range l.Services { sid := structs.NewServiceID(s.Name, &s.EnterpriseMeta) if wantSID.Matches(sid) { return &s } + if s.Name == structs.WildcardSpecifier && + s.NamespaceOrDefault() == wantSID.NamespaceOrDefault() && + s.PartitionOrDefault() == wantSID.PartitionOrDefault() { + // Make a copy so we don't take a reference to the loop variable + found := s + foundSameNSWildcard = &found + } } - return nil + // Didn't find an exact match. Return the wildcard from same service if we + // found one. + return foundSameNSWildcard } func generateUpstreamIngressDomains(listenerKey proxycfg.IngressListenerKey, u structs.Upstream) []string { @@ -753,6 +764,38 @@ func injectHeaderManipToRoute(dest *structs.ServiceRouteDestination, r *envoy_ro return nil } +func injectHeaderManipToVirtualHost(dest *structs.IngressService, vh *envoy_route_v3.VirtualHost) error { + if !dest.RequestHeaders.IsZero() { + vh.RequestHeadersToAdd = append( + vh.RequestHeadersToAdd, + makeHeadersValueOptions(dest.RequestHeaders.Add, true)..., + ) + vh.RequestHeadersToAdd = append( + vh.RequestHeadersToAdd, + makeHeadersValueOptions(dest.RequestHeaders.Set, false)..., + ) + vh.RequestHeadersToRemove = append( + vh.RequestHeadersToRemove, + dest.RequestHeaders.Remove..., + ) + } + if !dest.ResponseHeaders.IsZero() { + vh.ResponseHeadersToAdd = append( + vh.ResponseHeadersToAdd, + makeHeadersValueOptions(dest.ResponseHeaders.Add, true)..., + ) + vh.ResponseHeadersToAdd = append( + vh.ResponseHeadersToAdd, + makeHeadersValueOptions(dest.ResponseHeaders.Set, false)..., + ) + vh.ResponseHeadersToRemove = append( + vh.ResponseHeadersToRemove, + dest.ResponseHeaders.Remove..., + ) + } + return nil +} + func injectHeaderManipToWeightedCluster(split *structs.ServiceSplit, c *envoy_route_v3.WeightedCluster_ClusterWeight) error { if !split.RequestHeaders.IsZero() { c.RequestHeadersToAdd = append( diff --git a/agent/xds/routes_test.go b/agent/xds/routes_test.go index 63f78b7c65..d90622c34b 100644 --- a/agent/xds/routes_test.go +++ b/agent/xds/routes_test.go @@ -155,6 +155,30 @@ func TestRoutesFromSnapshot(t *testing.T) { }, }, } + snap.IngressGateway.Listeners = map[proxycfg.IngressListenerKey]structs.IngressListener{ + {Protocol: "http", Port: 8080}: { + Port: 8080, + Services: []structs.IngressService{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + }, + {Protocol: "http", Port: 443}: { + Port: 443, + Services: []structs.IngressService{ + { + Name: "baz", + }, + { + Name: "qux", + }, + }, + }, + } // We do not add baz/qux here so that we test the chain.IsDefault() case entries := []structs.ConfigEntry{ @@ -216,6 +240,45 @@ func TestRoutesFromSnapshot(t *testing.T) { snap.IngressGateway.Listeners[k] = l }, }, + { + name: "ingress-with-sds-listener-level", + create: proxycfg.TestConfigSnapshotIngressWithRouter, + setup: setupIngressWithTwoHTTPServices(t, ingressSDSOpts{ + // Listener-level SDS means all services share the default route. + listenerSDS: true, + }), + }, + { + name: "ingress-with-sds-listener-level-wildcard", + create: proxycfg.TestConfigSnapshotIngressWithRouter, + setup: setupIngressWithTwoHTTPServices(t, ingressSDSOpts{ + // Listener-level SDS means all services share the default route. + listenerSDS: true, + wildcard: true, + }), + }, + { + name: "ingress-with-sds-service-level", + create: proxycfg.TestConfigSnapshotIngressWithRouter, + setup: setupIngressWithTwoHTTPServices(t, ingressSDSOpts{ + listenerSDS: false, + // Services should get separate routes and no default since they all + // have custom certs. + webSDS: true, + fooSDS: true, + }), + }, + { + name: "ingress-with-sds-service-level-mixed-tls", + create: proxycfg.TestConfigSnapshotIngressWithRouter, + setup: setupIngressWithTwoHTTPServices(t, ingressSDSOpts{ + listenerSDS: false, + // Web needs a separate route as it has custom filter chain but foo + // should use default route for listener. + webSDS: true, + fooSDS: false, + }), + }, { name: "terminating-gateway-lb-config", create: proxycfg.TestConfigSnapshotTerminatingGateway, @@ -324,6 +387,17 @@ func TestRoutesFromSnapshot(t *testing.T) { gName += ".v2compat" + // It's easy to miss a new type that encodes a version from just + // looking at the golden files so lets make it an error here. If + // there are ever false positives we can maybe include an allow list + // here as it seems safer to assume something was missed than to + // assume we'll notice the golden file being wrong. Note the first + // one matches both resourceApiVersion and transportApiVersion. I + // left it as a suffix in case there are other field names that + // follow that convention now or in the future. + require.NotContains(t, gotJSON, `ApiVersion": "V3"`) + require.NotContains(t, gotJSON, `type.googleapis.com/envoy.api.v3`) + require.JSONEq(t, goldenEnvoy(t, filepath.Join("routes", gName), envoyVersion, latestEnvoyVersion_v2, gotJSON), gotJSON) }) }) @@ -585,3 +659,155 @@ func TestEnvoyLBConfig_InjectToRouteAction(t *testing.T) { }) } } + +type ingressSDSOpts struct { + listenerSDS, webSDS, fooSDS, wildcard bool + entMetas map[string]*structs.EnterpriseMeta +} + +// setupIngressWithTwoHTTPServices can be used with +// proxycfg.TestConfigSnapshotIngressWithRouter to generate a setup func for an +// ingress listener with multiple HTTP services and varying SDS configurations +// since those affect how we generate routes. +func setupIngressWithTwoHTTPServices(t *testing.T, o ingressSDSOpts) func(snap *proxycfg.ConfigSnapshot) { + return func(snap *proxycfg.ConfigSnapshot) { + + snap.IngressGateway.TLSConfig.SDS = nil + + webUpstream := structs.Upstream{ + DestinationName: "web", + // We use empty not default here because of the way upstream identifiers + // vary between OSS and Enterprise currently causing test conflicts. In + // real life `proxycfg` always sets ingress upstream namespaces to + // `NamespaceOrDefault` which shouldn't matter because we should be + // consistent within a single binary it's just inconvenient if OSS and + // enterprise tests generate different output. + DestinationNamespace: o.entMetas["web"].NamespaceOrEmpty(), + DestinationPartition: o.entMetas["web"].PartitionOrEmpty(), + LocalBindPort: 9191, + IngressHosts: []string{ + "www.example.com", + }, + } + fooUpstream := structs.Upstream{ + DestinationName: "foo", + DestinationNamespace: o.entMetas["foo"].NamespaceOrEmpty(), + DestinationPartition: o.entMetas["foo"].PartitionOrEmpty(), + LocalBindPort: 9191, + IngressHosts: []string{ + "foo.example.com", + }, + } + + // Setup additional HTTP service on same listener with default router + snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ + {Protocol: "http", Port: 9191}: {webUpstream, fooUpstream}, + } + il := structs.IngressListener{ + Port: 9191, + Services: []structs.IngressService{ + { + Name: "web", + Hosts: []string{"www.example.com"}, + }, + { + Name: "foo", + Hosts: []string{"foo.example.com"}, + }, + }, + } + for i, svc := range il.Services { + if em, ok := o.entMetas[svc.Name]; ok && em != nil { + il.Services[i].EnterpriseMeta = *em + } + } + + // Now set the appropriate SDS configs + if o.listenerSDS { + il.TLS = &structs.GatewayTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "listener-cluster", + CertResource: "listener-cert", + }, + } + } + if o.webSDS { + il.Services[0].TLS = &structs.GatewayServiceTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "web-cluster", + CertResource: "www-cert", + }, + } + } + if o.fooSDS { + il.Services[1].TLS = &structs.GatewayServiceTLSConfig{ + SDS: &structs.GatewayTLSSDSConfig{ + ClusterName: "foo-cluster", + CertResource: "foo-cert", + }, + } + } + + if o.wildcard { + // undo all that and set just a single wildcard config with no TLS to test + // the lookup path where we have to compare an actual resolved upstream to + // a wildcard config. + il.Services = []structs.IngressService{ + { + Name: "*", + }, + } + // We also don't support user-specified hosts with wildcard so remove + // those from the upstreams. + ups := snap.IngressGateway.Upstreams[proxycfg.IngressListenerKey{Protocol: "http", Port: 9191}] + for i := range ups { + ups[i].IngressHosts = nil + } + snap.IngressGateway.Upstreams[proxycfg.IngressListenerKey{Protocol: "http", Port: 9191}] = ups + } + + snap.IngressGateway.Listeners[proxycfg.IngressListenerKey{Protocol: "http", Port: 9191}] = il + + entries := []structs.ConfigEntry{ + &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, + &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "web", + ConnectTimeout: 22 * time.Second, + }, + &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "foo", + ConnectTimeout: 22 * time.Second, + }, + } + for i, e := range entries { + switch v := e.(type) { + // Add other Service types here if we ever need them above + case *structs.ServiceResolverConfigEntry: + if em, ok := o.entMetas[v.Name]; ok && em != nil { + v.EnterpriseMeta = *em + entries[i] = v + } + } + } + + webChain := discoverychain.TestCompileConfigEntries(t, "web", + o.entMetas["web"].NamespaceOrDefault(), + o.entMetas["web"].PartitionOrDefault(), "dc1", + connect.TestClusterID+".consul", "dc1", nil, entries...) + fooChain := discoverychain.TestCompileConfigEntries(t, "foo", + o.entMetas["foo"].NamespaceOrDefault(), + o.entMetas["web"].PartitionOrDefault(), "dc1", + connect.TestClusterID+".consul", "dc1", nil, entries...) + + snap.IngressGateway.DiscoveryChain[webUpstream.Identifier()] = webChain + snap.IngressGateway.DiscoveryChain[fooUpstream.Identifier()] = fooChain + } +} diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..b5531913dd --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.envoy-1-18-x.golden @@ -0,0 +1,154 @@ +{ + "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": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "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_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s1" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-1" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "*.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-2" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "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-sds-listener+service-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..b3d8cf47f8 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,154 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s1" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-1" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "*.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-2" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.envoy-1-18-x.golden new file mode 100644 index 0000000000..e858c2afba --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.envoy-1-18-x.golden @@ -0,0 +1,82 @@ +{ + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "listener-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "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-sds-listener-gw-level-http.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..2593dd6f73 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.v2compat.envoy-1-16-x.golden @@ -0,0 +1,82 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.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.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "listener-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-mixed-tls.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-mixed-tls.envoy-1-18-x.golden new file mode 100644 index 0000000000..8500d36f4d --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-mixed-tls.envoy-1-18-x.golden @@ -0,0 +1,89 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "insecure:1.2.3.4:9090", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 9090 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.insecure.default.default.dc1", + "cluster": "insecure.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "secure:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.secure.default.default.dc1", + "cluster": "secure.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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "listener-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "listener-sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "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-sds-listener-gw-level-mixed-tls.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-mixed-tls.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..6569cc6aea --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-mixed-tls.v2compat.envoy-1-16-x.golden @@ -0,0 +1,89 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "insecure:1.2.3.4:9090", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 9090 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy", + "statPrefix": "upstream.insecure.default.default.dc1", + "cluster": "insecure.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "secure:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy", + "statPrefix": "upstream.secure.default.default.dc1", + "cluster": "secure.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "listener-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "listener-sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..73042063b1 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level.envoy-1-18-x.golden @@ -0,0 +1,64 @@ +{ + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "cert-resource", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "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-sds-listener-gw-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..907f8bfb89 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,64 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.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.config.filter.network.tcp_proxy.v2.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.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "cert-resource", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-listener+service-level.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener+service-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..70b6200188 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener+service-level.envoy-1-18-x.golden @@ -0,0 +1,142 @@ +{ + "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": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "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_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s1" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "clusterNames": [ + "sds-cluster-1" + ] + } + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "*.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "clusterNames": [ + "sds-cluster-2" + ] + } + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "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-sds-listener-listener+service-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener+service-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..3eb45c4f31 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener+service-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,144 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s1" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "clusterNames": [ + "sds-cluster-1" + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "*.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "clusterNames": [ + "sds-cluster-2" + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-listener-level.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..8e223cf3ea --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener-level.envoy-1-18-x.golden @@ -0,0 +1,64 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "foo:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.foo.default.default.dc1", + "cluster": "foo.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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "listener-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "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-sds-listener-listener-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..7f24292858 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-listener-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,64 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "foo:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy", + "statPrefix": "upstream.foo.default.default.dc1", + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "listener-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-service-level.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-service-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..c887414511 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-service-level.envoy-1-18-x.golden @@ -0,0 +1,147 @@ +{ + "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": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "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_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s1" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "clusterNames": [ + "sds-cluster-1" + ] + } + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "s2.example.com" + ] + }, + "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_s2", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s2" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s2.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "clusterNames": [ + "sds-cluster-2" + ] + } + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "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-sds-listener-service-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-service-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..4c53a0af2b --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-service-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,149 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s1" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "clusterNames": [ + "sds-cluster-1" + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "s2.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s2", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s2" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s2.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "clusterNames": [ + "sds-cluster-2" + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener.envoy-1-18-x.golden new file mode 100644 index 0000000000..aff11429cb --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener.envoy-1-18-x.golden @@ -0,0 +1,58 @@ +{ + "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.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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "cert-resource", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "clusterNames": [ + "sds-cluster" + ] + } + } + } + ] + }, + "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-sds-listener.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..0013a1a29e --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener.v2compat.envoy-1-16-x.golden @@ -0,0 +1,58 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.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.config.filter.network.tcp_proxy.v2.TcpProxy", + "statPrefix": "upstream.db.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "cert-resource", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "clusterNames": [ + "sds-cluster" + ] + } + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.envoy-1-18-x.golden new file mode 100644 index 0000000000..dbfb9f40ed --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.envoy-1-18-x.golden @@ -0,0 +1,122 @@ +{ + "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": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "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_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s1" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-1" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "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": { + + } + } + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "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-sds-service-level-mixed-no-tls.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..e6717b87b4 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.v2compat.envoy-1-16-x.golden @@ -0,0 +1,122 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s1" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-1" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-sds-service-level.envoy-1-18-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-service-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..cc7c0c30cc --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-service-level.envoy-1-18-x.golden @@ -0,0 +1,159 @@ +{ + "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": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "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_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s1" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-1" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "s2.example.com" + ] + }, + "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_s2", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080_s2" + }, + "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": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s2.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V3", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-2" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V3" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "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-sds-service-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/listeners/ingress-with-sds-service-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..1481476470 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-sds-service-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,159 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.Listener", + "name": "http:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "s1.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s1", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s1" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s1.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-1" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + }, + { + "filterChainMatch": { + "serverNames": [ + "s2.example.com" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080_s2", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V2" + }, + "routeConfigName": "8080_s2" + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router" + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificateSdsSecretConfigs": [ + { + "name": "s2.example.com-cert", + "sdsConfig": { + "apiConfigSource": { + "apiType": "GRPC", + "transportApiVersion": "V2", + "grpcServices": [ + { + "envoyGrpc": { + "clusterName": "sds-cluster-2" + }, + "timeout": "5s" + } + ] + }, + "resourceApiVersion": "V2" + } + } + ] + }, + "requireClientCertificate": false + } + } + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector" + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.envoy-1-18-x.golden b/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.envoy-1-18-x.golden new file mode 100644 index 0000000000..3c251942ec --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.envoy-1-18-x.golden @@ -0,0 +1,48 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "web.ingress.*", + "web.ingress.*:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "name": "foo", + "domains": [ + "foo.ingress.*", + "foo.ingress.*:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..d1d8bae6c1 --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-listener-level-wildcard.v2compat.envoy-1-16-x.golden @@ -0,0 +1,48 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "9191", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "web.ingress.*", + "web.ingress.*:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "name": "foo", + "domains": [ + "foo.ingress.*", + "foo.ingress.*:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-listener-level.envoy-1-18-x.golden b/agent/xds/testdata/routes/ingress-with-sds-listener-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..0dc02c5056 --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-listener-level.envoy-1-18-x.golden @@ -0,0 +1,48 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "www.example.com", + "www.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "name": "foo", + "domains": [ + "foo.example.com", + "foo.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-listener-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/routes/ingress-with-sds-listener-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..7261889dae --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-listener-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,48 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "9191", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "www.example.com", + "www.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "name": "foo", + "domains": [ + "foo.example.com", + "foo.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.envoy-1-18-x.golden b/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.envoy-1-18-x.golden new file mode 100644 index 0000000000..44ab48e588 --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.envoy-1-18-x.golden @@ -0,0 +1,55 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191", + "virtualHosts": [ + { + "name": "foo", + "domains": [ + "foo.example.com", + "foo.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + }, + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191_web", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "www.example.com", + "www.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..07fbfd855f --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-service-level-mixed-tls.v2compat.envoy-1-16-x.golden @@ -0,0 +1,55 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "9191", + "virtualHosts": [ + { + "name": "foo", + "domains": [ + "foo.example.com", + "foo.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + }, + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "9191_web", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "www.example.com", + "www.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-service-level.envoy-1-18-x.golden b/agent/xds/testdata/routes/ingress-with-sds-service-level.envoy-1-18-x.golden new file mode 100644 index 0000000000..1e207d6efc --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-service-level.envoy-1-18-x.golden @@ -0,0 +1,55 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191_foo", + "virtualHosts": [ + { + "name": "foo", + "domains": [ + "foo.example.com", + "foo.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + }, + { + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "name": "9191_web", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "www.example.com", + "www.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/ingress-with-sds-service-level.v2compat.envoy-1-16-x.golden b/agent/xds/testdata/routes/ingress-with-sds-service-level.v2compat.envoy-1-16-x.golden new file mode 100644 index 0000000000..b53d5be47c --- /dev/null +++ b/agent/xds/testdata/routes/ingress-with-sds-service-level.v2compat.envoy-1-16-x.golden @@ -0,0 +1,55 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "9191_foo", + "virtualHosts": [ + { + "name": "foo", + "domains": [ + "foo.example.com", + "foo.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + }, + { + "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "name": "9191_web", + "virtualHosts": [ + { + "name": "web", + "domains": [ + "www.example.com", + "www.example.com:9191" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "validateClusters": true + } + ], + "typeUrl": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/version_compat.go b/agent/xds/version_compat.go index aa4ad55eb2..c75eda0750 100644 --- a/agent/xds/version_compat.go +++ b/agent/xds/version_compat.go @@ -440,7 +440,14 @@ func convertTypedConfigsToV2(pb proto.Message) error { return nil case *envoy_core_v2.ConfigSource: if x.ConfigSourceSpecifier != nil { - if _, ok := x.ConfigSourceSpecifier.(*envoy_core_v2.ConfigSource_Ads); !ok { + switch spec := x.ConfigSourceSpecifier.(type) { + case *envoy_core_v2.ConfigSource_Ads: + // Nothing else to do + break + case *envoy_core_v2.ConfigSource_ApiConfigSource: + spec.ApiConfigSource.TransportApiVersion = envoy_core_v2.ApiVersion_V2 + break + default: return fmt.Errorf("%T: ConfigSourceSpecifier type %T not handled", x, x.ConfigSourceSpecifier) } } @@ -491,8 +498,30 @@ func convertTypedConfigsToV2(pb proto.Message) error { case *envoy_http_rbac_v2.RBAC: return nil case *envoy_tls_v2.UpstreamTlsContext: + if x.CommonTlsContext != nil { + if err := convertTypedConfigsToV2(x.CommonTlsContext); err != nil { + return fmt.Errorf("%T: %w", x, err) + } + } return nil case *envoy_tls_v2.DownstreamTlsContext: + if x.CommonTlsContext != nil { + if err := convertTypedConfigsToV2(x.CommonTlsContext); err != nil { + return fmt.Errorf("%T: %w", x, err) + } + } + return nil + case *envoy_tls_v2.CommonTlsContext: + for _, sds := range x.TlsCertificateSdsSecretConfigs { + if err := convertTypedConfigsToV2(sds); err != nil { + return fmt.Errorf("%T: %w", x, err) + } + } + return nil + case *envoy_tls_v2.SdsSecretConfig: + if err := convertTypedConfigsToV2(x.SdsConfig); err != nil { + return fmt.Errorf("%T: %w", x, err) + } return nil case *envoy_grpc_stats_v2.FilterConfig: return nil diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 737b814d42..c3eb07f12e 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -40,6 +40,19 @@ type IngressGatewayConfigEntry struct { type GatewayTLSConfig struct { // Indicates that TLS should be enabled for this gateway service. Enabled bool + + // SDS allows configuring TLS certificate from an SDS service. + SDS *GatewayTLSSDSConfig `json:",omitempty"` +} + +type GatewayServiceTLSConfig struct { + // SDS allows configuring TLS certificate from an SDS service. + SDS *GatewayTLSSDSConfig `json:",omitempty"` +} + +type GatewayTLSSDSConfig struct { + ClusterName string `json:",omitempty" alias:"cluster_name"` + CertResource string `json:",omitempty" alias:"cert_resource"` } // IngressListener manages the configuration for a listener on a specific port. @@ -59,6 +72,9 @@ type IngressListener struct { // For "tcp" protocol listeners, only a single service is allowed. // For "http" listeners, multiple services can be declared. Services []IngressService + + // TLS allows specifying some TLS configuration per listener. + TLS *GatewayTLSConfig `json:",omitempty"` } // IngressService manages configuration for services that are exposed to @@ -93,6 +109,9 @@ type IngressService struct { // Namespacing is a Consul Enterprise feature. Namespace string `json:",omitempty"` + // TLS allows specifying some TLS configuration per listener. + TLS *GatewayServiceTLSConfig `json:",omitempty"` + // Allow HTTP header manipulation to be configured. RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"` ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"` diff --git a/api/config_entry_gateways_test.go b/api/config_entry_gateways_test.go index 22b15259b9..bc8d6d0e49 100644 --- a/api/config_entry_gateways_test.go +++ b/api/config_entry_gateways_test.go @@ -86,8 +86,26 @@ func TestAPI_ConfigEntries_IngressGateway(t *testing.T) { ResponseHeaders: &HTTPHeaderModifiers{ Remove: []string{"x-foo"}, }, + TLS: &GatewayServiceTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "foo", + CertResource: "bar", + }, + }, }, }, + TLS: &GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "baz", + CertResource: "qux", + }, + }, + }, + } + ingress1.TLS = GatewayTLSConfig{ + SDS: &GatewayTLSSDSConfig{ + ClusterName: "qux", + CertResource: "bug", }, } diff --git a/test/integration/connect/envoy/Dockerfile-test-sds-server b/test/integration/connect/envoy/Dockerfile-test-sds-server new file mode 100644 index 0000000000..4ace04b394 --- /dev/null +++ b/test/integration/connect/envoy/Dockerfile-test-sds-server @@ -0,0 +1,8 @@ +FROM golang:latest + +WORKDIR /go/src +COPY ./test-sds-server . + +RUN go build -v -o test-sds-server sds.go + +CMD ["/go/src/test-sds-server"] \ No newline at end of file diff --git a/test/integration/connect/envoy/case-ingress-gateway-sds/capture.sh b/test/integration/connect/envoy/case-ingress-gateway-sds/capture.sh new file mode 100644 index 0000000000..41ea5cb24f --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-sds/capture.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 ingress-gateway primary || true \ No newline at end of file diff --git a/test/integration/connect/envoy/case-ingress-gateway-sds/config_entries.hcl b/test/integration/connect/envoy/case-ingress-gateway-sds/config_entries.hcl new file mode 100644 index 0000000000..16db5bec94 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-sds/config_entries.hcl @@ -0,0 +1,60 @@ +config_entries { + bootstrap = [ + { + kind = "proxy-defaults" + name = "global" + config { + protocol = "http" + } + }, + { + kind = "ingress-gateway" + name = "ingress-gateway" + + listeners = [ + { + port = 9999 + protocol = "http" + services = [ + { + name = "*" + } + ] + tls { + sds { + cluster_name = "sds-cluster" + cert_resource = "wildcard.ingress.consul" + } + } + }, + { + port = 9998 + protocol = "http" + services = [ + { + name = "s1" + hosts = ["foo.example.com"] + tls { + sds { + cluster_name = "sds-cluster" + cert_resource = "foo.example.com" + } + } + }, + { + # Route to s2 on a differet domain with different cert + name = "s2" + hosts = ["www.example.com"] + tls { + sds { + cluster_name = "sds-cluster" + cert_resource = "www.example.com" + } + } + } + ] + } + ] + } + ] +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-sds/service_gateway.hcl b/test/integration/connect/envoy/case-ingress-gateway-sds/service_gateway.hcl new file mode 100644 index 0000000000..2c9f7a3aaf --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-sds/service_gateway.hcl @@ -0,0 +1,46 @@ +services { + name = "ingress-gateway" + kind = "ingress-gateway" + + proxy { + config { + # Note that http2_protocol_options is a deprecated field and Envoy 1.17 + # and up would prefer: + # typed_extension_protocol_options: + # envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + # "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + # explicit_http_config: + # http2_protocol_options: + # + # But that breaks 1.15 and 1.16. For now use this which is supported by + # all our supported versions to avoid needing to setup different + # bootstrap based on the envoy version. + envoy_extra_static_clusters_json = <&2 - return 1 - fi + echo "GOT: $output" + + [ "$status" == 0 ] + [ "$output" == "FORTIO_NAME=${EXPECT_NAME}" ] } function assert_expected_fortio_name_pattern { @@ -889,4 +930,4 @@ function assert_expected_fortio_host_header { echo "expected Host header: $EXPECT_HOST, actual Host header: $GOT" 1>&2 return 1 fi -} +} \ No newline at end of file diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index 2015c373f9..890a567155 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -89,6 +89,10 @@ function init_workdir { # move all of the registration files OUT of the consul config dir now find workdir/${DC}/consul -type f -name 'service_*.hcl' -exec mv -f {} workdir/${DC}/register \; + # copy the ca-certs for SDS so we can verify the right ones are served + mkdir -p workdir/test-sds-server/certs + cp test-sds-server/certs/ca-root.crt workdir/test-sds-server/certs/ca-root.crt + if test -d "${CASE_DIR}/data" then cp -r ${CASE_DIR}/data/* workdir/${DC}/data @@ -283,6 +287,7 @@ function run_tests { CASE_DIR="${CASE_DIR?CASE_DIR must be set to the path of the test case}" CASE_NAME=$( basename $CASE_DIR | cut -c6- ) export CASE_NAME + export SKIP_CASE="" init_vars @@ -296,6 +301,12 @@ function run_tests { global_setup + # Allow vars.sh to set a reason to skip this test case based on the ENV + if [ "$SKIP_CASE" != "" ] ; then + echoyellow "SKIPPING CASE: $SKIP_CASE" + return 0 + fi + # Wipe state wipe_volumes @@ -366,6 +377,10 @@ function suite_setup { docker build -t consul-dev-envoy:${ENVOY_VERSION} \ --build-arg ENVOY_VERSION=${ENVOY_VERSION} \ -f Dockerfile-consul-envoy . + + # pre-build the test-sds-server container + echo "Rebuilding 'test-sds-server' image..." + docker build -t test-sds-server -f Dockerfile-test-sds-server . } function suite_teardown { @@ -576,6 +591,13 @@ function run_container_jaeger { --collector.zipkin.http-port=9411 } +function run_container_test-sds-server { + docker run -d --name $(container_name) \ + $WORKDIR_SNIPPET \ + $(network_snippet primary) \ + "test-sds-server" +} + function container_name { echo "envoy_${FUNCNAME[1]/#run_container_/}_1" } diff --git a/test/integration/connect/envoy/test-sds-server/certs/ca-root.crt b/test/integration/connect/envoy/test-sds-server/certs/ca-root.crt new file mode 100644 index 0000000000..97805246ec --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/ca-root.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE5jCCAs4CCQCSUow3YnwtFTANBgkqhkiG9w0BAQsFADA1MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExGTAXBgNVBAMMEFNEUyBUZXN0IENBIENlcnQwHhcNMjEw +ODI0MTIzMjM4WhcNMzEwODIyMTIzMjM4WjA1MQswCQYDVQQGEwJVUzELMAkGA1UE +CAwCQ0ExGTAXBgNVBAMMEFNEUyBUZXN0IENBIENlcnQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDFGKACcPEVX4qMV6+ycjsTC5BAeGvB/9k1g6vULdEM +atyZMJ74i/vMuI6NSNKbB+XKyZVfbLDkoU2vlnDIWljX13WiFOHA7yueuIFyYTWl +7OG3SIoABqefh14dd86DyBrBYsNIp//QyzFNX9D98Ss3dnBnINTvFfFZKQ/hR90r +wtOtgh51vUTHU8dhHP2i7t/YoHn5yIUrtkLYKOe76loveqRE9G34QHPo00EHEg/X +0cCSOwzos+wK9ebLzgXdquvuIf8e8xkwJEpo/1MZ/0Tq9zsGWkNSM6G0jQ0qrHoa +X+LMGY5JnyZTMjuwLI5UtU9b4aSxbUbVoHftVxnkfVPSOD1770QLLpYIUBEvbonh +Y3r4zejQY2ES1sbMIOX+9lYaZKGGwd8/777bjeYTI+oIHlWgDMqiLhwzCOuQQ2Jo +c7ilfWItyhAmucJdpHY80aFGGUhbqUWftrFnuSRJlxUy2+sEqIQiv1D1/q3pLcOP +rPB/GozfeCW346LO5NpJmBdWItO37EARmH1cZW3PbX6FeXHWgm2MjWNR0E3K4Biv +r0QeYagzjIjTiXqfZAMPscjHZI3YpMDtXuHUD2ppoCpAu0Qw5CKmvw834P2xezM9 +Y28CfciGxknYtkf3hd1Vgr0aRnBd5PRJq3iNdfO/a/YpDpdhFhDi29MPPnAJxBpj +CwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAIFjEsrFR2AwhPuBCY9rB8NXiuWU6A +O9hXxF36VUolJpD5F/E3J5LGSRlI4H5fAX09aoPNfwqTN4jBWOg0pAZP2oZ1BBQ8 +VQZwzGki9uJTKDnr09mnnMFbdVceYN+nh2HlknEyVj6oSGpD/D1WvhOpeAct30f4 +Z/kKnfM7awUJ/0VsbtVwcBMJTuoFflig9NXyqU1QRA2wEvnFSPG7bqprWE4FtODL +71Fj9gB4M/PFs8+dB9UeSeI20834hRw8zI/QwpMyAs/5EU6wxjaVQtEba396oVQU +BC+UxxQOmK/m31OKJYBLrzeeRD/im4H9fsbWiw4C0xi72OFDWjsRay5mxZlF9N2G +LPIwS9f34JU5HBa/jutShfHE8xnUOeLiXz7AZJ4cNbN+OMPBF40gr67mGCTLpidd +Kldl57UBQacrkecficpmL2KuCAZ80m1QxTJzTIzwSgqPfElsilNFNzeKaM+Qct/S +XSOEsd7St7PxzceiyNkOOV6W+b4GVmxgiClQsWqxMmH7P9X6B+M0SSHFwIxaf126 +VpMOKRXH838s8aFl38PfnEY3v6mS5irvSk9iUsYRpcBHETVzltHzF5Qyn7lp9ncf +iNlIeFEPABODkkU8KQ5MxAwHn5XoXIfJBRrmBfKvxEMCxdUrmUl3ZcsQMvj0c6xJ +MUv9D4WRXMb3XA== +-----END CERTIFICATE----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/ca-root.key b/test/integration/connect/envoy/test-sds-server/certs/ca-root.key new file mode 100644 index 0000000000..ac37bebcc2 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/ca-root.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAxRigAnDxFV+KjFevsnI7EwuQQHhrwf/ZNYOr1C3RDGrcmTCe ++Iv7zLiOjUjSmwflysmVX2yw5KFNr5ZwyFpY19d1ohThwO8rnriBcmE1pezht0iK +AAann4deHXfOg8gawWLDSKf/0MsxTV/Q/fErN3ZwZyDU7xXxWSkP4UfdK8LTrYIe +db1Ex1PHYRz9ou7f2KB5+ciFK7ZC2Cjnu+paL3qkRPRt+EBz6NNBBxIP19HAkjsM +6LPsCvXmy84F3arr7iH/HvMZMCRKaP9TGf9E6vc7BlpDUjOhtI0NKqx6Gl/izBmO +SZ8mUzI7sCyOVLVPW+GksW1G1aB37VcZ5H1T0jg9e+9ECy6WCFARL26J4WN6+M3o +0GNhEtbGzCDl/vZWGmShhsHfP+++243mEyPqCB5VoAzKoi4cMwjrkENiaHO4pX1i +LcoQJrnCXaR2PNGhRhlIW6lFn7axZ7kkSZcVMtvrBKiEIr9Q9f6t6S3Dj6zwfxqM +33glt+OizuTaSZgXViLTt+xAEZh9XGVtz21+hXlx1oJtjI1jUdBNyuAYr69EHmGo +M4yI04l6n2QDD7HIx2SN2KTA7V7h1A9qaaAqQLtEMOQipr8PN+D9sXszPWNvAn3I +hsZJ2LZH94XdVYK9GkZwXeT0Sat4jXXzv2v2KQ6XYRYQ4tvTDz5wCcQaYwsCAwEA +AQKCAgBn+Zan0wcLfGxtnvB8FdDeOjJuIFjQbfUbSwwBke3/O/yF/+VNPyOlmxLw +q6MWz/LEqNVZEtC6u/FsmNOEjkziCLLwv01c548+Wx1GxstzbeShOO48F0akXNgk +eYiDcrPSoxRcJuxILHkk1VA+NtTSnHOto99RBWVd4NCUysF9jXj3xnXlLOS0tpiu +vYl8Q8Ho2LegMs02Ax9+Qw4Ezxumgm9YRQ4icXX0NEXQwhGDjA6n1ej4dbonChQo +HdZwWvO9m0en/xyhjfOHMnKXe1fTEHud3U44di5vPh3dDkIX2c+eVjj+06t3Jj/g +r5CiVUe5M+8a/ofouI+rsFBjfk4R4d7uC9FEvuv8SLzBBkCvZK3VLpX+8YCdlaVE +y+2iTp0C42FB4RvSj8aV3qu6mxbyA1fa/Q17GiexYYuBqnc+yQANs5uBra+Lidn7 +W66D0cMEJS3IZJ5atpasFBiXh7a/xXAQBq6H3daEGyvgDZt144qqqVZBrGq5ZIYm +4fbFMIbEC1xPovnPLlYPGeYwydXYGbKaPZ3S5Q30QAj+j7L1vvczvf0CmeYTc4dI +qrQiLe8ksZ/wD7X5xOUPRFGKmiUqDLPDxGKX4kRKNmEZ7zo9lp+/LAAty5ekFS3f +I5Vi0MjLCTCOt1xtgtArUuEK7QsV1p+CXfExhA/aALjnw/tWSQKCAQEA7fMmb8sp +JYrkoJZHLz/jsvYAkY4B+j3L9Fdbd5b8kh+hvw9J5Av6IETJfWCxI+gPuyX6/0KK +UzOv+bU/tT1Z/4Cb5S5TobTO83Yfs8XA1ML8ZMl8b9w7vpyUBoR93DCREpd1uF0Y +89o1eKOPpjCvJMqDN+A3SQ3rqmkeEiFA1iNhhexjoon3u+v6xKfmG6txKcsS+vtq +v8dKEvMsy25AkC6CnHpFR9WpuaCu48nRFif9ru5eZCKN3Vu+WFG/KuVCKtxZAFtE +GVPvZ7ZkRY5V2uEDjeLXIz8ygv4ZcoRI79k2+SkoIbs1mrSWQqFVUDtaiekgyt3K +vXG/jElg1NmTpwKCAQEA1Awe9vsADWCQo54cTToHe6+N/6DKddjvEFKly6QDPbxq +zBbJNgQ8CCxF19lZY8nOMZsRi8qwb/ytnK2lYBp8+x/5g6NsQ6QkuWUmSPBJR2O/ +370284V1fwj/L9LxO3FS2Ci6AVlsMxEHG1Uifq80F80kSt2Qfdh3qTGrIrH9xFhQ +RDGRaTURIWZEtpmJrmwLZFeWLJVOwAfRKuz+vEuOK2f1GwfK5avcGnRxXvp0SJU1 +uO+Aamj5i5aBz//gMhx/QAAOgVEhrED2bPKPj1erDa/yksB6t73fg3Gki7LdPf/E +g3nBMTXMkI6MNA1h9IeRTnJXJgJpr6Qqy18wybux/QKCAQEA2OJSu30rAPwrTbAM +LCYIzjrp48HTTZr8BreFOGjXx/yq+jHeQM0l7DmmifATJc5EYGnK8NVerV2kXW92 +JSVJndhEwE1Mj0z1gPW/CaYLECK12MvJ70+G6UNgrNwguA1QfeittSCuOL5BFLfI +nEstqNQpbEmjOqRElOwBK7dBmK1hG6eOXT1yH8iEprD+zWOj3tspbriw4SKuAr3B +q57PnFu4UxYjhclbTUYHgrqWKKLE2KiOQqk4M3aG9Yt1Oo0ClXyIZwnI9WkLArY9 +iSSmXr3P9oi3XXoh+UHQQhAPRwbu04ZO/QfdYSiO+SaCU4H2EVP7vs0QGULPu/e6 +GehDawKCAQBo35nM8zUCaLkAgyliNIoQ7TGgtUiM4n7SJt4jCnoj5XU4fSiE07FR +JXxhlfi09tFJDXWvGs8KhADllahOEKuxlA3WwlPAXIVhQBqgRl8ntLjoEnAEHwSO +kMeNQpnWmWSsze+4zR5LX2eRaBjIaSSthOMnN8/HvfDouEz4uulUW41PPOi7DOjt +COSBHwzOPFeiCAOZBztMlFEqFs6iGAg3hZHfDYqW7tnMJ70OiXZLk6hfT+zGNCb/ +l6+aTOX4QsnYwG5sHNE3nWWDWrAP5/8MpPGnDRVknL7YBrOn+A2eJUpu8B/Rk9cZ +w7ap49iHlSkTkE9z2AunN92GegzqKS+NAoIBABIyp3GOvAHfMKPDWoP9YvSeyxfd +nnnA+OqS5CrpXcifLaY1kDdpPvI8Kixi+6+gtU4N5yoi2nfiG7X7JsWA//OPBymp +OSqIlrC9adAM6UdoYuQHb4v2Wdk98Hdk5Su+xIERP09fC1PXWh6c2W5YzrBqsNKg +RIq/oz1arcgESAbhFNqYMHoMKcFVnkS8XO8JvL9mQbnGYCwUsbcWb6rfKQtrspMF +Owy2EOn0F9a1pj1VndHSRR6Tu5NgpcZtWlaOXTUuvtn594zRjotYeGu/nmyhTO2x +ZE3IsZpHujFlUPctmFeRle5AL0Ev6ikfsgafF44kqA2D8iM236BeKCLHpSg= +-----END RSA PRIVATE KEY----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/ca-root.srl b/test/integration/connect/envoy/test-sds-server/certs/ca-root.srl new file mode 100644 index 0000000000..f58ad6fd68 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/ca-root.srl @@ -0,0 +1 @@ +E3EC7A5D1F03189F diff --git a/test/integration/connect/envoy/test-sds-server/certs/foo.example.com.crt b/test/integration/connect/envoy/test-sds-server/certs/foo.example.com.crt new file mode 100644 index 0000000000..03fafb3861 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/foo.example.com.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIECjCCAfKgAwIBAgIJAOPsel0fAxibMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEZMBcGA1UEAwwQU0RTIFRlc3QgQ0EgQ2VydDAe +Fw0yMTA4MjQxMjMyMzhaFw0zMTA4MjIxMjMyMzhaMDQxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJDQTEYMBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoNqz7MhSbI3pn+vNfO/3FrEkC64jmxrSK9bQ +W4WXhbstaM1SbrIqlzacOhslyj5RjzNb1d1TAhdGzrrLiSkCGsstAnMOQIFzP1j9 +2Jiesm5ypiqiOACnwEmSOcfH91N3l/9rLB14uon3q3bLuKv2TjIKeQ21WlIIUSF4 +RwTZGb1sagj8qT9+k/byt94cURVK8DljAuY7v74XLoomRyr4hqaUnamE+K9/rPHi +3IlcTdECHX57SLmfJvSD5YU1O+6l8+u2F79E1/Po2ckNuTFopwnAO487US8qRVjs +3bn/iu9WcMNdTwwEdLEVPPAGNNEvl5gKzrIr+0aoE/XCJuDKFQIDAQABox4wHDAa +BgNVHREEEzARgg9mb28uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggIBADYg +UcWXPZdH1QPgWNgbs3q9ldNi1bl7BmPxuZio3a/yP1+xHWFVKvHJEQZI2OgdESCV +zkLhpVOzpK9MO+pCSzE3ZOtM7+nKtHwmOGsjK7pOJblwXb3kXi6stEellKXOU4h3 +zYR5L0uZu7OBTnigvYSwT3cvTMQFRttVVJ5UYr+s84QUrKesiYlitUqiHmiBRitx +/3Pxft/V5KvIuovJD7YXJij+hst17Wmoe/lpN6vzGBFaNJE25TZe+momCfU1Cn/F +v0n63wwoR6LsSUuI5T7gFdUb/JXrJWV37P1fs/rk/8H2c6IYxVRjGSNGRIQ+QhdU +2hVXEWexdieRcGsL75E0p9Ok5OjICPkdlkhhMRkJv/oVA9YNaqLk8t0ZtJmWX7Jr +3rILNispG1UnfaBYFtXSSPUbQqyujeqMcXIg1JOAK1tjcMGPrd8BcaU8tT0Ycvg3 +kOcTayd9ZwuJhHTpNW090d3K55FJhEmR+1S4/JoHI1MLEUDRACdxnCKhJ+X9qPlk +DBqyudDRaoe54eoUXtxBujB3rgTrWpryBvxJ5cXEeN8W0Yv8dTEywlHF3X+sp3U/ +5hlMrzsAn7+O+ZnxnSPGrIP/UxSqEoSz1Jz7mNhIXYTV1V1L8mRyDAEIBLMmTyEj +hg2RBdjqu5KFPFogazXpe9mAK+hxb5TfsroWn/Zk +-----END CERTIFICATE----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/foo.example.com.key b/test/integration/connect/envoy/test-sds-server/certs/foo.example.com.key new file mode 100644 index 0000000000..50c0da94f4 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/foo.example.com.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAoNqz7MhSbI3pn+vNfO/3FrEkC64jmxrSK9bQW4WXhbstaM1S +brIqlzacOhslyj5RjzNb1d1TAhdGzrrLiSkCGsstAnMOQIFzP1j92Jiesm5ypiqi +OACnwEmSOcfH91N3l/9rLB14uon3q3bLuKv2TjIKeQ21WlIIUSF4RwTZGb1sagj8 +qT9+k/byt94cURVK8DljAuY7v74XLoomRyr4hqaUnamE+K9/rPHi3IlcTdECHX57 +SLmfJvSD5YU1O+6l8+u2F79E1/Po2ckNuTFopwnAO487US8qRVjs3bn/iu9WcMNd +TwwEdLEVPPAGNNEvl5gKzrIr+0aoE/XCJuDKFQIDAQABAoIBAHbCQcj19XTUKcK6 +k9JEUQdd7aD6BqvDV5yqUtbfgQlpMpOH0+6KeEuANEPqOzJgZFL3tjvl1h1bFhoX +a6JutnEVxLtkOweBC5efmntJ4xEV3mu0WmRCh5e+OcmTKJ3/7/I2z+eYMsR2jHb5 +lFtaCtWcuIK7jkOLATz1GxpECA2p0zQZOlg2KyO2oyypig52p+kzR79L8EWcK3gX +fJHdgycPkqbWb0mal3MPyHmMuYSvIeh40uTnTu0MAQh+N32247vuxXZu0zSeiOXe +vJdgPRi8FpNe97DVmcx/JUKjmfuBySL8w6Jg1hrXGZTpEgEZVYWITEP1SmnGYGGW +mzsyzMECgYEAzHw/ZqaEnXl+XmhuigFr5yOlfIy7NHrvCnXt08Ba791QGqEHPA+K +8MXb5Yw8Vwb9VOnNTRN3nzDwBL1et9+oanH3ngAu8tSC7DfZhkPDS7EhE4sLU3k0 +C616Qn/+q+QEQctMZQKuk4yy+BZf5yLNOASZxgY5HemGIuy1oOV/U20CgYEAyWCV +q6G8kLHUvXnwL86rJPdtQwwcUoGTkKEwRTqf94mW2IKHA/NNP9bIpAI799HtxIEw +C9yX/0grAKwzpztqos+pcb5Ly8dOwg0DNVsOdAVHE5EBeLeQD/gP1t+VkYRBGEhF ++VRJwbrgg9WMCDPj/zLskRiWm3nRT/BTsN6aAEkCgYEAp/H8GJD3JmcclOlssEgO +mV47kpn7P1UgcxT8agf4KD06h0RuLrQNR0caHeQZwthoxI7qIT2157dHynzGCHrX +VeWYm9pGtQY5KU2NiKqrAcXPBsASY1KOnnCyk1+QiRjTLj6M1gFn5KOQchXeWnXI +2xNcQLnnu1uK4bBMVINpGdkCgYA14uuKFuh/i6aS4Utdb58qcC9Drrzxcw0KuadS +DyL5OU8tNphsfTGhsJbWFGb5pKpMWAmEUw41WJlxP6M+z850LL952WMs73Nqx9Kg +93HBqBvh536OUAhzzXxnkkLSwqIsnkJjOqPV/GzRulYTZ9dN1dGp7ft3NTzGeFfc +z2RESQKBgG4RDl1uHt0t6+dPH+apexI+uwJ9cfM0z+e7BjRjkkuQiSBUDUQJzYdK +iIm0ftmO5esvgYWCJVndNGWbacOoq1/9W9WMjjpFMDgBTcZayt9yi9qc38AVhqGW +x/FiQOZ8Eg2LE2lMEQVrIyXnKopZgU+wHlJPWoLvccK+cLXZuevg +-----END RSA PRIVATE KEY----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/gen-certs.sh b/test/integration/connect/envoy/test-sds-server/certs/gen-certs.sh new file mode 100755 index 0000000000..975c9d3d19 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/gen-certs.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -eEuo pipefail +unset CDPATH + +# force the script to first switch to the directory containing the script before +# messing with the filesystem +cd "$(dirname "$0")" +rm -rf *.crt *.key + +openssl genrsa -out ca-root.key 4096 +openssl req -x509 -new -nodes -key ca-root.key -out ca-root.crt \ + -subj "/C=US/ST=CA/O=/CN=SDS Test CA Cert" \ + -sha256 -days 3650 + +function gen_cert { + local FILE_NAME=$1 + local DNS_NAME=$2 + + openssl genrsa -out "$FILE_NAME.key" 2048 + openssl req -new -key "$FILE_NAME.key" -out "$FILE_NAME.csr" \ + -reqexts SAN \ + -config <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:$DNS_NAME")) \ + -subj "/C=US/ST=CA/O=/CN=$DNS_NAME" + + openssl x509 -req -in "$FILE_NAME.csr" \ + -CA ca-root.crt -CAkey ca-root.key -CAcreateserial \ + -out "$FILE_NAME.crt" -days 3650 -sha256 \ + -extfile <(printf "subjectAltName=DNS:$DNS_NAME") + + rm "$FILE_NAME.csr" +} + +DOMAINS="www.example.com foo.example.com *.ingress.consul" + +for domain in $DOMAINS +do + # * in file names is interpreted as a global and all sorts of things go + # strange! + FILE_NAME="$domain" + if [ ${domain:0:2} == "*." ]; then + FILE_NAME="wildcard.${domain:2}" + fi + gen_cert $FILE_NAME $domain +done \ No newline at end of file diff --git a/test/integration/connect/envoy/test-sds-server/certs/wildcard.ingress.consul.crt b/test/integration/connect/envoy/test-sds-server/certs/wildcard.ingress.consul.crt new file mode 100644 index 0000000000..218999c897 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/wildcard.ingress.consul.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDDCCAfSgAwIBAgIJAOPsel0fAxicMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEZMBcGA1UEAwwQU0RTIFRlc3QgQ0EgQ2VydDAe +Fw0yMTA4MjQxMjMyMzhaFw0zMTA4MjIxMjMyMzhaMDUxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJDQTEZMBcGA1UEAwwQKi5pbmdyZXNzLmNvbnN1bDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALdsZUrmtIPO914Gcsgr7llxD6Dq/+c4zj5M +GYaFegfCK8mj27Lunxibz7ANkeb/TY7ujBSUjC12PzKNwZTtqs9ZuMlBvxcZeesB +FawiWZ1P1TUJ/J8E+j0Egx6nIa5zd8/tZRR6wnBr/bCSUpr6ZQtkPSPA8vh7/cea +al0SDsgq+ssbgpWf4EvnUDK6TH5x2iymJKkycppi2d4RkIha5uu9eznPkWKMNcFN +nLCPOdljpJ2bC96hurdQAIElJAg+iJr+Oten1GtxlzhHi2U0IBFJ+aRObsTQYR2f +AUGIEqS+E5vVPKsTZnitVL9DdEagl8NLMGTr3k2Ok4AOU+42uVkCAwEAAaMfMB0w +GwYDVR0RBBQwEoIQKi5pbmdyZXNzLmNvbnN1bDANBgkqhkiG9w0BAQsFAAOCAgEA +sNL1izKiFrY41+dK1RYtNAhtlexn+Jyizh8t6aDQfTEyYM8v7+FK5CmLECkGO+5Z +0+HsHB4KwG1OhiTPbFdxyI3na2hluoxPM3ykR1erC10XoKSuerK8vO4JFEJSFVp1 +iqW926YgDnM38565JofXTj4keRvPyFGVW5y1HckgviDeYEpAuunolz3w2w7yuvfc +EQydhl2WZHD1+6uRMKGmJz8f+7JwanfpW5XdwuVR6LH9uHcuDDR5xa+7jmDhQzto +V3hejsVrCV85dyIELseLb1R+T36HXrAWUM3IBYd9DclwwhKo48TXfh3br8XFLkUV +N7IobJthBVqtQqW7V/yI/zYZn+SPhX7BudUVf/dJwDq1hgxwmUo/vcx2HnlSoEDw +M/r1RwY1QY0omZNn3G87VGZjC2tqL8FrT7Yu94EOZ21hneoaH+5Igt4groQ4aZl2 +MGVNdC9Slv8K7n+RyQbbo+JdfKDSDoi8FD5XYlaRSgA+TAn9ginl4/516cwcIZrU +rxnx0QVjFzLeaFa51x6IC0FEE1Sp8dJQxJCMFndgCkIc8TC03SrCCSAvbIOj9CrD +dT3/uAP+xQGZVGwRVY9qMH+gxE+KbjEukY1RmxrDFtSVUEwFPVhpgJmoHDy/N+lh +BV6NlYN2M59mFI/rJh01yRCv0W0Qe0idJ76fVYOv1jk= +-----END CERTIFICATE----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/wildcard.ingress.consul.key b/test/integration/connect/envoy/test-sds-server/certs/wildcard.ingress.consul.key new file mode 100644 index 0000000000..69f7b874d8 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/wildcard.ingress.consul.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAt2xlSua0g873XgZyyCvuWXEPoOr/5zjOPkwZhoV6B8IryaPb +su6fGJvPsA2R5v9Nju6MFJSMLXY/Mo3BlO2qz1m4yUG/Fxl56wEVrCJZnU/VNQn8 +nwT6PQSDHqchrnN3z+1lFHrCcGv9sJJSmvplC2Q9I8Dy+Hv9x5pqXRIOyCr6yxuC +lZ/gS+dQMrpMfnHaLKYkqTJymmLZ3hGQiFrm6717Oc+RYow1wU2csI852WOknZsL +3qG6t1AAgSUkCD6Imv4616fUa3GXOEeLZTQgEUn5pE5uxNBhHZ8BQYgSpL4Tm9U8 +qxNmeK1Uv0N0RqCXw0swZOveTY6TgA5T7ja5WQIDAQABAoIBAQCIgTX9ASbUdUGp +Cvl/ORqAG+E6q8lYxfDPcgkJEOrZPUsjHDwTtPxX7xUoe2Da3aNVvPgpFMghyT+N +Guw3Lo0RFT+tH63bvgz6tOrxGPtCVLhiaZVErwqgQGxoco9hafN/nbe8/wMXcXby +YmLxjWmHicjqg90Oyg/67VM9AoRXuEo1vKaJQGOHz8kC1HUUzj7NJDDqZP9WAoqO +EKcc/Q20SKuvI+IEdkuJ3Z+mxDU8jj8JtKOHp7q+5GpTqPonVxUC5nvH0wGelrAl +uMws3IvqEgqzRuMWB6LY/1vMBxDjtMvR0c7N5HoK6UvWdefcvmAQgXy5LmUgEEWH +nb0cpAABAoGBANjoe5/76d57jAHV5XZkme5TM/Eo2W3E658p45saL2L1kBycy8uS +8KRdB4A7kwgllZQjUQSYpIOtSgiYP8c62OjXe7ysRZ6Da5fKD8bcSsC3kjoVA5Ei +cy8mz+iwQnhpVsr08T9GgGBs7RRtn44ThYrM1j+avA2ABV+Kh/4LSVlZAoGBANh7 +BjxFg+iLiwzYA14R48kgY5U8fds+BciuS2X4zPGw/vm+JxipW6Xh1xdI7BHene4A ++FBTIXoVidE3FJ+s+qbNbWj6efQu1AUg60GkUoSI9mYz0CxLE+7YI50CMK5gzt8Y +XykHG3t/E3I1x+oyIM3o0UgYTIO2FQs1O7xTTGABAoGBAJbRDyQuBG6teKvODb5E +NMOBFpyXypaIVUxV0+wESO6Fz64VV540jR27kXuX8KO5fkeCRtLrT3g/BTr0oWPl +huxe56pIHiAZQLZJyK58fX2CJio8cj53tZ8TXXxtEcqnc7GqnhNg8eIZ2r5ZepdO ++4uG4XFYJWk9mn7T07rQHjABAoGAM7U/Cgp4tYUPU6QNOd0AEbyAzNbEISgxbXvk +WpkRKvHiFXlMEWHymC7Xl6I9cQ9BneXxag5RQr3+S1DixjklLc6HwbmCg8bjsc3p +I6bCZxHn+QWLRekw+63447nwMtbA0x0ZLc2azObPmEosVE5g844W3yeerx9A64pt +mFA1QAECgYEAlFKaFgtaWWR0b4RtSvlpDJNuBaBKueG6otpyOOXbndegfugaI8jV +VWdnsRPForAuBUbg9fgO/Pfq5+x+V9u2GdchVPA16oolmDZF6CpNhxAa3hARdxD6 +MhYm0HJ2KySUhuAiChDM9UG6Fn02wz7KHRATuDzf3J9pdt1fA2XXj+I= +-----END RSA PRIVATE KEY----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/www.example.com.crt b/test/integration/connect/envoy/test-sds-server/certs/www.example.com.crt new file mode 100644 index 0000000000..3d0f178448 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/www.example.com.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIECjCCAfKgAwIBAgIJAOPsel0fAxiaMA0GCSqGSIb3DQEBCwUAMDUxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEZMBcGA1UEAwwQU0RTIFRlc3QgQ0EgQ2VydDAe +Fw0yMTA4MjQxMjMyMzhaFw0zMTA4MjIxMjMyMzhaMDQxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIDAJDQTEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtAihQ1cJlIQEx6r7nmATL8OVh0wsOlKRwBBp +0imcTfrhB4UAA8UnoPktozQe2PqAaeDEYvjmn6hxkhs+6Al5hpDPDLz+HO7FAXy+ +K29ywUtd+XQutzMS5ss5gPpGx4l1u6L+25avfG0AkUKF612RCrablLddTCGfMmiJ +pnKPP6P/7I7aM0WSUFSRC632q91bDAF75/3hVs05/catN2FhCv+xyHsgAnoI5wmI +bpbVik55aPkACI6oqWHiyYJpSCgUslLHAvgd9BdrTrerKrH7MNP/0nlMeC95DrpN +wklUKhT3Dj90LFAnUewFsppUJf1tY3yqFSvCQ3926RsN9zF5lQIDAQABox4wHDAa +BgNVHREEEzARgg93d3cuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggIBALos +qLaJEInX1V7qp0U7B4x0U+FLZLvqif+K0o6dIoMyN+pdSbLvXnWPx5qKVdJxQ3V/ +DA7AVBbeOkdqSiVO0aDmH8/XSA+cVjMpd1fSm2tZcT0Ur1fLsTnv4ZP8Oaak6TY9 +SVLNE9EiNhRG8ykYO3zshDZAhgKxeTvP//gjMGrSyEgT7fvccE/gxZcXhzEonuMc +837E85anz23/Ygo31AOIMyTqOzyR2DFof9LVuh85PP1Cl86q4Co2+ApFzipzgr4t +yvrADAPcyiDAR9fLpemorTIH9UyJPsqri0tSLOkjjk3vO43O8DIijTYcERc3FZId +/Ju8ZKa+89wfL2OzmpogEBieaWiif4adcGRJngq9C6qSk4/p5/iqvMhbdFJNIknD +88OSet0eDFIL1jy/h+ibFTzat6BAflxwCu7tn9lmP7+c8rTvRd8hXsu7n8bUZ9V4 +LVHlGdKFfrcyJ5q/yr77GPDapkyQt/MP7Y+DVNtnO/ryupf8v4JjfK42cfHbbClJ +gLTJI4PgRYtXMzWLbbrzMbeydVYiV1NLxQeB4fVHkm9AXcfb+aNG/85X3TUuIrqs +AlalaWIcNsiN8ghHRzH4Zlg7JjBnqQyPnw7xzZVVcoE0t83paYVB6TvcpqWbfb41 +PnjvHQb39zDf7iU9cVC2hQpBmyA1ctq1hubxArXP +-----END CERTIFICATE----- diff --git a/test/integration/connect/envoy/test-sds-server/certs/www.example.com.key b/test/integration/connect/envoy/test-sds-server/certs/www.example.com.key new file mode 100644 index 0000000000..848203342d --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/certs/www.example.com.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtAihQ1cJlIQEx6r7nmATL8OVh0wsOlKRwBBp0imcTfrhB4UA +A8UnoPktozQe2PqAaeDEYvjmn6hxkhs+6Al5hpDPDLz+HO7FAXy+K29ywUtd+XQu +tzMS5ss5gPpGx4l1u6L+25avfG0AkUKF612RCrablLddTCGfMmiJpnKPP6P/7I7a +M0WSUFSRC632q91bDAF75/3hVs05/catN2FhCv+xyHsgAnoI5wmIbpbVik55aPkA +CI6oqWHiyYJpSCgUslLHAvgd9BdrTrerKrH7MNP/0nlMeC95DrpNwklUKhT3Dj90 +LFAnUewFsppUJf1tY3yqFSvCQ3926RsN9zF5lQIDAQABAoIBACWVG50EIWLYgGry +HZ69LaQt7xwUYbM3I2f1xTFzMEbzFWDNeehPkus+uTE1oy7mdEImArkqfnJb5oF6 +oJIt0CmwNxjQpzeA/K1HzichF9KUGiUGBO6Hge2eBP4QwQ/MeUBXUV6jwTR3SMQK +IAy3Np0G68mK0bYf2qzaN8oAZjkBeJerROyfQ02tuXKbcnK4C/ksVOT/efEb6WHK +H6CasvodiyUdzpeL9uM//cgsMoHkA/Bw56okDkrAYVacQKqHHzZR1A7kbMa/Fbk4 +Gj8xF6CR+Ui/csyNX6pbPEynbosyTAhwZsHCKNareKbj0gpAQWzG0Cg0Rbxvyz9c +kYQzgo0CgYEA4zYJL6kuBecU2iDWxI2TvfESUUyC5c3jRPemdPzXY/n1Tv0D4dER +O7HaNOYX+Q07IZ+eRigHKkRdFxEQEXZxGC3n8XawtG7XSwe+YV1xfmj3axA4hoEf +Uky/2Ci5M8mXHiTAM2vge9BX60XbZUEywlB1WIC6iLgaynG/+qR7U9cCgYEAythR +mRoPKrdooq7x+MM+d5CNfPP7Stzz3Yby902GknLWwSD2UnhkgiZK1Rx9Y5aSAhhq +dgHqtz7aKwCqCzKEMB3uNGLSmU7WnlqDMAebyXzjybgdtyBbgDmppfj/6KP9MoxD +7pV+WQbItzAXVJLm7fgsG50ncbR01ACAA/IxsHMCgYB639Qj9EgxZQC/3haHgVEu +3VpcoRYBFVEdERjyF3KcXKcvKmGZE6lQMSSiivTX+THLQzkjE66cSxBU0yNvbjBH +NTxENz3ktjS0HOH2YcRo6nczbThEdTvtFBiD27IcsZ6J7LIMnQBvtQJ29/ZoUdQT +hdOQIz29hmLmrJF4CM+xTQKBgH/xa55vImvmHZinUjZ/KBKQWb4bwkAZF7R93xh/ +jMcoqi5M1TF1gKfa8U50Vt6O5W9u6Q+f6+VDPqbrF1ZSubAD4PCn+H/8i0B8hMyx +r+Cp5p+ggxWz0iMRu+DVKs0ZSqYsdNiy8GvE7KsnKUAOHJyy8VdFi9CbaykhW52h +kRfjAoGBAJm+ZylqeopxFigMzqe+ibqQ03y08aHqSnvH99QXBQPTlSOmgqMOz52v +jYc4GlnB+G77oaXbJ+74qVS159k6b0fQSNzN+IILq7/QNKsmXpPmbwhiRgQJOdSB +9VbSPXeEKlr70QJ7iCfGvymVo/QPj0eQfrN9JdWoRDn49QoeptV0 +-----END RSA PRIVATE KEY----- diff --git a/test/integration/connect/envoy/test-sds-server/go.mod b/test/integration/connect/envoy/test-sds-server/go.mod new file mode 100644 index 0000000000..7aa65b5815 --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/go.mod @@ -0,0 +1,9 @@ +module test-sds-server + +go 1.16 + +require ( + github.com/envoyproxy/go-control-plane v0.9.9 + github.com/hashicorp/go-hclog v0.16.2 + google.golang.org/grpc v1.40.0 +) diff --git a/test/integration/connect/envoy/test-sds-server/go.sum b/test/integration/connect/envoy/test-sds-server/go.sum new file mode 100644 index 0000000000..f4cb806dfc --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/go.sum @@ -0,0 +1,134 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed h1:OZmjad4L3H8ncOIR8rnb5MREYqG8ixi5+WbeUsquF0c= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.9 h1:vQLjymTobffN2R0F8eTqw6q7iozfRO5Z0m+/4Vw+/uA= +github.com/envoyproxy/go-control-plane v0.9.9/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/test/integration/connect/envoy/test-sds-server/sds.go b/test/integration/connect/envoy/test-sds-server/sds.go new file mode 100644 index 0000000000..30020daa3d --- /dev/null +++ b/test/integration/connect/envoy/test-sds-server/sds.go @@ -0,0 +1,162 @@ +package main + +import ( + "context" + "io/ioutil" + "net" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + secretservice "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3" + "github.com/envoyproxy/go-control-plane/pkg/cache/types" + cache "github.com/envoyproxy/go-control-plane/pkg/cache/v3" + xds "github.com/envoyproxy/go-control-plane/pkg/server/v3" + "github.com/hashicorp/go-hclog" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" +) + +const ( + sdsTypeURI = "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" +) + +func main() { + log := hclog.Default() + log.SetLevel(hclog.Trace) + + if err := run(log); err != nil { + log.Error("failed to run SDS server", "err", err) + os.Exit(1) + } +} + +func run(log hclog.Logger) error { + cache := cache.NewLinearCache(sdsTypeURI) + + addr := "0.0.0.0:1234" + if a := os.Getenv("SDS_BIND_ADDR"); a != "" { + addr = a + } + certPath := "certs" + if p := os.Getenv("SDS_CERT_PATH"); p != "" { + certPath = p + } + + if err := loadCertsFromPath(cache, log, certPath); err != nil { + return err + } + + l, err := net.Listen("tcp", addr) + if err != nil { + return err + } + defer l.Close() + log.Info("==> SDS listening", "addr", addr) + + callbacks := makeLoggerCallbacks(log) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + xdsServer := xds.NewServer(ctx, cache, callbacks) + grpcServer := grpc.NewServer() + grpclog.SetLogger(log.StandardLogger(nil)) + + secretservice.RegisterSecretDiscoveryServiceServer(grpcServer, xdsServer) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-sigs + grpcServer.Stop() + cancel() + }() + + if err := grpcServer.Serve(l); err != nil { + return err + } + + return nil +} + +func loadCertsFromPath(cache *cache.LinearCache, log hclog.Logger, dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + return err + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + if !strings.HasSuffix(entry.Name(), ".crt") { + continue + } + + certName := strings.TrimSuffix(entry.Name(), ".crt") + cert, err := ioutil.ReadFile(filepath.Join(dir, entry.Name())) + if err != nil { + return err + } + keyFile := certName + ".key" + key, err := ioutil.ReadFile(filepath.Join(dir, keyFile)) + if err != nil { + return err + } + var res tls.Secret + res.Name = certName + res.Type = &tls.Secret_TlsCertificate{ + TlsCertificate: &tls.TlsCertificate{ + CertificateChain: &core.DataSource{ + Specifier: &core.DataSource_InlineBytes{ + InlineBytes: cert, + }, + }, + PrivateKey: &core.DataSource{ + Specifier: &core.DataSource_InlineBytes{ + InlineBytes: key, + }, + }, + }, + } + + if err := cache.UpdateResource(certName, types.Resource(&res)); err != nil { + return err + } + log.Info("Loaded cert from file", "name", certName) + } + return nil +} + +func makeLoggerCallbacks(log hclog.Logger) *xds.CallbackFuncs { + return &xds.CallbackFuncs{ + + StreamOpenFunc: func(_ context.Context, id int64, addr string) error { + log.Trace("gRPC stream opened", "id", id, "addr", addr) + return nil + }, + StreamClosedFunc: func(id int64) { + log.Trace("gRPC stream closed", "id", id) + }, + StreamRequestFunc: func(id int64, req *discovery.DiscoveryRequest) error { + log.Trace("gRPC stream request", "id", id, + "node.id", req.Node.Id, + "req.typeURL", req.TypeUrl, + "req.version", req.VersionInfo, + ) + return nil + }, + StreamResponseFunc: func(id int64, req *discovery.DiscoveryRequest, resp *discovery.DiscoveryResponse) { + log.Trace("gRPC stream response", "id", id, + "resp.typeURL", resp.TypeUrl, + "resp.version", resp.VersionInfo, + ) + }, + } +}