Skip to content

Commit

Permalink
Merge pull request #10016 from hashicorp/topology-update
Browse files Browse the repository at this point in the history
  • Loading branch information
freddygv authored Apr 15, 2021
2 parents 439a7fc + dcd951d commit 3be304b
Show file tree
Hide file tree
Showing 11 changed files with 587 additions and 142 deletions.
3 changes: 3 additions & 0 deletions .changelog/10016.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
connect: Update the service mesh visualization to account for transparent proxies.
```
95 changes: 80 additions & 15 deletions agent/consul/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,50 @@ func registerTestCatalogEntriesMap(t *testing.T, codec rpc.ClientCodec, registra
func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token string) {
t.Helper()

// api and api-proxy on node foo - upstream: web
// ingress-gateway on node edge - upstream: api
// api and api-proxy on node foo - transparent proxy
// web and web-proxy on node bar - upstream: redis
// web and web-proxy on node baz - upstream: redis
// web and web-proxy on node baz - transparent proxy
// redis and redis-proxy on node zip
registrations := map[string]*structs.RegisterRequest{
"Node edge": {
Datacenter: "dc1",
Node: "edge",
ID: types.NodeID("8e3481c0-760e-4b5f-a3b8-6c8c559e8a15"),
Address: "127.0.0.1",
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "edge",
CheckID: "edge:alive",
Name: "edge-liveness",
Status: api.HealthPassing,
},
},
WriteRequest: structs.WriteRequest{Token: token},
},
"Service ingress on edge": {
Datacenter: "dc1",
Node: "edge",
SkipNodeUpdate: true,
Service: &structs.NodeService{
Kind: structs.ServiceKindIngressGateway,
ID: "ingress",
Service: "ingress",
Port: 8443,
Address: "198.18.1.1",
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "edge",
CheckID: "edge:ingress",
Name: "ingress-liveness",
Status: api.HealthPassing,
ServiceID: "ingress",
ServiceName: "ingress",
},
},
WriteRequest: structs.WriteRequest{Token: token},
},
"Node foo": {
Datacenter: "dc1",
Node: "foo",
Expand Down Expand Up @@ -627,13 +666,8 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
Port: 8443,
Address: "198.18.1.2",
Proxy: structs.ConnectProxyConfig{
Mode: structs.ProxyModeTransparent,
DestinationServiceName: "api",
Upstreams: structs.Upstreams{
{
DestinationName: "web",
LocalBindPort: 8080,
},
},
},
},
Checks: structs.HealthChecks{
Expand Down Expand Up @@ -767,13 +801,8 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
Port: 8443,
Address: "198.18.1.40",
Proxy: structs.ConnectProxyConfig{
Mode: structs.ProxyModeTransparent,
DestinationServiceName: "web",
Upstreams: structs.Upstreams{
{
DestinationName: "redis",
LocalBindPort: 123,
},
},
},
},
Checks: structs.HealthChecks{
Expand Down Expand Up @@ -855,7 +884,10 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
}
registerTestCatalogEntriesMap(t, codec, registrations)

// Add intentions: deny all, web -> redis with L7 perms, but omit intention for api -> web
// ingress -> api gateway config entry (but no intention)
// wildcard deny intention
// api -> web exact intention
// web -> redis exact intention
entries := []structs.ConfigEntryRequest{
{
Datacenter: "dc1",
Expand All @@ -868,6 +900,39 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
},
WriteRequest: structs.WriteRequest{Token: token},
},
{
Datacenter: "dc1",
Entry: &structs.IngressGatewayConfigEntry{
Kind: structs.IngressGateway,
Name: "ingress",
Listeners: []structs.IngressListener{
{
Port: 8443,
Protocol: "http",
Services: []structs.IngressService{
{
Name: "api",
},
},
},
},
},
WriteRequest: structs.WriteRequest{Token: token},
},
{
Datacenter: "dc1",
Entry: &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "web",
Sources: []*structs.SourceIntention{
{
Action: structs.IntentionActionAllow,
Name: "api",
},
},
},
WriteRequest: structs.WriteRequest{Token: token},
},
{
Datacenter: "dc1",
Entry: &structs.ServiceIntentionsConfigEntry{
Expand Down
142 changes: 123 additions & 19 deletions agent/consul/internal_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1690,37 +1690,46 @@ func TestInternal_ServiceTopology(t *testing.T) {
codec := rpcClient(t, s1)
defer codec.Close()

// api and api-proxy on node foo - upstream: web
// wildcard deny intention
// ingress-gateway on node edge - upstream: api
// ingress -> api gateway config entry (but no intention)

// api and api-proxy on node foo - transparent proxy
// api -> web exact intention

// web and web-proxy on node bar - upstream: redis
// web and web-proxy on node baz - upstream: redis
// web and web-proxy on node baz - transparent proxy
// web -> redis exact intention

// redis and redis-proxy on node zip
// wildcard deny intention
// web -> redis exact intentino

registerTestTopologyEntries(t, codec, "")

var (
api = structs.NewServiceName("api", structs.DefaultEnterpriseMeta())
web = structs.NewServiceName("web", structs.DefaultEnterpriseMeta())
redis = structs.NewServiceName("redis", structs.DefaultEnterpriseMeta())
ingress = structs.NewServiceName("ingress", structs.DefaultEnterpriseMeta())
api = structs.NewServiceName("api", structs.DefaultEnterpriseMeta())
web = structs.NewServiceName("web", structs.DefaultEnterpriseMeta())
redis = structs.NewServiceName("redis", structs.DefaultEnterpriseMeta())
)

t.Run("api", func(t *testing.T) {
t.Run("ingress", func(t *testing.T) {
retry.Run(t, func(r *retry.R) {
args := structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "api",
ServiceName: "ingress",
}
var out structs.IndexedServiceTopology
require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
require.False(r, out.FilteredByACLs)
require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

// bar/web, bar/web-proxy, baz/web, baz/web-proxy
require.Len(r, out.ServiceTopology.Upstreams, 4)
// foo/api, foo/api-proxy
require.Len(r, out.ServiceTopology.Upstreams, 2)
require.Len(r, out.ServiceTopology.Downstreams, 0)

expectUp := map[string]structs.IntentionDecisionSummary{
web.String(): {
api.String(): {
DefaultAllow: true,
Allowed: false,
HasPermissions: false,
ExternalSource: "nomad",
Expand All @@ -1730,25 +1739,35 @@ func TestInternal_ServiceTopology(t *testing.T) {
},
}
require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

expectUpstreamSources := map[string]string{
api.String(): structs.TopologySourceRegistration,
}
require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)
require.Empty(r, out.ServiceTopology.DownstreamSources)

// The ingress gateway has an explicit upstream
require.False(r, out.ServiceTopology.TransparentProxy)
})
})

t.Run("web", func(t *testing.T) {
t.Run("api", func(t *testing.T) {
retry.Run(t, func(r *retry.R) {
args := structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "web",
ServiceName: "api",
}
var out structs.IndexedServiceTopology
require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
require.False(r, out.FilteredByACLs)
require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

// foo/api, foo/api-proxy
require.Len(r, out.ServiceTopology.Downstreams, 2)
// edge/ingress
require.Len(r, out.ServiceTopology.Downstreams, 1)

expectDown := map[string]structs.IntentionDecisionSummary{
api.String(): {
ingress.String(): {
DefaultAllow: true,
Allowed: false,
HasPermissions: false,
ExternalSource: "nomad",
Expand All @@ -1759,17 +1778,84 @@ func TestInternal_ServiceTopology(t *testing.T) {
}
require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)

expectDownstreamSources := map[string]string{
ingress.String(): structs.TopologySourceRegistration,
}
require.Equal(r, expectDownstreamSources, out.ServiceTopology.DownstreamSources)

// bar/web, bar/web-proxy, baz/web, baz/web-proxy
require.Len(r, out.ServiceTopology.Upstreams, 4)

expectUp := map[string]structs.IntentionDecisionSummary{
web.String(): {
DefaultAllow: true,
Allowed: true,
HasPermissions: false,
HasExact: true,
},
}
require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

expectUpstreamSources := map[string]string{
web.String(): structs.TopologySourceSpecificIntention,
}
require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)

// The only instance of api's proxy is in transparent mode
require.True(r, out.ServiceTopology.TransparentProxy)
})
})

t.Run("web", func(t *testing.T) {
retry.Run(t, func(r *retry.R) {
args := structs.ServiceSpecificRequest{
Datacenter: "dc1",
ServiceName: "web",
}
var out structs.IndexedServiceTopology
require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
require.False(r, out.FilteredByACLs)
require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

// foo/api, foo/api-proxy
require.Len(r, out.ServiceTopology.Downstreams, 2)

expectDown := map[string]structs.IntentionDecisionSummary{
api.String(): {
DefaultAllow: true,
Allowed: true,
HasPermissions: false,
HasExact: true,
},
}
require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)

expectDownstreamSources := map[string]string{
api.String(): structs.TopologySourceSpecificIntention,
}
require.Equal(r, expectDownstreamSources, out.ServiceTopology.DownstreamSources)

// zip/redis, zip/redis-proxy
require.Len(r, out.ServiceTopology.Upstreams, 2)

expectUp := map[string]structs.IntentionDecisionSummary{
redis.String(): {
DefaultAllow: true,
Allowed: false,
HasPermissions: true,
HasExact: true,
},
}
require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

expectUpstreamSources := map[string]string{
// We prefer from-registration over intention source when there is a mix
redis.String(): structs.TopologySourceRegistration,
}
require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)

// Not all instances of web are in transparent mode
require.False(r, out.ServiceTopology.TransparentProxy)
})
})

Expand All @@ -1791,12 +1877,22 @@ func TestInternal_ServiceTopology(t *testing.T) {

expectDown := map[string]structs.IntentionDecisionSummary{
web.String(): {
DefaultAllow: true,
Allowed: false,
HasPermissions: true,
HasExact: true,
},
}
require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)

expectDownstreamSources := map[string]string{
web.String(): structs.TopologySourceRegistration,
}
require.Equal(r, expectDownstreamSources, out.ServiceTopology.DownstreamSources)
require.Empty(r, out.ServiceTopology.UpstreamSources)

// No proxies are in transparent mode
require.False(r, out.ServiceTopology.TransparentProxy)
})
})
}
Expand All @@ -1821,9 +1917,17 @@ func TestInternal_ServiceTopology_ACL(t *testing.T) {
codec := rpcClient(t, s1)
defer codec.Close()

// api and api-proxy on node foo - upstream: web
// wildcard deny intention
// ingress-gateway on node edge - upstream: api
// ingress -> api gateway config entry (but no intention)

// api and api-proxy on node foo - transparent proxy
// api -> web exact intention

// web and web-proxy on node bar - upstream: redis
// web and web-proxy on node baz - upstream: redis
// web and web-proxy on node baz - transparent proxy
// web -> redis exact intention

// redis and redis-proxy on node zip
registerTestTopologyEntries(t, codec, TestDefaultMasterToken)

Expand Down
Loading

0 comments on commit 3be304b

Please sign in to comment.