mirror of https://github.com/hashicorp/consul
consul: break acl filtering into a separate struct
parent
0159511714
commit
6623538c93
210
consul/acl.go
210
consul/acl.go
|
@ -2,6 +2,7 @@ package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -193,10 +194,125 @@ func (s *Server) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *struc
|
||||||
return compiled, nil
|
return compiled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyDiscoveryACLs is used to filter results from our service catalog based
|
// aclFilter is used to filter results from our state store based on ACL rules
|
||||||
// on the configured rules for the request ACL. Nodes or services which do
|
// configured for the provided token.
|
||||||
// not match the ACL rules will be dropped from the result.
|
type aclFilter struct {
|
||||||
func (s *Server) applyDiscoveryACLs(token string, subj interface{}) error {
|
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
|
// Get the ACL from the token
|
||||||
acl, err := s.resolveToken(token)
|
acl, err := s.resolveToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -208,97 +324,27 @@ func (s *Server) applyDiscoveryACLs(token string, subj interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
filt := func(service string) bool {
|
// Create the filter
|
||||||
// Don't filter the "consul" service or empty service names
|
filt := &aclFilter{acl, s.logger}
|
||||||
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) {
|
switch v := subj.(type) {
|
||||||
// Filter health checks
|
|
||||||
case *structs.IndexedHealthChecks:
|
case *structs.IndexedHealthChecks:
|
||||||
for i := 0; i < len(v.HealthChecks); i++ {
|
filt.filterHealthChecks(&v.HealthChecks)
|
||||||
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:
|
case *structs.IndexedServices:
|
||||||
for svc, _ := range v.Services {
|
filt.filterServices(v.Services)
|
||||||
if filt(svc) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(v.Services, svc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter service nodes
|
|
||||||
case *structs.IndexedServiceNodes:
|
case *structs.IndexedServiceNodes:
|
||||||
for i := 0; i < len(v.ServiceNodes); i++ {
|
filt.filterServiceNodes(&v.ServiceNodes)
|
||||||
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:
|
case *structs.IndexedNodeServices:
|
||||||
for svc, _ := range v.NodeServices.Services {
|
filt.filterNodeServices(v.NodeServices)
|
||||||
if filt(svc) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(v.NodeServices.Services, svc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter check service nodes
|
|
||||||
case *structs.IndexedCheckServiceNodes:
|
case *structs.IndexedCheckServiceNodes:
|
||||||
for i := 0; i < len(v.Nodes); i++ {
|
filt.filterCheckServiceNodes(&v.Nodes)
|
||||||
cs := v.Nodes[i]
|
|
||||||
if filt(cs.Service.Service) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
v.Nodes = append(v.Nodes[:i], v.Nodes[i+1:]...)
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter node dumps
|
|
||||||
case *structs.IndexedNodeDump:
|
case *structs.IndexedNodeDump:
|
||||||
for i := 0; i < len(v.Dump); i++ {
|
filt.filterNodeDump(&v.Dump)
|
||||||
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--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (c *Catalog) ListNodes(args *structs.DCSpecificRequest, reply *structs.Inde
|
||||||
state.QueryTables("Nodes"),
|
state.QueryTables("Nodes"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.Nodes = state.Nodes()
|
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"),
|
state.QueryTables("Services"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.Services = state.Services()
|
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 {
|
} else {
|
||||||
reply.Index, reply.ServiceNodes = state.ServiceNodes(args.ServiceName)
|
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
|
// Provide some metrics
|
||||||
|
@ -203,6 +203,6 @@ func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs
|
||||||
state.QueryTables("NodeServices"),
|
state.QueryTables("NodeServices"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.NodeServices = state.NodeServices(args.Node)
|
reply.Index, reply.NodeServices = state.NodeServices(args.Node)
|
||||||
return c.srv.applyDiscoveryACLs(args.Token, reply)
|
return c.srv.aclFilter(args.Token, reply)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (h *Health) NodeChecks(args *structs.NodeSpecificRequest,
|
||||||
state.QueryTables("NodeChecks"),
|
state.QueryTables("NodeChecks"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.HealthChecks = state.NodeChecks(args.Node)
|
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"),
|
state.QueryTables("ServiceChecks"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.HealthChecks = state.ServiceChecks(args.ServiceName)
|
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 {
|
} else {
|
||||||
reply.Index, reply.Nodes = state.CheckServiceNodes(args.ServiceName)
|
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
|
// Provide some metrics
|
||||||
|
|
|
@ -25,8 +25,12 @@ func (m *Internal) NodeInfo(args *structs.NodeSpecificRequest,
|
||||||
&reply.QueryMeta,
|
&reply.QueryMeta,
|
||||||
state.QueryTables("NodeInfo"),
|
state.QueryTables("NodeInfo"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.Dump = state.NodeInfo(args.Node)
|
index, dump := state.NodeInfo(args.Node)
|
||||||
return m.srv.applyDiscoveryACLs(args.Token, reply)
|
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,
|
&reply.QueryMeta,
|
||||||
state.QueryTables("NodeDump"),
|
state.QueryTables("NodeDump"),
|
||||||
func() error {
|
func() error {
|
||||||
reply.Index, reply.Dump = state.NodeDump()
|
index, dump := state.NodeDump()
|
||||||
return m.srv.applyDiscoveryACLs(args.Token, reply)
|
if err := m.srv.aclFilter(args.Token, &dump); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reply.Index, reply.Dump = index, dump
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue