mirror of https://github.com/hashicorp/consul
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.pull/7191/head
parent
3a46e1d15f
commit
d8c0be2c84
|
@ -737,10 +737,23 @@ func (s *HTTPServer) AgentHealthServiceByID(resp http.ResponseWriter, req *http.
|
||||||
return nil, err
|
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
|
var sid structs.ServiceID
|
||||||
sid.Init(serviceID, &entMeta)
|
sid.Init(serviceID, &entMeta)
|
||||||
|
|
||||||
if service := s.agent.State.Service(sid); service != nil {
|
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)
|
code, status, healthChecks := agentHealthService(sid, s)
|
||||||
if returnTextPlain(req) {
|
if returnTextPlain(req) {
|
||||||
return status, CodeWithPayloadError{StatusCode: code, Reason: status, ContentType: "text/plain"}
|
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
|
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
|
code := http.StatusNotFound
|
||||||
status := fmt.Sprintf("ServiceName %s Not Found", serviceName)
|
status := fmt.Sprintf("ServiceName %s Not Found", serviceName)
|
||||||
services := s.agent.State.Services(&entMeta)
|
services := s.agent.State.Services(&entMeta)
|
||||||
|
|
|
@ -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) {
|
func TestAgent_Checks_ACLFilter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := NewTestAgent(t, t.Name(), TestACLConfig())
|
a := NewTestAgent(t, t.Name(), TestACLConfig())
|
||||||
|
|
Loading…
Reference in New Issue