mirror of https://github.com/hashicorp/consul
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>pull/10622/head
parent
70f29c2312
commit
bdacb71d22
|
@ -9,6 +9,7 @@ import (
|
||||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
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_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_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"
|
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb"
|
"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.
|
// 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{
|
tlsContext := &envoy_tls_v3.UpstreamTlsContext{
|
||||||
CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
|
CommonTlsContext: commonTLSContext,
|
||||||
Sni: sni,
|
Sni: sni,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,6 +612,13 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
|
||||||
sni := target.SNI
|
sni := target.SNI
|
||||||
clusterName := CustomizeClusterName(target.Name, chain)
|
clusterName := CustomizeClusterName(target.Name, chain)
|
||||||
|
|
||||||
|
spiffeID := connect.SpiffeIDService{
|
||||||
|
Host: cfgSnap.Roots.TrustDomain,
|
||||||
|
Namespace: target.Namespace,
|
||||||
|
Datacenter: target.Datacenter,
|
||||||
|
Service: target.Service,
|
||||||
|
}
|
||||||
|
|
||||||
if failoverThroughMeshGateway {
|
if failoverThroughMeshGateway {
|
||||||
actualTargetID := firstHealthyTarget(
|
actualTargetID := firstHealthyTarget(
|
||||||
chain.Targets,
|
chain.Targets,
|
||||||
|
@ -609,6 +630,13 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
|
||||||
if actualTargetID != targetID {
|
if actualTargetID != targetID {
|
||||||
actualTarget := chain.Targets[actualTargetID]
|
actualTarget := chain.Targets[actualTargetID]
|
||||||
sni = actualTarget.SNI
|
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{}
|
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{
|
tlsContext := &envoy_tls_v3.UpstreamTlsContext{
|
||||||
CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
|
CommonTlsContext: commonTLSContext,
|
||||||
Sni: sni,
|
Sni: sni,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -688,6 +721,22 @@ func (s *ResourceGenerator) makeUpstreamClustersForDiscoveryChain(
|
||||||
return out, nil
|
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
|
// makeClusterFromUserConfig returns the listener config decoded from an
|
||||||
// arbitrary proto3 json format string or an error if it's invalid.
|
// arbitrary proto3 json format string or an error if it's invalid.
|
||||||
//
|
//
|
||||||
|
|
|
@ -242,15 +242,16 @@ func xdsNewPublicTransportSocket(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
snap *proxycfg.ConfigSnapshot,
|
snap *proxycfg.ConfigSnapshot,
|
||||||
) *envoy_core_v3.TransportSocket {
|
) *envoy_core_v3.TransportSocket {
|
||||||
return xdsNewTransportSocket(t, snap, true, true, "")
|
return xdsNewTransportSocket(t, snap, true, true, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func xdsNewUpstreamTransportSocket(
|
func xdsNewUpstreamTransportSocket(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
snap *proxycfg.ConfigSnapshot,
|
snap *proxycfg.ConfigSnapshot,
|
||||||
sni string,
|
sni string,
|
||||||
|
uri string,
|
||||||
) *envoy_core_v3.TransportSocket {
|
) *envoy_core_v3.TransportSocket {
|
||||||
return xdsNewTransportSocket(t, snap, false, false, sni)
|
return xdsNewTransportSocket(t, snap, false, false, sni, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
func xdsNewTransportSocket(
|
func xdsNewTransportSocket(
|
||||||
|
@ -259,11 +260,12 @@ func xdsNewTransportSocket(
|
||||||
downstream bool,
|
downstream bool,
|
||||||
requireClientCert bool,
|
requireClientCert bool,
|
||||||
sni string,
|
sni string,
|
||||||
|
uri string,
|
||||||
) *envoy_core_v3.TransportSocket {
|
) *envoy_core_v3.TransportSocket {
|
||||||
// Assume just one root for now, can get fancier later if needed.
|
// Assume just one root for now, can get fancier later if needed.
|
||||||
caPEM := snap.Roots.Roots[0].RootCert
|
caPEM := snap.Roots.Roots[0].RootCert
|
||||||
|
|
||||||
commonTlsContext := &envoy_tls_v3.CommonTlsContext{
|
commonTLSContext := &envoy_tls_v3.CommonTlsContext{
|
||||||
TlsParams: &envoy_tls_v3.TlsParameters{},
|
TlsParams: &envoy_tls_v3.TlsParameters{},
|
||||||
TlsCertificates: []*envoy_tls_v3.TlsCertificate{{
|
TlsCertificates: []*envoy_tls_v3.TlsCertificate{{
|
||||||
CertificateChain: xdsNewInlineString(snap.Leaf().CertPEM),
|
CertificateChain: xdsNewInlineString(snap.Leaf().CertPEM),
|
||||||
|
@ -275,6 +277,9 @@ func xdsNewTransportSocket(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if uri != "" {
|
||||||
|
require.NoError(t, injectSANMatcher(commonTLSContext, uri))
|
||||||
|
}
|
||||||
|
|
||||||
var tlsContext proto.Message
|
var tlsContext proto.Message
|
||||||
if downstream {
|
if downstream {
|
||||||
|
@ -284,12 +289,12 @@ func xdsNewTransportSocket(
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsContext = &envoy_tls_v3.DownstreamTlsContext{
|
tlsContext = &envoy_tls_v3.DownstreamTlsContext{
|
||||||
CommonTlsContext: commonTlsContext,
|
CommonTlsContext: commonTLSContext,
|
||||||
RequireClientCertificate: requireClientCertPB,
|
RequireClientCertificate: requireClientCertPB,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tlsContext = &envoy_tls_v3.UpstreamTlsContext{
|
tlsContext = &envoy_tls_v3.UpstreamTlsContext{
|
||||||
CommonTlsContext: commonTlsContext,
|
CommonTlsContext: commonTLSContext,
|
||||||
Sni: sni,
|
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 {
|
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 {
|
switch fixtureName {
|
||||||
case "tcp:local_app":
|
case "tcp:local_app":
|
||||||
return &envoy_cluster_v3.Cluster{
|
return &envoy_cluster_v3.Cluster{
|
||||||
|
@ -375,7 +388,7 @@ func makeTestCluster(t *testing.T, snap *proxycfg.ConfigSnapshot, fixtureName st
|
||||||
}
|
}
|
||||||
case "tcp:db":
|
case "tcp:db":
|
||||||
return &envoy_cluster_v3.Cluster{
|
return &envoy_cluster_v3.Cluster{
|
||||||
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
Name: dbSNI,
|
||||||
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
||||||
Type: envoy_cluster_v3.Cluster_EDS,
|
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{},
|
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
|
||||||
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
||||||
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
AltStatName: dbSNI,
|
||||||
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
||||||
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
||||||
},
|
},
|
||||||
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
|
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":
|
case "tcp:db:timeout":
|
||||||
return &envoy_cluster_v3.Cluster{
|
return &envoy_cluster_v3.Cluster{
|
||||||
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
Name: dbSNI,
|
||||||
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
||||||
Type: envoy_cluster_v3.Cluster_EDS,
|
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{},
|
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
|
||||||
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
||||||
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
AltStatName: dbSNI,
|
||||||
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
||||||
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
||||||
},
|
},
|
||||||
ConnectTimeout: ptypes.DurationProto(1337 * time.Second),
|
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":
|
case "http2:db":
|
||||||
return &envoy_cluster_v3.Cluster{
|
return &envoy_cluster_v3.Cluster{
|
||||||
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
Name: dbSNI,
|
||||||
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
||||||
Type: envoy_cluster_v3.Cluster_EDS,
|
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{},
|
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
|
||||||
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
||||||
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
AltStatName: dbSNI,
|
||||||
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
||||||
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
||||||
},
|
},
|
||||||
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
|
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{},
|
Http2ProtocolOptions: &envoy_core_v3.Http2ProtocolOptions{},
|
||||||
}
|
}
|
||||||
case "http:db":
|
case "http:db":
|
||||||
return &envoy_cluster_v3.Cluster{
|
return &envoy_cluster_v3.Cluster{
|
||||||
Name: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
Name: dbSNI,
|
||||||
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
||||||
Type: envoy_cluster_v3.Cluster_EDS,
|
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{},
|
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
|
||||||
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
||||||
AltStatName: "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
|
AltStatName: dbSNI,
|
||||||
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
CommonLbConfig: &envoy_cluster_v3.Cluster_CommonLbConfig{
|
||||||
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
HealthyPanicThreshold: &envoy_type_v3.Percent{Value: 0},
|
||||||
},
|
},
|
||||||
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
|
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{},
|
// HttpProtocolOptions: &envoy_core_v3.Http1ProtocolOptions{},
|
||||||
}
|
}
|
||||||
case "tcp:geo-cache":
|
case "tcp:geo-cache":
|
||||||
return &envoy_cluster_v3.Cluster{
|
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{
|
ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{
|
||||||
Type: envoy_cluster_v3.Cluster_EDS,
|
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{},
|
CircuitBreakers: &envoy_cluster_v3.CircuitBreakers{},
|
||||||
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
OutlierDetection: &envoy_cluster_v3.OutlierDetection{},
|
||||||
ConnectTimeout: ptypes.DurationProto(5 * time.Second),
|
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:
|
default:
|
||||||
t.Fatalf("unexpected fixture name: %s", fixtureName)
|
t.Fatalf("unexpected fixture name: %s", fixtureName)
|
||||||
|
|
Loading…
Reference in New Issue