acl: initial pass at service discovery acls

This commit is contained in:
Ryan Uber 2015-06-09 12:36:25 -07:00
parent b9bd6110e2
commit 4f3e661b8c
4 changed files with 115 additions and 13 deletions

View File

@ -192,3 +192,105 @@ func (s *Server) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *struc
s.aclCache.Add(id, cached)
return compiled, nil
}
// discoveryFilter is used to determine if we should return a given node
// or service based on the ACL passed in.
func (s *Server) discoveryFilter(node, service string, acl acl.ACL) bool {
if acl == nil {
return true
}
// Filter service discovery ACLs
if service != "" && service != ConsulServiceID && !acl.ServiceRead(service) {
s.logger.Printf("[DEBUG] consul: reading service '%s' denied due to ACLs", service)
return false
}
// Filtering passed
return true
}
// applyDiscoveryACLs is used to filter results from our service catalog based
// on the configured rules for the request ACL. Nodes or services which do
// not match the ACL rules will be dropped from the result.
func (s *Server) applyDiscoveryACLs(token string, subj interface{}) error {
// Get the ACL from the token
acl, err := s.resolveToken(token)
if err != nil {
return err
}
// Fast path if ACLs are not enabled
if acl == nil {
return nil
}
filt := func(service string) bool {
// Don't filter the "consul" service or empty service names
if service == "" || service == ConsulServiceID {
return true
}
// Check the ACL
if !acl.ServiceRead(service) {
s.logger.Printf("[DEBUG] consul: reading service '%s' denied due to ACLs", service)
return false
}
return true
}
switch v := subj.(type) {
// Filter health checks
case *structs.IndexedHealthChecks:
for i := 0; i < len(v.HealthChecks); i++ {
hc := v.HealthChecks[i]
if filt(hc.ServiceName) {
continue
}
v.HealthChecks = append(v.HealthChecks[:i], v.HealthChecks[i+1:]...)
i--
}
// Filter services
case *structs.IndexedServices:
for svc, _ := range v.Services {
if filt(svc) {
continue
}
delete(v.Services, svc)
}
// Filter service nodes
case *structs.IndexedServiceNodes:
for i := 0; i < len(v.ServiceNodes); i++ {
node := v.ServiceNodes[i]
if filt(node.ServiceName) {
continue
}
v.ServiceNodes = append(v.ServiceNodes[:i], v.ServiceNodes[i+1:]...)
i--
}
// Filter node services
case *structs.IndexedNodeServices:
for svc, _ := range v.NodeServices.Services {
if filt(svc) {
continue
}
delete(v.NodeServices.Services, svc)
}
// Filter check service nodes
case *structs.IndexedCheckServiceNodes:
for i := 0; i < len(v.Nodes); i++ {
cs := v.Nodes[i]
if filt(cs.Service.Service) {
continue
}
v.Nodes = append(v.Nodes[:i], v.Nodes[i+1:]...)
i--
}
}
return nil
}

View File

@ -126,7 +126,7 @@ func (c *Catalog) ListNodes(args *structs.DCSpecificRequest, reply *structs.Inde
state.QueryTables("Nodes"),
func() error {
reply.Index, reply.Nodes = state.Nodes()
return nil
return c.srv.applyDiscoveryACLs(args.Token, reply)
})
}
@ -143,7 +143,7 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I
state.QueryTables("Services"),
func() error {
reply.Index, reply.Services = state.Services()
return nil
return c.srv.applyDiscoveryACLs(args.Token, reply)
})
}
@ -169,7 +169,7 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
} else {
reply.Index, reply.ServiceNodes = state.ServiceNodes(args.ServiceName)
}
return nil
return c.srv.applyDiscoveryACLs(args.Token, reply)
})
// Provide some metrics
@ -203,6 +203,6 @@ func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs
state.QueryTables("NodeServices"),
func() error {
reply.Index, reply.NodeServices = state.NodeServices(args.Node)
return nil
return c.srv.applyDiscoveryACLs(args.Token, reply)
})
}

View File

@ -43,7 +43,7 @@ func (h *Health) NodeChecks(args *structs.NodeSpecificRequest,
state.QueryTables("NodeChecks"),
func() error {
reply.Index, reply.HealthChecks = state.NodeChecks(args.Node)
return nil
return h.srv.applyDiscoveryACLs(args.Token, reply)
})
}
@ -67,7 +67,7 @@ func (h *Health) ServiceChecks(args *structs.ServiceSpecificRequest,
state.QueryTables("ServiceChecks"),
func() error {
reply.Index, reply.HealthChecks = state.ServiceChecks(args.ServiceName)
return nil
return h.srv.applyDiscoveryACLs(args.Token, reply)
})
}
@ -93,7 +93,7 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
} else {
reply.Index, reply.Nodes = state.CheckServiceNodes(args.ServiceName)
}
return nil
return h.srv.applyDiscoveryACLs(args.Token, reply)
})
// Provide some metrics

View File

@ -12,39 +12,39 @@ type Internal struct {
srv *Server
}
// ChecksInState is used to get all the checks in a given state
// NodeInfo is used to retrieve information about a specific node.
func (m *Internal) NodeInfo(args *structs.NodeSpecificRequest,
reply *structs.IndexedNodeDump) error {
if done, err := m.srv.forward("Internal.NodeInfo", args, args, reply); done {
return err
}
// Get the state specific checks
// Get the node info
state := m.srv.fsm.State()
return m.srv.blockingRPC(&args.QueryOptions,
&reply.QueryMeta,
state.QueryTables("NodeInfo"),
func() error {
reply.Index, reply.Dump = state.NodeInfo(args.Node)
return nil
return m.srv.applyDiscoveryACLs(args.Token, reply)
})
}
// ChecksInState is used to get all the checks in a given state
// NodeDump is used to generate information about all of the nodes.
func (m *Internal) NodeDump(args *structs.DCSpecificRequest,
reply *structs.IndexedNodeDump) error {
if done, err := m.srv.forward("Internal.NodeDump", args, args, reply); done {
return err
}
// Get the state specific checks
// Get all the node info
state := m.srv.fsm.State()
return m.srv.blockingRPC(&args.QueryOptions,
&reply.QueryMeta,
state.QueryTables("NodeDump"),
func() error {
reply.Index, reply.Dump = state.NodeDump()
return nil
return m.srv.applyDiscoveryACLs(args.Token, reply)
})
}