TLS Origination for Terminating Gateways (#7671)

pull/7716/head
Freddy 2020-04-27 16:25:37 -06:00 committed by GitHub
parent c1dc2f12f7
commit 137a2c32c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 448 additions and 137 deletions

View File

@ -34,6 +34,7 @@ func TestGatewayServices(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
}, },
} }
reply := args.Get(2).(*structs.IndexedGatewayServices) reply := args.Get(2).(*structs.IndexedGatewayServices)

View File

@ -731,6 +731,7 @@ func TestInternal_TerminatingGatewayServices(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
}, },
{ {
Name: "db", Name: "db",
@ -740,6 +741,7 @@ func TestInternal_TerminatingGatewayServices(t *testing.T) {
CAFile: "ca.crt", CAFile: "ca.crt",
CertFile: "client.crt", CertFile: "client.crt",
KeyFile: "client.key", KeyFile: "client.key",
SNI: "my-alt-domain",
}, },
}, },
}, },
@ -766,6 +768,7 @@ func TestInternal_TerminatingGatewayServices(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
}, },
{ {
Service: structs.NewServiceID("db", nil), Service: structs.NewServiceID("db", nil),
@ -782,6 +785,7 @@ func TestInternal_TerminatingGatewayServices(t *testing.T) {
CAFile: "ca.crt", CAFile: "ca.crt",
CertFile: "client.crt", CertFile: "client.crt",
KeyFile: "client.key", KeyFile: "client.key",
SNI: "my-alt-domain",
FromWildcard: true, FromWildcard: true,
}, },
} }

View File

