diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index b284c8029..b8f9452c3 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -172,10 +172,10 @@ func (s *HTTPHandlers) AgentMetricsStream(resp http.ResponseWriter, req *http.Re var token string s.parseToken(req, &token) rule, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, nil, nil) - if err != nil { + switch { + case err != nil: return nil, err - } - if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow { + case rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow: return nil, acl.ErrPermissionDenied } diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index 77dbb9489..7fa629dac 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -18,6 +18,8 @@ import ( "testing" "time" + "github.com/armon/go-metrics" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-uuid" "github.com/hashicorp/serf/serf" "github.com/stretchr/testify/assert" @@ -1406,6 +1408,91 @@ func TestAgent_Metrics_ACLDeny(t *testing.T) { }) } +func TestHTTPHandlers_AgentMetricsStream_ACLDeny(t *testing.T) { + bd := BaseDeps{} + bd.Tokens = new(tokenStore.Store) + sink := metrics.NewInmemSink(30*time.Millisecond, time.Second) + bd.MetricsHandler = sink + d := fakeResolveTokenDelegate{authorizer: acl.DenyAll()} + agent := &Agent{ + baseDeps: bd, + delegate: d, + tokens: bd.Tokens, + config: &config.RuntimeConfig{NodeName: "the-node"}, + logger: hclog.NewInterceptLogger(nil), + } + h := HTTPHandlers{agent: agent, denylist: NewDenylist(nil)} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + resp := httptest.NewRecorder() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/v1/agent/metrics/stream", nil) + require.NoError(t, err) + handle := h.handler(false) + handle.ServeHTTP(resp, req) + require.Equal(t, http.StatusForbidden, resp.Code) + require.Contains(t, resp.Body.String(), "Permission denied") +} + +func TestHTTPHandlers_AgentMetricsStream(t *testing.T) { + bd := BaseDeps{} + bd.Tokens = new(tokenStore.Store) + sink := metrics.NewInmemSink(20*time.Millisecond, time.Second) + bd.MetricsHandler = sink + d := fakeResolveTokenDelegate{} + agent := &Agent{ + baseDeps: bd, + delegate: d, + tokens: bd.Tokens, + config: &config.RuntimeConfig{NodeName: "the-node"}, + logger: hclog.NewInterceptLogger(nil), + } + h := HTTPHandlers{agent: agent, denylist: NewDenylist(nil)} + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Millisecond) + defer cancel() + + // produce some metrics + go func() { + for ctx.Err() == nil { + sink.SetGauge([]string{"the-key"}, 12) + time.Sleep(5 * time.Millisecond) + } + }() + + resp := httptest.NewRecorder() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/v1/agent/metrics/stream", nil) + require.NoError(t, err) + handle := h.handler(false) + handle.ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code) + + decoder := json.NewDecoder(resp.Body) + var summary metrics.MetricsSummary + err = decoder.Decode(&summary) + require.NoError(t, err) + + expected := []metrics.GaugeValue{ + {Name: "the-key", Value: 12, DisplayLabels: map[string]string{}}, + } + require.Equal(t, expected, summary.Gauges) + + // There should be at least two intervals worth of metrics + err = decoder.Decode(&summary) + require.NoError(t, err) + require.Equal(t, expected, summary.Gauges) +} + +type fakeResolveTokenDelegate struct { + delegate + authorizer acl.Authorizer +} + +func (f fakeResolveTokenDelegate) ResolveTokenAndDefaultMeta(_ string, _ *structs.EnterpriseMeta, _ *acl.AuthorizerContext) (acl.Authorizer, error) { + return f.authorizer, nil +} + func TestAgent_Reload(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short")