Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update topology endpoint to account for transparent proxy #10016

Merged
merged 4 commits into from
Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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