agent: add ACL enforcement to the v1/agent/health/service/* endpoints

This adds acl enforcement to the two endpoints that were missing it.

Note that in the case of getting a services health by its id, we still
must first lookup the service so we still "leak" information about a
service with that ID existing. There isn't really a way around it though
as ACLs are meant to check service names.
This commit is contained in:
Matt Keeler 2020-01-31 09:57:38 -05:00
parent 94de5adb02
commit 4bf1c2e4f7
No known key found for this signature in database
GPG Key ID: 04DBAE1857E0081B
2 changed files with 80 additions and 0 deletions

View File

@ -737,10 +737,23 @@ func (s *HTTPServer) AgentHealthServiceByID(resp http.ResponseWriter, req *http.
return nil, err
}
var token string
s.parseToken(req, &token)
// need to resolve to default the meta
var authzContext acl.AuthorizerContext
authz, err := s.agent.resolveTokenAndDefaultMeta(token, &entMeta, &authzContext)
if err != nil {
return nil, err
}
var sid structs.ServiceID
sid.Init(serviceID, &entMeta)
if service := s.agent.State.Service(sid); service != nil {
if authz != nil && authz.ServiceRead(service.Service, &authzContext) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
code, status, healthChecks := agentHealthService(sid, s)
if returnTextPlain(req) {
return status, CodeWithPayloadError{StatusCode: code, Reason: status, ContentType: "text/plain"}
@ -777,6 +790,20 @@ func (s *HTTPServer) AgentHealthServiceByName(resp http.ResponseWriter, req *htt
return nil, err
}
var token string
s.parseToken(req, &token)
// need to resolve to default the meta
var authzContext acl.AuthorizerContext
authz, err := s.agent.resolveTokenAndDefaultMeta(token, &entMeta, &authzContext)
if err != nil {
return nil, err
}
if authz != nil && authz.ServiceRead(serviceName, &authzContext) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
code := http.StatusNotFound
status := fmt.Sprintf("ServiceName %s Not Found", serviceName)
services := s.agent.State.Services(&entMeta)

View File

@ -1081,6 +1081,59 @@ func TestAgent_HealthServiceByName(t *testing.T) {
})
}
func TestAgent_HealthServicesACLEnforcement(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), TestACLConfigWithParams(nil))
defer a.Shutdown()
service := &structs.NodeService{
ID: "mysql1",
Service: "mysql",
}
require.NoError(t, a.AddService(service, nil, false, "", ConfigSourceLocal))
service = &structs.NodeService{
ID: "foo1",
Service: "foo",
}
require.NoError(t, a.AddService(service, nil, false, "", ConfigSourceLocal))
// no token
t.Run("no-token-health-by-id", func(t *testing.T) {
req, err := http.NewRequest("GET", "/v1/agent/health/service/id/mysql1", nil)
require.NoError(t, err)
resp := httptest.NewRecorder()
_, err = a.srv.AgentHealthServiceByID(resp, req)
require.Equal(t, acl.ErrPermissionDenied, err)
})
t.Run("no-token-health-by-name", func(t *testing.T) {
req, err := http.NewRequest("GET", "/v1/agent/health/service/name/mysql", nil)
require.NoError(t, err)
resp := httptest.NewRecorder()
_, err = a.srv.AgentHealthServiceByName(resp, req)
require.Equal(t, acl.ErrPermissionDenied, err)
})
t.Run("root-token-health-by-id", func(t *testing.T) {
req, err := http.NewRequest("GET", "/v1/agent/health/service/id/foo1", nil)
require.NoError(t, err)
req.Header.Add("X-Consul-Token", TestDefaultMasterToken)
resp := httptest.NewRecorder()
_, err = a.srv.AgentHealthServiceByID(resp, req)
require.NotEqual(t, acl.ErrPermissionDenied, err)
})
t.Run("root-token-health-by-name", func(t *testing.T) {
req, err := http.NewRequest("GET", "/v1/agent/health/service/name/foo", nil)
require.NoError(t, err)
req.Header.Add("X-Consul-Token", TestDefaultMasterToken)
resp := httptest.NewRecorder()
_, err = a.srv.AgentHealthServiceByName(resp, req)
require.NotEqual(t, acl.ErrPermissionDenied, err)
})
}
func TestAgent_Checks_ACLFilter(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), TestACLConfig())