mirror of https://github.com/status-im/consul.git
Update viz endpoint to include topology from intentions
This commit is contained in:
parent
932fbddd27
commit
8e74eaa684
|
@ -573,11 +573,50 @@ func registerTestCatalogEntriesMap(t *testing.T, codec rpc.ClientCodec, registra
|
||||||
func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token string) {
|
func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token string) {
|
||||||
t.Helper()
|
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 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
|
// redis and redis-proxy on node zip
|
||||||
registrations := map[string]*structs.RegisterRequest{
|
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": {
|
"Node foo": {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Node: "foo",
|
Node: "foo",
|
||||||
|
@ -627,13 +666,8 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
|
||||||
Port: 8443,
|
Port: 8443,
|
||||||
Address: "198.18.1.2",
|
Address: "198.18.1.2",
|
||||||
Proxy: structs.ConnectProxyConfig{
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
Mode: structs.ProxyModeTransparent,
|
||||||
DestinationServiceName: "api",
|
DestinationServiceName: "api",
|
||||||
Upstreams: structs.Upstreams{
|
|
||||||
{
|
|
||||||
DestinationName: "web",
|
|
||||||
LocalBindPort: 8080,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Checks: structs.HealthChecks{
|
Checks: structs.HealthChecks{
|
||||||
|
@ -767,13 +801,8 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
|
||||||
Port: 8443,
|
Port: 8443,
|
||||||
Address: "198.18.1.40",
|
Address: "198.18.1.40",
|
||||||
Proxy: structs.ConnectProxyConfig{
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
Mode: structs.ProxyModeTransparent,
|
||||||
DestinationServiceName: "web",
|
DestinationServiceName: "web",
|
||||||
Upstreams: structs.Upstreams{
|
|
||||||
{
|
|
||||||
DestinationName: "redis",
|
|
||||||
LocalBindPort: 123,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Checks: structs.HealthChecks{
|
Checks: structs.HealthChecks{
|
||||||
|
@ -855,7 +884,10 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
|
||||||
}
|
}
|
||||||
registerTestCatalogEntriesMap(t, codec, registrations)
|
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{
|
entries := []structs.ConfigEntryRequest{
|
||||||
{
|
{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
|
@ -868,6 +900,39 @@ func registerTestTopologyEntries(t *testing.T, codec rpc.ClientCodec, token stri
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: token},
|
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",
|
Datacenter: "dc1",
|
||||||
Entry: &structs.ServiceIntentionsConfigEntry{
|
Entry: &structs.ServiceIntentionsConfigEntry{
|
||||||
|
|
|
@ -1690,20 +1690,67 @@ func TestInternal_ServiceTopology(t *testing.T) {
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
defer codec.Close()
|
defer codec.Close()
|
||||||
|
|
||||||
// api and api-proxy on node foo - upstream: web
|
|
||||||
// web and web-proxy on node bar - upstream: redis
|
|
||||||
// web and web-proxy on node baz - upstream: redis
|
|
||||||
// redis and redis-proxy on node zip
|
|
||||||
// wildcard deny intention
|
// wildcard deny intention
|
||||||
// web -> redis exact intentino
|
// 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 - transparent proxy
|
||||||
|
// web -> redis exact intention
|
||||||
|
|
||||||
|
// redis and redis-proxy on node zip
|
||||||
|
|
||||||
registerTestTopologyEntries(t, codec, "")
|
registerTestTopologyEntries(t, codec, "")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
api = structs.NewServiceName("api", structs.DefaultEnterpriseMeta())
|
ingress = structs.NewServiceName("ingress", structs.DefaultEnterpriseMeta())
|
||||||
web = structs.NewServiceName("web", structs.DefaultEnterpriseMeta())
|
api = structs.NewServiceName("api", structs.DefaultEnterpriseMeta())
|
||||||
redis = structs.NewServiceName("redis", structs.DefaultEnterpriseMeta())
|
web = structs.NewServiceName("web", structs.DefaultEnterpriseMeta())
|
||||||
|
redis = structs.NewServiceName("redis", structs.DefaultEnterpriseMeta())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
t.Run("ingress", func(t *testing.T) {
|
||||||
|
retry.Run(t, func(r *retry.R) {
|
||||||
|
args := structs.ServiceSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
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)
|
||||||
|
|
||||||
|
// foo/api, foo/api-proxy
|
||||||
|
require.Len(r, out.ServiceTopology.Upstreams, 2)
|
||||||
|
require.Len(r, out.ServiceTopology.Downstreams, 0)
|
||||||
|
|
||||||
|
expectUp := map[string]structs.IntentionDecisionSummary{
|
||||||
|
api.String(): {
|
||||||
|
DefaultAllow: true,
|
||||||
|
Allowed: false,
|
||||||
|
HasPermissions: false,
|
||||||
|
ExternalSource: "nomad",
|
||||||
|
|
||||||
|
// From wildcard deny
|
||||||
|
HasExact: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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("api", func(t *testing.T) {
|
t.Run("api", func(t *testing.T) {
|
||||||
retry.Run(t, func(r *retry.R) {
|
retry.Run(t, func(r *retry.R) {
|
||||||
args := structs.ServiceSpecificRequest{
|
args := structs.ServiceSpecificRequest{
|
||||||
|
@ -1715,12 +1762,11 @@ func TestInternal_ServiceTopology(t *testing.T) {
|
||||||
require.False(r, out.FilteredByACLs)
|
require.False(r, out.FilteredByACLs)
|
||||||
require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)
|
require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)
|
||||||
|
|
||||||
// bar/web, bar/web-proxy, baz/web, baz/web-proxy
|
// edge/ingress
|
||||||
require.Len(r, out.ServiceTopology.Upstreams, 4)
|
require.Len(r, out.ServiceTopology.Downstreams, 1)
|
||||||
require.Len(r, out.ServiceTopology.Downstreams, 0)
|
|
||||||
|
|
||||||
expectUp := map[string]structs.IntentionDecisionSummary{
|
expectDown := map[string]structs.IntentionDecisionSummary{
|
||||||
web.String(): {
|
ingress.String(): {
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
HasPermissions: false,
|
HasPermissions: false,
|
||||||
|
@ -1730,7 +1776,33 @@ func TestInternal_ServiceTopology(t *testing.T) {
|
||||||
HasExact: false,
|
HasExact: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
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)
|
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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1751,16 +1823,18 @@ func TestInternal_ServiceTopology(t *testing.T) {
|
||||||
expectDown := map[string]structs.IntentionDecisionSummary{
|
expectDown := map[string]structs.IntentionDecisionSummary{
|
||||||
api.String(): {
|
api.String(): {
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
Allowed: false,
|
Allowed: true,
|
||||||
HasPermissions: false,
|
HasPermissions: false,
|
||||||
ExternalSource: "nomad",
|
HasExact: true,
|
||||||
|
|
||||||
// From wildcard deny
|
|
||||||
HasExact: false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)
|
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
|
// zip/redis, zip/redis-proxy
|
||||||
require.Len(r, out.ServiceTopology.Upstreams, 2)
|
require.Len(r, out.ServiceTopology.Upstreams, 2)
|
||||||
|
|
||||||
|
@ -1773,6 +1847,15 @@ func TestInternal_ServiceTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)
|
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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1801,6 +1884,15 @@ func TestInternal_ServiceTopology(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)
|
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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1825,9 +1917,17 @@ func TestInternal_ServiceTopology_ACL(t *testing.T) {
|
||||||
codec := rpcClient(t, s1)
|
codec := rpcClient(t, s1)
|
||||||
defer codec.Close()
|
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 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
|
// redis and redis-proxy on node zip
|
||||||
registerTestTopologyEntries(t, codec, TestDefaultMasterToken)
|
registerTestTopologyEntries(t, codec, TestDefaultMasterToken)
|
||||||
|
|
||||||
|
|
|
@ -2853,6 +2853,8 @@ func checkProtocolMatch(tx ReadTxn, ws memdb.WatchSet, svc *structs.GatewayServi
|
||||||
return idx, svc.Protocol == protocol, nil
|
return idx, svc.Protocol == protocol, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(freddy) Split this up. The upstream/downstream logic is very similar.
|
||||||
|
// TODO(freddy) Add comprehensive state store test
|
||||||
func (s *Store) ServiceTopology(
|
func (s *Store) ServiceTopology(
|
||||||
ws memdb.WatchSet,
|
ws memdb.WatchSet,
|
||||||
dc, service string,
|
dc, service string,
|
||||||
|
@ -2863,14 +2865,15 @@ func (s *Store) ServiceTopology(
|
||||||
tx := s.db.ReadTxn()
|
tx := s.db.ReadTxn()
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
|
sn := structs.NewServiceName(service, entMeta)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
maxIdx uint64
|
maxIdx uint64
|
||||||
protocol string
|
protocol string
|
||||||
err error
|
err error
|
||||||
|
fullyTransparent bool
|
||||||
sn = structs.NewServiceName(service, entMeta)
|
hasTransparent bool
|
||||||
)
|
)
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case structs.ServiceKindIngressGateway:
|
case structs.ServiceKindIngressGateway:
|
||||||
maxIdx, protocol, err = metricsProtocolForIngressGateway(tx, ws, sn)
|
maxIdx, protocol, err = metricsProtocolForIngressGateway(tx, ws, sn)
|
||||||
|
@ -2884,6 +2887,38 @@ func (s *Store) ServiceTopology(
|
||||||
return 0, nil, fmt.Errorf("failed to fetch protocol for service %s: %v", sn.String(), err)
|
return 0, nil, fmt.Errorf("failed to fetch protocol for service %s: %v", sn.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch connect endpoints for the target service in order to learn if its proxies are configured as
|
||||||
|
// transparent proxies.
|
||||||
|
if entMeta == nil {
|
||||||
|
entMeta = structs.DefaultEnterpriseMeta()
|
||||||
|
}
|
||||||
|
q := Query{Value: service, EnterpriseMeta: *entMeta}
|
||||||
|
|
||||||
|
idx, proxies, err := serviceNodesTxn(tx, ws, indexConnect, q)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("failed to fetch connect endpoints for service %s: %v", sn.String(), err)
|
||||||
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fullyTransparent = true
|
||||||
|
for _, proxy := range proxies {
|
||||||
|
switch proxy.ServiceProxy.Mode {
|
||||||
|
case structs.ProxyModeTransparent:
|
||||||
|
hasTransparent = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Only consider the target proxy to be transparent when all instances are in that mode.
|
||||||
|
// This is done because the flag is used to display warnings about proxies needing to enable
|
||||||
|
// transparent proxy mode. If ANY instance isn't in the right mode then the warming applies.
|
||||||
|
fullyTransparent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0, nil, fmt.Errorf("unsupported kind %q", kind)
|
return 0, nil, fmt.Errorf("unsupported kind %q", kind)
|
||||||
}
|
}
|
||||||
|
@ -2895,7 +2930,48 @@ func (s *Store) ServiceTopology(
|
||||||
if idx > maxIdx {
|
if idx > maxIdx {
|
||||||
maxIdx = idx
|
maxIdx = idx
|
||||||
}
|
}
|
||||||
idx, upstreams, err := s.combinedServiceNodesTxn(tx, ws, upstreamNames)
|
|
||||||
|
var (
|
||||||
|
seenUpstreams = make(map[string]struct{})
|
||||||
|
upstreamSources = make(map[string]string)
|
||||||
|
)
|
||||||
|
for _, un := range upstreamNames {
|
||||||
|
if _, ok := seenUpstreams[un.String()]; !ok {
|
||||||
|
seenUpstreams[un.String()] = struct{}{}
|
||||||
|
}
|
||||||
|
upstreamSources[un.String()] = structs.TopologySourceRegistration
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, intentionUpstreams, err := s.intentionTopologyTxn(tx, ws, sn, false, defaultAllow)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamDecisions := make(map[string]structs.IntentionDecisionSummary)
|
||||||
|
for _, svc := range intentionUpstreams {
|
||||||
|
if _, ok := seenUpstreams[svc.Name.String()]; ok {
|
||||||
|
// Avoid duplicating entry
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
upstreamDecisions[svc.Name.String()] = svc.Decision
|
||||||
|
upstreamNames = append(upstreamNames, svc.Name)
|
||||||
|
|
||||||
|
var source string
|
||||||
|
switch {
|
||||||
|
case svc.Decision.HasExact:
|
||||||
|
source = structs.TopologySourceSpecificIntention
|
||||||
|
case svc.Decision.DefaultAllow:
|
||||||
|
source = structs.TopologySourceDefaultAllow
|
||||||
|
default:
|
||||||
|
source = structs.TopologySourceWildcardIntention
|
||||||
|
}
|
||||||
|
upstreamSources[svc.Name.String()] = source
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, unfilteredUpstreams, err := s.combinedServiceNodesTxn(tx, ws, upstreamNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, fmt.Errorf("failed to get upstreams for %q: %v", sn.String(), err)
|
return 0, nil, fmt.Errorf("failed to get upstreams for %q: %v", sn.String(), err)
|
||||||
}
|
}
|
||||||
|
@ -2903,14 +2979,32 @@ func (s *Store) ServiceTopology(
|
||||||
maxIdx = idx
|
maxIdx = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
upstreamDecisions := make(map[string]structs.IntentionDecisionSummary)
|
var upstreams structs.CheckServiceNodes
|
||||||
|
for _, upstream := range unfilteredUpstreams {
|
||||||
|
sn := upstream.Service.CompoundServiceName()
|
||||||
|
if upstream.Service.Kind == structs.ServiceKindConnectProxy {
|
||||||
|
sn = structs.NewServiceName(upstream.Service.Proxy.DestinationServiceName, &upstream.Service.EnterpriseMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid returning upstreams from intentions when none of the proxy instances of the target are in transparent mode.
|
||||||
|
if !hasTransparent && upstreamSources[sn.String()] != structs.TopologySourceRegistration {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
upstreams = append(upstreams, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
matchEntry := structs.IntentionMatchEntry{
|
matchEntry := structs.IntentionMatchEntry{
|
||||||
Namespace: entMeta.NamespaceOrDefault(),
|
Namespace: entMeta.NamespaceOrDefault(),
|
||||||
Name: service,
|
Name: service,
|
||||||
}
|
}
|
||||||
// The given service is a source relative to its upstreams
|
_, srcIntentions, err := compatIntentionMatchOneTxn(
|
||||||
_, srcIntentions, err := compatIntentionMatchOneTxn(tx, ws, matchEntry, structs.IntentionMatchSource)
|
tx,
|
||||||
|
ws,
|
||||||
|
matchEntry,
|
||||||
|
|
||||||
|
// The given service is a source relative to its upstreams
|
||||||
|
structs.IntentionMatchSource,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, fmt.Errorf("failed to query intentions for %s", sn.String())
|
return 0, nil, fmt.Errorf("failed to query intentions for %s", sn.String())
|
||||||
}
|
}
|
||||||
|
@ -2930,7 +3024,48 @@ func (s *Store) ServiceTopology(
|
||||||
if idx > maxIdx {
|
if idx > maxIdx {
|
||||||
maxIdx = idx
|
maxIdx = idx
|
||||||
}
|
}
|
||||||
idx, downstreams, err := s.combinedServiceNodesTxn(tx, ws, downstreamNames)
|
|
||||||
|
var (
|
||||||
|
seenDownstreams = make(map[string]struct{})
|
||||||
|
downstreamSources = make(map[string]string)
|
||||||
|
)
|
||||||
|
for _, dn := range downstreamNames {
|
||||||
|
if _, ok := seenDownstreams[dn.String()]; !ok {
|
||||||
|
seenDownstreams[dn.String()] = struct{}{}
|
||||||
|
}
|
||||||
|
downstreamSources[dn.String()] = structs.TopologySourceRegistration
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, intentionDownstreams, err := s.intentionTopologyTxn(tx, ws, sn, true, defaultAllow)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
|
downstreamDecisions := make(map[string]structs.IntentionDecisionSummary)
|
||||||
|
for _, svc := range intentionDownstreams {
|
||||||
|
if _, ok := seenDownstreams[svc.Name.String()]; ok {
|
||||||
|
// Avoid duplicating entry
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
downstreamNames = append(downstreamNames, svc.Name)
|
||||||
|
downstreamDecisions[svc.Name.String()] = svc.Decision
|
||||||
|
|
||||||
|
var source string
|
||||||
|
switch {
|
||||||
|
case svc.Decision.HasExact:
|
||||||
|
source = structs.TopologySourceSpecificIntention
|
||||||
|
case svc.Decision.DefaultAllow:
|
||||||
|
source = structs.TopologySourceDefaultAllow
|
||||||
|
default:
|
||||||
|
source = structs.TopologySourceWildcardIntention
|
||||||
|
}
|
||||||
|
downstreamSources[svc.Name.String()] = source
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, unfilteredDownstreams, err := s.combinedServiceNodesTxn(tx, ws, downstreamNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, fmt.Errorf("failed to get downstreams for %q: %v", sn.String(), err)
|
return 0, nil, fmt.Errorf("failed to get downstreams for %q: %v", sn.String(), err)
|
||||||
}
|
}
|
||||||
|
@ -2938,12 +3073,39 @@ func (s *Store) ServiceTopology(
|
||||||
maxIdx = idx
|
maxIdx = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
// The given service is a destination relative to its downstreams
|
// Store downstreams with at least one instance in transparent proxy mode.
|
||||||
_, dstIntentions, err := compatIntentionMatchOneTxn(tx, ws, matchEntry, structs.IntentionMatchDestination)
|
// This is to avoid returning downstreams from intentions when none of the downstreams are transparent proxies.
|
||||||
|
tproxyMap := make(map[structs.ServiceName]struct{})
|
||||||
|
for _, downstream := range unfilteredDownstreams {
|
||||||
|
if downstream.Service.Proxy.Mode == structs.ProxyModeTransparent {
|
||||||
|
sn := structs.NewServiceName(downstream.Service.Proxy.DestinationServiceName, &downstream.Service.EnterpriseMeta)
|
||||||
|
tproxyMap[sn] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var downstreams structs.CheckServiceNodes
|
||||||
|
for _, downstream := range unfilteredDownstreams {
|
||||||
|
sn := downstream.Service.CompoundServiceName()
|
||||||
|
if downstream.Service.Kind == structs.ServiceKindConnectProxy {
|
||||||
|
sn = structs.NewServiceName(downstream.Service.Proxy.DestinationServiceName, &downstream.Service.EnterpriseMeta)
|
||||||
|
}
|
||||||
|
if _, ok := tproxyMap[sn]; !ok && downstreamSources[sn.String()] != structs.TopologySourceRegistration {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
downstreams = append(downstreams, downstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, dstIntentions, err := compatIntentionMatchOneTxn(
|
||||||
|
tx,
|
||||||
|
ws,
|
||||||
|
matchEntry,
|
||||||
|
|
||||||
|
// The given service is a destination relative to its downstreams
|
||||||
|
structs.IntentionMatchDestination,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, fmt.Errorf("failed to query intentions for %s", sn.String())
|
return 0, nil, fmt.Errorf("failed to query intentions for %s", sn.String())
|
||||||
}
|
}
|
||||||
downstreamDecisions := make(map[string]structs.IntentionDecisionSummary)
|
|
||||||
for _, dn := range downstreamNames {
|
for _, dn := range downstreamNames {
|
||||||
decision, err := s.IntentionDecision(dn.Name, dn.NamespaceOrDefault(), dstIntentions, structs.IntentionMatchSource, defaultAllow, false)
|
decision, err := s.IntentionDecision(dn.Name, dn.NamespaceOrDefault(), dstIntentions, structs.IntentionMatchSource, defaultAllow, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2954,11 +3116,14 @@ func (s *Store) ServiceTopology(
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &structs.ServiceTopology{
|
resp := &structs.ServiceTopology{
|
||||||
|
TransparentProxy: fullyTransparent,
|
||||||
MetricsProtocol: protocol,
|
MetricsProtocol: protocol,
|
||||||
Upstreams: upstreams,
|
Upstreams: upstreams,
|
||||||
Downstreams: downstreams,
|
Downstreams: downstreams,
|
||||||
UpstreamDecisions: upstreamDecisions,
|
UpstreamDecisions: upstreamDecisions,
|
||||||
DownstreamDecisions: downstreamDecisions,
|
DownstreamDecisions: downstreamDecisions,
|
||||||
|
UpstreamSources: upstreamSources,
|
||||||
|
DownstreamSources: downstreamSources,
|
||||||
}
|
}
|
||||||
return maxIdx, resp, nil
|
return maxIdx, resp, nil
|
||||||
}
|
}
|
||||||
|
@ -2995,7 +3160,6 @@ func (s *Store) combinedServiceNodesTxn(tx ReadTxn, ws memdb.WatchSet, names []s
|
||||||
|
|
||||||
// downstreamsForServiceTxn will find all downstream services that could route traffic to the input service.
|
// downstreamsForServiceTxn will find all downstream services that could route traffic to the input service.
|
||||||
// There are two factors at play. Upstreams defined in a proxy registration, and the discovery chain for those upstreams.
|
// There are two factors at play. Upstreams defined in a proxy registration, and the discovery chain for those upstreams.
|
||||||
// TODO (freddy): Account for ingress gateways
|
|
||||||
func (s *Store) downstreamsForServiceTxn(tx ReadTxn, ws memdb.WatchSet, dc string, service structs.ServiceName) (uint64, []structs.ServiceName, error) {
|
func (s *Store) downstreamsForServiceTxn(tx ReadTxn, ws memdb.WatchSet, dc string, service structs.ServiceName) (uint64, []structs.ServiceName, error) {
|
||||||
// First fetch services that have discovery chains that eventually route to the target service
|
// First fetch services that have discovery chains that eventually route to the target service
|
||||||
idx, sources, err := s.discoveryChainSourcesTxn(tx, ws, dc, service)
|
idx, sources, err := s.discoveryChainSourcesTxn(tx, ws, dc, service)
|
||||||
|
|
|
@ -933,6 +933,11 @@ func intentionMatchGetParams(entry structs.IntentionMatchEntry) ([][]interface{}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServiceWithDecision struct {
|
||||||
|
Name structs.ServiceName
|
||||||
|
Decision structs.IntentionDecisionSummary
|
||||||
|
}
|
||||||
|
|
||||||
// IntentionTopology returns the upstreams or downstreams of a service. Upstreams and downstreams are inferred from
|
// IntentionTopology returns the upstreams or downstreams of a service. Upstreams and downstreams are inferred from
|
||||||
// intentions. If intentions allow a connection from the target to some candidate service, the candidate service is considered
|
// intentions. If intentions allow a connection from the target to some candidate service, the candidate service is considered
|
||||||
// an upstream of the target.
|
// an upstream of the target.
|
||||||
|
@ -941,6 +946,25 @@ func (s *Store) IntentionTopology(ws memdb.WatchSet,
|
||||||
tx := s.db.ReadTxn()
|
tx := s.db.ReadTxn()
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
|
idx, services, err := s.intentionTopologyTxn(tx, ws, target, downstreams, defaultDecision)
|
||||||
|
if err != nil {
|
||||||
|
requested := "upstreams"
|
||||||
|
if downstreams {
|
||||||
|
requested = "downstreams"
|
||||||
|
}
|
||||||
|
return 0, nil, fmt.Errorf("failed to fetch %s for %s: %v", requested, target.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp structs.ServiceList
|
||||||
|
for _, svc := range services {
|
||||||
|
resp = append(resp, svc.Name)
|
||||||
|
}
|
||||||
|
return idx, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
|
||||||
|
target structs.ServiceName, downstreams bool, defaultDecision acl.EnforcementDecision) (uint64, []ServiceWithDecision, error) {
|
||||||
|
|
||||||
var maxIdx uint64
|
var maxIdx uint64
|
||||||
|
|
||||||
// If querying the upstreams for a service, we first query intentions that apply to the target service as a source.
|
// If querying the upstreams for a service, we first query intentions that apply to the target service as a source.
|
||||||
|
@ -999,7 +1023,7 @@ func (s *Store) IntentionTopology(ws memdb.WatchSet,
|
||||||
if downstreams {
|
if downstreams {
|
||||||
decisionMatchType = structs.IntentionMatchSource
|
decisionMatchType = structs.IntentionMatchSource
|
||||||
}
|
}
|
||||||
result := make(structs.ServiceList, 0, len(allServices))
|
result := make([]ServiceWithDecision, 0, len(allServices))
|
||||||
for _, candidate := range allServices {
|
for _, candidate := range allServices {
|
||||||
if candidate.Name == structs.ConsulServiceName {
|
if candidate.Name == structs.ConsulServiceName {
|
||||||
continue
|
continue
|
||||||
|
@ -1016,7 +1040,11 @@ func (s *Store) IntentionTopology(ws memdb.WatchSet,
|
||||||
if !decision.Allowed || target.Matches(candidate) {
|
if !decision.Allowed || target.Matches(candidate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, candidate)
|
|
||||||
|
result = append(result, ServiceWithDecision{
|
||||||
|
Name: candidate,
|
||||||
|
Decision: decision,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return maxIdx, result, err
|
return maxIdx, result, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,22 @@ const (
|
||||||
MeshGatewayModeRemote MeshGatewayMode = "remote"
|
MeshGatewayModeRemote MeshGatewayMode = "remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO (freddy) Should we have a TopologySourceMixed when there is a mix of proxy reg and tproxy?
|
||||||
|
// Currently we label as proxy-registration if ANY instance has the explicit upstream definition.
|
||||||
|
// TopologySourceRegistration is used to label upstreams or downstreams from explicit upstream definitions
|
||||||
|
TopologySourceRegistration = "proxy-registration"
|
||||||
|
|
||||||
|
// TopologySourceSpecificIntention is used to label upstreams or downstreams from specific intentions
|
||||||
|
TopologySourceSpecificIntention = "specific-intention"
|
||||||
|
|
||||||
|
// TopologySourceWildcardIntention is used to label upstreams or downstreams from wildcard intentions
|
||||||
|
TopologySourceWildcardIntention = "wildcard-intention"
|
||||||
|
|
||||||
|
// TopologySourceDefaultAllow is used to label upstreams or downstreams from default allow ACL policy
|
||||||
|
TopologySourceDefaultAllow = "default-allow"
|
||||||
|
)
|
||||||
|
|
||||||
// MeshGatewayConfig controls how Mesh Gateways are configured and used
|
// MeshGatewayConfig controls how Mesh Gateways are configured and used
|
||||||
// This is a struct to allow for future additions without having more free-hanging
|
// This is a struct to allow for future additions without having more free-hanging
|
||||||
// configuration items all over the place
|
// configuration items all over the place
|
||||||
|
|
|
@ -1924,6 +1924,17 @@ type ServiceTopology struct {
|
||||||
|
|
||||||
// MetricsProtocol is the protocol of the service being queried
|
// MetricsProtocol is the protocol of the service being queried
|
||||||
MetricsProtocol string
|
MetricsProtocol string
|
||||||
|
|
||||||
|
// TransparentProxy describes whether all instances of the proxy
|
||||||
|
// service are in transparent mode.
|
||||||
|
TransparentProxy bool
|
||||||
|
|
||||||
|
// (Up|Down)streamSources are maps with labels for why each service is being
|
||||||
|
// returned. Services can be upstreams or downstreams due to
|
||||||
|
// explicit upstream definition or various types of intention policies:
|
||||||
|
// specific, wildcard, or default allow.
|
||||||
|
UpstreamSources map[string]string
|
||||||
|
DownstreamSources map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexedConfigEntries has its own encoding logic which differs from
|
// IndexedConfigEntries has its own encoding logic which differs from
|
||||||
|
|
|
@ -19,19 +19,21 @@ import (
|
||||||
|
|
||||||
// ServiceSummary is used to summarize a service
|
// ServiceSummary is used to summarize a service
|
||||||
type ServiceSummary struct {
|
type ServiceSummary struct {
|
||||||
Kind structs.ServiceKind `json:",omitempty"`
|
Kind structs.ServiceKind `json:",omitempty"`
|
||||||
Name string
|
Name string
|
||||||
Datacenter string
|
Datacenter string
|
||||||
Tags []string
|
Tags []string
|
||||||
Nodes []string
|
Nodes []string
|
||||||
ExternalSources []string
|
ExternalSources []string
|
||||||
externalSourceSet map[string]struct{} // internal to track uniqueness
|
externalSourceSet map[string]struct{} // internal to track uniqueness
|
||||||
checks map[string]*structs.HealthCheck
|
checks map[string]*structs.HealthCheck
|
||||||
InstanceCount int
|
InstanceCount int
|
||||||
ChecksPassing int
|
ChecksPassing int
|
||||||
ChecksWarning int
|
ChecksWarning int
|
||||||
ChecksCritical int
|
ChecksCritical int
|
||||||
GatewayConfig GatewayConfig
|
GatewayConfig GatewayConfig
|
||||||
|
TransparentProxy bool
|
||||||
|
transparentProxySet bool
|
||||||
|
|
||||||
structs.EnterpriseMeta
|
structs.EnterpriseMeta
|
||||||
}
|
}
|
||||||
|
@ -61,14 +63,16 @@ type ServiceListingSummary struct {
|
||||||
type ServiceTopologySummary struct {
|
type ServiceTopologySummary struct {
|
||||||
ServiceSummary
|
ServiceSummary
|
||||||
|
|
||||||
|
Source string
|
||||||
Intention structs.IntentionDecisionSummary
|
Intention structs.IntentionDecisionSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceTopology struct {
|
type ServiceTopology struct {
|
||||||
Protocol string
|
Protocol string
|
||||||
Upstreams []*ServiceTopologySummary
|
TransparentProxy bool
|
||||||
Downstreams []*ServiceTopologySummary
|
Upstreams []*ServiceTopologySummary
|
||||||
FilteredByACLs bool
|
Downstreams []*ServiceTopologySummary
|
||||||
|
FilteredByACLs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// UINodes is used to list the nodes in a given datacenter. We return a
|
// UINodes is used to list the nodes in a given datacenter. We return a
|
||||||
|
@ -334,6 +338,7 @@ RPC:
|
||||||
sum := ServiceTopologySummary{
|
sum := ServiceTopologySummary{
|
||||||
ServiceSummary: *svc,
|
ServiceSummary: *svc,
|
||||||
Intention: out.ServiceTopology.UpstreamDecisions[sn.String()],
|
Intention: out.ServiceTopology.UpstreamDecisions[sn.String()],
|
||||||
|
Source: out.ServiceTopology.UpstreamSources[sn.String()],
|
||||||
}
|
}
|
||||||
upstreamResp = append(upstreamResp, &sum)
|
upstreamResp = append(upstreamResp, &sum)
|
||||||
}
|
}
|
||||||
|
@ -344,15 +349,17 @@ RPC:
|
||||||
sum := ServiceTopologySummary{
|
sum := ServiceTopologySummary{
|
||||||
ServiceSummary: *svc,
|
ServiceSummary: *svc,
|
||||||
Intention: out.ServiceTopology.DownstreamDecisions[sn.String()],
|
Intention: out.ServiceTopology.DownstreamDecisions[sn.String()],
|
||||||
|
Source: out.ServiceTopology.DownstreamSources[sn.String()],
|
||||||
}
|
}
|
||||||
downstreamResp = append(downstreamResp, &sum)
|
downstreamResp = append(downstreamResp, &sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
topo := ServiceTopology{
|
topo := ServiceTopology{
|
||||||
Protocol: out.ServiceTopology.MetricsProtocol,
|
TransparentProxy: out.ServiceTopology.TransparentProxy,
|
||||||
Upstreams: upstreamResp,
|
Protocol: out.ServiceTopology.MetricsProtocol,
|
||||||
Downstreams: downstreamResp,
|
Upstreams: upstreamResp,
|
||||||
FilteredByACLs: out.FilteredByACLs,
|
Downstreams: downstreamResp,
|
||||||
|
FilteredByACLs: out.FilteredByACLs,
|
||||||
}
|
}
|
||||||
return topo, nil
|
return topo, nil
|
||||||
}
|
}
|
||||||
|
@ -410,6 +417,17 @@ func summarizeServices(dump structs.ServiceDump, cfg *config.RuntimeConfig, dc s
|
||||||
}
|
}
|
||||||
destination.checks[uid] = check
|
destination.checks[uid] = check
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only consider the target service to be transparent when all its proxy instances are in that mode.
|
||||||
|
// This is done because the flag is used to display warnings about proxies needing to enable
|
||||||
|
// transparent proxy mode. If ANY instance isn't in the right mode then the warming applies.
|
||||||
|
if svc.Proxy.Mode == structs.ProxyModeTransparent && !destination.transparentProxySet {
|
||||||
|
destination.TransparentProxy = true
|
||||||
|
}
|
||||||
|
if svc.Proxy.Mode != structs.ProxyModeTransparent {
|
||||||
|
destination.TransparentProxy = false
|
||||||
|
}
|
||||||
|
destination.transparentProxySet = true
|
||||||
}
|
}
|
||||||
for _, tag := range svc.Tags {
|
for _, tag := range svc.Tags {
|
||||||
found := false
|
found := false
|
||||||
|
|
|
@ -396,6 +396,7 @@ func TestUiServices(t *testing.T) {
|
||||||
|
|
||||||
// internal accounting that users don't see can be blown away
|
// internal accounting that users don't see can be blown away
|
||||||
for _, sum := range summary {
|
for _, sum := range summary {
|
||||||
|
sum.transparentProxySet = false
|
||||||
sum.externalSourceSet = nil
|
sum.externalSourceSet = nil
|
||||||
sum.checks = nil
|
sum.checks = nil
|
||||||
}
|
}
|
||||||
|
@ -1078,12 +1079,7 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
Address: "198.18.1.2",
|
Address: "198.18.1.2",
|
||||||
Proxy: structs.ConnectProxyConfig{
|
Proxy: structs.ConnectProxyConfig{
|
||||||
DestinationServiceName: "api",
|
DestinationServiceName: "api",
|
||||||
Upstreams: structs.Upstreams{
|
Mode: structs.ProxyModeTransparent,
|
||||||
{
|
|
||||||
DestinationName: "web",
|
|
||||||
LocalBindPort: 8080,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Checks: structs.HealthChecks{
|
Checks: structs.HealthChecks{
|
||||||
|
@ -1210,12 +1206,7 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
Address: "198.18.1.40",
|
Address: "198.18.1.40",
|
||||||
Proxy: structs.ConnectProxyConfig{
|
Proxy: structs.ConnectProxyConfig{
|
||||||
DestinationServiceName: "web",
|
DestinationServiceName: "web",
|
||||||
Upstreams: structs.Upstreams{
|
Mode: structs.ProxyModeTransparent,
|
||||||
{
|
|
||||||
DestinationName: "redis",
|
|
||||||
LocalBindPort: 123,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Checks: structs.HealthChecks{
|
Checks: structs.HealthChecks{
|
||||||
|
@ -1296,8 +1287,10 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add intentions: deny all, ingress -> api, web -> redis with L7 perms, but omit intention for api -> web
|
// ingress -> api gateway config entry (but no intention)
|
||||||
// Add ingress config: ingress -> api
|
// wildcard deny intention
|
||||||
|
// api -> web exact intention
|
||||||
|
// web -> redis exact intention
|
||||||
{
|
{
|
||||||
entries := []structs.ConfigEntryRequest{
|
entries := []structs.ConfigEntryRequest{
|
||||||
{
|
{
|
||||||
|
@ -1318,6 +1311,20 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Entry: &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "*",
|
||||||
|
Meta: map[string]string{structs.MetaExternalSource: "nomad"},
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "*",
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Entry: &structs.ServiceIntentionsConfigEntry{
|
Entry: &structs.ServiceIntentionsConfigEntry{
|
||||||
|
@ -1342,12 +1349,11 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Entry: &structs.ServiceIntentionsConfigEntry{
|
Entry: &structs.ServiceIntentionsConfigEntry{
|
||||||
Kind: structs.ServiceIntentions,
|
Kind: structs.ServiceIntentions,
|
||||||
Name: "*",
|
Name: "web",
|
||||||
Meta: map[string]string{structs.MetaExternalSource: "nomad"},
|
|
||||||
Sources: []*structs.SourceIntention{
|
Sources: []*structs.SourceIntention{
|
||||||
{
|
{
|
||||||
Name: "*",
|
Action: structs.IntentionActionAllow,
|
||||||
Action: structs.IntentionActionDeny,
|
Name: "api",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1419,16 +1425,18 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
require.NoError(r, checkIndex(resp))
|
require.NoError(r, checkIndex(resp))
|
||||||
|
|
||||||
expect := ServiceTopology{
|
expect := ServiceTopology{
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
TransparentProxy: false,
|
||||||
Upstreams: []*ServiceTopologySummary{
|
Upstreams: []*ServiceTopologySummary{
|
||||||
{
|
{
|
||||||
ServiceSummary: ServiceSummary{
|
ServiceSummary: ServiceSummary{
|
||||||
Name: "api",
|
Name: "api",
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Nodes: []string{"foo"},
|
Nodes: []string{"foo"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 3,
|
ChecksPassing: 3,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
TransparentProxy: true,
|
||||||
},
|
},
|
||||||
Intention: structs.IntentionDecisionSummary{
|
Intention: structs.IntentionDecisionSummary{
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
|
@ -1436,6 +1444,7 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
HasPermissions: false,
|
HasPermissions: false,
|
||||||
HasExact: true,
|
HasExact: true,
|
||||||
},
|
},
|
||||||
|
Source: structs.TopologySourceRegistration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Downstreams: []*ServiceTopologySummary{},
|
Downstreams: []*ServiceTopologySummary{},
|
||||||
|
@ -1447,6 +1456,7 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
for _, u := range result.Upstreams {
|
for _, u := range result.Upstreams {
|
||||||
u.externalSourceSet = nil
|
u.externalSourceSet = nil
|
||||||
u.checks = nil
|
u.checks = nil
|
||||||
|
u.transparentProxySet = false
|
||||||
}
|
}
|
||||||
require.Equal(r, expect, result)
|
require.Equal(r, expect, result)
|
||||||
})
|
})
|
||||||
|
@ -1462,17 +1472,19 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
require.NoError(r, checkIndex(resp))
|
require.NoError(r, checkIndex(resp))
|
||||||
|
|
||||||
expect := ServiceTopology{
|
expect := ServiceTopology{
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
TransparentProxy: true,
|
||||||
Downstreams: []*ServiceTopologySummary{
|
Downstreams: []*ServiceTopologySummary{
|
||||||
{
|
{
|
||||||
ServiceSummary: ServiceSummary{
|
ServiceSummary: ServiceSummary{
|
||||||
Name: "ingress",
|
Name: "ingress",
|
||||||
Kind: structs.ServiceKindIngressGateway,
|
Kind: structs.ServiceKindIngressGateway,
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Nodes: []string{"edge"},
|
Nodes: []string{"edge"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 1,
|
ChecksPassing: 1,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
TransparentProxy: false,
|
||||||
},
|
},
|
||||||
Intention: structs.IntentionDecisionSummary{
|
Intention: structs.IntentionDecisionSummary{
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
|
@ -1480,29 +1492,29 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
HasPermissions: false,
|
HasPermissions: false,
|
||||||
HasExact: true,
|
HasExact: true,
|
||||||
},
|
},
|
||||||
|
Source: structs.TopologySourceRegistration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Upstreams: []*ServiceTopologySummary{
|
Upstreams: []*ServiceTopologySummary{
|
||||||
{
|
{
|
||||||
ServiceSummary: ServiceSummary{
|
ServiceSummary: ServiceSummary{
|
||||||
Name: "web",
|
Name: "web",
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Nodes: []string{"bar", "baz"},
|
Nodes: []string{"bar", "baz"},
|
||||||
InstanceCount: 2,
|
InstanceCount: 2,
|
||||||
ChecksPassing: 3,
|
ChecksPassing: 3,
|
||||||
ChecksWarning: 1,
|
ChecksWarning: 1,
|
||||||
ChecksCritical: 2,
|
ChecksCritical: 2,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
TransparentProxy: false,
|
||||||
},
|
},
|
||||||
Intention: structs.IntentionDecisionSummary{
|
Intention: structs.IntentionDecisionSummary{
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
Allowed: false,
|
Allowed: true,
|
||||||
HasPermissions: false,
|
HasPermissions: false,
|
||||||
ExternalSource: "nomad",
|
HasExact: true,
|
||||||
|
|
||||||
// From wildcard deny
|
|
||||||
HasExact: false,
|
|
||||||
},
|
},
|
||||||
|
Source: structs.TopologySourceSpecificIntention,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
FilteredByACLs: false,
|
FilteredByACLs: false,
|
||||||
|
@ -1511,10 +1523,12 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
|
|
||||||
// Internal accounting that is not returned in JSON response
|
// Internal accounting that is not returned in JSON response
|
||||||
for _, u := range result.Upstreams {
|
for _, u := range result.Upstreams {
|
||||||
|
u.transparentProxySet = false
|
||||||
u.externalSourceSet = nil
|
u.externalSourceSet = nil
|
||||||
u.checks = nil
|
u.checks = nil
|
||||||
}
|
}
|
||||||
for _, d := range result.Downstreams {
|
for _, d := range result.Downstreams {
|
||||||
|
d.transparentProxySet = false
|
||||||
d.externalSourceSet = nil
|
d.externalSourceSet = nil
|
||||||
d.checks = nil
|
d.checks = nil
|
||||||
}
|
}
|
||||||
|
@ -1532,17 +1546,19 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
require.NoError(r, checkIndex(resp))
|
require.NoError(r, checkIndex(resp))
|
||||||
|
|
||||||
expect := ServiceTopology{
|
expect := ServiceTopology{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
|
TransparentProxy: false,
|
||||||
Upstreams: []*ServiceTopologySummary{
|
Upstreams: []*ServiceTopologySummary{
|
||||||
{
|
{
|
||||||
ServiceSummary: ServiceSummary{
|
ServiceSummary: ServiceSummary{
|
||||||
Name: "redis",
|
Name: "redis",
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Nodes: []string{"zip"},
|
Nodes: []string{"zip"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 2,
|
ChecksPassing: 2,
|
||||||
ChecksCritical: 1,
|
ChecksCritical: 1,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
TransparentProxy: false,
|
||||||
},
|
},
|
||||||
Intention: structs.IntentionDecisionSummary{
|
Intention: structs.IntentionDecisionSummary{
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
|
@ -1550,27 +1566,27 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
HasPermissions: true,
|
HasPermissions: true,
|
||||||
HasExact: true,
|
HasExact: true,
|
||||||
},
|
},
|
||||||
|
Source: structs.TopologySourceRegistration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Downstreams: []*ServiceTopologySummary{
|
Downstreams: []*ServiceTopologySummary{
|
||||||
{
|
{
|
||||||
ServiceSummary: ServiceSummary{
|
ServiceSummary: ServiceSummary{
|
||||||
Name: "api",
|
Name: "api",
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Nodes: []string{"foo"},
|
Nodes: []string{"foo"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 3,
|
ChecksPassing: 3,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
TransparentProxy: true,
|
||||||
},
|
},
|
||||||
Intention: structs.IntentionDecisionSummary{
|
Intention: structs.IntentionDecisionSummary{
|
||||||
DefaultAllow: true,
|
DefaultAllow: true,
|
||||||
Allowed: false,
|
Allowed: true,
|
||||||
HasPermissions: false,
|
HasPermissions: false,
|
||||||
ExternalSource: "nomad",
|
HasExact: true,
|
||||||
|
|
||||||
// From wildcard deny
|
|
||||||
HasExact: false,
|
|
||||||
},
|
},
|
||||||
|
Source: structs.TopologySourceSpecificIntention,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
FilteredByACLs: false,
|
FilteredByACLs: false,
|
||||||
|
@ -1579,10 +1595,12 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
|
|
||||||
// Internal accounting that is not returned in JSON response
|
// Internal accounting that is not returned in JSON response
|
||||||
for _, u := range result.Upstreams {
|
for _, u := range result.Upstreams {
|
||||||
|
u.transparentProxySet = false
|
||||||
u.externalSourceSet = nil
|
u.externalSourceSet = nil
|
||||||
u.checks = nil
|
u.checks = nil
|
||||||
}
|
}
|
||||||
for _, d := range result.Downstreams {
|
for _, d := range result.Downstreams {
|
||||||
|
d.transparentProxySet = false
|
||||||
d.externalSourceSet = nil
|
d.externalSourceSet = nil
|
||||||
d.checks = nil
|
d.checks = nil
|
||||||
}
|
}
|
||||||
|
@ -1620,6 +1638,7 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
HasPermissions: true,
|
HasPermissions: true,
|
||||||
HasExact: true,
|
HasExact: true,
|
||||||
},
|
},
|
||||||
|
Source: structs.TopologySourceRegistration,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
FilteredByACLs: false,
|
FilteredByACLs: false,
|
||||||
|
@ -1628,6 +1647,7 @@ func TestUIServiceTopology(t *testing.T) {
|
||||||
|
|
||||||
// Internal accounting that is not returned in JSON response
|
// Internal accounting that is not returned in JSON response
|
||||||
for _, d := range result.Downstreams {
|
for _, d := range result.Downstreams {
|
||||||
|
d.transparentProxySet = false
|
||||||
d.externalSourceSet = nil
|
d.externalSourceSet = nil
|
||||||
d.checks = nil
|
d.checks = nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue