diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 0296c3e1cc..1ce49faec0 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -54,6 +54,21 @@ func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (int }, nil } +func (s *HTTPServer) AgentMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Fetch the ACL token, if any, and enforce agent policy. + var token string + s.parseToken(req, &token) + acl, err := s.agent.resolveToken(token) + if err != nil { + return nil, err + } + if acl != nil && !acl.AgentRead(s.agent.config.NodeName) { + return nil, errPermissionDenied + } + + return s.agent.MemSink.DisplayMetrics(resp, req) +} + func (s *HTTPServer) AgentReload(resp http.ResponseWriter, req *http.Request) (interface{}, error) { if req.Method != "PUT" { resp.WriteHeader(http.StatusMethodNotAllowed) diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index 1d9fe5ff7e..0b2dd2f0eb 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -239,6 +239,34 @@ func TestAgent_Self_ACLDeny(t *testing.T) { }) } +func TestAgent_Metrics_ACLDeny(t *testing.T) { + t.Parallel() + a := NewTestAgent(t.Name(), TestACLConfig()) + defer a.Shutdown() + + t.Run("no token", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/v1/agent/metrics", nil) + if _, err := a.srv.AgentSelf(nil, req); !isPermissionDenied(err) { + t.Fatalf("err: %v", err) + } + }) + + t.Run("agent master token", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/v1/agent/metrics?token=towel", nil) + if _, err := a.srv.AgentSelf(nil, req); err != nil { + t.Fatalf("err: %v", err) + } + }) + + t.Run("read-only token", func(t *testing.T) { + ro := makeReadOnlyAgentACL(t, a.srv) + req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/agent/metrics?token=%s", ro), nil) + if _, err := a.srv.AgentSelf(nil, req); err != nil { + t.Fatalf("err: %v", err) + } + }) +} + func TestAgent_Reload(t *testing.T) { t.Parallel() cfg := TestConfig() diff --git a/agent/http.go b/agent/http.go index 968f2100d3..8806ee2d2c 100644 --- a/agent/http.go +++ b/agent/http.go @@ -98,7 +98,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { handleFuncMetrics("/v1/agent/maintenance", s.wrap(s.AgentNodeMaintenance)) handleFuncMetrics("/v1/agent/reload", s.wrap(s.AgentReload)) handleFuncMetrics("/v1/agent/monitor", s.wrap(s.AgentMonitor)) - handleFuncMetrics("/v1/agent/metrics", s.wrap(s.requireAgentRead(s.agent.MemSink.DisplayMetrics))) + handleFuncMetrics("/v1/agent/metrics", s.wrap(s.AgentMetrics)) handleFuncMetrics("/v1/agent/services", s.wrap(s.AgentServices)) handleFuncMetrics("/v1/agent/checks", s.wrap(s.AgentChecks)) handleFuncMetrics("/v1/agent/members", s.wrap(s.AgentMembers)) @@ -264,26 +264,6 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque } } -type handlerFunc func(resp http.ResponseWriter, req *http.Request) (interface{}, error) - -// requireAgentRead wraps the given function, requiring a token with agent read permissions -func (s *HTTPServer) requireAgentRead(handler handlerFunc) handlerFunc { - return func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { - // Fetch the ACL token, if any, and enforce agent policy. - var token string - s.parseToken(req, &token) - acl, err := s.agent.resolveToken(token) - if err != nil { - return nil, err - } - if acl != nil && !acl.AgentRead(s.agent.config.NodeName) { - return nil, errPermissionDenied - } - - return handler(resp, req) - } -} - // marshalJSON marshals the object into JSON, respecting the user's pretty-ness // configuration. func (s *HTTPServer) marshalJSON(req *http.Request, obj interface{}) ([]byte, error) { diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index fde27bdbf0..89dffbc89e 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -1345,3 +1345,4 @@ items which are reloaded include: * Watches * HTTP Client Address * Node Metadata +* Metric Prefix Filter diff --git a/website/source/docs/agent/telemetry.html.md b/website/source/docs/agent/telemetry.html.md index b63f686868..02bb3767ca 100644 --- a/website/source/docs/agent/telemetry.html.md +++ b/website/source/docs/agent/telemetry.html.md @@ -22,7 +22,8 @@ getting a better view of what Consul is doing. Additionally, if the [`telemetry` configuration options](/docs/agent/options.html#telemetry) are provided, the telemetry information will be streamed to a [statsite](http://github.com/armon/statsite) or [statsd](http://github.com/etsy/statsd) server where -it can be aggregated and flushed to Graphite or any other metrics store. +it can be aggregated and flushed to Graphite or any other metrics store. This +information can also be viewed with the [metrics endpoint](/api/agent.html#view-metrics) Below is sample output of a telemetry dump: