2014-08-08 22:32:43 +00:00
|
|
|
package consul
|
|
|
|
|
|
|
|
import (
|
2014-08-08 22:52:52 +00:00
|
|
|
"errors"
|
2015-06-11 23:46:15 +00:00
|
|
|
"fmt"
|
2015-06-11 19:08:21 +00:00
|
|
|
"log"
|
2015-06-11 21:14:43 +00:00
|
|
|
"os"
|
2014-08-08 22:52:52 +00:00
|
|
|
"strings"
|
2014-08-08 22:32:43 +00:00
|
|
|
"time"
|
|
|
|
|
2014-08-11 22:01:38 +00:00
|
|
|
"github.com/armon/go-metrics"
|
2014-08-08 22:32:43 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
2014-08-08 22:52:52 +00:00
|
|
|
"github.com/hashicorp/consul/consul/structs"
|
2015-06-18 23:18:10 +00:00
|
|
|
"github.com/hashicorp/golang-lru"
|
2014-08-08 22:52:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// aclNotFound indicates there is no matching ACL
|
|
|
|
aclNotFound = "ACL not found"
|
2014-08-11 21:54:18 +00:00
|
|
|
|
2014-08-12 17:58:02 +00:00
|
|
|
// rootDenied is returned when attempting to resolve a root ACL
|
|
|
|
rootDenied = "Cannot resolve root ACL"
|
|
|
|
|
2014-08-12 22:32:44 +00:00
|
|
|
// permissionDenied is returned when an ACL based rejection happens
|
|
|
|
permissionDenied = "Permission denied"
|
|
|
|
|
|
|
|
// aclDisabled is returned when ACL changes are not permitted
|
|
|
|
// since they are disabled.
|
|
|
|
aclDisabled = "ACL support disabled"
|
|
|
|
|
2014-08-11 21:54:18 +00:00
|
|
|
// anonymousToken is the token ID we re-write to if there
|
|
|
|
// is no token ID provided
|
|
|
|
anonymousToken = "anonymous"
|
2015-06-18 23:18:10 +00:00
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// redactedToken is shown in structures with embedded tokens when they
|
|
|
|
// are not allowed to be displayed
|
|
|
|
redactedToken = "<hidden>"
|
|
|
|
|
2015-06-18 23:18:10 +00:00
|
|
|
// Maximum number of cached ACL entries
|
|
|
|
aclCacheSize = 256
|
2014-08-08 22:32:43 +00:00
|
|
|
)
|
|
|
|
|
2014-08-12 22:32:44 +00:00
|
|
|
var (
|
|
|
|
permissionDeniedErr = errors.New(permissionDenied)
|
|
|
|
)
|
|
|
|
|
2014-08-08 22:32:43 +00:00
|
|
|
// aclCacheEntry is used to cache non-authoritative ACL's
|
|
|
|
// If non-authoritative, then we must respect a TTL
|
|
|
|
type aclCacheEntry struct {
|
|
|
|
ACL acl.ACL
|
|
|
|
Expires time.Time
|
2014-08-09 00:38:39 +00:00
|
|
|
ETag string
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// aclFault is used to fault in the rules for an ACL if we take a miss
|
2014-08-12 17:38:57 +00:00
|
|
|
func (s *Server) aclFault(id string) (string, string, error) {
|
2014-08-11 22:01:38 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "acl", "fault"}, time.Now())
|
2014-08-08 22:32:43 +00:00
|
|
|
state := s.fsm.State()
|
|
|
|
_, acl, err := state.ACLGet(id)
|
|
|
|
if err != nil {
|
2014-08-12 17:38:57 +00:00
|
|
|
return "", "", err
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
|
|
|
if acl == nil {
|
2014-08-12 17:38:57 +00:00
|
|
|
return "", "", errors.New(aclNotFound)
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
2014-08-12 17:38:57 +00:00
|
|
|
|
2014-08-12 22:32:44 +00:00
|
|
|
// Management tokens have no policy and inherit from the
|
|
|
|
// 'manage' root policy
|
2014-08-12 17:38:57 +00:00
|
|
|
if acl.Type == structs.ACLTypeManagement {
|
2014-08-12 22:32:44 +00:00
|
|
|
return "manage", "", nil
|
2014-08-12 17:38:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise use the base policy
|
|
|
|
return s.config.ACLDefaultPolicy, acl.Rules, nil
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// resolveToken is used to resolve an ACL is any is appropriate
|
|
|
|
func (s *Server) resolveToken(id string) (acl.ACL, error) {
|
|
|
|
// Check if there is no ACL datacenter (ACL's disabled)
|
|
|
|
authDC := s.config.ACLDatacenter
|
2014-08-11 21:54:18 +00:00
|
|
|
if len(authDC) == 0 {
|
2014-08-08 22:32:43 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
2014-08-11 22:01:38 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "acl", "resolveToken"}, time.Now())
|
2014-08-08 22:32:43 +00:00
|
|
|
|
2014-08-11 21:54:18 +00:00
|
|
|
// Handle the anonymous token
|
|
|
|
if len(id) == 0 {
|
|
|
|
id = anonymousToken
|
2014-08-12 17:58:02 +00:00
|
|
|
} else if acl.RootACL(id) != nil {
|
|
|
|
return nil, errors.New(rootDenied)
|
2014-08-11 21:54:18 +00:00
|
|
|
}
|
|
|
|
|
2014-08-08 22:32:43 +00:00
|
|
|
// Check if we are the ACL datacenter and the leader, use the
|
|
|
|
// authoritative cache
|
|
|
|
if s.config.Datacenter == authDC && s.IsLeader() {
|
|
|
|
return s.aclAuthCache.GetACL(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use our non-authoritative cache
|
2015-06-18 23:18:10 +00:00
|
|
|
return s.aclCache.lookupACL(id, authDC)
|
|
|
|
}
|
|
|
|
|
|
|
|
// rpcFn is used to make an RPC call to the client or server.
|
|
|
|
type rpcFn func(string, interface{}, interface{}) error
|
|
|
|
|
|
|
|
// aclCache is used to cache ACL's and policies.
|
|
|
|
type aclCache struct {
|
|
|
|
config *Config
|
|
|
|
logger *log.Logger
|
|
|
|
|
|
|
|
// acls is a non-authoritative ACL cache
|
|
|
|
acls *lru.Cache
|
|
|
|
|
|
|
|
// aclPolicyCache is a policy cache
|
|
|
|
policies *lru.Cache
|
|
|
|
|
|
|
|
// The RPC function used to talk to the client/server
|
|
|
|
rpc rpcFn
|
|
|
|
}
|
|
|
|
|
|
|
|
// newAclCache returns a new cache layer for ACLs and policies
|
|
|
|
func newAclCache(conf *Config, logger *log.Logger, rpc rpcFn) (*aclCache, error) {
|
|
|
|
var err error
|
|
|
|
cache := &aclCache{
|
|
|
|
config: conf,
|
|
|
|
logger: logger,
|
|
|
|
rpc: rpc,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the non-authoritative ACL cache
|
|
|
|
cache.acls, err = lru.New(aclCacheSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to create ACL cache: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the ACL policy cache
|
|
|
|
cache.policies, err = lru.New(aclCacheSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to create ACL policy cache: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cache, nil
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// lookupACL is used when we are non-authoritative, and need
|
|
|
|
// to resolve an ACL
|
2015-06-18 23:18:10 +00:00
|
|
|
func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
|
2014-08-08 22:32:43 +00:00
|
|
|
// Check the cache for the ACL
|
|
|
|
var cached *aclCacheEntry
|
2015-06-18 23:18:10 +00:00
|
|
|
raw, ok := c.acls.Get(id)
|
2014-08-08 22:32:43 +00:00
|
|
|
if ok {
|
|
|
|
cached = raw.(*aclCacheEntry)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for live cache
|
|
|
|
if cached != nil && time.Now().Before(cached.Expires) {
|
2014-08-11 22:01:38 +00:00
|
|
|
metrics.IncrCounter([]string{"consul", "acl", "cache_hit"}, 1)
|
2014-08-08 22:32:43 +00:00
|
|
|
return cached.ACL, nil
|
2014-08-11 22:01:38 +00:00
|
|
|
} else {
|
|
|
|
metrics.IncrCounter([]string{"consul", "acl", "cache_miss"}, 1)
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to refresh the policy
|
2014-08-08 23:55:47 +00:00
|
|
|
args := structs.ACLPolicyRequest{
|
2014-08-08 22:52:52 +00:00
|
|
|
Datacenter: authDC,
|
|
|
|
ACL: id,
|
|
|
|
}
|
2014-08-18 22:20:21 +00:00
|
|
|
if cached != nil {
|
|
|
|
args.ETag = cached.ETag
|
|
|
|
}
|
2014-08-08 22:52:52 +00:00
|
|
|
var out structs.ACLPolicy
|
2015-06-18 23:18:10 +00:00
|
|
|
err := c.rpc("ACL.GetPolicy", &args, &out)
|
2014-08-08 22:52:52 +00:00
|
|
|
|
|
|
|
// Handle the happy path
|
|
|
|
if err == nil {
|
2015-06-18 23:18:10 +00:00
|
|
|
return c.useACLPolicy(id, authDC, cached, &out)
|
2014-08-08 22:52:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check for not-found
|
|
|
|
if strings.Contains(err.Error(), aclNotFound) {
|
|
|
|
return nil, errors.New(aclNotFound)
|
|
|
|
} else {
|
2015-06-18 23:18:10 +00:00
|
|
|
c.logger.Printf("[ERR] consul.acl: Failed to get policy for '%s': %v", id, err)
|
2014-08-08 22:52:52 +00:00
|
|
|
}
|
2014-08-08 22:32:43 +00:00
|
|
|
|
|
|
|
// Unable to refresh, apply the down policy
|
2015-06-18 23:18:10 +00:00
|
|
|
switch c.config.ACLDownPolicy {
|
2014-08-08 22:32:43 +00:00
|
|
|
case "allow":
|
|
|
|
return acl.AllowAll(), nil
|
|
|
|
case "extend-cache":
|
|
|
|
if cached != nil {
|
|
|
|
return cached.ACL, nil
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
return acl.DenyAll(), nil
|
|
|
|
}
|
|
|
|
}
|
2014-08-09 00:38:39 +00:00
|
|
|
|
|
|
|
// useACLPolicy handles an ACLPolicy response
|
2015-06-18 23:18:10 +00:00
|
|
|
func (c *aclCache) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *structs.ACLPolicy) (acl.ACL, error) {
|
2014-08-09 00:38:39 +00:00
|
|
|
// Check if we can used the cached policy
|
|
|
|
if cached != nil && cached.ETag == p.ETag {
|
|
|
|
if p.TTL > 0 {
|
|
|
|
cached.Expires = time.Now().Add(p.TTL)
|
|
|
|
}
|
|
|
|
return cached.ACL, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for a cached compiled policy
|
|
|
|
var compiled acl.ACL
|
2015-06-18 23:18:10 +00:00
|
|
|
raw, ok := c.policies.Get(p.ETag)
|
2014-08-09 00:38:39 +00:00
|
|
|
if ok {
|
|
|
|
compiled = raw.(acl.ACL)
|
|
|
|
} else {
|
2014-08-12 17:54:56 +00:00
|
|
|
// Resolve the parent policy
|
|
|
|
parent := acl.RootACL(p.Parent)
|
|
|
|
if parent == nil {
|
|
|
|
var err error
|
2015-06-18 23:18:10 +00:00
|
|
|
parent, err = c.lookupACL(p.Parent, authDC)
|
2014-08-12 17:54:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-09 00:38:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Compile the ACL
|
2014-08-12 17:54:56 +00:00
|
|
|
acl, err := acl.New(parent, p.Policy)
|
2014-08-09 00:38:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache the policy
|
2015-06-18 23:18:10 +00:00
|
|
|
c.policies.Add(p.ETag, acl)
|
2014-08-09 00:38:39 +00:00
|
|
|
compiled = acl
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache the ACL
|
|
|
|
cached = &aclCacheEntry{
|
|
|
|
ACL: compiled,
|
|
|
|
ETag: p.ETag,
|
|
|
|
}
|
|
|
|
if p.TTL > 0 {
|
|
|
|
cached.Expires = time.Now().Add(p.TTL)
|
|
|
|
}
|
2015-06-18 23:18:10 +00:00
|
|
|
c.acls.Add(id, cached)
|
2014-08-11 21:01:45 +00:00
|
|
|
return compiled, nil
|
2014-08-09 00:38:39 +00:00
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-06-11 21:14:43 +00:00
|
|
|
// newAclFilter constructs a new aclFilter.
|
|
|
|
func newAclFilter(acl acl.ACL, logger *log.Logger) *aclFilter {
|
|
|
|
if logger == nil {
|
|
|
|
logger = log.New(os.Stdout, "", log.LstdFlags)
|
|
|
|
}
|
|
|
|
return &aclFilter{acl, logger}
|
|
|
|
}
|
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
return f.acl.ServiceRead(service)
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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--
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
*checks = hc
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
|
|
|
delete(services, svc)
|
|
|
|
}
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node)
|
|
|
|
sn = append(sn[:i], sn[i+1:]...)
|
|
|
|
i--
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
*nodes = sn
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
|
|
|
delete(services.Services, svc)
|
|
|
|
}
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 22:00:26 +00:00
|
|
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node.Node)
|
2015-06-11 19:08:21 +00:00
|
|
|
csn = append(csn[:i], csn[i+1:]...)
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
*nodes = csn
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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) {
|
2015-06-09 19:36:25 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
|
|
|
|
info.Services = append(info.Services[:i], info.Services[i+1:]...)
|
2015-06-09 19:36:25 +00:00
|
|
|
i--
|
|
|
|
}
|
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// Filter checks
|
|
|
|
for i := 0; i < len(info.Checks); i++ {
|
|
|
|
chk := info.Checks[i]
|
|
|
|
if f.filterService(chk.ServiceName) {
|
2015-06-09 19:36:25 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
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--
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
}
|
2015-06-11 20:05:33 +00:00
|
|
|
*dump = nd
|
2015-06-11 19:08:21 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 23:59:00 +00:00
|
|
|
// redactPreparedQueryTokens will redact any tokens unless the client has a
|
|
|
|
// management token. This eases the transition to delegated authority over
|
|
|
|
// prepared queries, since it was easy to capture management tokens in Consul
|
|
|
|
// 0.6.3 and earlier, and we don't want to willy-nilly show those. This does
|
|
|
|
// have the limitation of preventing delegated non-management users from seeing
|
|
|
|
// captured tokens, but they can at least see whether or not a token is set.
|
|
|
|
func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
|
|
|
|
// Management tokens can see everything with no filtering.
|
|
|
|
if f.acl.ACLList() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let the user see if there's a blank token, otherwise we need
|
|
|
|
// to redact it, since we know they don't have a management
|
|
|
|
// token.
|
|
|
|
if (*query).Token != "" {
|
|
|
|
// Redact the token, using a copy of the query structure
|
|
|
|
// since we could be pointed at a live instance from the
|
|
|
|
// state store so it's not safe to modify it. Note that
|
|
|
|
// this clone will still point to things like underlying
|
|
|
|
// arrays in the original, but for modifying just the
|
|
|
|
// token it will be safe to use.
|
|
|
|
clone := *(*query)
|
|
|
|
clone.Token = redactedToken
|
|
|
|
*query = &clone
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
// filterPreparedQueries is used to filter prepared queries based on ACL rules.
|
|
|
|
// We prune entries the user doesn't have access to, and we redact any tokens
|
2016-02-26 23:59:00 +00:00
|
|
|
// if the user doesn't have a management token.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
|
2016-02-24 09:26:16 +00:00
|
|
|
// Management tokens can see everything with no filtering.
|
|
|
|
if f.acl.ACLList() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we need to see what the token has access to.
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
ret := make(structs.PreparedQueries, 0, len(*queries))
|
|
|
|
for _, query := range *queries {
|
2016-02-24 09:26:16 +00:00
|
|
|
// If no prefix ACL applies to this query then filter it, since
|
|
|
|
// we know at this point the user doesn't have a management
|
2016-02-25 00:26:43 +00:00
|
|
|
// token, otherwise see what the policy says.
|
|
|
|
prefix, ok := query.GetACLPrefix()
|
|
|
|
if !ok || !f.acl.PreparedQueryRead(prefix) {
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
f.logger.Printf("[DEBUG] consul: dropping prepared query %q from result due to ACLs", query.ID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-02-26 23:59:00 +00:00
|
|
|
// Redact any tokens if necessary. We make a copy of just the
|
|
|
|
// pointer so we don't mess with the caller's slice.
|
|
|
|
final := query
|
|
|
|
f.redactPreparedQueryTokens(&final)
|
|
|
|
ret = append(ret, final)
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
}
|
|
|
|
*queries = ret
|
|
|
|
}
|
|
|
|
|
2015-06-11 19:48:38 +00:00
|
|
|
// filterACL is used to filter results from our service catalog based on the
|
2015-06-11 19:08:21 +00:00
|
|
|
// rules configured for the provided token. The subject is scrubbed and
|
|
|
|
// modified in-place, leaving only resources the token can access.
|
2015-06-11 19:48:38 +00:00
|
|
|
func (s *Server) filterACL(token string, subj interface{}) error {
|
2015-06-11 19:08:21 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the filter
|
2015-06-11 21:14:43 +00:00
|
|
|
filt := newAclFilter(acl, s.logger)
|
2015-06-11 19:08:21 +00:00
|
|
|
|
|
|
|
switch v := subj.(type) {
|
|
|
|
case *structs.IndexedHealthChecks:
|
|
|
|
filt.filterHealthChecks(&v.HealthChecks)
|
|
|
|
|
|
|
|
case *structs.IndexedServices:
|
|
|
|
filt.filterServices(v.Services)
|
|
|
|
|
|
|
|
case *structs.IndexedServiceNodes:
|
|
|
|
filt.filterServiceNodes(&v.ServiceNodes)
|
|
|
|
|
|
|
|
case *structs.IndexedNodeServices:
|
2015-06-12 23:46:15 +00:00
|
|
|
if v.NodeServices != nil {
|
|
|
|
filt.filterNodeServices(v.NodeServices)
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
|
|
|
case *structs.IndexedCheckServiceNodes:
|
2015-06-11 19:08:21 +00:00
|
|
|
filt.filterCheckServiceNodes(&v.Nodes)
|
2015-06-11 01:40:40 +00:00
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
case *structs.CheckServiceNodes:
|
|
|
|
filt.filterCheckServiceNodes(v)
|
|
|
|
|
2015-06-11 01:40:40 +00:00
|
|
|
case *structs.IndexedNodeDump:
|
2015-06-11 19:08:21 +00:00
|
|
|
filt.filterNodeDump(&v.Dump)
|
2015-06-11 23:46:15 +00:00
|
|
|
|
Creates new "prepared-query" ACL type and new token capture behavior.
Prior to this change, prepared queries had the following behavior for
ACLs, which will need to change to support templates:
1. A management token, or a token with read access to the service being
queried needed to be provided in order to create a prepared query.
2. The token used to create the prepared query was stored with the query
in the state store and used to execute the query.
3. A management token, or the token used to create the query needed to be
supplied to perform and CRUD operations on an existing prepared query.
This was pretty subtle and complicated behavior, and won't work for
templates since the service name is computed at execution time. To solve
this, we introduce a new "prepared-query" ACL type, where the prefix
applies to the query name for static prepared query types and to the
prefix for template prepared query types.
With this change, the new behavior is:
1. A management token, or a token with "prepared-query" write access to
the query name or (soon) the given template prefix is required to do
any CRUD operations on a prepared query, or to list prepared queries
(the list is filtered by this ACL).
2. You will no longer need a management token to list prepared queries,
but you will only be able to see prepared queries that you have access
to (you get an empty list instead of permission denied).
3. When listing or getting a query, because it was easy to capture
management tokens given the past behavior, this will always blank out
the "Token" field (replacing the contents as <hidden>) for all tokens
unless a management token is supplied. Going forward, we should
discourage people from binding tokens for execution unless strictly
necessary.
4. No token will be captured by default when a prepared query is created.
If the user wishes to supply an execution token then can pass it in via
the "Token" field in the prepared query definition. Otherwise, this
field will default to empty.
5. At execution time, we will use the captured token if it exists with the
prepared query definition, otherwise we will use the token that's passed
in with the request, just like we do for other RPCs (or you can use the
agent's configured token for DNS).
6. Prepared queries with no name (accessible only by ID) will not require
ACLs to create or modify (execution time will depend on the service ACL
configuration). Our argument here is that these are designed to be
ephemeral and the IDs are as good as an ACL. Management tokens will be
able to list all of these.
These changes enable templates, but also enable delegation of authority to
manage the prepared query namespace.
2016-02-23 08:12:58 +00:00
|
|
|
case *structs.IndexedPreparedQueries:
|
|
|
|
filt.filterPreparedQueries(&v.Queries)
|
|
|
|
|
2016-02-26 23:59:00 +00:00
|
|
|
case **structs.PreparedQuery:
|
|
|
|
filt.redactPreparedQueryTokens(v)
|
|
|
|
|
2015-06-11 23:46:15 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("Unhandled type passed to ACL filter: %#v", subj))
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|