diff --git a/consul/acl.go b/consul/acl.go index 1a8cd9cef2..bdffc2513b 100644 --- a/consul/acl.go +++ b/consul/acl.go @@ -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 diff --git a/consul/catalog_endpoint.go b/consul/catalog_endpoint.go index 3e05755c6f..dc9a20f273 100644 --- a/consul/catalog_endpoint.go +++ b/consul/catalog_endpoint.go @@ -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) }) } diff --git a/consul/health_endpoint.go b/consul/health_endpoint.go index 93e7a44f2d..ab8ebf81eb 100644 --- a/consul/health_endpoint.go +++ b/consul/health_endpoint.go @@ -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 diff --git a/consul/internal_endpoint.go b/consul/internal_endpoint.go index 4cacd96290..361472fa7c 100644 --- a/consul/internal_endpoint.go +++ b/consul/internal_endpoint.go @@ -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 }) }