Validate Subject Alternative Name for upstreams

These changes ensure that the identity of services dialed is
cryptographically verified.

For all upstreams we validate against SPIFFE IDs in the format used by
Consul's service mesh:

spiffe://<trust-domain>/ns/<namespace>/dc/<datacenter>/svc/<service>
This commit is contained in:
freddygv 2021-06-28 19:58:12 -06:00
parent 70f29c2312
commit bdacb71d22
2 changed files with 84 additions and 22 deletions

View File

@ -9,6 +9,7 @@ import (
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/golang/protobuf/jsonpb"
@ -528,9 +529,22 @@ func (s *ResourceGenerator) makeUpstreamClusterForPreparedQuery(upstream structs
}
}
spiffeID := connect.SpiffeIDService{
Host: cfgSnap.Roots.TrustDomain,
Namespace: upstream.DestinationNamespace,
Datacenter: dc,
Service: upstream.DestinationName,
}
// Enable TLS upstream with the configured client certificate.
commonTLSContext := makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf())
err = injectSANMatcher(commonTLSContext, spiffeID.URI().String())
if err != nil {
return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err)
}
tlsContext := &envoy_tls_v3.UpstreamTlsContext{
CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
CommonTlsContext: commonTLSContext,
Sni: sni,
}
@ -598,6 +612,13 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
sni := target.SNI
clusterName := CustomizeClusterName(target.Name, chain)
spiffeID := connect.SpiffeIDService{
Host: cfgSnap.Roots.TrustDomain,
Namespace: target.Namespace,
Datacenter: target.Datacenter,
Service: target.Service,
}
if failoverThroughMeshGateway {
actualTargetID := firstHealthyTarget(
chain.Targets,
@ -609,6 +630,13 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
if actualTargetID != targetID {
actualTarget := chain.Targets[actualTargetID]
sni = actualTarget.SNI
spiffeID = connect.SpiffeIDService{
Host: cfgSnap.Roots.TrustDomain,
Namespace: actualTarget.Namespace,
Datacenter: actualTarget.Datacenter,
Service: actualTarget.Service,
}
}
}
@ -658,9 +686,14 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
c.Http2ProtocolOptions = &envoy_core_v3.Http2ProtocolOptions{}
}
// Enable TLS upstream with the configured client certificate.
commonTLSContext := makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf())
err = injectSANMatcher(commonTLSContext, spiffeID.URI().String())
if err != nil {
return nil, fmt.Errorf("failed to inject SAN matcher rules for cluster %q: %v", sni, err)
}
tlsContext := &envoy_tls_v3.UpstreamTlsContext{
CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
CommonTlsContext: commonTLSContext,
Sni: sni,
}
@ -688,6 +721,22 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
return out, nil
}
// injectSANMatcher updates a TLS context so that it verifies the upstream SAN.
func injectSANMatcher(tlsContext *envoy_tls_v3.CommonTlsContext, uri string) error {
validationCtx, ok := tlsContext.ValidationContextType.(*envoy_tls_v3.CommonTlsContext_ValidationContext)
if !ok {
return fmt.Errorf("invalid type: expected CommonTlsContext_ValidationContext, got %T",
tlsContext.ValidationContextType)
}
validationCtx.ValidationContext.MatchSubjectAltNames = []*envoy_matcher_v3.StringMatcher{
{
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{Exact: uri},
},
}
return nil
}
// makeClusterFromUserConfig returns the listener config decoded from an
// arbitrary proto3 json format string or an error if it's invalid.
//

View File

@ -242,15 +242,16 @@ func xdsNewPublicTransportSocket(
t *testing.T,
snap *proxycfg.ConfigSnapshot,
) *envoy_core_v3.TransportSocket {
return xdsNewTransportSocket(t, snap, true, true, "")
return xdsNewTransportSocket(t, snap, true, true, "", "")
}
func xdsNewUpstreamTransportSocket(
t *testing.T,
snap *proxycfg.ConfigSnapshot,
sni string,
uri string,
) *envoy_core_v3.TransportSocket {
return xdsNewTransportSocket(t, snap, false, false, sni)
return xdsNewTransportSocket(t, snap, false, false, sni, uri)
}
func xdsNewTransportSocket(
@ -259,11 +260,12 @@ func xdsNewTransportSocket(
downstream bool,
requireClientCert bool,
sni string,
uri string,
) *envoy_core_v3.TransportSocket {
// Assume just one root for now, can get fancier later if needed.
caPEM := snap.Roots.Roots[0].RootCert
commonTlsContext := &envoy_tls_v3.CommonTlsContext{
commonTLSContext := &envoy_tls_v3.CommonTlsContext{
TlsParams: &envoy_tls_v3.TlsParameters{},
TlsCertificates: []*envoy_tls_v3.TlsCertificate{{
CertificateChain: xdsNewInlineString(snap.Leaf().CertPEM),
@ -275,6 +277,9 @@ func xdsNewTransportSocket(
},
},
}
if uri != "" {
require.NoError(t, injectSANMatcher(commonTLSContext, uri))
}
var tlsContext proto.Message
if downstream {
@ -284,12 +289,12 @@ func xdsNewTransportSocket(
}
tlsContext = &envoy_tls_v3.DownstreamTlsContext{
CommonTlsContext: commonTlsContext,
CommonTlsContext: commonTLSContext,
RequireClientCertificate: requireClientCertPB,
}
} else {
tlsContext = &envoy_tls_v3.UpstreamTlsContext{
CommonTlsContext: commonTlsContext,
CommonTlsContext: commonTLSContext,
Sni: sni,
}
}
@ -356,6 +361,14 @@ func makeTestResource(t *testing.T, raw interface{}) *envoy_discovery_v3.Resourc
}
func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName string) *envoy_cluster_v3.Cluster {
var (
dbSNI = "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
dbURI = "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
geocacheSNI = "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
geocacheURI = "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache"
)
switch fixtureName {
case "tcp:local_app":
return &envoy_cluster_v3.Cluster{
@ -375,7 +388,7 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
}
case "tcp:db":
return &envoy_cluster_v3.Cluster{
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
Name: dbSNI,
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
Type: envoy_cluster_v3.Cluster_EDS,
},
@ -384,16 +397,16 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
},
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
AltStatName: dbSNI,
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
},
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, dbSNI, dbURI),
}
case "tcp:db:timeout":
return &envoy_cluster_v3.Cluster{
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
Name: dbSNI,
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
Type: envoy_cluster_v3.Cluster_EDS,
},
@ -402,16 +415,16 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
},
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
AltStatName: dbSNI,
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
},
ConnectTimeout: ptypes.DurationProto(1337 * time.Second),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, dbSNI, dbURI),
}
case "http2:db":
return &envoy_cluster_v3.Cluster{
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
Name: dbSNI,
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
Type: envoy_cluster_v3.Cluster_EDS,
},
@ -420,17 +433,17 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
},
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
AltStatName: dbSNI,
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
},
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, dbSNI, dbURI),
Http2ProtocolOptions: &envoy_core_v3.Http2ProtocolOptions{},
}
case "http:db":
return &envoy_cluster_v3.Cluster{
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
Name: dbSNI,
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
Type: envoy_cluster_v3.Cluster_EDS,
},
@ -439,17 +452,17 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
},
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
AltStatName: dbSNI,
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
},
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, dbSNI, dbURI),
// HttpProtocolOptions: &envoy_core_v3.Http1ProtocolOptions{},
}
case "tcp:geo-cache":
return &envoy_cluster_v3.Cluster{
Name: "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
Name: geocacheSNI,
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
Type: envoy_cluster_v3.Cluster_EDS,
},
@ -459,7 +472,7 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"),
TransportSocket: xdsNewUpstreamTransportSocket(t, snap, geocacheSNI, geocacheURI),
}
default:
t.Fatalf("unexpected fixture name: %s", fixtureName)