Merge pull request #10903 from hashicorp/feature/ingress-sds

Add Support to for providing TLS certificates for Ingress listeners from an SDS source
This commit is contained in:
Paul Banks 2021-09-23 16:19:05 +01:00 committed by GitHub
commit f8412cf5fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 5072 additions and 139 deletions

3
.changelog/10903.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
connect: Add low-level feature to allow an Ingress to retrieve TLS certificates from SDS.
```

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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,
},
},
},
}
}

View File

@ -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)
})
}
}

View File

@ -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(

View File

@ -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
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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

View File

@ -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"`

View File

@ -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",
},
}

View File

@ -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"]

View File

@ -0,0 +1,3 @@
#!/bin/bash
snapshot_envoy_admin localhost:20000 ingress-gateway primary || true

View File

@ -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"
}
}
}
]
}
]
}
]
}

View File

@ -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 = <<EOF
{
"name": "sds-cluster",
"connect_timeout": "5s",
"http2_protocol_options": {},
"load_assignment": {
"cluster_name": "sds-cluster",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "127.0.0.1",
"port_value": 1234
}
}
}
}
]
}
]
}
}
EOF
}
}
}

View File

@ -0,0 +1,13 @@
#!/bin/bash
set -euo pipefail
# wait for bootstrap to apply config entries
wait_for_config_entry ingress-gateway ingress-gateway
wait_for_config_entry proxy-defaults global
register_services primary
gen_envoy_bootstrap ingress-gateway 20000 primary true
gen_envoy_bootstrap s1 19000
gen_envoy_bootstrap s2 19001

View File

@ -0,0 +1,7 @@
#!/bin/bash
export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES ingress-gateway-primary test-sds-server"
if is_set $TEST_V2_XDS; then
export SKIP_CASE="test SDS server doesn't support V2"
fi

View File

@ -0,0 +1,66 @@
#!/usr/bin/env bats
load helpers
@test "ingress proxy admin is up on :20000" {
retry_default curl -f -s localhost:20000/stats -o /dev/null
}
@test "s1 proxy admin is up on :19000" {
retry_default curl -f -s localhost:19000/stats -o /dev/null
}
@test "s2 proxy admin is up on :19001" {
retry_default curl -f -s localhost:19001/stats -o /dev/null
}
@test "s1 proxy listener should be up and have right cert" {
assert_proxy_presents_cert_uri localhost:21000 s1
}
@test "s2 proxy listener should be up and have right cert" {
assert_proxy_presents_cert_uri localhost:21001 s2
}
@test "ingress-gateway should have healthy endpoints for s1" {
assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1
}
@test "ingress-gateway should have healthy endpoints for s2" {
assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2 HEALTHY 1
}
@test "ingress should be able to connect to s1 using Host header" {
assert_expected_fortio_name s1 https://s1.ingress.consul 9999
}
@test "ingress should be able to connect to s2 using Host header" {
assert_expected_fortio_name s2 https://s2.ingress.consul 9999
}
@test "ingress should be able to connect to s1 using a user-specified Host" {
assert_expected_fortio_name s1 https://foo.example.com 9998
}
@test "ingress should serve SDS-supplied cert for wildcard service" {
# Make sure the Cert was the one SDS served and didn't just happen to have the
# right domain from Connect.
assert_cert_signed_by_ca /workdir/test-sds-server/certs/ca-root.crt \
localhost:9999 '*.ingress.consul'
}
@test "ingress should serve SDS-supplied cert for specific service" {
# Make sure the Cert was the one SDS served and didn't just happen to have the
# right domain from Connect.
assert_cert_signed_by_ca /workdir/test-sds-server/certs/ca-root.crt \
localhost:9998 foo.example.com
}
@test "ingress should serve SDS-supplied cert for second specific service on same http listener" {
# Make sure the Cert was the one SDS served and didn't just happen to have the
# right domain from Connect. This also ensures that listeners work when we've
# had to split their routing tables due to different certs for different
# hostnames.
assert_cert_signed_by_ca /workdir/test-sds-server/certs/ca-root.crt \
localhost:9998 www.example.com
}

View File

@ -100,7 +100,13 @@ function is_set {
function get_cert {
local HOSTPORT=$1
CERT=$(openssl s_client -connect $HOSTPORT -showcerts </dev/null)
local SERVER_NAME=$2
local CA_FILE=$3
local SNI_FLAG=""
if [ -n "$SERVER_NAME" ]; then
SNI_FLAG="-servername $SERVER_NAME"
fi
CERT=$(openssl s_client -connect $HOSTPORT $SNI_FLAG -showcerts </dev/null)
openssl x509 -noout -text <<< "$CERT"
}
@ -122,16 +128,34 @@ function assert_proxy_presents_cert_uri {
function assert_dnssan_in_cert {
local HOSTPORT=$1
local DNSSAN=$2
local SERVER_NAME=${3:-$DNSSAN}
CERT=$(retry_default get_cert $HOSTPORT)
CERT=$(retry_default get_cert $HOSTPORT $SERVER_NAME)
echo "WANT DNSSAN: ${DNSSAN}"
echo "WANT DNSSAN: ${DNSSAN} (SNI: ${SERVER_NAME})"
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep -Eo "DNS:${DNSSAN}"
}
function assert_cert_signed_by_ca {
local CA_FILE=$1
local HOSTPORT=$2
local DNSSAN=$3
local SERVER_NAME=${4:-$DNSSAN}
local SNI_FLAG=""
if [ -n "$SERVER_NAME" ]; then
SNI_FLAG="-servername $SERVER_NAME"
fi
CERT=$(openssl s_client -connect $HOSTPORT $SNI_FLAG -CAfile $CA_FILE -showcerts </dev/null)
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep 'Verify return code: 0 (ok)'
}
function assert_envoy_version {
local ADMINPORT=$1
run retry_default curl -f -s localhost:$ADMINPORT/server_info
@ -823,8 +847,25 @@ function get_upstream_fortio_name {
if [[ -n "${DEBUG_HEADER_VALUE}" ]]; then
extra_args="-H x-test-debug:${DEBUG_HEADER_VALUE}"
fi
run retry_default curl -v -s -f -H"Host: ${HOST}" $extra_args \
"localhost:${PORT}${PREFIX}/debug?env=dump"
# split proto if https:// is at the front of the host since the --resolve
# string needs just a bare host.
local PROTO=""
local CA_FILE=""
if [ "${HOST:0:8}" = "https://" ]; then
HOST="${HOST:8}"
PROTO="https://"
extra_args="${extra_args} --cacert /workdir/test-sds-server/certs/ca-root.crt"
fi
# We use --resolve instead of setting a Host header since we need the right
# name to be sent for SNI in some cases too.
run retry_default curl -v -s -f --resolve "${HOST}:${PORT}:127.0.0.1" $extra_args \
"${PROTO}${HOST}:${PORT}${PREFIX}/debug?env=dump"
# Useful Debugging but breaks the expectation that the value output is just
# the grep output when things don't fail
if [ "$status" != 0 ]; then
echo "GOT FORTIO OUTPUT: $output"
fi
[ "$status" == 0 ]
echo "$output" | grep -E "^FORTIO_NAME="
}
@ -836,12 +877,12 @@ function assert_expected_fortio_name {
local URL_PREFIX=${4:-""}
local DEBUG_HEADER_VALUE="${5:-""}"
GOT=$(get_upstream_fortio_name ${HOST} ${PORT} "${URL_PREFIX}" "${DEBUG_HEADER_VALUE}")
run get_upstream_fortio_name ${HOST} ${PORT} "${URL_PREFIX}" "${DEBUG_HEADER_VALUE}"
if [ "$GOT" != "FORTIO_NAME=${EXPECT_NAME}" ]; then
echo "expected name: $EXPECT_NAME, actual name: $GOT" 1>&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
}
}

View File

@ -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"
}

View File

@ -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-----

View File

@ -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-----

View File

@ -0,0 +1 @@
E3EC7A5D1F03189F

View File

@ -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-----

View File

@ -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-----

View File

@ -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

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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
)

View File

@ -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=

View File

@ -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,
)
},
}
}