Backport of Inline API Gateway TLS cert code into release/1.15.x (#16306)

* Inline API Gateway TLS cert code (#16295)

* Include secret type when building resources from config snapshot

* First pass at generating envoy secrets from api-gateway snapshot

* Update comments for xDS update order

* Add secret type + corresponding golden files to existing tests

* Initialize test helpers for testing api-gateway resource generation

* Generate golden files for new api-gateway xDS resource test

* Support ADS for TLS certificates on api-gateway

* Configure TLS on api-gateway listeners

* Inline TLS cert code

* update tests

* Add SNI support so we can have multiple certificates

* Remove commented out section from helper

* regen deep-copy

* Add tcp tls test

---------

Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>

* Fix bad merge

---------

Co-authored-by: Andrew Stucki <andrew.stucki@hashicorp.com>
Co-authored-by: Nathan Coleman <nathan.coleman@hashicorp.com>
pull/16308/head^2
hc-github-team-consul-core 2023-02-17 14:46:49 -05:00 committed by GitHub
parent 3cba165d78
commit 4920cb4a77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1808 additions and 32 deletions

View File

@ -310,6 +310,23 @@ func (o *configSnapshotAPIGateway) DeepCopy() *configSnapshotAPIGateway {
cp.Listeners[k2] = cp_Listeners_v2
}
}
if o.ListenerCertificates != nil {
cp.ListenerCertificates = make(map[IngressListenerKey][]structs.InlineCertificateConfigEntry, len(o.ListenerCertificates))
for k2, v2 := range o.ListenerCertificates {
var cp_ListenerCertificates_v2 []structs.InlineCertificateConfigEntry
if v2 != nil {
cp_ListenerCertificates_v2 = make([]structs.InlineCertificateConfigEntry, len(v2))
copy(cp_ListenerCertificates_v2, v2)
for i3 := range v2 {
{
retV := v2[i3].DeepCopy()
cp_ListenerCertificates_v2[i3] = *retV
}
}
}
cp.ListenerCertificates[k2] = cp_ListenerCertificates_v2
}
}
if o.BoundListeners != nil {
cp.BoundListeners = make(map[string]structs.BoundAPIGatewayListener, len(o.BoundListeners))
for k2, v2 := range o.BoundListeners {

View File

@ -734,6 +734,8 @@ type configSnapshotAPIGateway struct {
// Listeners is the original listener config from the api-gateway config
// entry to save us trying to pass fields through Upstreams
Listeners map[string]structs.APIGatewayListener
// this acts as an intermediary for inlining certificates
ListenerCertificates map[IngressListenerKey][]structs.InlineCertificateConfigEntry
BoundListeners map[string]structs.BoundAPIGatewayListener
}
@ -751,6 +753,9 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI
watchedUpstreamEndpoints := make(map[UpstreamID]map[string]structs.CheckServiceNodes)
watchedGatewayEndpoints := make(map[UpstreamID]map[string]structs.CheckServiceNodes)
// reset the cached certificates
c.ListenerCertificates = make(map[IngressListenerKey][]structs.InlineCertificateConfigEntry)
for name, listener := range c.Listeners {
boundListener, ok := c.BoundListeners[name]
if !ok {
@ -802,17 +807,18 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI
watchedGatewayEndpoints[id] = gatewayEndpoints
}
// Configure TLS for the ingress listener
tls, err := c.toIngressTLS()
if err != nil {
return configSnapshotIngressGateway{}, err
}
ingressListener.TLS = tls
key := IngressListenerKey{
Port: listener.Port,
Protocol: string(listener.Protocol),
}
// Configure TLS for the ingress listener
tls, err := c.toIngressTLS(key, listener, boundListener)
if err != nil {
return configSnapshotIngressGateway{}, err
}
ingressListener.TLS = tls
ingressListeners[key] = ingressListener
ingressUpstreams[key] = upstreams
}
@ -905,9 +911,25 @@ DOMAIN_LOOP:
return services, upstreams, compiled, err
}
func (c *configSnapshotAPIGateway) toIngressTLS() (*structs.GatewayTLSConfig, error) {
// TODO (t-eckert) this is dependent on future SDS work.
return &structs.GatewayTLSConfig{}, nil
func (c *configSnapshotAPIGateway) toIngressTLS(key IngressListenerKey, listener structs.APIGatewayListener, bound structs.BoundAPIGatewayListener) (*structs.GatewayTLSConfig, error) {
if len(listener.TLS.Certificates) == 0 {
return nil, nil
}
for _, certRef := range bound.Certificates {
cert, ok := c.Certificates.Get(certRef)
if !ok {
continue
}
c.ListenerCertificates[key] = append(c.ListenerCertificates[key], *cert)
}
return &structs.GatewayTLSConfig{
Enabled: true,
TLSMinVersion: listener.TLS.MinVersion,
TLSMaxVersion: listener.TLS.MaxVersion,
CipherSuites: listener.TLS.CipherSuites,
}, nil
}
type configSnapshotIngressGateway struct {

View File

@ -0,0 +1,157 @@
package proxycfg
import (
"fmt"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/consul/discoverychain"
"github.com/mitchellh/go-testing-interface"
"github.com/hashicorp/consul/agent/structs"
)
func TestConfigSnapshotAPIGateway(
t testing.T,
variation string,
nsFn func(ns *structs.NodeService),
configFn func(entry *structs.APIGatewayConfigEntry, boundEntry *structs.BoundAPIGatewayConfigEntry),
routes []structs.BoundRoute,
certificates []structs.InlineCertificateConfigEntry,
extraUpdates []UpdateEvent,
additionalEntries ...structs.ConfigEntry,
) *ConfigSnapshot {
roots, placeholderLeaf := TestCerts(t)
entry := &structs.APIGatewayConfigEntry{
Kind: structs.APIGateway,
Name: "api-gateway",
}
boundEntry := &structs.BoundAPIGatewayConfigEntry{
Kind: structs.BoundAPIGateway,
Name: "api-gateway",
}
if configFn != nil {
configFn(entry, boundEntry)
}
baseEvents := []UpdateEvent{
{
CorrelationID: rootsWatchID,
Result: roots,
},
{
CorrelationID: leafWatchID,
Result: placeholderLeaf,
},
{
CorrelationID: gatewayConfigWatchID,
Result: &structs.ConfigEntryResponse{
Entry: entry,
},
},
{
CorrelationID: gatewayConfigWatchID,
Result: &structs.ConfigEntryResponse{
Entry: boundEntry,
},
},
}
for _, route := range routes {
// Add the watch event for the route.
watch := UpdateEvent{
CorrelationID: routeConfigWatchID,
Result: &structs.ConfigEntryResponse{
Entry: route,
},
}
baseEvents = append(baseEvents, watch)
// Add the watch event for the discovery chain.
entries := []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"protocol": route.GetProtocol(),
},
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "api-gateway",
},
}
// Add a discovery chain watch event for each service.
for _, serviceName := range route.GetServiceNames() {
discoChain := UpdateEvent{
CorrelationID: fmt.Sprintf("discovery-chain:%s", UpstreamIDString("", "", serviceName.Name, &serviceName.EnterpriseMeta, "")),
Result: &structs.DiscoveryChainResponse{
Chain: discoverychain.TestCompileConfigEntries(t, serviceName.Name, "default", "default", "dc1", connect.TestClusterID+".consul", nil, entries...),
},
}
baseEvents = append(baseEvents, discoChain)
}
}
for _, certificate := range certificates {
inlineCertificate := certificate
baseEvents = append(baseEvents, UpdateEvent{
CorrelationID: inlineCertificateConfigWatchID,
Result: &structs.ConfigEntryResponse{
Entry: &inlineCertificate,
},
})
}
upstreams := structs.TestUpstreams(t)
baseEvents = testSpliceEvents(baseEvents, setupTestVariationConfigEntriesAndSnapshot(
t, variation, upstreams, additionalEntries...,
))
return testConfigSnapshotFixture(t, &structs.NodeService{
Kind: structs.ServiceKindAPIGateway,
Service: "api-gateway",
Address: "1.2.3.4",
Meta: nil,
TaggedAddresses: nil,
}, nsFn, nil, testSpliceEvents(baseEvents, extraUpdates))
}
// TestConfigSnapshotAPIGateway_NilConfigEntry is used to test when
// the update event for the config entry returns nil
// since this always happens on the first watch if it doesn't exist.
func TestConfigSnapshotAPIGateway_NilConfigEntry(
t testing.T,
) *ConfigSnapshot {
roots, _ := TestCerts(t)
baseEvents := []UpdateEvent{
{
CorrelationID: rootsWatchID,
Result: roots,
},
{
CorrelationID: gatewayConfigWatchID,
Result: &structs.ConfigEntryResponse{
Entry: nil, // The first watch on a config entry will return nil if the config entry doesn't exist.
},
},
{
CorrelationID: gatewayConfigWatchID,
Result: &structs.ConfigEntryResponse{
Entry: nil, // The first watch on a config entry will return nil if the config entry doesn't exist.
},
},
}
return testConfigSnapshotFixture(t, &structs.NodeService{
Kind: structs.ServiceKindAPIGateway,
Service: "api-gateway",
Address: "1.2.3.4",
Meta: nil,
TaggedAddresses: nil,
}, nil, nil, testSpliceEvents(baseEvents, nil))
}

View File

@ -64,6 +64,30 @@ func (e *InlineCertificateConfigEntry) Validate() error {
return nil
}
func (e *InlineCertificateConfigEntry) Hosts() ([]string, error) {
certificateBlock, _ := pem.Decode([]byte(e.Certificate))
if certificateBlock == nil {
return nil, errors.New("failed to parse certificate PEM")
}
certificate, err := x509.ParseCertificate(certificateBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
hosts := []string{certificate.Subject.CommonName}
for _, name := range certificate.DNSNames {
hosts = append(hosts, name)
}
for _, ip := range certificate.IPAddresses {
hosts = append(hosts, ip.String())
}
return hosts, nil
}
func (e *InlineCertificateConfigEntry) CanRead(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)

View File

@ -1,6 +1,7 @@
package structs
import (
"fmt"
"sort"
"time"
@ -24,6 +25,10 @@ type ResourceReference struct {
acl.EnterpriseMeta
}
func (r *ResourceReference) String() string {
return fmt.Sprintf("%s:%s/%s/%s/%s", r.Kind, r.PartitionOrDefault(), r.NamespaceOrDefault(), r.Name, r.SectionName)
}
func (r *ResourceReference) IsSame(other *ResourceReference) bool {
if r == nil && other == nil {
return true

View File

@ -466,20 +466,21 @@ func (s *Server) applyEnvoyExtensions(resources *xdscommon.IndexedResources, cfg
return nil
}
// https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#eventual-consistency-considerations
var xDSUpdateOrder = []xDSUpdateOperation{
// TODO Update comments
// 1. SDS updates (if any) can be pushed here with no harm.
{TypeUrl: xdscommon.SecretType, Upsert: true},
// 1. CDS updates (if any) must always be pushed first.
// 2. CDS updates (if any) must always be pushed before the following types.
{TypeUrl: xdscommon.ClusterType, Upsert: true},
// 2. EDS updates (if any) must arrive after CDS updates for the respective clusters.
// 3. EDS updates (if any) must arrive after CDS updates for the respective clusters.
{TypeUrl: xdscommon.EndpointType, Upsert: true},
// 3. LDS updates must arrive after corresponding CDS/EDS updates.
// 4. LDS updates must arrive after corresponding CDS/EDS updates.
{TypeUrl: xdscommon.ListenerType, Upsert: true, Remove: true},
// 4. RDS updates related to the newly added listeners must arrive after CDS/EDS/LDS updates.
// 5. RDS updates related to the newly added listeners must arrive after CDS/EDS/LDS updates.
{TypeUrl: xdscommon.RouteType, Upsert: true, Remove: true},
// 5. (NOT IMPLEMENTED YET IN CONSUL) VHDS updates (if any) related to the newly added RouteConfigurations must arrive after RDS updates.
// 6. (NOT IMPLEMENTED YET IN CONSUL) VHDS updates (if any) related to the newly added RouteConfigurations must arrive after RDS updates.
// {},
// 6. Stale CDS clusters, related EDS endpoints (ones no longer being referenced) and SDS secrets can then be removed.
// 7. Stale CDS clusters, related EDS endpoints (ones no longer being referenced) and SDS secrets can then be removed.
{TypeUrl: xdscommon.ClusterType, Remove: true},
{TypeUrl: xdscommon.EndpointType, Remove: true},
{TypeUrl: xdscommon.SecretType, Remove: true},

View File

@ -2328,6 +2328,9 @@ func makeHTTPInspectorListenerFilter() (*envoy_listener_v3.ListenerFilter, error
}
func makeSNIFilterChainMatch(sniMatches ...string) *envoy_listener_v3.FilterChainMatch {
if sniMatches == nil {
return nil
}
return &envoy_listener_v3.FilterChainMatch{
ServerNames: sniMatches,
}

View File

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/types"
)
@ -25,6 +26,15 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap
return nil, fmt.Errorf("no listener config found for listener on proto/port %s/%d", listenerKey.Protocol, listenerKey.Port)
}
var isAPIGatewayWithTLS bool
var certs []structs.InlineCertificateConfigEntry
if cfgSnap.APIGateway.ListenerCertificates != nil {
certs = cfgSnap.APIGateway.ListenerCertificates[listenerKey]
}
if certs != nil {
isAPIGatewayWithTLS = true
}
tlsContext, err := makeDownstreamTLSContextFromSnapshotListenerConfig(cfgSnap, listenerCfg)
if err != nil {
return nil, err
@ -72,6 +82,7 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap
logger: s.Logger,
}
l := makeListener(opts)
filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{
accessLogs: &cfgSnap.Proxy.AccessLogs,
routeName: uid.EnvoyID(),
@ -87,8 +98,30 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap
l.FilterChains = []*envoy_listener_v3.FilterChain{
filterChain,
}
resources = append(resources, l)
if isAPIGatewayWithTLS {
// construct SNI filter chains
l.FilterChains, err = makeInlineOverrideFilterChains(cfgSnap, cfgSnap.IngressGateway.TLSConfig, listenerKey, listenerFilterOpts{
useRDS: useRDS,
protocol: listenerKey.Protocol,
routeName: listenerKey.RouteName(),
cluster: clusterName,
statPrefix: "ingress_upstream_",
accessLogs: &cfgSnap.Proxy.AccessLogs,
logger: s.Logger,
}, certs)
if err != nil {
return nil, err
}
// add the tls inspector to do SNI introspection
tlsInspector, err := makeTLSInspectorListenerFilter()
if err != nil {
return nil, err
}
l.ListenerFilters = []*envoy_listener_v3.ListenerFilter{tlsInspector}
}
resources = append(resources, l)
} else {
// If multiple upstreams share this port, make a special listener for the protocol.
listenerOpts := makeListenerOpts{
@ -121,6 +154,13 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap
return nil, err
}
if isAPIGatewayWithTLS {
sniFilterChains, err = makeInlineOverrideFilterChains(cfgSnap, cfgSnap.IngressGateway.TLSConfig, listenerKey, filterOpts, certs)
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()
@ -134,7 +174,7 @@ func (s *ResourceGenerator) makeIngressGatewayListeners(address string, cfgSnap
// 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) {
if len(sniFilterChains) < len(upstreams) && !isAPIGatewayWithTLS {
defaultFilter, err := makeListenerFilter(filterOpts)
if err != nil {
return nil, err
@ -379,10 +419,133 @@ func makeSDSOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
return chains, nil
}
// when we have multiple certificates on a single listener, we need
// to duplicate the filter chains with multiple TLS contexts
func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
tlsCfg structs.GatewayTLSConfig,
listenerKey proxycfg.IngressListenerKey,
filterOpts listenerFilterOpts,
certs []structs.InlineCertificateConfigEntry) ([]*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
constructChain := func(name string, hosts []string, tlsContext *envoy_tls_v3.CommonTlsContext) error {
filterOpts.filterName = name
filter, err := makeListenerFilter(filterOpts)
if err != nil {
return err
}
// Configure alpn protocols on TLSContext
tlsContext.AlpnProtocols = getAlpnProtocols(listenerCfg.Protocol)
transportSocket, err := makeDownstreamTLSTransportSocket(&envoy_tls_v3.DownstreamTlsContext{
CommonTlsContext: tlsContext,
RequireClientCertificate: &wrapperspb.BoolValue{Value: false},
})
if err != nil {
return err
}
chains = append(chains, &envoy_listener_v3.FilterChain{
FilterChainMatch: makeSNIFilterChainMatch(hosts...),
Filters: []*envoy_listener_v3.Filter{
filter,
},
TransportSocket: transportSocket,
})
return nil
}
multipleCerts := len(certs) > 1
allCertHosts := map[string]struct{}{}
overlappingHosts := map[string]struct{}{}
if multipleCerts {
// we only need to prune out overlapping hosts if we have more than
// one certificate
for _, cert := range certs {
hosts, err := cert.Hosts()
if err != nil {
return nil, fmt.Errorf("unable to parse hosts from x509 certificate: %v", hosts)
}
for _, host := range hosts {
if _, ok := allCertHosts[host]; ok {
overlappingHosts[host] = struct{}{}
}
allCertHosts[host] = struct{}{}
}
}
}
for _, cert := range certs {
var hosts []string
// if we only have one cert, we just use it for all ingress
if multipleCerts {
// otherwise, we need an SNI per cert and to fallback to our ingress
// gateway certificate signed by our Consul CA
certHosts, err := cert.Hosts()
if err != nil {
return nil, fmt.Errorf("unable to parse hosts from x509 certificate: %v", hosts)
}
// filter out any overlapping hosts so we don't have collisions in our filter chains
for _, host := range certHosts {
if _, ok := overlappingHosts[host]; !ok {
hosts = append(hosts, host)
}
}
if len(hosts) == 0 {
// all of our hosts are overlapping, so we just skip this filter and it'll be
// handled by the default filter chain
continue
}
}
if err := constructChain(cert.Name, hosts, makeInlineTLSContextFromGatewayTLSConfig(tlsCfg, cert)); err != nil {
return nil, err
}
}
if multipleCerts {
// if we have more than one cert, add a default handler that uses the leaf cert from connect
if err := constructChain("default", nil, makeCommonTLSContext(cfgSnap.Leaf(), cfgSnap.RootPEMs(), makeTLSParametersFromGatewayTLSConfig(tlsCfg))); err != nil {
return nil, err
}
}
return chains, nil
}
func makeTLSParametersFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *envoy_tls_v3.TlsParameters {
return makeTLSParametersFromTLSConfig(tlsCfg.TLSMinVersion, tlsCfg.TLSMaxVersion, tlsCfg.CipherSuites)
}
func makeInlineTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig, cert structs.InlineCertificateConfigEntry) *envoy_tls_v3.CommonTlsContext {
return &envoy_tls_v3.CommonTlsContext{
TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg),
TlsCertificates: []*envoy_tls_v3.TlsCertificate{{
CertificateChain: &envoy_core_v3.DataSource{
Specifier: &envoy_core_v3.DataSource_InlineString{
InlineString: lib.EnsureTrailingNewline(cert.Certificate),
},
},
PrivateKey: &envoy_core_v3.DataSource{
Specifier: &envoy_core_v3.DataSource_InlineString{
InlineString: lib.EnsureTrailingNewline(cert.PrivateKey),
},
},
}},
}
}
func makeCommonTLSContextFromGatewayTLSConfig(tlsCfg structs.GatewayTLSConfig) *envoy_tls_v3.CommonTlsContext {
return &envoy_tls_v3.CommonTlsContext{
TlsParams: makeTLSParametersFromGatewayTLSConfig(tlsCfg),
@ -396,6 +559,7 @@ func makeCommonTLSContextFromGatewayServiceTLSConfig(tlsCfg structs.GatewayServi
TlsCertificateSdsSecretConfigs: makeTLSCertificateSdsSecretConfigsFromSDS(*tlsCfg.SDS),
}
}
func makeTLSCertificateSdsSecretConfigsFromSDS(sdsCfg structs.GatewayTLSSDSConfig) []*envoy_tls_v3.SdsSecretConfig {
return []*envoy_tls_v3.SdsSecretConfig{
{

View File

@ -527,6 +527,210 @@ func TestListenersFromSnapshot(t *testing.T) {
}, nil)
},
},
{
name: "api-gateway",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, nil, nil, nil, nil)
},
},
{
name: "api-gateway-nil-config-entry",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway_NilConfigEntry(t)
},
},
{
name: "api-gateway-tcp-listener",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
entry.Listeners = []structs.APIGatewayListener{
{
Name: "listener",
Protocol: structs.ListenerProtocolTCP,
Port: 8080,
},
}
bound.Listeners = []structs.BoundAPIGatewayListener{
{
Name: "listener",
},
}
}, nil, nil, nil)
},
},
{
name: "api-gateway-tcp-listener-with-tcp-route",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
entry.Listeners = []structs.APIGatewayListener{
{
Name: "listener",
Protocol: structs.ListenerProtocolTCP,
Port: 8080,
},
}
bound.Listeners = []structs.BoundAPIGatewayListener{
{
Name: "listener",
Routes: []structs.ResourceReference{
{
Name: "tcp-route",
Kind: structs.TCPRoute,
},
},
},
}
}, []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "tcp-route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Kind: structs.APIGateway,
Name: "api-gateway",
},
},
Services: []structs.TCPService{
{Name: "tcp-service"},
},
},
}, nil, nil)
},
},
{
name: "api-gateway-http-listener",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
entry.Listeners = []structs.APIGatewayListener{
{
Name: "listener",
Protocol: structs.ListenerProtocolHTTP,
Port: 8080,
},
}
bound.Listeners = []structs.BoundAPIGatewayListener{
{
Name: "listener",
Routes: []structs.ResourceReference{},
},
}
}, nil, nil, nil)
},
},
{
name: "api-gateway-http-listener-with-http-route",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
entry.Listeners = []structs.APIGatewayListener{
{
Name: "listener",
Protocol: structs.ListenerProtocolHTTP,
Port: 8080,
},
}
bound.Listeners = []structs.BoundAPIGatewayListener{
{
Name: "listener",
Routes: []structs.ResourceReference{
{
Name: "http-route",
Kind: structs.HTTPRoute,
},
},
},
}
}, []structs.BoundRoute{
&structs.HTTPRouteConfigEntry{
Name: "http-route",
Kind: structs.HTTPRoute,
Parents: []structs.ResourceReference{
{
Kind: structs.APIGateway,
Name: "api-gateway",
},
},
Rules: []structs.HTTPRouteRule{
{
Services: []structs.HTTPService{
{Name: "http-service"},
},
},
},
},
}, nil, nil)
},
},
{
name: "api-gateway-tcp-listener-with-tcp-and-http-route",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
entry.Listeners = []structs.APIGatewayListener{
{
Name: "listener-tcp",
Protocol: structs.ListenerProtocolTCP,
Port: 8080,
},
{
Name: "listener-http",
Protocol: structs.ListenerProtocolHTTP,
Port: 8081,
},
}
bound.Listeners = []structs.BoundAPIGatewayListener{
{
Name: "listener-tcp",
Routes: []structs.ResourceReference{
{
Name: "tcp-route",
Kind: structs.TCPRoute,
},
},
},
{
Name: "listener-http",
Routes: []structs.ResourceReference{
{
Name: "http-route",
Kind: structs.HTTPRoute,
},
},
},
}
}, []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Name: "tcp-route",
Kind: structs.TCPRoute,
Parents: []structs.ResourceReference{
{
Kind: structs.APIGateway,
Name: "api-gateway",
},
},
Services: []structs.TCPService{
{Name: "tcp-service"},
},
},
&structs.HTTPRouteConfigEntry{
Name: "http-route",
Kind: structs.HTTPRoute,
Parents: []structs.ResourceReference{
{
Kind: structs.APIGateway,
Name: "api-gateway",
},
},
Rules: []structs.HTTPRouteRule{
{
Services: []structs.HTTPService{
{Name: "http-service"},
},
},
},
},
}, nil, nil)
},
},
{
name: "ingress-gateway",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {

View File

@ -35,8 +35,7 @@ func NewResourceGenerator(
func (g *ResourceGenerator) AllResourcesFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot) (map[string][]proto.Message, error) {
all := make(map[string][]proto.Message)
// TODO Add xdscommon.SecretType
for _, typeUrl := range []string{xdscommon.ListenerType, xdscommon.RouteType, xdscommon.ClusterType, xdscommon.EndpointType} {
for _, typeUrl := range []string{xdscommon.ListenerType, xdscommon.RouteType, xdscommon.ClusterType, xdscommon.EndpointType, xdscommon.SecretType} {
res, err := g.resourcesFromSnapshot(typeUrl, cfgSnap)
if err != nil {
return nil, fmt.Errorf("failed to generate xDS resources for %q: %v", typeUrl, err)

View File

@ -9,6 +9,8 @@ import (
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/hashicorp/consul/agent/xds/testcommon"
"github.com/hashicorp/consul/envoyextensions/xdscommon"
@ -25,6 +27,7 @@ var testTypeUrlToPrettyName = map[string]string{
xdscommon.RouteType: "routes",
xdscommon.ClusterType: "clusters",
xdscommon.EndpointType: "endpoints",
xdscommon.SecretType: "secrets",
}
type goldenTestCase struct {
@ -60,7 +63,7 @@ func TestAllResourcesFromSnapshot(t *testing.T) {
// We need to replace the TLS certs with deterministic ones to make golden
// files workable. Note we don't update these otherwise they'd change
// golder files for every test case and so not be any use!
// golden files for every test case and so not be any use!
testcommon.SetupTLSRootsAndLeaf(t, snap)
if tt.setup != nil {
@ -82,6 +85,7 @@ func TestAllResourcesFromSnapshot(t *testing.T) {
xdscommon.RouteType,
xdscommon.ClusterType,
xdscommon.EndpointType,
xdscommon.SecretType,
}
require.Len(t, resources, len(typeUrls))
@ -101,6 +105,8 @@ func TestAllResourcesFromSnapshot(t *testing.T) {
return items[i].(*envoy_cluster_v3.Cluster).Name < items[j].(*envoy_cluster_v3.Cluster).Name
case xdscommon.EndpointType:
return items[i].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName < items[j].(*envoy_endpoint_v3.ClusterLoadAssignment).ClusterName
case xdscommon.SecretType:
return items[i].(*envoy_tls_v3.Secret).Name < items[j].(*envoy_tls_v3.Secret).Name
default:
panic("not possible")
}
@ -165,6 +171,7 @@ func TestAllResourcesFromSnapshot(t *testing.T) {
tests = append(tests, getMeshGatewayPeeringGoldenTestCases()...)
tests = append(tests, getTrafficControlPeeringGoldenTestCases()...)
tests = append(tests, getEnterpriseGoldenTestCases()...)
tests = append(tests, getAPIGatewayGoldenTestCases()...)
latestEnvoyVersion := xdscommon.EnvoyVersions[0]
for _, envoyVersion := range xdscommon.EnvoyVersions {
@ -256,3 +263,100 @@ func getTrafficControlPeeringGoldenTestCases() []goldenTestCase {
},
}
}
const (
gatewayTestPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ
httBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo
NvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC
yYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409g9X5VU88/Bmmrz4cMyxce86Kc2ug
5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftrXOvuCbO5IBRHMOBHiHTZ4rtGuhMa
Ir21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+WmQIDAQABAoIBACYvceUzp2MK4gYA
GWPOP2uKbBdM0l+hHeNV0WAM+dHMfmMuL4pkT36ucqt0ySOLjw6rQyOZG5nmA6t9
sv0g4ae2eCMlyDIeNi1Yavu4Wt6YX4cTXbQKThm83C6W2X9THKbauBbxD621bsDK
7PhiGPN60yPue7YwFQAPqqD4YaK+s22HFIzk9gwM/rkvAUNwRv7SyHMiFe4Igc1C
Eev7iHWzvj5Heoz6XfF+XNF9DU+TieSUAdjd56VyUb8XL4+uBTOhHwLiXvAmfaMR
HvpcxeKnYZusS6NaOxcUHiJnsLNWrxmJj9WEGgQzuLxcLjTe4vVmELVZD8t3QUKj
PAxu8tUCgYEA7KIWVn9dfVpokReorFym+J8FzLwSktP9RZYEMonJo00i8aii3K9s
u/aSwRWQSCzmON1ZcxZzWhwQF9usz6kGCk//9+4hlVW90GtNK0RD+j7sp4aT2JI8
9eLEjTG+xSXa7XWe98QncjjL9lu/yrRncSTxHs13q/XP198nn2aYuQ8CgYEA2Dnt
sRBzv0fFEvzzFv7G/5f85mouN38TUYvxNRTjBLCXl9DeKjDkOVZ2b6qlfQnYXIru
H+W+v+AZEb6fySXc8FRab7lkgTMrwE+aeI4rkW7asVwtclv01QJ5wMnyT84AgDD/
Dgt/RThFaHgtU9TW5GOZveL+l9fVPn7vKFdTJdcCgYEArJ99zjHxwJ1whNAOk1av
09UmRPm6TvRo4heTDk8oEoIWCNatoHI0z1YMLuENNSnT9Q280FFDayvnrY/qnD7A
kktT/sjwJOG8q8trKzIMqQS4XWm2dxoPcIyyOBJfCbEY6XuRsUuePxwh5qF942EB
yS9a2s6nC4Ix0lgPrqAIr48CgYBgS/Q6riwOXSU8nqCYdiEkBYlhCJrKpnJxF9T1
ofa0yPzKZP/8ZEfP7VzTwHjxJehQ1qLUW9pG08P2biH1UEKEWdzo8vT6wVJT1F/k
HtTycR8+a+Hlk2SHVRHqNUYQGpuIe8mrdJ1as4Pd0d/F/P0zO9Rlh+mAsGPM8HUM
T0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj
nZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX
kHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/
-----END RSA PRIVATE KEY-----`
gatewayTestCertificate = `-----BEGIN CERTIFICATE-----
MIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
UzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB
ptqOdam2ch4BHQGhNhX/MrDwwuZQhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2
jQlhqTodElkbsd5vWY8R/bxJWQSoNvVE12TlzECxGpJEiHt4W0r8pGffk+rvplji
UyCfnT1kGF3znOSjK1hRMTn6RKWCyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409
g9X5VU88/Bmmrz4cMyxce86Kc2ug5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftr
XOvuCbO5IBRHMOBHiHTZ4rtGuhMaIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+W
mQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBfCqoUIdPf/HGSbOorPyZWbyizNtHJ
GL7x9cAeIYxpI5Y/WcO1o5v94lvrgm3FNfJoGKbV66+JxOge731FrfMpHplhar1Z
RahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK
NtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO
qwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww
AAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r
-----END CERTIFICATE-----`
)
func getAPIGatewayGoldenTestCases() []goldenTestCase {
return []goldenTestCase{
{
name: "api-gateway-with-tcp-route-and-inline-certificate",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshotAPIGateway(t, "default", nil, func(entry *structs.APIGatewayConfigEntry, bound *structs.BoundAPIGatewayConfigEntry) {
entry.Listeners = []structs.APIGatewayListener{
{
Name: "listener",
Protocol: structs.ListenerProtocolTCP,
Port: 8080,
TLS: structs.APIGatewayTLSConfiguration{
Certificates: []structs.ResourceReference{{
Kind: structs.InlineCertificate,
Name: "certificate",
}},
},
},
}
bound.Listeners = []structs.BoundAPIGatewayListener{
{
Name: "listener",
Certificates: []structs.ResourceReference{{
Kind: structs.InlineCertificate,
Name: "certificate",
}},
Routes: []structs.ResourceReference{{
Kind: structs.TCPRoute,
Name: "route",
}},
},
}
}, []structs.BoundRoute{
&structs.TCPRouteConfigEntry{
Kind: structs.TCPRoute,
Name: "route",
Services: []structs.TCPService{{
Name: "service",
}},
},
}, []structs.InlineCertificateConfigEntry{{
Kind: structs.InlineCertificate,
Name: "certificate",
PrivateKey: gatewayTestPrivateKey,
Certificate: gatewayTestCertificate,
}}, nil)
},
},
}
}

View File

@ -21,18 +21,10 @@ func (s *ResourceGenerator) secretsFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot
case structs.ServiceKindConnectProxy,
structs.ServiceKindTerminatingGateway,
structs.ServiceKindMeshGateway,
structs.ServiceKindIngressGateway:
structs.ServiceKindIngressGateway,
structs.ServiceKindAPIGateway:
return nil, nil
// Only API gateways utilize secrets
case structs.ServiceKindAPIGateway:
return s.secretsFromSnapshotAPIGateway(cfgSnap)
default:
return nil, fmt.Errorf("Invalid service kind: %v", cfgSnap.Kind)
}
}
func (s *ResourceGenerator) secretsFromSnapshotAPIGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
var res []proto.Message
// TODO
return res, nil
}

View File

@ -22,6 +22,9 @@ func SetupTLSRootsAndLeaf(t *testing.T, snap *proxycfg.ConfigSnapshot) {
case structs.ServiceKindMeshGateway:
snap.MeshGateway.Leaf.CertPEM = loadTestResource(t, "test-leaf-cert")
snap.MeshGateway.Leaf.PrivateKeyPEM = loadTestResource(t, "test-leaf-key")
case structs.ServiceKindAPIGateway:
snap.APIGateway.Leaf.CertPEM = loadTestResource(t, "test-leaf-cert")
snap.APIGateway.Leaf.PrivateKeyPEM = loadTestResource(t, "test-leaf-key")
}
}
if snap.Roots != nil {

View File

@ -0,0 +1,55 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"altStatName": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"connectTimeout": "5s",
"circuitBreakers": {},
"outlierDetection": {},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"commonTlsContext": {
"tlsParams": {},
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
}
}
],
"validationContext": {
"trustedCa": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
},
"matchSubjectAltNames": [
{
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/service"
}
]
}
},
"sni": "service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
}
],
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"nonce": "00000001"
}

View File

@ -0,0 +1,49 @@
{
"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",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
}
}
],
"tracing": {
"randomSampling": {}
}
}
}
]
}
],
"trafficDirection": "OUTBOUND"
}
],
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,74 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "http:1.2.3.4:8081",
"address": {
"socketAddress": {
"address": "1.2.3.4",
"portValue": 8081
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.filters.network.http_connection_manager",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"statPrefix": "ingress_upstream_8081",
"rds": {
"configSource": {
"ads": {},
"resourceApiVersion": "V3"
},
"routeConfigName": "8081"
},
"httpFilters": [
{
"name": "envoy.filters.http.router",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
}
}
],
"tracing": {
"randomSampling": {}
}
}
}
]
}
],
"trafficDirection": "OUTBOUND"
},
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "tcp-service: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.tcp-service.default.default.dc1",
"cluster": "tcp-service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"trafficDirection": "OUTBOUND"
}
],
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,32 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "tcp-service: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.tcp-service.default.default.dc1",
"cluster": "tcp-service.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
}
],
"trafficDirection": "OUTBOUND"
}
],
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,60 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "service: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": "ingress_upstream_certificate",
"cluster": "service.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": {},
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV\nUzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB\nptqOdam2ch4BHQGhNhX/MrDwwuZQhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2\njQlhqTodElkbsd5vWY8R/bxJWQSoNvVE12TlzECxGpJEiHt4W0r8pGffk+rvplji\nUyCfnT1kGF3znOSjK1hRMTn6RKWCyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409\ng9X5VU88/Bmmrz4cMyxce86Kc2ug5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftr\nXOvuCbO5IBRHMOBHiHTZ4rtGuhMaIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+W\nmQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBfCqoUIdPf/HGSbOorPyZWbyizNtHJ\nGL7x9cAeIYxpI5Y/WcO1o5v94lvrgm3FNfJoGKbV66+JxOge731FrfMpHplhar1Z\nRahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK\nNtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO\nqwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww\nAAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ\nhttBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo\nNvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC\nyYaBvXQiB4SGilfLgJcEpOJKtISIxmZ+S409g9X5VU88/Bmmrz4cMyxce86Kc2ug\n5/MOv0CjWDJwlrv8njneV2zvraQ61DDwQftrXOvuCbO5IBRHMOBHiHTZ4rtGuhMa\nIr21V4vb6n8c4YzXiFvhUYcyX7rltGZzVd+WmQIDAQABAoIBACYvceUzp2MK4gYA\nGWPOP2uKbBdM0l+hHeNV0WAM+dHMfmMuL4pkT36ucqt0ySOLjw6rQyOZG5nmA6t9\nsv0g4ae2eCMlyDIeNi1Yavu4Wt6YX4cTXbQKThm83C6W2X9THKbauBbxD621bsDK\n7PhiGPN60yPue7YwFQAPqqD4YaK+s22HFIzk9gwM/rkvAUNwRv7SyHMiFe4Igc1C\nEev7iHWzvj5Heoz6XfF+XNF9DU+TieSUAdjd56VyUb8XL4+uBTOhHwLiXvAmfaMR\nHvpcxeKnYZusS6NaOxcUHiJnsLNWrxmJj9WEGgQzuLxcLjTe4vVmELVZD8t3QUKj\nPAxu8tUCgYEA7KIWVn9dfVpokReorFym+J8FzLwSktP9RZYEMonJo00i8aii3K9s\nu/aSwRWQSCzmON1ZcxZzWhwQF9usz6kGCk//9+4hlVW90GtNK0RD+j7sp4aT2JI8\n9eLEjTG+xSXa7XWe98QncjjL9lu/yrRncSTxHs13q/XP198nn2aYuQ8CgYEA2Dnt\nsRBzv0fFEvzzFv7G/5f85mouN38TUYvxNRTjBLCXl9DeKjDkOVZ2b6qlfQnYXIru\nH+W+v+AZEb6fySXc8FRab7lkgTMrwE+aeI4rkW7asVwtclv01QJ5wMnyT84AgDD/\nDgt/RThFaHgtU9TW5GOZveL+l9fVPn7vKFdTJdcCgYEArJ99zjHxwJ1whNAOk1av\n09UmRPm6TvRo4heTDk8oEoIWCNatoHI0z1YMLuENNSnT9Q280FFDayvnrY/qnD7A\nkktT/sjwJOG8q8trKzIMqQS4XWm2dxoPcIyyOBJfCbEY6XuRsUuePxwh5qF942EB\nyS9a2s6nC4Ix0lgPrqAIr48CgYBgS/Q6riwOXSU8nqCYdiEkBYlhCJrKpnJxF9T1\nofa0yPzKZP/8ZEfP7VzTwHjxJehQ1qLUW9pG08P2biH1UEKEWdzo8vT6wVJT1F/k\nHtTycR8+a+Hlk2SHVRHqNUYQGpuIe8mrdJ1as4Pd0d/F/P0zO9Rlh+mAsGPM8HUM\nT0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj\nnZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX\nkHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/\n-----END RSA PRIVATE KEY-----\n"
}
}
]
},
"requireClientCertificate": false
}
}
}
],
"listenerFilters": [
{
"name": "envoy.filters.listener.tls_inspector",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector"
}
}
],
"trafficDirection": "OUTBOUND"
}
],
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

@ -0,0 +1,5 @@
{
"versionInfo": "00000001",
"typeUrl": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
"nonce": "00000001"
}

View File

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

View File

@ -0,0 +1,4 @@
services {
name = "api-gateway"
kind = "api-gateway"
}

View File

@ -0,0 +1,287 @@
#!/bin/bash
set -euo pipefail
upsert_config_entry primary '
kind = "api-gateway"
name = "api-gateway"
listeners = [
{
name = "listener-one"
port = 9999
protocol = "http"
tls = {
certificates = [
{
kind = "inline-certificate"
name = "host-consul-example"
}
]
}
},
{
name = "listener-two"
port = 9998
protocol = "http"
tls = {
certificates = [
{
kind = "inline-certificate"
name = "host-consul-example"
},
{
kind = "inline-certificate"
name = "also-host-consul-example"
},
{
kind = "inline-certificate"
name = "other-consul-example"
}
]
}
}
]
'
upsert_config_entry primary '
kind = "inline-certificate"
name = "host-consul-example"
private_key = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg
5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F
eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga
9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv
OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz
9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67
yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7
OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs
qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo
Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5
LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6
Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg
llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn
gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ
eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA
hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn
orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN
JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC
2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s
1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn
IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS
2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq
wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe
RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK
+QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ==
-----END RSA PRIVATE KEY-----
EOF
certificate = <<EOF
-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD
VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc
MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq
oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P
BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4
GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH
bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6
8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N
L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc
U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi
SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq
MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq
J3TrQE3YVrL4D9xnklT86WDnZKApJg==
-----END CERTIFICATE-----
EOF
'
upsert_config_entry primary '
kind = "inline-certificate"
name = "also-host-consul-example"
private_key = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg
5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F
eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga
9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv
OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz
9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67
yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7
OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs
qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo
Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5
LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6
Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg
llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn
gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ
eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA
hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn
orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN
JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC
2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s
1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn
IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS
2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq
wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe
RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK
+QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ==
-----END RSA PRIVATE KEY-----
EOF
certificate = <<EOF
-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD
VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc
MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq
oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P
BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4
GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH
bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6
8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N
L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc
U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi
SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq
MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq
J3TrQE3YVrL4D9xnklT86WDnZKApJg==
-----END CERTIFICATE-----
EOF
'
upsert_config_entry primary '
kind = "inline-certificate"
name = "other-consul-example"
private_key = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7Qgf93OZYlpM9UvfbFBUeK+udCTfy+yRbTKYgti5qKyZQwnT
7GtxpNxWWNT7ecqGZJb3BxhIVjTYZwZYB1zDzHzE7zTVydrTbRP4MhaJhqWNdPdq
Ctth9QsX2r6KFPogvUPaYVrQBbXHoGTVcCZlFJOlEGGnuIU2cJcZEryxJKv3mB4P
FpHKP/YXy3qQb4A0+REGI93atGCxhCO5Mu5lgG+x54nKCdIXy3LVAyDqSjPuR01s
+ifdDkYUfvVY3DOb0bcNaMwi752GkfPuuxsKeHAsHmWHWOxkFiQAy3eSLJjSDghF
66JVZ6wx74Awb8NBm9nmYTbnZAUn3Z0tN6jlFwIDAQABAoIBAGh8EWNJ8M4bEht7
A5TCYEoG3zbRXlmNAZoKGJJtKIIC+1hCx8lKn4DVo7ZqxCOus8k5htD40kI170KS
2FD+gkzsnv724lqlfFdz2w9xQdQ5u/5YZcU9aZPT/QLuxP10OORVObl6h4JM3B+G
81MJibslTjjHY2CCUDoXUPUiek+4KTI6BYn9PL+ReCaH9qv85hIWvkgbwU2f9i3n
T8MysgDBNoKbVAqsH5Hq/zFkW5iad/zpgW+REgYK/gjBfUNEsRusm1GYim4XJtZf
SmXBtdYhk8kDBrMk1mNdZeMXMfl0VwZaHca8QmqrgDm//grUYKLWZO/PbDoI5c+w
/MTN6tkCgYEA956LZJ+Ts9g9k+jLcUV1hOAmR6cAL5tjEkBPVC5FC+5o42qOgI58
5NQjmVCM+mD9w+deAXkT/IDXzV28Y02lZ1pyOXTN+ChE2ZBym+yu2zyLj45lJzx5
h+lC7aeCBIGyjP/t6wFoFdCaluAsWpU4SY1DWtExcCLvXs0+zDiYjZMCgYEA9Q3Y
mlrAvdzb61x8BAEPpBTMSu0a+jo8mkJoUFS4ndFXEl5yPzqf6IUrFgzqgVruQg7k
dIZK5uDqNI7sXDXulpPFHbJdpAZq3Dlkv0VOvcgRycuWgDAyOngCSRN8JC7u4P5j
54xgAYQSCC/kPolFMHntXQLjzPevJNrkKnJfXO0CgYEAyCf5ByJSs0o1JE1Fvc7m
mrzRVJPye4kAQS2IskQgfe9+C24DuHj1DcdI61IIUw95sRRhkZE8jZvcVN3TPPXz
oKKkuDrpjxGF7dNsQQvFn+PF8AmrTFb+6dSszAvd9iScnor110OwzglsHE8iqyn5
cMLmUg/NBZbHpPsFKvEIp08CgYAmQ1Az4cHAo5CvMlSm52eCzkCL3nPc6GT4DTBu
gpwFAF/hHWAnYUcArnJo0gF3yzPympKvYxyk6i+Hn11mlIE5f79CgMxARURAOLHz
b6X42hl08dYBFAVzvbNVp7Y1jCJ+fRoqWG/RLMcIAjpYTWTBSfh3EnFxWqc9UPRZ
cFxVjQKBgQDUXDww+jXXTFlYkbhgNOtQS57M8jKWHnUxEc7IXOjF/0WGEEBAc2uc
fFW5dnPyS3D6vecWA8KB8LKcXey3ZcDZnVkahSgYhYHrGJrANzhfM3yli+lSFWsT
r295+0za3P3LVL4aKS+ok9oYZaVyMe/vlCMrIS+sh6SEc4+v0qNa3g==
-----END RSA PRIVATE KEY-----
EOF
certificate = <<EOF
-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC2H6+PYz23xDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQwwCgYD
VQQLDANGb28xHTAbBgNVBAMMFG90aGVyLmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTM0OVoXDTI4MDIxNjAyMTM0OVowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRm9vMR0w
GwYDVQQDDBRvdGhlci5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAO0IH/dzmWJaTPVL32xQVHivrnQk38vskW0ymILYuaismUMJ
0+xrcaTcVljU+3nKhmSW9wcYSFY02GcGWAdcw8x8xO801cna020T+DIWiYaljXT3
agrbYfULF9q+ihT6IL1D2mFa0AW1x6Bk1XAmZRSTpRBhp7iFNnCXGRK8sSSr95ge
DxaRyj/2F8t6kG+ANPkRBiPd2rRgsYQjuTLuZYBvseeJygnSF8ty1QMg6koz7kdN
bPon3Q5GFH71WNwzm9G3DWjMIu+dhpHz7rsbCnhwLB5lh1jsZBYkAMt3kiyY0g4I
ReuiVWesMe+AMG/DQZvZ5mE252QFJ92dLTeo5RcCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAijm6blixjl+pMRAj7EajoPjU+GqhooZayJrvdwvofwcPxQYpkPuh7Uc6
l2z494b75cRzMw7wS+iW/ad8NYrfw1JwHMsUfncxs5LDO5GsKl9Krg/39goDl3wC
ywTcl00y+FMYfldNPjKDLunENmn+yPa2pKuBVQ0yOKALp+oUeJFVzRNPV5fohlBi
HjypkO0KaVmCG6P01cqCgVkNzxnX9qQYP3YXX1yt5iOcI7QcoOa5WnRhOuD8WqJ1
v3AZGYNvKyXf9E5nD0y2Cmz6t1awjFjzMlXMx6AdHrjWqxtHhYQ1xz4P4NfzK27m
cCtURSzXMgcrSeZLepBfdICf+0/0+Q==
-----END CERTIFICATE-----
EOF
'
upsert_config_entry primary '
Kind = "proxy-defaults"
Name = "global"
Config {
protocol = "http"
}
'
upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-one"
rules = [
{
services = [
{
name = "s1"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-one"
}
]
'
upsert_config_entry primary '
kind = "http-route"
name = "api-gateway-route-two"
rules = [
{
services = [
{
name = "s2"
}
]
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-two"
}
]
'
upsert_config_entry primary '
kind = "service-intentions"
name = "s1"
sources {
name = "api-gateway"
action = "allow"
}
'
upsert_config_entry primary '
kind = "service-intentions"
name = "s2"
sources {
name = "api-gateway"
action = "deny"
}
'
register_services primary
gen_envoy_bootstrap api-gateway 20000 primary true
gen_envoy_bootstrap s1 19000
gen_envoy_bootstrap s2 19001

View File

@ -0,0 +1,3 @@
#!/bin/bash
export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES api-gateway-primary"

View File

@ -0,0 +1,48 @@
#!/usr/bin/env bats
load helpers
@test "api gateway proxy admin is up on :20000" {
retry_default curl -f -s localhost:20000/stats -o /dev/null
}
@test "api gateway should have be accepted and not conflicted" {
assert_config_entry_status Accepted True Accepted primary api-gateway api-gateway
assert_config_entry_status Conflicted False NoConflict primary api-gateway api-gateway
}
@test "api gateway should have healthy endpoints for s1" {
assert_config_entry_status Bound True Bound primary http-route api-gateway-route-one
assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1
}
@test "api gateway should have healthy endpoints for s2" {
assert_config_entry_status Bound True Bound primary http-route api-gateway-route-two
assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2 HEALTHY 1
}
@test "api gateway should be able to connect to s1 via configured port" {
run retry_long curl -sk -f -d hello https://localhost:9999
[[ "$output" == *"hello"* ]]
}
@test "api gateway should be able to serve host.consul.example traffic on listener 1" {
assert_cert_has_cn localhost:9999 host.consul.example host.consul.example
}
@test "api gateway should be able to serve all other traffic with the host.consul.example certificate on listener 1" {
assert_cert_has_cn localhost:9999 host.consul.example other.consul.example
}
@test "api gateway should get an intentions error connecting to s2 via configured port" {
run retry_default sh -c "curl -sk https://localhost:9998 | grep RBAC"
[[ "$output" == "RBAC: access denied" ]]
}
@test "api gateway should be able to serve listener 2 with the other.consul.example certificate" {
assert_cert_has_cn localhost:9998 other.consul.example other.consul.example
}
@test "api gateway should fall back to a connect certificate on conflicted SNI on listener 2" {
assert_cert_has_cn localhost:9998 pri host.consul.example
}

View File

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

View File

@ -0,0 +1,4 @@
services {
name = "api-gateway"
kind = "api-gateway"
}

View File

@ -0,0 +1,279 @@
#!/bin/bash
set -euo pipefail
upsert_config_entry primary '
kind = "api-gateway"
name = "api-gateway"
listeners = [
{
name = "listener-one"
port = 9999
protocol = "tcp"
tls = {
certificates = [
{
kind = "inline-certificate"
name = "host-consul-example"
}
]
}
},
{
name = "listener-two"
port = 9998
protocol = "tcp"
tls = {
certificates = [
{
kind = "inline-certificate"
name = "host-consul-example"
},
{
kind = "inline-certificate"
name = "also-host-consul-example"
},
{
kind = "inline-certificate"
name = "other-consul-example"
}
]
}
}
]
'
upsert_config_entry primary '
kind = "inline-certificate"
name = "host-consul-example"
private_key = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg
5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F
eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga
9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv
OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz
9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67
yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7
OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs
qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo
Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5
LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6
Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg
llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn
gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ
eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA
hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn
orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN
JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC
2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s
1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn
IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS
2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq
wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe
RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK
+QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ==
-----END RSA PRIVATE KEY-----
EOF
certificate = <<EOF
-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD
VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc
MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq
oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P
BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4
GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH
bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6
8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N
L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc
U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi
SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq
MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq
J3TrQE3YVrL4D9xnklT86WDnZKApJg==
-----END CERTIFICATE-----
EOF
'
upsert_config_entry primary '
kind = "inline-certificate"
name = "also-host-consul-example"
private_key = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg
5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F
eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga
9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv
OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz
9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67
yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7
OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs
qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo
Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5
LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6
Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg
llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn
gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ
eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA
hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn
orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN
JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC
2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s
1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn
IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS
2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq
wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe
RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK
+QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ==
-----END RSA PRIVATE KEY-----
EOF
certificate = <<EOF
-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD
VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc
MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq
oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P
BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4
GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH
bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6
8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N
L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc
U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi
SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq
MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq
J3TrQE3YVrL4D9xnklT86WDnZKApJg==
-----END CERTIFICATE-----
EOF
'
upsert_config_entry primary '
kind = "inline-certificate"
name = "other-consul-example"
private_key = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7Qgf93OZYlpM9UvfbFBUeK+udCTfy+yRbTKYgti5qKyZQwnT
7GtxpNxWWNT7ecqGZJb3BxhIVjTYZwZYB1zDzHzE7zTVydrTbRP4MhaJhqWNdPdq
Ctth9QsX2r6KFPogvUPaYVrQBbXHoGTVcCZlFJOlEGGnuIU2cJcZEryxJKv3mB4P
FpHKP/YXy3qQb4A0+REGI93atGCxhCO5Mu5lgG+x54nKCdIXy3LVAyDqSjPuR01s
+ifdDkYUfvVY3DOb0bcNaMwi752GkfPuuxsKeHAsHmWHWOxkFiQAy3eSLJjSDghF
66JVZ6wx74Awb8NBm9nmYTbnZAUn3Z0tN6jlFwIDAQABAoIBAGh8EWNJ8M4bEht7
A5TCYEoG3zbRXlmNAZoKGJJtKIIC+1hCx8lKn4DVo7ZqxCOus8k5htD40kI170KS
2FD+gkzsnv724lqlfFdz2w9xQdQ5u/5YZcU9aZPT/QLuxP10OORVObl6h4JM3B+G
81MJibslTjjHY2CCUDoXUPUiek+4KTI6BYn9PL+ReCaH9qv85hIWvkgbwU2f9i3n
T8MysgDBNoKbVAqsH5Hq/zFkW5iad/zpgW+REgYK/gjBfUNEsRusm1GYim4XJtZf
SmXBtdYhk8kDBrMk1mNdZeMXMfl0VwZaHca8QmqrgDm//grUYKLWZO/PbDoI5c+w
/MTN6tkCgYEA956LZJ+Ts9g9k+jLcUV1hOAmR6cAL5tjEkBPVC5FC+5o42qOgI58
5NQjmVCM+mD9w+deAXkT/IDXzV28Y02lZ1pyOXTN+ChE2ZBym+yu2zyLj45lJzx5
h+lC7aeCBIGyjP/t6wFoFdCaluAsWpU4SY1DWtExcCLvXs0+zDiYjZMCgYEA9Q3Y
mlrAvdzb61x8BAEPpBTMSu0a+jo8mkJoUFS4ndFXEl5yPzqf6IUrFgzqgVruQg7k
dIZK5uDqNI7sXDXulpPFHbJdpAZq3Dlkv0VOvcgRycuWgDAyOngCSRN8JC7u4P5j
54xgAYQSCC/kPolFMHntXQLjzPevJNrkKnJfXO0CgYEAyCf5ByJSs0o1JE1Fvc7m
mrzRVJPye4kAQS2IskQgfe9+C24DuHj1DcdI61IIUw95sRRhkZE8jZvcVN3TPPXz
oKKkuDrpjxGF7dNsQQvFn+PF8AmrTFb+6dSszAvd9iScnor110OwzglsHE8iqyn5
cMLmUg/NBZbHpPsFKvEIp08CgYAmQ1Az4cHAo5CvMlSm52eCzkCL3nPc6GT4DTBu
gpwFAF/hHWAnYUcArnJo0gF3yzPympKvYxyk6i+Hn11mlIE5f79CgMxARURAOLHz
b6X42hl08dYBFAVzvbNVp7Y1jCJ+fRoqWG/RLMcIAjpYTWTBSfh3EnFxWqc9UPRZ
cFxVjQKBgQDUXDww+jXXTFlYkbhgNOtQS57M8jKWHnUxEc7IXOjF/0WGEEBAc2uc
fFW5dnPyS3D6vecWA8KB8LKcXey3ZcDZnVkahSgYhYHrGJrANzhfM3yli+lSFWsT
r295+0za3P3LVL4aKS+ok9oYZaVyMe/vlCMrIS+sh6SEc4+v0qNa3g==
-----END RSA PRIVATE KEY-----
EOF
certificate = <<EOF
-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC2H6+PYz23xDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQwwCgYD
VQQLDANGb28xHTAbBgNVBAMMFG90aGVyLmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTM0OVoXDTI4MDIxNjAyMTM0OVowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRm9vMR0w
GwYDVQQDDBRvdGhlci5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAO0IH/dzmWJaTPVL32xQVHivrnQk38vskW0ymILYuaismUMJ
0+xrcaTcVljU+3nKhmSW9wcYSFY02GcGWAdcw8x8xO801cna020T+DIWiYaljXT3
agrbYfULF9q+ihT6IL1D2mFa0AW1x6Bk1XAmZRSTpRBhp7iFNnCXGRK8sSSr95ge
DxaRyj/2F8t6kG+ANPkRBiPd2rRgsYQjuTLuZYBvseeJygnSF8ty1QMg6koz7kdN
bPon3Q5GFH71WNwzm9G3DWjMIu+dhpHz7rsbCnhwLB5lh1jsZBYkAMt3kiyY0g4I
ReuiVWesMe+AMG/DQZvZ5mE252QFJ92dLTeo5RcCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAijm6blixjl+pMRAj7EajoPjU+GqhooZayJrvdwvofwcPxQYpkPuh7Uc6
l2z494b75cRzMw7wS+iW/ad8NYrfw1JwHMsUfncxs5LDO5GsKl9Krg/39goDl3wC
ywTcl00y+FMYfldNPjKDLunENmn+yPa2pKuBVQ0yOKALp+oUeJFVzRNPV5fohlBi
HjypkO0KaVmCG6P01cqCgVkNzxnX9qQYP3YXX1yt5iOcI7QcoOa5WnRhOuD8WqJ1
v3AZGYNvKyXf9E5nD0y2Cmz6t1awjFjzMlXMx6AdHrjWqxtHhYQ1xz4P4NfzK27m
cCtURSzXMgcrSeZLepBfdICf+0/0+Q==
-----END CERTIFICATE-----
EOF
'
upsert_config_entry primary '
Kind = "proxy-defaults"
Name = "global"
Config {
protocol = "tcp"
}
'
upsert_config_entry primary '
kind = "tcp-route"
name = "api-gateway-route-one"
services = [
{
name = "s1"
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-one"
}
]
'
upsert_config_entry primary '
kind = "tcp-route"
name = "api-gateway-route-two"
services = [
{
name = "s2"
}
]
parents = [
{
name = "api-gateway"
sectionName = "listener-two"
}
]
'
upsert_config_entry primary '
kind = "service-intentions"
name = "s1"
sources {
name = "api-gateway"
action = "allow"
}
'
upsert_config_entry primary '
kind = "service-intentions"
name = "s2"
sources {
name = "api-gateway"
action = "deny"
}
'
register_services primary
gen_envoy_bootstrap api-gateway 20000 primary true
gen_envoy_bootstrap s1 19000
gen_envoy_bootstrap s2 19001

View File

@ -0,0 +1,3 @@
#!/bin/bash
export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES api-gateway-primary"

View File

@ -0,0 +1,43 @@
#!/usr/bin/env bats
load helpers
@test "api gateway proxy admin is up on :20000" {
retry_default curl -f -s localhost:20000/stats -o /dev/null
}
@test "api gateway should have be accepted and not conflicted" {
assert_config_entry_status Accepted True Accepted primary api-gateway api-gateway
assert_config_entry_status Conflicted False NoConflict primary api-gateway api-gateway
}
@test "api gateway should have healthy endpoints for s1" {
assert_config_entry_status Bound True Bound primary tcp-route api-gateway-route-one
assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1
}
@test "api gateway should have healthy endpoints for s2" {
assert_config_entry_status Bound True Bound primary tcp-route api-gateway-route-two
assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2 HEALTHY 1
}
@test "api gateway should be able to connect to s1 via configured port" {
run retry_long curl -sk -f -d hello https://localhost:9999
[[ "$output" == *"hello"* ]]
}
@test "api gateway should be able to serve host.consul.example traffic on listener 1" {
assert_cert_has_cn localhost:9999 host.consul.example host.consul.example
}
@test "api gateway should be able to serve all other traffic with the host.consul.example certificate on listener 1" {
assert_cert_has_cn localhost:9999 host.consul.example other.consul.example
}
@test "api gateway should be able to serve listener 2 with the other.consul.example certificate" {
assert_cert_has_cn localhost:9998 other.consul.example other.consul.example
}
@test "api gateway should fall back to a connect certificate on conflicted SNI on listener 2" {
assert_cert_has_cn localhost:9998 pri host.consul.example
}

View File

@ -147,6 +147,20 @@ function assert_cert_signed_by_ca {
echo "$CERT" | grep 'Verify return code: 0 (ok)'
}
function assert_cert_has_cn {
local HOSTPORT=$1
local CN=$2
local SERVER_NAME=${3:-$CN}
CERT=$(openssl s_client -connect $HOSTPORT -servername $SERVER_NAME -showcerts </dev/null 2>/dev/null)
echo "WANT CN: ${CN} (SNI: ${SERVER_NAME})"
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep "CN = ${CN}"
}
function assert_envoy_version {
local ADMINPORT=$1
run retry_default curl -f -s localhost:$ADMINPORT/server_info