event: support `X-Consul-Results-Filtered-By-ACLs` header in list (#11616)

This commit is contained in:
Dan Upton 2021-12-03 17:38:59 +00:00 committed by GitHub
parent 44bc833318
commit 86cf697e52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 34 deletions

View File

@ -127,17 +127,6 @@ RUN_QUERY:
// Get the recent events
events := s.agent.UserEvents()
// Filter the events using the ACL, if present
for i := 0; i < len(events); i++ {
name := events[i].Name
if authz.EventRead(name, nil) == acl.Allow {
continue
}
s.agent.logger.Debug("dropping event from result due to ACLs", "event", name)
events = append(events[:i], events[i+1:]...)
i--
}
// Filter the events if requested
if nameFilter != "" {
for i := 0; i < len(events); i++ {
@ -148,6 +137,36 @@ RUN_QUERY:
}
}
// Filter the events using the ACL, if present
//
// Note: we filter the results with ACLs *after* applying the user-supplied
// name filter, to ensure the filtered-by-acls header we set below does not
// include results that would be filtered out even if the user did have
// permission.
var removed bool
for i := 0; i < len(events); i++ {
name := events[i].Name
if authz.EventRead(name, nil) == acl.Allow {
continue
}
s.agent.logger.Debug("dropping event from result due to ACLs", "event", name)
removed = true
events = append(events[:i], events[i+1:]...)
i--
}
// Set the X-Consul-Results-Filtered-By-ACLs header, but only if the user is
// authenticated (to prevent information leaking).
//
// This is done automatically for HTTP endpoints that proxy to an RPC endpoint
// that sets QueryMeta.ResultsFilteredByACLs, but must be done manually for
// agent-local endpoints.
//
// For more information see the comment on: Server.maskResultsFilteredByACLs.
if token != "" {
setResultsFilteredByACLs(resp, removed)
}
// Determine the index
var index uint64
if len(events) == 0 {

View File

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/hashicorp/consul/testrpc"
"github.com/stretchr/testify/require"
)
func TestEventFire(t *testing.T) {
@ -199,47 +200,78 @@ func TestEventList_ACLFilter(t *testing.T) {
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Fire an event.
p := &UserEvent{Name: "foo"}
if err := a.UserEvent("dc1", "root", p); err != nil {
t.Fatalf("err: %v", err)
// Fire some events.
events := []*UserEvent{
{Name: "foo"},
{Name: "bar"},
}
for _, e := range events {
err := a.UserEvent("dc1", "root", e)
require.NoError(t, err)
}
t.Run("no token", func(t *testing.T) {
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("GET", "/v1/event/list", nil)
require := require.New(r)
req := httptest.NewRequest("GET", "/v1/event/list", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.EventList(resp, req)
if err != nil {
r.Fatal(err)
}
require.NoError(err)
list, ok := obj.([]*UserEvent)
if !ok {
r.Fatalf("bad: %#v", obj)
}
if len(list) != 0 {
r.Fatalf("bad: %#v", list)
}
require.True(ok)
require.Empty(list)
require.Empty(resp.Header().Get("X-Consul-Results-Filtered-By-ACLs"))
})
})
t.Run("token with access to one event type", func(t *testing.T) {
retry.Run(t, func(r *retry.R) {
require := require.New(r)
token := testCreateToken(t, a, `
event "foo" {
policy = "read"
}
`)
req := httptest.NewRequest("GET", fmt.Sprintf("/v1/event/list?token=%s", token), nil)
resp := httptest.NewRecorder()
obj, err := a.srv.EventList(resp, req)
require.NoError(err)
list, ok := obj.([]*UserEvent)
require.True(ok)
require.Len(list, 1)
require.Equal("foo", list[0].Name)
require.NotEmpty(resp.Header().Get("X-Consul-Results-Filtered-By-ACLs"))
})
})
t.Run("root token", func(t *testing.T) {
retry.Run(t, func(r *retry.R) {
req, _ := http.NewRequest("GET", "/v1/event/list?token=root", nil)
require := require.New(r)
req := httptest.NewRequest("GET", "/v1/event/list?token=root", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.EventList(resp, req)
if err != nil {
r.Fatal(err)
}
require.NoError(err)
list, ok := obj.([]*UserEvent)
if !ok {
r.Fatalf("bad: %#v", obj)
}
if len(list) != 1 || list[0].Name != "foo" {
r.Fatalf("bad: %#v", list)
require.True(ok)
require.Len(list, 2)
var names []string
for _, e := range list {
names = append(names, e.Name)
}
require.ElementsMatch([]string{"foo", "bar"}, names)
require.Empty(resp.Header().Get("X-Consul-Results-Filtered-By-ACLs"))
})
})
}