@ -1037,12 +1037,16 @@ func (s *Store) serviceNodes(ws memdb.WatchSet, serviceName string, connect bool
// Gateways are tracked in a separate table, and we append them to the result set. // Gateways are tracked in a separate table, and we append them to the result set.
// We append rather than replace since it allows users to migrate a service // We append rather than replace since it allows users to migrate a service
// to the mesh with a mix of sidecars and gateways until all its instances have a sidecar. // to the mesh with a mix of sidecars and gateways until all its instances have a sidecar.
var idx uint64
if connect { if connect {
// Look up gateway nodes associated with the service // Look up gateway nodes associated with the service
_, nodes, chs, err := s.serviceGatewayNodes(tx, ws, serviceName, structs.ServiceKindTerminatingGateway, entMeta) gwIdx, nodes, chs, err := s.serviceGatewayNodes(tx, ws, serviceName, structs.ServiceKindTerminatingGateway, entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed gateway nodes lookup: %v", err) return 0, nil, fmt.Errorf("failed gateway nodes lookup: %v", err)
} }
if idx < gwIdx {
idx = gwIdx
}
for _, ch := range chs { for _, ch := range chs {
ws.Add(ch) ws.Add(ch)
@ -1059,7 +1063,10 @@ func (s *Store) serviceNodes(ws memdb.WatchSet, serviceName string, connect bool
} }
// Get the table index. // Get the table index.
idx := s.maxIndexForService(tx, serviceName, len(results) > 0, false, entMeta) svcIdx := s.maxIndexForService(tx, serviceName, len(results) > 0, false, entMeta)
if idx < svcIdx {
idx = svcIdx
}
return idx, results, nil return idx, results, nil
} }
@ -2035,12 +2042,16 @@ func (s *Store) checkServiceNodesTxn(tx *memdb.Txn, ws memdb.WatchSet, serviceNa
// Gateways are tracked in a separate table, and we append them to the result set. // Gateways are tracked in a separate table, and we append them to the result set.
// We append rather than replace since it allows users to migrate a service // We append rather than replace since it allows users to migrate a service
// to the mesh with a mix of sidecars and gateways until all its instances have a sidecar. // to the mesh with a mix of sidecars and gateways until all its instances have a sidecar.
var idx uint64
if connect { if connect {
// Look up gateway nodes associated with the service // Look up gateway nodes associated with the service
_, nodes, _, err := s.serviceGatewayNodes(tx, ws, serviceName, structs.ServiceKindTerminatingGateway, entMeta) gwIdx, nodes, _, err := s.serviceGatewayNodes(tx, ws, serviceName, structs.ServiceKindTerminatingGateway, entMeta)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed gateway nodes lookup: %v", err) return 0, nil, fmt.Errorf("failed gateway nodes lookup: %v", err)
} }
if idx < gwIdx {
idx = gwIdx
}
for i := 0; i < len(nodes); i++ { for i := 0; i < len(nodes); i++ {
results = append(results, nodes[i]) results = append(results, nodes[i])
serviceNames[nodes[i].ServiceName] = struct{}{} serviceNames[nodes[i].ServiceName] = struct{}{}
@ -2056,7 +2067,6 @@ func (s *Store) checkServiceNodesTxn(tx *memdb.Txn, ws memdb.WatchSet, serviceNa
// (~682 service instances). See // (~682 service instances). See
// https://github.com/hashicorp/consul/issues/4984 // https://github.com/hashicorp/consul/issues/4984
watchOptimized := false watchOptimized := false
idx := uint64(0)
if len(serviceNames) > 0 { if len(serviceNames) > 0 {
// Assume optimization will work since it really should at this point. For // Assume optimization will work since it really should at this point. For
// safety we'll sanity check this below for each service name. // safety we'll sanity check this below for each service name.
@ -2527,6 +2537,7 @@ func (s *Store) terminatingConfigGatewayServices(tx *memdb.Txn, gateway structs.
KeyFile: svc.KeyFile, KeyFile: svc.KeyFile,
CertFile: svc.CertFile, CertFile: svc.CertFile,
CAFile: svc.CAFile, CAFile: svc.CAFile,
SNI: svc.SNI,
} }
gatewayServices = append(gatewayServices, mapping) gatewayServices = append(gatewayServices, mapping)
@ -2684,10 +2695,22 @@ func (s *Store) serviceGatewayNodes(tx *memdb.Txn, ws memdb.WatchSet, service st
if err != nil { if err != nil {
return 0, nil, nil, fmt.Errorf("failed service lookup: %s", err) return 0, nil, nil, fmt.Errorf("failed service lookup: %s", err)
} }
var exists bool
for svc := gwServices.Next(); svc != nil; svc = gwServices.Next() { for svc := gwServices.Next(); svc != nil; svc = gwServices.Next() {
sn := svc.(*structs.ServiceNode) sn := svc.(*structs.ServiceNode)
ret = append(ret, sn) ret = append(ret, sn)
// Tracking existence to know whether we should check extinction index for service
exists = true
} }
// This prevents the index from sliding back in case all instances of the service are deregistered
svcIdx := s.maxIndexForService(tx, mapping.Gateway.ID, exists, false, &mapping.Service.EnterpriseMeta)
if maxIdx < svcIdx {
maxIdx = svcIdx
}
watchChans = append(watchChans, gwServices.WatchCh()) watchChans = append(watchChans, gwServices.WatchCh())
} }
return maxIdx, ret, watchChans, nil return maxIdx, ret, watchChans, nil

View File

@ -2168,7 +2168,7 @@ func TestStateStore_ConnectServiceNodes_Gateways(t *testing.T) {
ws = memdb.NewWatchSet() ws = memdb.NewWatchSet()
idx, nodes, err = s.ConnectServiceNodes(ws, "db", nil) idx, nodes, err = s.ConnectServiceNodes(ws, "db", nil)
assert.Nil(err) assert.Nil(err)
assert.Equal(idx, uint64(14)) assert.Equal(idx, uint64(17))
assert.Len(nodes, 2) assert.Len(nodes, 2)
// Check sidecar // Check sidecar
@ -2191,12 +2191,12 @@ func TestStateStore_ConnectServiceNodes_Gateways(t *testing.T) {
assert.True(watchFired(ws)) assert.True(watchFired(ws))
// Watch should fire when a gateway instance is de-registered // Watch should fire when a gateway instance is de-registered
assert.Nil(s.DeleteService(29, "bar", "gateway", nil)) assert.Nil(s.DeleteService(19, "bar", "gateway", nil))
assert.True(watchFired(ws)) assert.True(watchFired(ws))
idx, nodes, err = s.ConnectServiceNodes(ws, "db", nil) idx, nodes, err = s.ConnectServiceNodes(ws, "db", nil)
assert.Nil(err) assert.Nil(err)
assert.Equal(idx, uint64(14)) assert.Equal(idx, uint64(19))
assert.Len(nodes, 2) assert.Len(nodes, 2)
// Check the new gateway // Check the new gateway
@ -2205,6 +2205,22 @@ func TestStateStore_ConnectServiceNodes_Gateways(t *testing.T) {
assert.Equal("gateway", nodes[1].ServiceName) assert.Equal("gateway", nodes[1].ServiceName)
assert.Equal("gateway-2", nodes[1].ServiceID) assert.Equal("gateway-2", nodes[1].ServiceID)
assert.Equal(443, nodes[1].ServicePort) assert.Equal(443, nodes[1].ServicePort)
// Index should not slide back after deleting all instances of the gateway
assert.Nil(s.DeleteService(20, "foo", "gateway-2", nil))
assert.True(watchFired(ws))
idx, nodes, err = s.ConnectServiceNodes(ws, "db", nil)
assert.Nil(err)
assert.Equal(idx, uint64(20))
assert.Len(nodes, 1)
// Ensure that remaining node is the proxy and not a gateway
assert.Equal(structs.ServiceKindConnectProxy, nodes[0].ServiceKind)
assert.Equal("foo", nodes[0].Node)
assert.Equal("proxy", nodes[0].ServiceName)
assert.Equal("proxy", nodes[0].ServiceID)
assert.Equal(8000, nodes[0].ServicePort)
} }
func TestStateStore_Service_Snapshot(t *testing.T) { func TestStateStore_Service_Snapshot(t *testing.T) {
@ -3622,6 +3638,11 @@ func TestStateStore_CheckConnectServiceNodes_Gateways(t *testing.T) {
assert.Nil(s.EnsureService(22, "foo", &structs.NodeService{Kind: structs.ServiceKindTerminatingGateway, ID: "gateway-2", Service: "gateway", Port: 443})) assert.Nil(s.EnsureService(22, "foo", &structs.NodeService{Kind: structs.ServiceKindTerminatingGateway, ID: "gateway-2", Service: "gateway", Port: 443}))
assert.True(watchFired(ws)) assert.True(watchFired(ws))
idx, nodes, err = s.CheckConnectServiceNodes(ws, "db", nil)
assert.Nil(err)
assert.Equal(idx, uint64(22))
assert.Len(nodes, 3)
// Watch should fire when a gateway instance is de-registered // Watch should fire when a gateway instance is de-registered
assert.Nil(s.DeleteService(23, "bar", "gateway", nil)) assert.Nil(s.DeleteService(23, "bar", "gateway", nil))
assert.True(watchFired(ws)) assert.True(watchFired(ws))
@ -3637,6 +3658,22 @@ func TestStateStore_CheckConnectServiceNodes_Gateways(t *testing.T) {
assert.Equal("gateway", nodes[1].Service.Service) assert.Equal("gateway", nodes[1].Service.Service)
assert.Equal("gateway-2", nodes[1].Service.ID) assert.Equal("gateway-2", nodes[1].Service.ID)
assert.Equal(443, nodes[1].Service.Port) assert.Equal(443, nodes[1].Service.Port)
// Index should not slide back after deleting all instances of the gateway
assert.Nil(s.DeleteService(24, "foo", "gateway-2", nil))
assert.True(watchFired(ws))
idx, nodes, err = s.CheckConnectServiceNodes(ws, "db", nil)
assert.Nil(err)
assert.Equal(idx, uint64(24))
assert.Len(nodes, 1)
// Ensure that remaining node is the proxy and not a gateway
assert.Equal(structs.ServiceKindConnectProxy, nodes[0].Service.Kind)
assert.Equal("foo", nodes[0].Node.Node)
assert.Equal("proxy", nodes[0].Service.Service)
assert.Equal("proxy", nodes[0].Service.ID)
assert.Equal(8000, nodes[0].Service.Port)
} }
func BenchmarkCheckServiceNodes(b *testing.B) { func BenchmarkCheckServiceNodes(b *testing.B) {
@ -4484,6 +4521,7 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
}, },
{ {
Name: "db", Name: "db",
@ -4493,6 +4531,7 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
CAFile: "ca.crt", CAFile: "ca.crt",
CertFile: "client.crt", CertFile: "client.crt",
KeyFile: "client.key", KeyFile: "client.key",
SNI: "my-alt-domain",
}, },
}, },
}, nil)) }, nil))
@ -4513,6 +4552,7 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
CreateIndex: 22, CreateIndex: 22,
ModifyIndex: 22, ModifyIndex: 22,
@ -4547,6 +4587,7 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
CreateIndex: 22, CreateIndex: 22,
ModifyIndex: 22, ModifyIndex: 22,
@ -4568,6 +4609,7 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
CAFile: "ca.crt", CAFile: "ca.crt",
CertFile: "client.crt", CertFile: "client.crt",
KeyFile: "client.key", KeyFile: "client.key",
SNI: "my-alt-domain",
FromWildcard: true, FromWildcard: true,
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
CreateIndex: 23, CreateIndex: 23,
@ -4594,6 +4636,7 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
CAFile: "api/ca.crt", CAFile: "api/ca.crt",
CertFile: "api/client.crt", CertFile: "api/client.crt",
KeyFile: "api/client.key", KeyFile: "api/client.key",
SNI: "my-domain",
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
CreateIndex: 22, CreateIndex: 22,
ModifyIndex: 22, ModifyIndex: 22,

View File

@ -93,6 +93,11 @@ type configSnapshotTerminatingGateway struct {
// ServiceGroups is a map of service id to the service instances of that // ServiceGroups is a map of service id to the service instances of that
// service in the local datacenter. // service in the local datacenter.
ServiceGroups map[structs.ServiceID]structs.CheckServiceNodes ServiceGroups map[structs.ServiceID]structs.CheckServiceNodes
// GatewayServices is a map of service id to the config entry association
// between the gateway and a service. TLS configuration stored here is
// used for TLS origination from the gateway to the linked service.
GatewayServices map[structs.ServiceID]structs.GatewayService
} }
func (c *configSnapshotTerminatingGateway) IsEmpty() bool { func (c *configSnapshotTerminatingGateway) IsEmpty() bool {
@ -105,7 +110,8 @@ func (c *configSnapshotTerminatingGateway) IsEmpty() bool {
len(c.ServiceGroups) == 0 && len(c.ServiceGroups) == 0 &&
len(c.WatchedServices) == 0 && len(c.WatchedServices) == 0 &&
len(c.ServiceResolvers) == 0 && len(c.ServiceResolvers) == 0 &&
len(c.WatchedResolvers) == 0 len(c.WatchedResolvers) == 0 &&
len(c.GatewayServices) == 0
} }
type configSnapshotMeshGateway struct { type configSnapshotMeshGateway struct {

View File

@ -543,6 +543,7 @@ func (s *state) initialConfigSnapshot() ConfigSnapshot {
snap.TerminatingGateway.ServiceLeaves = make(map[structs.ServiceID]*structs.IssuedCert) snap.TerminatingGateway.ServiceLeaves = make(map[structs.ServiceID]*structs.IssuedCert)
snap.TerminatingGateway.ServiceGroups = make(map[structs.ServiceID]structs.CheckServiceNodes) snap.TerminatingGateway.ServiceGroups = make(map[structs.ServiceID]structs.CheckServiceNodes)
snap.TerminatingGateway.ServiceResolvers = make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry) snap.TerminatingGateway.ServiceResolvers = make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry)
snap.TerminatingGateway.GatewayServices = make(map[structs.ServiceID]structs.GatewayService)
case structs.ServiceKindMeshGateway: case structs.ServiceKindMeshGateway:
snap.MeshGateway.WatchedServices = make(map[structs.ServiceID]context.CancelFunc) snap.MeshGateway.WatchedServices = make(map[structs.ServiceID]context.CancelFunc)
snap.MeshGateway.WatchedDatacenters = make(map[string]context.CancelFunc) snap.MeshGateway.WatchedDatacenters = make(map[string]context.CancelFunc)
@ -914,6 +915,9 @@ func (s *state) handleUpdateTerminatingGateway(u cache.UpdateEvent, snap *Config
// Make sure to add every service to this map, we use it to cancel watches below. // Make sure to add every service to this map, we use it to cancel watches below.
svcMap[svc.Service] = struct{}{} svcMap[svc.Service] = struct{}{}
// Store the gateway <-> service mapping for TLS origination
snap.TerminatingGateway.GatewayServices[svc.Service] = *svc
// Watch the health endpoint to discover endpoints for the service // Watch the health endpoint to discover endpoints for the service
if _, ok := snap.TerminatingGateway.WatchedServices[svc.Service]; !ok { if _, ok := snap.TerminatingGateway.WatchedServices[svc.Service]; !ok {
ctx, cancel := context.WithCancel(s.ctx) ctx, cancel := context.WithCancel(s.ctx)
@ -1013,6 +1017,13 @@ func (s *state) handleUpdateTerminatingGateway(u cache.UpdateEvent, snap *Config
} }
} }
// Delete gateway service mapping for services that were not in the update
for sid, _ := range snap.TerminatingGateway.GatewayServices {
if _, ok := svcMap[sid]; !ok {
delete(snap.TerminatingGateway.GatewayServices, sid)
}
}
// Cancel service instance watches for services that were not in the update // Cancel service instance watches for services that were not in the update
for sid, cancelFn := range snap.TerminatingGateway.WatchedServices { for sid, cancelFn := range snap.TerminatingGateway.WatchedServices {
if _, ok := svcMap[sid]; !ok { if _, ok := svcMap[sid]; !ok {

View File

@ -921,6 +921,10 @@ func TestState_WatchesAndUpdates(t *testing.T) {
require.Len(t, snap.TerminatingGateway.WatchedResolvers, 2) require.Len(t, snap.TerminatingGateway.WatchedResolvers, 2)
require.Contains(t, snap.TerminatingGateway.WatchedResolvers, db) require.Contains(t, snap.TerminatingGateway.WatchedResolvers, db)
require.Contains(t, snap.TerminatingGateway.WatchedResolvers, billing) require.Contains(t, snap.TerminatingGateway.WatchedResolvers, billing)
require.Len(t, snap.TerminatingGateway.GatewayServices, 2)
require.Contains(t, snap.TerminatingGateway.GatewayServices, db)
require.Contains(t, snap.TerminatingGateway.GatewayServices, billing)
}, },
}, },
verificationStage{ verificationStage{
@ -1048,6 +1052,9 @@ func TestState_WatchesAndUpdates(t *testing.T) {
require.Len(t, snap.TerminatingGateway.WatchedResolvers, 1) require.Len(t, snap.TerminatingGateway.WatchedResolvers, 1)
require.Contains(t, snap.TerminatingGateway.WatchedResolvers, billing) require.Contains(t, snap.TerminatingGateway.WatchedResolvers, billing)
require.Len(t, snap.TerminatingGateway.GatewayServices, 1)
require.Contains(t, snap.TerminatingGateway.GatewayServices, billing)
// There was no update event for billing's leaf/endpoints, so length is 0 // There was no update event for billing's leaf/endpoints, so length is 0
require.Len(t, snap.TerminatingGateway.ServiceGroups, 0) require.Len(t, snap.TerminatingGateway.ServiceGroups, 0)
require.Len(t, snap.TerminatingGateway.ServiceLeaves, 0) require.Len(t, snap.TerminatingGateway.ServiceLeaves, 0)

View File

@ -1496,6 +1496,18 @@ func testConfigSnapshotTerminatingGateway(t testing.T, populateServices bool) *C
web: webNodes, web: webNodes,
api: apiNodes, api: apiNodes,
}, },
GatewayServices: map[structs.ServiceID]structs.GatewayService{
web: {
Service: web,
CAFile: "ca.cert.pem",
},
api: {
Service: api,
CAFile: "ca.cert.pem",
CertFile: "api.cert.pem",
KeyFile: "api.key.pem",
},
},
} }
snap.TerminatingGateway.ServiceLeaves = map[structs.ServiceID]*structs.IssuedCert{ snap.TerminatingGateway.ServiceLeaves = map[structs.ServiceID]*structs.IssuedCert{
structs.NewServiceID("web", nil): { structs.NewServiceID("web", nil): {

View File

@ -200,6 +200,9 @@ type LinkedService struct {
// from the gateway to the linked service // from the gateway to the linked service
KeyFile string `json:",omitempty"` KeyFile string `json:",omitempty"`
// SNI is the optional name to specify during the TLS handshake with a linked service
SNI string `json:",omitempty"`
EnterpriseMeta `hcl:",squash" mapstructure:",squash"` EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
} }
@ -250,8 +253,9 @@ func (e *TerminatingGatewayConfigEntry) Validate() error {
} }
seen[cid] = true seen[cid] = true
// If any TLS config flag was specified, all must be // If either client cert config file was specified then the CA file, client cert, and key file must be specified
if (svc.CAFile != "" || svc.CertFile != "" || svc.KeyFile != "") && // Specifying only a CAFile is allowed for one-way TLS
if (svc.CertFile != "" || svc.KeyFile != "") &&
!(svc.CAFile != "" && svc.CertFile != "" && svc.KeyFile != "") { !(svc.CAFile != "" && svc.CertFile != "" && svc.KeyFile != "") {
return fmt.Errorf("Service %q must have a CertFile, CAFile, and KeyFile specified for TLS origination", svc.Name) return fmt.Errorf("Service %q must have a CertFile, CAFile, and KeyFile specified for TLS origination", svc.Name)
@ -299,6 +303,7 @@ type GatewayService struct {
CAFile string CAFile string
CertFile string CertFile string
KeyFile string KeyFile string
SNI string
FromWildcard bool FromWildcard bool
RaftIndex RaftIndex
} }
@ -312,18 +317,22 @@ func (g *GatewayService) IsSame(o *GatewayService) bool {
g.Port == o.Port && g.Port == o.Port &&
g.CAFile == o.CAFile && g.CAFile == o.CAFile &&
g.CertFile == o.CertFile && g.CertFile == o.CertFile &&
g.KeyFile == o.KeyFile g.KeyFile == o.KeyFile &&
g.SNI == o.SNI &&
g.FromWildcard == o.FromWildcard
} }
func (g *GatewayService) Clone() *GatewayService { func (g *GatewayService) Clone() *GatewayService {
return &GatewayService{ return &GatewayService{
Gateway: g.Gateway, Gateway: g.Gateway,
Service: g.Service, Service: g.Service,
GatewayKind: g.GatewayKind, GatewayKind: g.GatewayKind,
Port: g.Port, Port: g.Port,
CAFile: g.CAFile, CAFile: g.CAFile,
CertFile: g.CertFile, CertFile: g.CertFile,
KeyFile: g.KeyFile, KeyFile: g.KeyFile,
RaftIndex: g.RaftIndex, SNI: g.SNI,
FromWildcard: g.FromWildcard,
RaftIndex: g.RaftIndex,
} }
} }

View File

@ -315,8 +315,8 @@ func TestTerminatingConfigEntry_Validate(t *testing.T) {
Name: "terminating-gw-west", Name: "terminating-gw-west",
Services: []LinkedService{ Services: []LinkedService{
{ {
Name: "web", Name: "web",
CAFile: "ca.crt", CertFile: "client.crt",
}, },
}, },
}, },
@ -324,20 +324,6 @@ func TestTerminatingConfigEntry_Validate(t *testing.T) {
}, },
{ {
name: "not all TLS options provided-2", name: "not all TLS options provided-2",
entry: TerminatingGatewayConfigEntry{
Kind: "terminating-gateway",
Name: "terminating-gw-west",
Services: []LinkedService{
{
Name: "web",
CertFile: "client.crt",
},
},
},
expectErr: "must have a CertFile, CAFile, and KeyFile",
},
{
name: "not all TLS options provided-3",
entry: TerminatingGatewayConfigEntry{ entry: TerminatingGatewayConfigEntry{
Kind: "terminating-gateway", Kind: "terminating-gateway",
Name: "terminating-gw-west", Name: "terminating-gw-west",
@ -350,51 +336,6 @@ func TestTerminatingConfigEntry_Validate(t *testing.T) {
}, },
expectErr: "must have a CertFile, CAFile, and KeyFile", expectErr: "must have a CertFile, CAFile, and KeyFile",
}, },
{
name: "not all TLS options provided-4",
entry: TerminatingGatewayConfigEntry{
Kind: "terminating-gateway",
Name: "terminating-gw-west",
Services: []LinkedService{
{
Name: "web",
CAFile: "ca.crt",
KeyFile: "tls.key",
},
},
},
expectErr: "must have a CertFile, CAFile, and KeyFile",
},
{
name: "not all TLS options provided-5",
entry: TerminatingGatewayConfigEntry{
Kind: "terminating-gateway",
Name: "terminating-gw-west",
Services: []LinkedService{
{
Name: "web",
CAFile: "ca.crt",
CertFile: "client.crt",
},
},
},
expectErr: "must have a CertFile, CAFile, and KeyFile",
},
{
name: "not all TLS options provided-6",
entry: TerminatingGatewayConfigEntry{
Kind: "terminating-gateway",
Name: "terminating-gw-west",
Services: []LinkedService{
{
Name: "web",
KeyFile: "tls.key",
CertFile: "client.crt",
},
},
},
expectErr: "must have a CertFile, CAFile, and KeyFile",
},
{ {
name: "all TLS options provided", name: "all TLS options provided",
entry: TerminatingGatewayConfigEntry{ entry: TerminatingGatewayConfigEntry{
@ -410,6 +351,19 @@ func TestTerminatingConfigEntry_Validate(t *testing.T) {
}, },
}, },
}, },
{
name: "only providing ca file is allowed",
entry: TerminatingGatewayConfigEntry{
Kind: "terminating-gateway",
Name: "terminating-gw-west",
Services: []LinkedService{
{
Name: "web",
CAFile: "ca.crt",
},
},
},
},
} }
for _, test := range cases { for _, test := range cases {

View File

@ -658,12 +658,14 @@ func TestDecodeConfigEntry(t *testing.T) {
ca_file = "/etc/payments/ca.pem", ca_file = "/etc/payments/ca.pem",
cert_file = "/etc/payments/cert.pem", cert_file = "/etc/payments/cert.pem",
key_file = "/etc/payments/tls.key", key_file = "/etc/payments/tls.key",
sni = "mydomain",
}, },
{ {
name = "*", name = "*",
ca_file = "/etc/all/ca.pem", ca_file = "/etc/all/ca.pem",
cert_file = "/etc/all/cert.pem", cert_file = "/etc/all/cert.pem",
key_file = "/etc/all/tls.key", key_file = "/etc/all/tls.key",
sni = "my-alt-domain",
}, },
] ]
`, `,
@ -676,12 +678,14 @@ func TestDecodeConfigEntry(t *testing.T) {
CAFile = "/etc/payments/ca.pem", CAFile = "/etc/payments/ca.pem",
CertFile = "/etc/payments/cert.pem", CertFile = "/etc/payments/cert.pem",
KeyFile = "/etc/payments/tls.key", KeyFile = "/etc/payments/tls.key",
SNI = "mydomain",
}, },
{ {
Name = "*", Name = "*",
CAFile = "/etc/all/ca.pem", CAFile = "/etc/all/ca.pem",
CertFile = "/etc/all/cert.pem", CertFile = "/etc/all/cert.pem",
KeyFile = "/etc/all/tls.key", KeyFile = "/etc/all/tls.key",
SNI = "my-alt-domain",
}, },
] ]
`, `,
@ -694,12 +698,14 @@ func TestDecodeConfigEntry(t *testing.T) {
CAFile: "/etc/payments/ca.pem", CAFile: "/etc/payments/ca.pem",
CertFile: "/etc/payments/cert.pem", CertFile: "/etc/payments/cert.pem",
KeyFile: "/etc/payments/tls.key", KeyFile: "/etc/payments/tls.key",
SNI: "mydomain",
}, },
{ {
Name: "*", Name: "*",
CAFile: "/etc/all/ca.pem", CAFile: "/etc/all/ca.pem",
CertFile: "/etc/all/cert.pem", CertFile: "/etc/all/cert.pem",
KeyFile: "/etc/all/tls.key", KeyFile: "/etc/all/tls.key",
SNI: "my-alt-domain",
}, },
}, },
}, },

View File

@ -2139,22 +2139,28 @@ func TestGatewayService_IsSame(t *testing.T) {
ca := "ca.pem" ca := "ca.pem"
cert := "client.pem" cert := "client.pem"
key := "tls.key" key := "tls.key"
sni := "mydomain"
wildcard := false
g := &GatewayService{ g := &GatewayService{
Gateway: gateway, Gateway: gateway,
Service: svc, Service: svc,
GatewayKind: kind, GatewayKind: kind,
CAFile: ca, CAFile: ca,
CertFile: cert, CertFile: cert,
KeyFile: key, KeyFile: key,
SNI: sni,
FromWildcard: wildcard,
} }
other := &GatewayService{ other := &GatewayService{
Gateway: gateway, Gateway: gateway,
Service: svc, Service: svc,
GatewayKind: kind, GatewayKind: kind,
CAFile: ca, CAFile: ca,
CertFile: cert, CertFile: cert,
KeyFile: key, KeyFile: key,
SNI: sni,
FromWildcard: wildcard,
} }
check := func(twiddle, restore func()) { check := func(twiddle, restore func()) {
t.Helper() t.Helper()
@ -2178,6 +2184,8 @@ func TestGatewayService_IsSame(t *testing.T) {
check(func() { other.CAFile = "/certs/cert.pem" }, func() { other.CAFile = ca }) check(func() { other.CAFile = "/certs/cert.pem" }, func() { other.CAFile = ca })
check(func() { other.CertFile = "/certs/cert.pem" }, func() { other.CertFile = cert }) check(func() { other.CertFile = "/certs/cert.pem" }, func() { other.CertFile = cert })
check(func() { other.KeyFile = "/certs/cert.pem" }, func() { other.KeyFile = key }) check(func() { other.KeyFile = "/certs/cert.pem" }, func() { other.KeyFile = key })
check(func() { other.SNI = "alt-domain" }, func() { other.SNI = sni })
check(func() { other.FromWildcard = true }, func() { other.FromWildcard = wildcard })
if !g.IsSame(other) { if !g.IsSame(other) {
t.Fatalf("should be equal, was %#v VS %#v", g, other) t.Fatalf("should be equal, was %#v VS %#v", g, other)

View File

@ -31,7 +31,7 @@ func (s *Server) clustersFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot, _ string
case structs.ServiceKindConnectProxy: case structs.ServiceKindConnectProxy:
return s.clustersFromSnapshotConnectProxy(cfgSnap) return s.clustersFromSnapshotConnectProxy(cfgSnap)
case structs.ServiceKindTerminatingGateway: case structs.ServiceKindTerminatingGateway:
return s.clustersFromSnapshotTerminatingGateway(cfgSnap) return s.makeGatewayServiceClusters(cfgSnap)
case structs.ServiceKindMeshGateway: case structs.ServiceKindMeshGateway:
return s.clustersFromSnapshotMeshGateway(cfgSnap) return s.clustersFromSnapshotMeshGateway(cfgSnap)
case structs.ServiceKindIngressGateway: case structs.ServiceKindIngressGateway:
@ -119,12 +119,6 @@ func makeExposeClusterName(destinationPort int) string {
return fmt.Sprintf("exposed_cluster_%d", destinationPort) return fmt.Sprintf("exposed_cluster_%d", destinationPort)
} }
// clustersFromSnapshotTerminatingGateway returns the xDS API representation of the "clusters"
// for a terminating gateway. This will include 1 cluster per service and service subset.
func (s *Server) clustersFromSnapshotTerminatingGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
return s.clustersFromServicesAndResolvers(cfgSnap, cfgSnap.TerminatingGateway.ServiceGroups, cfgSnap.TerminatingGateway.ServiceResolvers)
}
// clustersFromSnapshotMeshGateway returns the xDS API representation of the "clusters" // clustersFromSnapshotMeshGateway returns the xDS API representation of the "clusters"
// for a mesh gateway. This will include 1 cluster per remote datacenter as well as // for a mesh gateway. This will include 1 cluster per remote datacenter as well as
// 1 cluster for each service subset. // 1 cluster for each service subset.
@ -141,7 +135,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
} }
clusterName := connect.DatacenterSNI(dc, cfgSnap.Roots.TrustDomain) clusterName := connect.DatacenterSNI(dc, cfgSnap.Roots.TrustDomain)
cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) cluster, err := s.makeGatewayCluster(cfgSnap, clusterName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -153,7 +147,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
for _, dc := range datacenters { for _, dc := range datacenters {
clusterName := cfgSnap.ServerSNIFn(dc, "") clusterName := cfgSnap.ServerSNIFn(dc, "")
cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) cluster, err := s.makeGatewayCluster(cfgSnap, clusterName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -164,7 +158,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
for _, srv := range cfgSnap.MeshGateway.ConsulServers { for _, srv := range cfgSnap.MeshGateway.ConsulServers {
clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node)
cluster, err := s.makeGatewayCluster(clusterName, cfgSnap) cluster, err := s.makeGatewayCluster(cfgSnap, clusterName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -173,7 +167,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
} }
// generate the per-service/subset clusters // generate the per-service/subset clusters
c, err := s.clustersFromServicesAndResolvers(cfgSnap, cfgSnap.MeshGateway.ServiceGroups, cfgSnap.MeshGateway.ServiceResolvers) c, err := s.makeGatewayServiceClusters(cfgSnap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -182,10 +176,20 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
return clusters, nil return clusters, nil
} }
func (s *Server) clustersFromServicesAndResolvers( func (s *Server) makeGatewayServiceClusters(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) {
cfgSnap *proxycfg.ConfigSnapshot, var services map[structs.ServiceID]structs.CheckServiceNodes
services map[structs.ServiceID]structs.CheckServiceNodes, var resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry
resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry) ([]proto.Message, error) {
switch cfgSnap.Kind {
case structs.ServiceKindTerminatingGateway:
services = cfgSnap.TerminatingGateway.ServiceGroups
resolvers = cfgSnap.TerminatingGateway.ServiceResolvers
case structs.ServiceKindMeshGateway:
services = cfgSnap.MeshGateway.ServiceGroups
resolvers = cfgSnap.MeshGateway.ServiceResolvers
default:
return nil, fmt.Errorf("unsupported gateway kind %q", cfgSnap.Kind)
}
clusters := make([]proto.Message, 0, len(services)) clusters := make([]proto.Message, 0, len(services))
@ -196,28 +200,34 @@ func (s *Server) clustersFromServicesAndResolvers(
// Create the cluster for default/unnamed services // Create the cluster for default/unnamed services
var cluster *envoy.Cluster var cluster *envoy.Cluster
var err error var err error
if hasResolver {
cluster, err = s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) if !hasResolver {
} else { // Use a zero value resolver with no timeout and no subsets
cluster, err = s.makeGatewayCluster(clusterName, cfgSnap) resolver = &structs.ServiceResolverConfigEntry{}
} }
cluster, err = s.makeGatewayClusterWithConnectTimeout(cfgSnap, clusterName, resolver.ConnectTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to make %s cluster: %v", cfgSnap.Kind, err) return nil, fmt.Errorf("failed to make %s cluster: %v", cfgSnap.Kind, err)
} }
if cfgSnap.Kind == structs.ServiceKindTerminatingGateway {
injectTerminatingGatewayTLSContext(cfgSnap, cluster, svc)
}
clusters = append(clusters, cluster) clusters = append(clusters, cluster)
// if there is a service-resolver for this service then also setup subset clusters for it // If there is a service-resolver for this service then also setup a cluster for each subset
if hasResolver { for subsetName := range resolver.Subsets {
// generate 1 cluster for each service subset clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
for subsetName := range resolver.Subsets {
clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
cluster, err := s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, resolver.ConnectTimeout) cluster, err := s.makeGatewayClusterWithConnectTimeout(cfgSnap, clusterName, resolver.ConnectTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to make %s cluster: %v", cfgSnap.Kind, err) return nil, fmt.Errorf("failed to make %s cluster: %v", cfgSnap.Kind, err)
}
clusters = append(clusters, cluster)
} }
if cfgSnap.Kind == structs.ServiceKindTerminatingGateway {
injectTerminatingGatewayTLSContext(cfgSnap, cluster, svc)
}
clusters = append(clusters, cluster)
} }
} }
@ -349,7 +359,7 @@ func (s *Server) makeUpstreamClusterForPreparedQuery(upstream structs.Upstream,
// Enable TLS upstream with the configured client certificate. // Enable TLS upstream with the configured client certificate.
c.TlsContext = &envoyauth.UpstreamTlsContext{ c.TlsContext = &envoyauth.UpstreamTlsContext{
CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.Leaf()), CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
Sni: sni, Sni: sni,
} }
@ -460,7 +470,7 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain(
// Enable TLS upstream with the configured client certificate. // Enable TLS upstream with the configured client certificate.
c.TlsContext = &envoyauth.UpstreamTlsContext{ c.TlsContext = &envoyauth.UpstreamTlsContext{
CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.Leaf()), CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
Sni: sni, Sni: sni,
} }
@ -528,15 +538,16 @@ func makeClusterFromUserConfig(configJSON string) (*envoy.Cluster, error) {
return &c, err return &c, err
} }
func (s *Server) makeGatewayCluster(clusterName string, cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Cluster, error) { func (s *Server) makeGatewayCluster(cfgSnap *proxycfg.ConfigSnapshot, clusterName string) (*envoy.Cluster, error) {
return s.makeGatewayClusterWithConnectTimeout(clusterName, cfgSnap, 0) return s.makeGatewayClusterWithConnectTimeout(cfgSnap, clusterName, 0)
} }
// makeGatewayClusterWithConnectTimeout initializes a gateway cluster // makeGatewayClusterWithConnectTimeout initializes a gateway cluster
// with the specified connect timeout. If the timeout is 0, the connect timeout // with the specified connect timeout. If the timeout is 0, the connect timeout
// defaults to use the configured gateway timeout. // defaults to use the configured gateway timeout.
func (s *Server) makeGatewayClusterWithConnectTimeout(clusterName string, cfgSnap *proxycfg.ConfigSnapshot, func (s *Server) makeGatewayClusterWithConnectTimeout(cfgSnap *proxycfg.ConfigSnapshot,
connectTimeout time.Duration) (*envoy.Cluster, error) { clusterName string, connectTimeout time.Duration) (*envoy.Cluster, error) {
cfg, err := ParseGatewayConfig(cfgSnap.Proxy.Config) cfg, err := ParseGatewayConfig(cfgSnap.Proxy.Config)
if err != nil { if err != nil {
// Don't hard fail on a config typo, just warn. The parse func returns // Don't hard fail on a config typo, just warn. The parse func returns
@ -548,7 +559,7 @@ func (s *Server) makeGatewayClusterWithConnectTimeout(clusterName string, cfgSna
connectTimeout = time.Duration(cfg.ConnectTimeoutMs) * time.Millisecond connectTimeout = time.Duration(cfg.ConnectTimeoutMs) * time.Millisecond
} }
return &envoy.Cluster{ cluster := envoy.Cluster{
Name: clusterName, Name: clusterName,
ConnectTimeout: connectTimeout, ConnectTimeout: connectTimeout,
ClusterDiscoveryType: &envoy.Cluster_Type{Type: envoy.Cluster_EDS}, ClusterDiscoveryType: &envoy.Cluster_Type{Type: envoy.Cluster_EDS},
@ -561,7 +572,21 @@ func (s *Server) makeGatewayClusterWithConnectTimeout(clusterName string, cfgSna
}, },
// Having an empty config enables outlier detection with default config. // Having an empty config enables outlier detection with default config.
OutlierDetection: &envoycluster.OutlierDetection{}, OutlierDetection: &envoycluster.OutlierDetection{},
}, nil }
return &cluster, nil
}
// injectTerminatingGatewayTLSContext adds an UpstreamTlsContext to a cluster for TLS origination
func injectTerminatingGatewayTLSContext(cfgSnap *proxycfg.ConfigSnapshot, cluster *envoy.Cluster, service structs.ServiceID) {
if mapping, ok := cfgSnap.TerminatingGateway.GatewayServices[service]; ok && mapping.CAFile != "" {
cluster.TlsContext = &envoyauth.UpstreamTlsContext{
CommonTlsContext: makeCommonTLSContextFromFiles(mapping.CAFile, mapping.CertFile, mapping.KeyFile),
// TODO (gateways) (freddy) If mapping.SNI is empty, does Envoy behave any differently if TlsContext.Sni is excluded?
Sni: mapping.SNI,
}
}
} }
func makeThresholdsIfNeeded(limits UpstreamLimits) []*envoycluster.CircuitBreakers_Thresholds { func makeThresholdsIfNeeded(limits UpstreamLimits) []*envoycluster.CircuitBreakers_Thresholds {

View File

@ -367,7 +367,7 @@ func makeListenerFromUserConfig(configJSON string) (*envoy.Listener, error) {
// specify custom listener params in config but still get our certs delivered // specify custom listener params in config but still get our certs delivered
// dynamically and intentions enforced without coming up with some complicated // dynamically and intentions enforced without coming up with some complicated
// templating/merging solution. // templating/merging solution.
func injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, token string, listener *envoy.Listener) error { func injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, token string, listener *envoy.Listener, setTLS bool) error {
authFilter, err := makeExtAuthFilter(token) authFilter, err := makeExtAuthFilter(token)
if err != nil { if err != nil {
return err return err
@ -378,7 +378,7 @@ func injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, token string, listen
append([]envoylistener.Filter{authFilter}, listener.FilterChains[idx].Filters...) append([]envoylistener.Filter{authFilter}, listener.FilterChains[idx].Filters...)
listener.FilterChains[idx].TlsContext = &envoyauth.DownstreamTlsContext{ listener.FilterChains[idx].TlsContext = &envoyauth.DownstreamTlsContext{
CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.Leaf()), CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.Leaf()),
RequireClientCertificate: &types.BoolValue{Value: true}, RequireClientCertificate: &types.BoolValue{Value: true},
} }
} }
@ -439,7 +439,7 @@ func (s *Server) makePublicListener(cfgSnap *proxycfg.ConfigSnapshot, token stri
} }
} }
err = injectConnectFilters(cfgSnap, token, l) err = injectConnectFilters(cfgSnap, token, l, true)
return l, err return l, err
} }
@ -642,7 +642,7 @@ func (s *Server) sniFilterChainTerminatingGateway(listener, cluster, token strin
tcpProxy, tcpProxy,
}, },
TlsContext: &envoyauth.DownstreamTlsContext{ TlsContext: &envoyauth.DownstreamTlsContext{
CommonTlsContext: makeCommonTLSContext(cfgSnap, cfgSnap.TerminatingGateway.ServiceLeaves[service]), CommonTlsContext: makeCommonTLSContextFromLeaf(cfgSnap, cfgSnap.TerminatingGateway.ServiceLeaves[service]),
RequireClientCertificate: &types.BoolValue{Value: true}, RequireClientCertificate: &types.BoolValue{Value: true},
}, },
}, err }, err
@ -1011,7 +1011,7 @@ func makeFilter(name string, cfg proto.Message) (envoylistener.Filter, error) {
}, nil }, nil
} }
func makeCommonTLSContext(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.IssuedCert) *envoyauth.CommonTlsContext { func makeCommonTLSContextFromLeaf(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.IssuedCert) *envoyauth.CommonTlsContext {
// Concatenate all the root PEMs into one. // Concatenate all the root PEMs into one.
// TODO(banks): verify this actually works with Envoy (docs are not clear). // TODO(banks): verify this actually works with Envoy (docs are not clear).
rootPEMS := "" rootPEMS := ""
@ -1050,3 +1050,42 @@ func makeCommonTLSContext(cfgSnap *proxycfg.ConfigSnapshot, leaf *structs.Issued
}, },
} }
} }
func makeCommonTLSContextFromFiles(caFile, certFile, keyFile string) *envoyauth.CommonTlsContext {
ctx := envoyauth.CommonTlsContext{
TlsParams: &envoyauth.TlsParameters{},
}
// Verify certificate of peer if caFile is specified
if caFile != "" {
ctx.ValidationContextType = &envoyauth.CommonTlsContext_ValidationContext{
ValidationContext: &envoyauth.CertificateValidationContext{
TrustedCa: &envoycore.DataSource{
Specifier: &envoycore.DataSource_Filename{
Filename: caFile,
},
},
},
}
}
// Present certificate for mTLS if cert and key files are specified
if certFile != "" && keyFile != "" {
ctx.TlsCertificates = []*envoyauth.TlsCertificate{
{
CertificateChain: &envoycore.DataSource{
Specifier: &envoycore.DataSource_Filename{
Filename: certFile,
},
},
PrivateKey: &envoycore.DataSource{
Specifier: &envoycore.DataSource_Filename{
Filename: keyFile,
},
},
},
}
}
return &ctx
}

