mirror of https://github.com/status-im/consul.git
intention: support `ResultsFilteredByACLs` flag/header (#11612)
This commit is contained in:
parent
0c4633a231
commit
c314be2ff9
|
@ -1420,11 +1420,14 @@ func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) {
|
||||||
|
|
||||||
// filterIntentions is used to filter intentions based on ACL rules.
|
// filterIntentions is used to filter intentions based on ACL rules.
|
||||||
// We prune entries the user doesn't have access to, and we redact any tokens
|
// We prune entries the user doesn't have access to, and we redact any tokens
|
||||||
// if the user doesn't have a management token.
|
// if the user doesn't have a management token. Returns true if any elements
|
||||||
func (f *aclFilter) filterIntentions(ixns *structs.Intentions) {
|
// were removed.
|
||||||
|
func (f *aclFilter) filterIntentions(ixns *structs.Intentions) bool {
|
||||||
ret := make(structs.Intentions, 0, len(*ixns))
|
ret := make(structs.Intentions, 0, len(*ixns))
|
||||||
|
var removed bool
|
||||||
for _, ixn := range *ixns {
|
for _, ixn := range *ixns {
|
||||||
if !ixn.CanRead(f.authorizer) {
|
if !ixn.CanRead(f.authorizer) {
|
||||||
|
removed = true
|
||||||
f.logger.Debug("dropping intention from result due to ACLs", "intention", ixn.ID)
|
f.logger.Debug("dropping intention from result due to ACLs", "intention", ixn.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1433,6 +1436,7 @@ func (f *aclFilter) filterIntentions(ixns *structs.Intentions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
*ixns = ret
|
*ixns = ret
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterNodeDump is used to filter through all parts of a node dump and
|
// filterNodeDump is used to filter through all parts of a node dump and
|
||||||
|
@ -1824,7 +1828,7 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
|
||||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterHealthChecks(&v.HealthChecks)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterHealthChecks(&v.HealthChecks)
|
||||||
|
|
||||||
case *structs.IndexedIntentions:
|
case *structs.IndexedIntentions:
|
||||||
filt.filterIntentions(&v.Intentions)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterIntentions(&v.Intentions)
|
||||||
|
|
||||||
case *structs.IndexedNodeDump:
|
case *structs.IndexedNodeDump:
|
||||||
filt.filterNodeDump(&v.Dump)
|
filt.filterNodeDump(&v.Dump)
|
||||||
|
|
|
@ -2243,54 +2243,63 @@ func TestACL_filterHealthChecks(t *testing.T) {
|
||||||
|
|
||||||
func TestACL_filterIntentions(t *testing.T) {
|
func TestACL_filterIntentions(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
fill := func() structs.Intentions {
|
logger := hclog.NewNullLogger()
|
||||||
return structs.Intentions{
|
|
||||||
&structs.Intention{
|
makeList := func() *structs.IndexedIntentions {
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d1",
|
return &structs.IndexedIntentions{
|
||||||
DestinationName: "bar",
|
Intentions: structs.Intentions{
|
||||||
},
|
&structs.Intention{
|
||||||
&structs.Intention{
|
ID: "f004177f-2c28-83b7-4229-eacc25fe55d1",
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
|
DestinationName: "bar",
|
||||||
DestinationName: "foo",
|
},
|
||||||
|
&structs.Intention{
|
||||||
|
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
|
||||||
|
DestinationName: "foo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try permissive filtering.
|
t.Run("allowed", func(t *testing.T) {
|
||||||
{
|
require := require.New(t)
|
||||||
ixns := fill()
|
|
||||||
filt := newACLFilter(acl.AllowAll(), nil)
|
|
||||||
filt.filterIntentions(&ixns)
|
|
||||||
assert.Len(ixns, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try restrictive filtering.
|
list := makeList()
|
||||||
{
|
filterACLWithAuthorizer(logger, acl.AllowAll(), list)
|
||||||
ixns := fill()
|
|
||||||
filt := newACLFilter(acl.DenyAll(), nil)
|
|
||||||
filt.filterIntentions(&ixns)
|
|
||||||
assert.Len(ixns, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Policy to see one
|
require.Len(list.Intentions, 2)
|
||||||
policy, err := acl.NewPolicyFromSource(`
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
service "foo" {
|
})
|
||||||
policy = "read"
|
|
||||||
}
|
|
||||||
`, acl.SyntaxLegacy, nil, nil)
|
|
||||||
assert.Nil(err)
|
|
||||||
perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
|
||||||
assert.Nil(err)
|
|
||||||
|
|
||||||
// Filter
|
t.Run("allowed to read 1", func(t *testing.T) {
|
||||||
{
|
require := require.New(t)
|
||||||
ixns := fill()
|
|
||||||
filt := newACLFilter(perms, nil)
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
filt.filterIntentions(&ixns)
|
service "foo" {
|
||||||
assert.Len(ixns, 1)
|
policy = "read"
|
||||||
}
|
}
|
||||||
|
`, acl.SyntaxLegacy, 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.Len(list.Intentions, 1)
|
||||||
|
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.Intentions)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_filterServices(t *testing.T) {
|
func TestACL_filterServices(t *testing.T) {
|
||||||
|
|
|
@ -550,17 +550,19 @@ func (s *Intention) List(args *structs.IntentionListRequest, reply *structs.Inde
|
||||||
} else {
|
} else {
|
||||||
reply.DataOrigin = structs.IntentionDataOriginLegacy
|
reply.DataOrigin = structs.IntentionDataOriginLegacy
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.srv.filterACL(args.Token, reply); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
raw, err := filter.Execute(reply.Intentions)
|
raw, err := filter.Execute(reply.Intentions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply.Intentions = raw.(structs.Intentions)
|
reply.Intentions = raw.(structs.Intentions)
|
||||||
|
|
||||||
|
// Note: we filter the results with ACLs *after* applying the user-supplied
|
||||||
|
// bexpr filter, to ensure QueryMeta.ResultsFilteredByACLs does not include
|
||||||
|
// results that would be filtered out even if the user did have permission.
|
||||||
|
if err := s.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -1635,6 +1635,7 @@ func TestIntentionList_acl(t *testing.T) {
|
||||||
var resp structs.IndexedIntentions
|
var resp structs.IndexedIntentions
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
||||||
require.Len(t, resp.Intentions, 0)
|
require.Len(t, resp.Intentions, 0)
|
||||||
|
require.False(t, resp.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test with management token
|
// Test with management token
|
||||||
|
@ -1646,6 +1647,7 @@ func TestIntentionList_acl(t *testing.T) {
|
||||||
var resp structs.IndexedIntentions
|
var resp structs.IndexedIntentions
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
||||||
require.Len(t, resp.Intentions, 3)
|
require.Len(t, resp.Intentions, 3)
|
||||||
|
require.False(t, resp.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test with user token
|
// Test with user token
|
||||||
|
@ -1657,6 +1659,7 @@ func TestIntentionList_acl(t *testing.T) {
|
||||||
var resp structs.IndexedIntentions
|
var resp structs.IndexedIntentions
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
||||||
require.Len(t, resp.Intentions, 1)
|
require.Len(t, resp.Intentions, 1)
|
||||||
|
require.True(t, resp.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("filtered", func(t *testing.T) {
|
t.Run("filtered", func(t *testing.T) {
|
||||||
|
@ -1671,6 +1674,7 @@ func TestIntentionList_acl(t *testing.T) {
|
||||||
var resp structs.IndexedIntentions
|
var resp structs.IndexedIntentions
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
|
||||||
require.Len(t, resp.Intentions, 1)
|
require.Len(t, resp.Intentions, 1)
|
||||||
|
require.False(t, resp.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue