fedstate: support `ResultsFilteredByACLs` in `ListMeshGateways` endpoint (#11644)

This commit is contained in:
Dan Upton 2021-12-03 20:56:55 +00:00 committed by GitHub
parent 361d9c2862
commit 047aa2ffb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 121 additions and 104 deletions

View File

@ -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)

View File

@ -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,8 +3157,12 @@ 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()
makeList := func() *structs.DatacenterIndexedCheckServiceNodes {
return &structs.DatacenterIndexedCheckServiceNodes{
DatacenterNodes: map[string]structs.CheckServiceNodes{
"dc1": []structs.CheckServiceNode{ "dc1": []structs.CheckServiceNode{
newTestMeshGatewayNode( newTestMeshGatewayNode(
"dc1", "gateway1a", "1.2.3.4", 5555, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing, "dc1", "gateway1a", "1.2.3.4", 5555, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing,
@ -3176,84 +3179,83 @@ func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) {
"dc2", "gateway2b", "8.7.6.5", 1111, map[string]string{structs.MetaWANFederationKey: "1"}, api.HealthPassing, "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)
require.Len(t, dcNodes, 2)
require.Equal(t, fill(t), dcNodes)
} }
service_prefix "" {
// Try restrictive filtering. policy = "read"
{
dcNodes := fill(t)
filt := newACLFilter(acl.DenyAll(), nil)
filt.filterDatacenterCheckServiceNodes(&dcNodes)
require.Len(t, dcNodes, 0)
} }
var (
policy *acl.Policy
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) `, acl.SyntaxCurrent, nil, nil)
require.NoError(t, err) require.NoError(err)
perms, err = acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(t, err)
{ authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
dcNodes := fill(t) require.NoError(err)
filt := newACLFilter(perms, nil)
filt.filterDatacenterCheckServiceNodes(&dcNodes) list := makeList()
require.Len(t, dcNodes, 0) filterACLWithAuthorizer(logger, authz, list)
require.Len(list.DatacenterNodes["dc1"], 2)
require.Len(list.DatacenterNodes["dc2"], 2)
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
})
t.Run("allowed to read the service, but not the node", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service_prefix "" {
policy = "read"
} }
// Allowed to see the node but not the service.
policy, err = acl.NewPolicyFromSource(`
node_prefix "" { policy = "read" }
`, acl.SyntaxCurrent, nil, nil) `, acl.SyntaxCurrent, nil, nil)
require.NoError(t, err) require.NoError(err)
perms, err = acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(t, err)
{ authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
dcNodes := fill(t) require.NoError(err)
filt := newACLFilter(perms, nil)
filt.filterDatacenterCheckServiceNodes(&dcNodes) list := makeList()
require.Len(t, dcNodes, 0) filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.DatacenterNodes)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
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"
} }
// Allowed to see the service AND the node
policy, err = acl.NewPolicyFromSource(`
service_prefix "" { policy = "read" }
node_prefix "" { policy = "read" }
`, acl.SyntaxCurrent, nil, nil) `, acl.SyntaxCurrent, nil, nil)
require.NoError(t, err) require.NoError(err)
_, err = acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(t, err)
// Now it should go through. authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
{ require.NoError(err)
dcNodes := fill(t)
filt := newACLFilter(acl.AllowAll(), nil) list := makeList()
filt.filterDatacenterCheckServiceNodes(&dcNodes) filterACLWithAuthorizer(logger, authz, list)
require.Len(t, dcNodes, 2)
require.Equal(t, fill(t), dcNodes) 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) {

View File

@ -505,6 +505,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
listDenied bool listDenied bool
listEmpty bool listEmpty bool
gwListEmpty bool gwListEmpty bool
gwFilteredByACLs bool
} }
cases := map[string]tcase{ cases := map[string]tcase{
@ -517,16 +518,19 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
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,
@ -535,6 +539,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
"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,
)
}) })
}) })
} }