View File

@ -13,6 +13,28 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -29,6 +51,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -45,6 +79,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -61,6 +107,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }

View File

@ -13,6 +13,28 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -29,6 +51,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -45,6 +79,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -61,6 +107,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }

View File

@ -13,6 +13,28 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }
@ -29,6 +51,18 @@
} }
}, },
"connectTimeout": "5s", "connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": { "outlierDetection": {
} }

View File

@ -129,6 +129,9 @@ type LinkedService struct {
// KeyFile is the optional path to a private key to use for TLS connections // KeyFile is the optional path to a private key to use for TLS connections
// from the gateway to the linked service // from the gateway to the linked service
KeyFile string `json:",omitempty"` KeyFile string `json:",omitempty"`
// SNI is the optional name to specify during the TLS handshake with a linked service
SNI string `json:",omitempty"`
} }
func (g *TerminatingGatewayConfigEntry) GetKind() string { func (g *TerminatingGatewayConfigEntry) GetKind() string {

View File

@ -185,6 +185,7 @@ func TestAPI_ConfigEntries_TerminatingGateway(t *testing.T) {
CAFile: "/etc/web/ca.crt", CAFile: "/etc/web/ca.crt",
CertFile: "/etc/web/client.crt", CertFile: "/etc/web/client.crt",
KeyFile: "/etc/web/tls.key", KeyFile: "/etc/web/tls.key",
SNI: "mydomain",
}, },
} }
@ -212,6 +213,7 @@ func TestAPI_ConfigEntries_TerminatingGateway(t *testing.T) {
CAFile: "/etc/certs/ca.crt", CAFile: "/etc/certs/ca.crt",
CertFile: "/etc/certs/client.crt", CertFile: "/etc/certs/client.crt",
KeyFile: "/etc/certs/tls.key", KeyFile: "/etc/certs/tls.key",
SNI: "mydomain",
}, },
} }
_, wm, err = configEntries.Set(terminating2, nil) _, wm, err = configEntries.Set(terminating2, nil)

View File

@ -686,7 +686,8 @@ func TestDecodeConfigEntry(t *testing.T) {
"Name": "web", "Name": "web",
"CAFile": "/etc/ca.pem", "CAFile": "/etc/ca.pem",
"CertFile": "/etc/cert.pem", "CertFile": "/etc/cert.pem",
"KeyFile": "/etc/tls.key" "KeyFile": "/etc/tls.key",
"SNI": "mydomain"
}, },
{ {
"Name": "api" "Name": "api"
@ -707,6 +708,7 @@ func TestDecodeConfigEntry(t *testing.T) {
CAFile: "/etc/ca.pem", CAFile: "/etc/ca.pem",
CertFile: "/etc/cert.pem", CertFile: "/etc/cert.pem",
KeyFile: "/etc/tls.key", KeyFile: "/etc/tls.key",
SNI: "mydomain",
}, },
{ {
Name: "api", Name: "api",

View File

@ -258,6 +258,7 @@ func TestParseConfigEntry(t *testing.T) {
ca_file = "/etc/ca.crt" ca_file = "/etc/ca.crt"
cert_file = "/etc/client.crt" cert_file = "/etc/client.crt"
key_file = "/etc/tls.key" key_file = "/etc/tls.key"
sni = "mydomain"
}, },
{ {
name = "*" name = "*"
@ -276,6 +277,7 @@ func TestParseConfigEntry(t *testing.T) {
CAFile = "/etc/ca.crt" CAFile = "/etc/ca.crt"
CertFile = "/etc/client.crt" CertFile = "/etc/client.crt"
KeyFile = "/etc/tls.key" KeyFile = "/etc/tls.key"
SNI = "mydomain"
}, },
{ {
Name = "*" Name = "*"
@ -294,7 +296,8 @@ func TestParseConfigEntry(t *testing.T) {
"namespace": "biz", "namespace": "biz",
"ca_file": "/etc/ca.crt", "ca_file": "/etc/ca.crt",
"cert_file": "/etc/client.crt", "cert_file": "/etc/client.crt",
"key_file": "/etc/tls.key" "key_file": "/etc/tls.key",
"sni": "mydomain"
}, },
{ {
"name": "*", "name": "*",
@ -314,7 +317,8 @@ func TestParseConfigEntry(t *testing.T) {
"Namespace": "biz", "Namespace": "biz",
"CAFile": "/etc/ca.crt", "CAFile": "/etc/ca.crt",
"CertFile": "/etc/client.crt", "CertFile": "/etc/client.crt",
"KeyFile": "/etc/tls.key" "KeyFile": "/etc/tls.key",
"SNI": "mydomain"
}, },
{ {
"Name": "*", "Name": "*",
@ -334,6 +338,7 @@ func TestParseConfigEntry(t *testing.T) {
CAFile: "/etc/ca.crt", CAFile: "/etc/ca.crt",
CertFile: "/etc/client.crt", CertFile: "/etc/client.crt",
KeyFile: "/etc/tls.key", KeyFile: "/etc/tls.key",
SNI: "mydomain",
}, },
{ {
Name: "*", Name: "*",
@ -352,6 +357,7 @@ func TestParseConfigEntry(t *testing.T) {
CAFile: "/etc/ca.crt", CAFile: "/etc/ca.crt",
CertFile: "/etc/client.crt", CertFile: "/etc/client.crt",
KeyFile: "/etc/tls.key", KeyFile: "/etc/tls.key",
SNI: "mydomain",
}, },
{ {
Name: "*", Name: "*",