Skip to content

Commit

Permalink
feat: tgtwy xDS generation for destinations
Browse files Browse the repository at this point in the history
Signed-off-by: Dhia Ayachi <[email protected]>
  • Loading branch information
DanStough committed Jun 16, 2022
1 parent bd4ddb3 commit 4b402e3
Show file tree
Hide file tree
Showing 12 changed files with 720 additions and 83 deletions.
34 changes: 29 additions & 5 deletions agent/consul/config_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,31 @@ func TestConfigEntry_ResolveServiceConfig_TransparentProxy(t *testing.T) {
TransparentProxy: structs.TransparentProxyConfig{OutboundListenerPort: 808},
},
},
{
name: "from service-defaults with endpoint",
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
Mode: structs.ProxyModeTransparent,
Destination: &structs.DestinationConfig{
Address: "hello.world.com",
Port: 443,
},
},
},
request: structs.ServiceConfigRequest{
Name: "foo",
Datacenter: "dc1",
},
expect: structs.ServiceConfigResponse{
Mode: structs.ProxyModeTransparent,
Destination: structs.DestinationConfig{
Address: "hello.world.com",
Port: 443,
},
},
},
{
name: "service-defaults overrides proxy-defaults",
entries: []structs.ConfigEntry{
Expand Down Expand Up @@ -1207,11 +1232,10 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
wildcard := structs.NewServiceID(structs.WildcardSpecifier, structs.WildcardEnterpriseMetaInDefaultPartition())

tt := []struct {
name string
entries []structs.ConfigEntry
request structs.ServiceConfigRequest
proxyCfg structs.ConnectProxyConfig
expect structs.ServiceConfigResponse
name string
entries []structs.ConfigEntry
request structs.ServiceConfigRequest
expect structs.ServiceConfigResponse
}{
{
name: "upstream config entries from Upstreams and service-defaults",
Expand Down
3 changes: 3 additions & 0 deletions agent/consul/merge_service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ func computeResolvedServiceConfig(
if serviceConf.Mode != structs.ProxyModeDefault {
thisReply.Mode = serviceConf.Mode
}
if serviceConf.Destination != nil {
thisReply.Destination = *serviceConf.Destination
}

thisReply.Meta = serviceConf.Meta
}
Expand Down
34 changes: 34 additions & 0 deletions agent/proxycfg/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,14 @@ type configSnapshotTerminatingGateway struct {
// GatewayServices is a map of service name 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.
// This map does not include GatewayServices that represent Endpoints to external
// destinations.
GatewayServices map[structs.ServiceName]structs.GatewayService

// DestinationServices is a map of service name to GatewayServices that represent
// a destination to an external destination of the service mesh.
DestinationServices map[structs.ServiceName]structs.GatewayService

// HostnameServices is a map of service name to service instances with a hostname as the address.
// If hostnames are configured they must be provided to Envoy via CDS not EDS.
HostnameServices map[structs.ServiceName]structs.CheckServiceNodes
Expand Down Expand Up @@ -269,6 +275,33 @@ func (c *configSnapshotTerminatingGateway) ValidServices() []structs.ServiceName
return out
}

// ValidDestinations returns the list of service keys (that represent exclusively endpoints) that have enough data to be emitted.
func (c *configSnapshotTerminatingGateway) ValidDestinations() []structs.ServiceName {
out := make([]structs.ServiceName, 0, len(c.DestinationServices))
for svc := range c.DestinationServices {
// It only counts if ALL of our watches have come back (with data or not).

// Skip the service if we don't have a cert to present for mTLS.
if cert, ok := c.ServiceLeaves[svc]; !ok || cert == nil {
continue
}

// Skip the service if we haven't gotten our intentions yet.
if _, intentionsSet := c.Intentions[svc]; !intentionsSet {
continue
}

// Skip the service if we haven't gotten our service config yet to know
// the protocol.
if _, ok := c.ServiceConfigs[svc]; !ok || c.ServiceConfigs[svc].Destination.Address == "" {
continue
}

out = append(out, svc)
}
return out
}

// isEmpty is a test helper
func (c *configSnapshotTerminatingGateway) isEmpty() bool {
if c == nil {
Expand All @@ -286,6 +319,7 @@ func (c *configSnapshotTerminatingGateway) isEmpty() bool {
len(c.ServiceConfigs) == 0 &&
len(c.WatchedConfigs) == 0 &&
len(c.GatewayServices) == 0 &&
len(c.DestinationServices) == 0 &&
len(c.HostnameServices) == 0 &&
!c.MeshConfigSet
}
Expand Down
20 changes: 17 additions & 3 deletions agent/proxycfg/terminating_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (s *handlerTerminatingGateway) initialize(ctx context.Context) (ConfigSnaps
snap.TerminatingGateway.ServiceResolversSet = make(map[structs.ServiceName]bool)
snap.TerminatingGateway.ServiceGroups = make(map[structs.ServiceName]structs.CheckServiceNodes)
snap.TerminatingGateway.GatewayServices = make(map[structs.ServiceName]structs.GatewayService)
snap.TerminatingGateway.DestinationServices = make(map[structs.ServiceName]structs.GatewayService)
snap.TerminatingGateway.HostnameServices = make(map[structs.ServiceName]structs.CheckServiceNodes)
return snap, nil
}
Expand Down Expand Up @@ -111,10 +112,15 @@ func (s *handlerTerminatingGateway) handleUpdate(ctx context.Context, u UpdateEv
svcMap[svc.Service] = struct{}{}

// Store the gateway <-> service mapping for TLS origination
snap.TerminatingGateway.GatewayServices[svc.Service] = *svc
if svc.ServiceKind == structs.GatewayServiceKindDestination {
snap.TerminatingGateway.DestinationServices[svc.Service] = *svc
} else {
snap.TerminatingGateway.GatewayServices[svc.Service] = *svc
}

// 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 && !(svc.ServiceKind == structs.GatewayServiceKindDestination) {

ctx, cancel := context.WithCancel(ctx)
err := s.dataSources.Health.Notify(ctx, &structs.ServiceSpecificRequest{
Datacenter: s.source.Datacenter,
Expand Down Expand Up @@ -213,7 +219,8 @@ func (s *handlerTerminatingGateway) handleUpdate(ctx context.Context, u UpdateEv

// Watch service resolvers for the service
// These are used to create clusters and endpoints for the service subsets
if _, ok := snap.TerminatingGateway.WatchedResolvers[svc.Service]; !ok {
if _, ok := snap.TerminatingGateway.WatchedResolvers[svc.Service]; !ok && !(svc.ServiceKind == structs.GatewayServiceKindDestination) {

ctx, cancel := context.WithCancel(ctx)
err := s.dataSources.ConfigEntry.Notify(ctx, &structs.ConfigEntryQuery{
Datacenter: s.source.Datacenter,
Expand Down Expand Up @@ -242,6 +249,13 @@ func (s *handlerTerminatingGateway) handleUpdate(ctx context.Context, u UpdateEv
}
}

// Delete endpoint service mapping for services that were not in the update
for sn := range snap.TerminatingGateway.DestinationServices {
if _, ok := svcMap[sn]; !ok {
delete(snap.TerminatingGateway.DestinationServices, sn)
}
}

// Clean up services with hostname mapping for services that were not in the update
for sn := range snap.TerminatingGateway.HostnameServices {
if _, ok := svcMap[sn]; !ok {
Expand Down
163 changes: 139 additions & 24 deletions agent/proxycfg/testing_terminating_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import (
"github.com/hashicorp/consul/agent/structs"
)

func TestConfigSnapshotTerminatingGateway(
t testing.T,
populateServices bool,
nsFn func(ns *structs.NodeService),
extraUpdates []UpdateEvent,
) *ConfigSnapshot {
func TestConfigSnapshotTerminatingGateway(t testing.T, populateServices bool, nsFn func(ns *structs.NodeService), extraUpdates []UpdateEvent) *ConfigSnapshot {
roots, _ := TestCerts(t)

var (
Expand All @@ -34,6 +29,7 @@ func TestConfigSnapshotTerminatingGateway(
},
}

tgtwyServices := []*structs.GatewayService{}
if populateServices {
webNodes := TestUpstreamNodes(t, web.Name)
webNodes[0].Service.Meta = map[string]string{"version": "1"}
Expand Down Expand Up @@ -156,28 +152,30 @@ func TestConfigSnapshotTerminatingGateway(
},
}

tgtwyServices = append(tgtwyServices,
&structs.GatewayService{
Service: web,
CAFile: "ca.cert.pem",
},
&structs.GatewayService{
Service: api,
CAFile: "ca.cert.pem",
CertFile: "api.cert.pem",
KeyFile: "api.key.pem",
},
&structs.GatewayService{
Service: db,
},
&structs.GatewayService{
Service: cache,
},
)

baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{
{
CorrelationID: gatewayServicesWatchID,
Result: &structs.IndexedGatewayServices{
Services: []*structs.GatewayService{
{
Service: web,
CAFile: "ca.cert.pem",
},
{
Service: api,
CAFile: "ca.cert.pem",
CertFile: "api.cert.pem",
KeyFile: "api.key.pem",
},
{
Service: db,
},
{
Service: cache,
},
},
Services: tgtwyServices,
},
},
{
Expand Down Expand Up @@ -342,6 +340,123 @@ func TestConfigSnapshotTerminatingGateway(
}, nsFn, nil, testSpliceEvents(baseEvents, extraUpdates))
}

func TestConfigSnapshotTerminatingGatewayDestinations(t testing.T, populateDestinations bool, extraUpdates []UpdateEvent) *ConfigSnapshot {
roots, _ := TestCerts(t)

var (
externalIPTCP = structs.NewServiceName("external-IP-TCP", nil)
externalHostnameTCP = structs.NewServiceName("external-hostname-TCP", nil)
)

baseEvents := []UpdateEvent{
{
CorrelationID: rootsWatchID,
Result: roots,
},
{
CorrelationID: gatewayServicesWatchID,
Result: &structs.IndexedGatewayServices{
Services: nil,
},
},
}

tgtwyServices := []*structs.GatewayService{}

if populateDestinations {
tgtwyServices = append(tgtwyServices,
&structs.GatewayService{
Service: externalIPTCP,
ServiceKind: structs.GatewayServiceKindDestination,
},
&structs.GatewayService{
Service: externalHostnameTCP,
ServiceKind: structs.GatewayServiceKindDestination,
},
)

baseEvents = testSpliceEvents(baseEvents, []UpdateEvent{
{
CorrelationID: gatewayServicesWatchID,
Result: &structs.IndexedGatewayServices{
Services: tgtwyServices,
},
},
// no intentions defined for these services
{
CorrelationID: serviceIntentionsIDPrefix + externalIPTCP.String(),
Result: &structs.IndexedIntentionMatches{
Matches: []structs.Intentions{
nil,
},
},
},
{
CorrelationID: serviceIntentionsIDPrefix + externalHostnameTCP.String(),
Result: &structs.IndexedIntentionMatches{
Matches: []structs.Intentions{
nil,
},
},
},
// ========
{
CorrelationID: serviceLeafIDPrefix + externalIPTCP.String(),
Result: &structs.IssuedCert{
CertPEM: "placeholder.crt",
PrivateKeyPEM: "placeholder.key",
},
},
{
CorrelationID: serviceLeafIDPrefix + externalHostnameTCP.String(),
Result: &structs.IssuedCert{
CertPEM: "placeholder.crt",
PrivateKeyPEM: "placeholder.key",
},
},
// ========
{
CorrelationID: serviceConfigIDPrefix + externalIPTCP.String(),
Result: &structs.ServiceConfigResponse{
Mode: structs.ProxyModeTransparent,
ProxyConfig: map[string]interface{}{"protocol": "tcp"},
Destination: structs.DestinationConfig{
Address: "192.168.0.1",
Port: 80,
},
},
},
{
CorrelationID: serviceConfigIDPrefix + externalHostnameTCP.String(),
Result: &structs.ServiceConfigResponse{
Mode: structs.ProxyModeTransparent,
ProxyConfig: map[string]interface{}{"protocol": "tcp"},
Destination: structs.DestinationConfig{
Address: "*.hashicorp.com",
Port: 8089,
},
},
},
})
}

return testConfigSnapshotFixture(t, &structs.NodeService{
Kind: structs.ServiceKindTerminatingGateway,
Service: "terminating-gateway",
Address: "1.2.3.4",
Port: 8443,
Proxy: structs.ConnectProxyConfig{
Mode: structs.ProxyModeTransparent,
},
TaggedAddresses: map[string]structs.ServiceAddress{
structs.TaggedAddressWAN: {
Address: "198.18.0.1",
Port: 443,
},
},
}, nil, nil, testSpliceEvents(baseEvents, extraUpdates))
}

func TestConfigSnapshotTerminatingGatewayServiceSubsets(t testing.T) *ConfigSnapshot {
return testConfigSnapshotTerminatingGatewayServiceSubsets(t, false)
}
Expand Down
11 changes: 11 additions & 0 deletions agent/structs/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,16 @@ type DestinationConfig struct {
Port int `json:",omitempty"`
}

func (d *DestinationConfig) HasHostname() bool {
ip := net.ParseIP(d.Address)
return ip == nil
}

func (d *DestinationConfig) HasIP() bool {
ip := net.ParseIP(d.Address)
return ip != nil
}

// ProxyConfigEntry is the top-level struct for global proxy configuration defaults.
type ProxyConfigEntry struct {
Kind string
Expand Down Expand Up @@ -1043,6 +1053,7 @@ type ServiceConfigResponse struct {
Expose ExposeConfig `json:",omitempty"`
TransparentProxy TransparentProxyConfig `json:",omitempty"`
Mode ProxyMode `json:",omitempty"`
Destination DestinationConfig `json:",omitempty"`
Meta map[string]string `json:",omitempty"`
QueryMeta
}
Expand Down
Loading

0 comments on commit 4b402e3

Please sign in to comment.