mirror of https://github.com/status-im/consul.git
fedstate: support `ResultsFilteredByACLs` in `ListMeshGateways` endpoint (#11644)
This commit is contained in:
parent
361d9c2862
commit
047aa2ffb0
|
@ -1379,17 +1379,22 @@ func (f *aclFilter) filterServiceTopology(topology *structs.ServiceTopology) boo
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterDatacenterCheckServiceNodes is used to filter nodes based on ACL rules.
|
// filterDatacenterCheckServiceNodes is used to filter nodes based on ACL rules.
|
||||||
func (f *aclFilter) filterDatacenterCheckServiceNodes(datacenterNodes *map[string]structs.CheckServiceNodes) {
|
// Returns true if any elements are removed.
|
||||||
|
func (f *aclFilter) filterDatacenterCheckServiceNodes(datacenterNodes *map[string]structs.CheckServiceNodes) bool {
|
||||||
dn := *datacenterNodes
|
dn := *datacenterNodes
|
||||||
out := make(map[string]structs.CheckServiceNodes)
|
out := make(map[string]structs.CheckServiceNodes)
|
||||||
|
var removed bool
|
||||||
for dc := range dn {
|
for dc := range dn {
|
||||||
nodes := dn[dc]
|
nodes := dn[dc]
|
||||||
f.filterCheckServiceNodes(&nodes)
|
if f.filterCheckServiceNodes(&nodes) {
|
||||||
|
removed = true
|
||||||
|
}
|
||||||
if len(nodes) > 0 {
|
if len(nodes) > 0 {
|
||||||
out[dc] = nodes
|
out[dc] = nodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*datacenterNodes = out
|
*datacenterNodes = out
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterSessions is used to filter a set of sessions based on ACLs. Returns
|
// filterSessions is used to filter a set of sessions based on ACLs. Returns
|
||||||
|
@ -1850,7 +1855,7 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
|
||||||
}
|
}
|
||||||
|
|
||||||
case *structs.DatacenterIndexedCheckServiceNodes:
|
case *structs.DatacenterIndexedCheckServiceNodes:
|
||||||
filt.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)
|
||||||
|
|
||||||
case *structs.IndexedCoordinates:
|
case *structs.IndexedCoordinates:
|
||||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterCoordinates(&v.Coordinates)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterCoordinates(&v.Coordinates)
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
||||||
"github.com/mitchellh/copystructure"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -3158,102 +3157,105 @@ func TestACL_filterNodes(t *testing.T) {
|
||||||
|
|
||||||
func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) {
|
func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Create some data.
|
|
||||||
fixture := map[string]structs.CheckServiceNodes{
|
logger := hclog.NewNullLogger()
|
||||||
"dc1": []structs.CheckServiceNode{
|
|
||||||
newTestMeshGatewayNode(
|
makeList := func() *structs.DatacenterIndexedCheckServiceNodes {
|
||||||
"dc1", "gateway1a", "1.2.3.4", 5555, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
return &structs.DatacenterIndexedCheckServiceNodes{
|
||||||
),
|
DatacenterNodes: map[string]structs.CheckServiceNodes{
|
||||||
newTestMeshGatewayNode(
|
"dc1": []structs.CheckServiceNode{
|
||||||
"dc1", "gateway2a", "4.3.2.1", 9999, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
newTestMeshGatewayNode(
|
||||||
),
|
"dc1", "gateway1a", "1.2.3.4", 5555, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
||||||
},
|
),
|
||||||
"dc2": []structs.CheckServiceNode{
|
newTestMeshGatewayNode(
|
||||||
newTestMeshGatewayNode(
|
"dc1", "gateway2a", "4.3.2.1", 9999, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
||||||
"dc2", "gateway1b", "5.6.7.8", 9999, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
),
|
||||||
),
|
},
|
||||||
newTestMeshGatewayNode(
|
"dc2": []structs.CheckServiceNode{
|
||||||
"dc2", "gateway2b", "8.7.6.5", 1111, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
newTestMeshGatewayNode(
|
||||||
),
|
"dc2", "gateway1b", "5.6.7.8", 9999, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
||||||
},
|
),
|
||||||
|
newTestMeshGatewayNode(
|
||||||
|
"dc2", "gateway2b", "8.7.6.5", 1111, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fill := func(t *testing.T) map[string]structs.CheckServiceNodes {
|
t.Run("allowed", func(t *testing.T) {
|
||||||
t.Helper()
|
require := require.New(t)
|
||||||
dup, err := copystructure.Copy(fixture)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return dup.(map[string]structs.CheckServiceNodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try permissive filtering.
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
{
|
node_prefix "" {
|
||||||
dcNodes := fill(t)
|
policy = "read"
|
||||||
filt := newACLFilter(acl.AllowAll(), nil)
|
}
|
||||||
filt.filterDatacenterCheckServiceNodes(&dcNodes)
|
service_prefix "" {
|
||||||
require.Len(t, dcNodes, 2)
|
policy = "read"
|
||||||
require.Equal(t, fill(t), dcNodes)
|
}
|
||||||
}
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
// Try restrictive filtering.
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
{
|
require.NoError(err)
|
||||||
dcNodes := fill(t)
|
|
||||||
filt := newACLFilter(acl.DenyAll(), nil)
|
|
||||||
filt.filterDatacenterCheckServiceNodes(&dcNodes)
|
|
||||||
require.Len(t, dcNodes, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
list := makeList()
|
||||||
policy *acl.Policy
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
err error
|
|
||||||
perms acl.Authorizer
|
|
||||||
)
|
|
||||||
// Allowed to see the service but not the node.
|
|
||||||
policy, err = acl.NewPolicyFromSource(`
|
|
||||||
service_prefix "" { policy = "read" }
|
|
||||||
`, acl.SyntaxCurrent, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
perms, err = acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
{
|
require.Len(list.DatacenterNodes["dc1"], 2)
|
||||||
dcNodes := fill(t)
|
require.Len(list.DatacenterNodes["dc2"], 2)
|
||||||
filt := newACLFilter(perms, nil)
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
filt.filterDatacenterCheckServiceNodes(&dcNodes)
|
})
|
||||||
require.Len(t, dcNodes, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allowed to see the node but not the service.
|
t.Run("allowed to read the service, but not the node", func(t *testing.T) {
|
||||||
policy, err = acl.NewPolicyFromSource(`
|
require := require.New(t)
|
||||||
node_prefix "" { policy = "read" }
|
|
||||||
`, acl.SyntaxCurrent, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
perms, err = acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
{
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
dcNodes := fill(t)
|
service_prefix "" {
|
||||||
filt := newACLFilter(perms, nil)
|
policy = "read"
|
||||||
filt.filterDatacenterCheckServiceNodes(&dcNodes)
|
}
|
||||||
require.Len(t, dcNodes, 0)
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
}
|
require.NoError(err)
|
||||||
|
|
||||||
// Allowed to see the service AND the node
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
policy, err = acl.NewPolicyFromSource(`
|
require.NoError(err)
|
||||||
service_prefix "" { policy = "read" }
|
|
||||||
node_prefix "" { policy = "read" }
|
|
||||||
`, acl.SyntaxCurrent, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Now it should go through.
|
list := makeList()
|
||||||
{
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
dcNodes := fill(t)
|
|
||||||
filt := newACLFilter(acl.AllowAll(), nil)
|
require.Empty(list.DatacenterNodes)
|
||||||
filt.filterDatacenterCheckServiceNodes(&dcNodes)
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
require.Len(t, dcNodes, 2)
|
})
|
||||||
require.Equal(t, fill(t), dcNodes)
|
|
||||||
}
|
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.DatacenterNodes)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("denied", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
|
||||||
|
|
||||||
|
require.Empty(list.DatacenterNodes)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_redactPreparedQueryTokens(t *testing.T) {
|
func TestACL_redactPreparedQueryTokens(t *testing.T) {
|
||||||
|
|
|
@ -502,9 +502,10 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
||||||
type tcase struct {
|
type tcase struct {
|
||||||
token string
|
token string
|
||||||
|
|
||||||
listDenied bool
|
listDenied bool
|
||||||
listEmpty bool
|
listEmpty bool
|
||||||
gwListEmpty bool
|
gwListEmpty bool
|
||||||
|
gwFilteredByACLs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := map[string]tcase{
|
cases := map[string]tcase{
|
||||||
|
@ -514,27 +515,31 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
||||||
gwListEmpty: true,
|
gwListEmpty: true,
|
||||||
},
|
},
|
||||||
"no perms": {
|
"no perms": {
|
||||||
token: nadaToken.SecretID,
|
token: nadaToken.SecretID,
|
||||||
listDenied: true,
|
listDenied: true,
|
||||||
gwListEmpty: true,
|
gwListEmpty: true,
|
||||||
|
gwFilteredByACLs: true,
|
||||||
},
|
},
|
||||||
"service:read": {
|
"service:read": {
|
||||||
token: svcReadToken.SecretID,
|
token: svcReadToken.SecretID,
|
||||||
listDenied: true,
|
listDenied: true,
|
||||||
gwListEmpty: true,
|
gwListEmpty: true,
|
||||||
|
gwFilteredByACLs: true,
|
||||||
},
|
},
|
||||||
"node:read": {
|
"node:read": {
|
||||||
token: nodeReadToken.SecretID,
|
token: nodeReadToken.SecretID,
|
||||||
listDenied: true,
|
listDenied: true,
|
||||||
gwListEmpty: true,
|
gwListEmpty: true,
|
||||||
|
gwFilteredByACLs: true,
|
||||||
},
|
},
|
||||||
"service:read and node:read": {
|
"service:read and node:read": {
|
||||||
token: svcAndNodeReadToken.SecretID,
|
token: svcAndNodeReadToken.SecretID,
|
||||||
listDenied: true,
|
listDenied: true,
|
||||||
},
|
},
|
||||||
"operator:read": {
|
"operator:read": {
|
||||||
token: opReadToken.SecretID,
|
token: opReadToken.SecretID,
|
||||||
gwListEmpty: true,
|
gwListEmpty: true,
|
||||||
|
gwFilteredByACLs: true,
|
||||||
},
|
},
|
||||||
"master token": {
|
"master token": {
|
||||||
token: "root",
|
token: "root",
|
||||||
|
@ -585,6 +590,11 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedMeshGateways.DatacenterNodes, out.DatacenterNodes)
|
require.Equal(t, expectedMeshGateways.DatacenterNodes, out.DatacenterNodes)
|
||||||
}
|
}
|
||||||
|
require.Equal(t,
|
||||||
|
tc.gwFilteredByACLs,
|
||||||
|
out.QueryMeta.ResultsFilteredByACLs,
|
||||||
|
"ResultsFilteredByACLs should be %v", tc.gwFilteredByACLs,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue