consul: break acl filtering into a separate struct

This commit is contained in:
Ryan Uber 2015-06-11 12:08:21 -07:00
parent 404d4d653c
commit 1ff496d6dd
4 changed files with 147 additions and 93 deletions

View File

@ -2,6 +2,7 @@ package consul
import (
"errors"
"log"
"strings"
"time"
@ -193,10 +194,125 @@ func (s *Server) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *struc
return compiled, nil
}
// 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 {
// aclFilter is used to filter results from our state store based on ACL rules
// configured for the provided token.
type aclFilter struct {
acl acl.ACL
logger *log.Logger
}
// filterService is used to determine if a service is accessible for an ACL.
func (f *aclFilter) filterService(service string) bool {
if service == "" || service == ConsulServiceID {
return true
}
return f.acl.ServiceRead(service)
}
// filterHealthChecks is used to filter a set of health checks down based on
// the configured ACL rules for a token.
func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) {
hc := *checks
for i := 0; i < len(hc); i++ {
check := hc[i]
if f.filterService(check.ServiceName) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", check.CheckID)
hc = append(hc[:i], hc[i+1:]...)
i--
}
*checks = hc
}
// filterServices is used to filter a set of services based on ACLs.
func (f *aclFilter) filterServices(services structs.Services) {
for svc, _ := range services {
if f.filterService(svc) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
delete(services, svc)
}
}
// filterServiceNodes is used to filter a set of nodes for a given service
// based on the configured ACL rules.
func (f *aclFilter) filterServiceNodes(nodes *structs.ServiceNodes) {
sn := *nodes
for i := 0; i < len(sn); i++ {
node := sn[i]
if f.filterService(node.ServiceName) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node)
sn = append(sn[:i], sn[i+1:]...)
i--
}
*nodes = sn
}
// filterNodeServices is used to filter services on a given node base on ACLs.
func (f *aclFilter) filterNodeServices(services *structs.NodeServices) {
for svc, _ := range services.Services {
if f.filterService(svc) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
delete(services.Services, svc)
}
}
// filterCheckServiceNodes is used to filter nodes based on ACL rules.
func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) {
csn := *nodes
for i := 0; i < len(csn); i++ {
node := csn[i]
if f.filterService(node.Service.Service) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node)
csn = append(csn[:i], csn[i+1:]...)
i--
}
*nodes = csn
}
// filterNodeDump is used to filter through all parts of a node dump and
// remove elements the provided ACL token cannot access.
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
nd := *dump
for i := 0; i < len(nd); i++ {
info := nd[i]
// Filter services
for i := 0; i < len(info.Services); i++ {
svc := info.Services[i].Service
if f.filterService(svc) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
info.Services = append(info.Services[:i], info.Services[i+1:]...)
i--
}
// Filter checks
for i := 0; i < len(info.Checks); i++ {
chk := info.Checks[i]
if f.filterService(chk.ServiceName) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", chk.CheckID)
info.Checks = append(info.Checks[:i], info.Checks[i+1:]...)
i--
}
}
}
// aclFilter is used to filter results from our service catalog based on the
// rules configured for the provided token. The subject is scrubbed and
// modified in-place, leaving only resources the token can access.
func (s *Server) aclFilter(token string, subj interface{}) error {
// Get the ACL from the token
acl, err := s.resolveToken(token)
if err != nil {
@ -208,97 +324,27 @@ func (s *Server) applyDiscoveryACLs(token string, subj interface{}) error {
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
}
// Create the filter
filt := &aclFilter{acl, s.logger}
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--
}
filt.filterHealthChecks(&v.HealthChecks)
// Filter services
case *structs.IndexedServices:
for svc, _ := range v.Services {
if filt(svc) {
continue
}
delete(v.Services, svc)
}
filt.filterServices(v.Services)
// 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--
}
filt.filterServiceNodes(&v.ServiceNodes)
// Filter node services
case *structs.IndexedNodeServices:
for svc, _ := range v.NodeServices.Services {
if filt(svc) {
continue
}
delete(v.NodeServices.Services, svc)
}
filt.filterNodeServices(v.NodeServices)
// 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--
}
filt.filterCheckServiceNodes(&v.Nodes)
// Filter node dumps
case *structs.IndexedNodeDump:
for i := 0; i < len(v.Dump); i++ {
dump := v.Dump[i]
// Filter the services
for i := 0; i < len(dump.Services); i++ {
svc := dump.Services[i]
if filt(svc.Service) {
continue
}
dump.Services = append(dump.Services[:i], dump.Services[i+1:]...)
i--
}
// Filter the checks
for i := 0; i < len(dump.Checks); i++ {
chk := dump.Checks[i]
if filt(chk.ServiceName) {
continue
}
dump.Checks = append(dump.Checks[:i], dump.Checks[i+1:]...)
i--
}
}
filt.filterNodeDump(&v.Dump)
}
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 c.srv.applyDiscoveryACLs(args.Token, reply)
return c.srv.aclFilter(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 c.srv.applyDiscoveryACLs(args.Token, reply)
return c.srv.aclFilter(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 c.srv.applyDiscoveryACLs(args.Token, reply)
return c.srv.aclFilter(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 c.srv.applyDiscoveryACLs(args.Token, reply)
return c.srv.aclFilter(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 h.srv.applyDiscoveryACLs(args.Token, reply)
return h.srv.aclFilter(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 h.srv.applyDiscoveryACLs(args.Token, reply)
return h.srv.aclFilter(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 h.srv.applyDiscoveryACLs(args.Token, reply)
return h.srv.aclFilter(args.Token, reply)
})
// Provide some metrics

View File

@ -25,8 +25,12 @@ func (m *Internal) NodeInfo(args *structs.NodeSpecificRequest,
&reply.QueryMeta,
state.QueryTables("NodeInfo"),
func() error {
reply.Index, reply.Dump = state.NodeInfo(args.Node)
return m.srv.applyDiscoveryACLs(args.Token, reply)
index, dump := state.NodeInfo(args.Node)
if err := m.srv.aclFilter(args.Token, &dump); err != nil {
return err
}
reply.Index, reply.Dump = index, dump
return nil
})
}
@ -43,8 +47,12 @@ func (m *Internal) NodeDump(args *structs.DCSpecificRequest,
&reply.QueryMeta,
state.QueryTables("NodeDump"),
func() error {
reply.Index, reply.Dump = state.NodeDump()
return m.srv.applyDiscoveryACLs(args.Token, reply)
index, dump := state.NodeDump()
if err := m.srv.aclFilter(args.Token, &dump); err != nil {
return err
}
reply.Index, reply.Dump = index, dump
return nil
})
}