event: support X-Consul-Results-Filtered-By-ACLs
header in list (#11616)
This commit is contained in:
parent
44bc833318
commit
86cf697e52
|
@ -127,17 +127,6 @@ RUN_QUERY:
|
||||||
// Get the recent events
|
// Get the recent events
|
||||||
events := s.agent.UserEvents()
|
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
|
// Filter the events if requested
|
||||||
if nameFilter != "" {
|
if nameFilter != "" {
|
||||||
for i := 0; i < len(events); i++ {
|
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
|
// Determine the index
|
||||||
var index uint64
|
var index uint64
|
||||||
if len(events) == 0 {
|
if len(events) == 0 {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEventFire(t *testing.T) {
|
func TestEventFire(t *testing.T) {
|
||||||
|
@ -199,47 +200,78 @@ func TestEventList_ACLFilter(t *testing.T) {
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
// Fire an event.
|
// Fire some events.
|
||||||
p := &UserEvent{Name: "foo"}
|
events := []*UserEvent{
|
||||||
if err := a.UserEvent("dc1", "root", p); err != nil {
|
{Name: "foo"},
|
||||||
t.Fatalf("err: %v", err)
|
{Name: "bar"},
|
||||||
|
}
|
||||||
|
for _, e := range events {
|
||||||
|
err := a.UserEvent("dc1", "root", e)
|
||||||
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("no token", func(t *testing.T) {
|
t.Run("no token", func(t *testing.T) {
|
||||||
retry.Run(t, func(r *retry.R) {
|
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()
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
obj, err := a.srv.EventList(resp, req)
|
obj, err := a.srv.EventList(resp, req)
|
||||||
if err != nil {
|
require.NoError(err)
|
||||||
r.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
list, ok := obj.([]*UserEvent)
|
list, ok := obj.([]*UserEvent)
|
||||||
if !ok {
|
require.True(ok)
|
||||||
r.Fatalf("bad: %#v", obj)
|
require.Empty(list)
|
||||||
}
|
require.Empty(resp.Header().Get("X-Consul-Results-Filtered-By-ACLs"))
|
||||||
if len(list) != 0 {
|
})
|
||||||
r.Fatalf("bad: %#v", list)
|
})
|
||||||
}
|
|
||||||
|
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) {
|
t.Run("root token", func(t *testing.T) {
|
||||||
retry.Run(t, func(r *retry.R) {
|
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()
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
obj, err := a.srv.EventList(resp, req)
|
obj, err := a.srv.EventList(resp, req)
|
||||||
if err != nil {
|
require.NoError(err)
|
||||||
r.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
list, ok := obj.([]*UserEvent)
|
list, ok := obj.([]*UserEvent)
|
||||||
if !ok {
|
require.True(ok)
|
||||||
r.Fatalf("bad: %#v", obj)
|
require.Len(list, 2)
|
||||||
}
|
|
||||||
if len(list) != 1 || list[0].Name != "foo" {
|
var names []string
|
||||||
r.Fatalf("bad: %#v", list)
|
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"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue