2014-08-08 22:32:43 +00:00
|
|
|
package consul
|
|
|
|
|
|
|
|
import (
|
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: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"
|
2017-07-06 10:34:00 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2017-09-14 19:31:01 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
|
|
|
"github.com/hashicorp/consul/sentinel"
|
2015-06-18 23:18:10 +00:00
|
|
|
"github.com/hashicorp/golang-lru"
|
2014-08-08 22:52:52 +00:00
|
|
|
)
|
|
|
|
|
2016-12-14 07:21:14 +00:00
|
|
|
// These must be kept in sync with the constants in command/agent/acl.go.
|
2014-08-08 22:52:52 +00:00
|
|
|
const (
|
2016-12-14 07:21:14 +00:00
|
|
|
// anonymousToken is the token ID we re-write to if there is no token ID
|
|
|
|
// provided.
|
2014-08-11 21:54:18 +00:00
|
|
|
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
|
2016-12-14 07:21:14 +00:00
|
|
|
// are not allowed to be displayed.
|
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 = "<hidden>"
|
|
|
|
|
2016-12-14 07:21:14 +00:00
|
|
|
// Maximum number of cached ACL entries.
|
2016-08-05 01:03:07 +00:00
|
|
|
aclCacheSize = 10 * 1024
|
2014-08-08 22:32:43 +00:00
|
|
|
)
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// aclCacheEntry is used to cache non-authoritative ACLs
|
2014-08-08 22:32:43 +00:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// aclLocalFault is used by the authoritative ACL cache to fault in the rules
|
|
|
|
// for an ACL if we take a miss. This goes directly to the state store, so it
|
|
|
|
// assumes its running in the ACL datacenter, or in a non-ACL datacenter when
|
|
|
|
// using its replicated ACLs during an outage.
|
|
|
|
func (s *Server) aclLocalFault(id string) (string, string, error) {
|
2017-10-04 23:43:27 +00:00
|
|
|
defer metrics.MeasureSince([]string{"acl", "fault"}, time.Now())
|
2016-08-04 00:01:32 +00:00
|
|
|
|
|
|
|
// Query the state store.
|
2014-08-08 22:32:43 +00:00
|
|
|
state := s.fsm.State()
|
2017-08-23 14:52:48 +00:00
|
|
|
_, rule, err := state.ACLGet(nil, id)
|
2014-08-08 22:32:43 +00:00
|
|
|
if err != nil {
|
2014-08-12 17:38:57 +00:00
|
|
|
return "", "", err
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
2017-08-23 14:52:48 +00:00
|
|
|
if rule == nil {
|
|
|
|
return "", "", acl.ErrNotFound
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
2014-08-12 17:38:57 +00:00
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// Management tokens have no policy and inherit from the 'manage' root
|
|
|
|
// policy.
|
2017-08-23 14:52:48 +00:00
|
|
|
if rule.Type == structs.ACLTypeManagement {
|
2014-08-12 22:32:44 +00:00
|
|
|
return "manage", "", nil
|
2014-08-12 17:38:57 +00:00
|
|
|
}
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// Otherwise use the default policy.
|
2017-08-23 14:52:48 +00:00
|
|
|
return s.config.ACLDefaultPolicy, rule.Rules, nil
|
2014-08-08 22:32:43 +00:00
|
|
|
}
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// resolveToken is the primary interface used by ACL-checkers (such as an
|
|
|
|
// endpoint handling a request) to resolve a token. If ACLs aren't enabled
|
|
|
|
// then this will return a nil token, otherwise it will attempt to use local
|
|
|
|
// cache and ultimately the ACL datacenter to get the policy associated with the
|
|
|
|
// token.
|
2014-08-08 22:32:43 +00:00
|
|
|
func (s *Server) resolveToken(id string) (acl.ACL, error) {
|
2016-08-04 00:01:32 +00:00
|
|
|
// Check if there is no ACL datacenter (ACLs disabled)
|
2014-08-08 22:32:43 +00:00
|
|
|
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
|
|
|
|
}
|
2017-10-04 23:43:27 +00:00
|
|
|
defer metrics.MeasureSince([]string{"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 {
|
2017-08-23 14:52:48 +00:00
|
|
|
return nil, acl.ErrRootDenied
|
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
|
|
|
|
|
2016-08-09 18:00:22 +00:00
|
|
|
// aclCache is used to cache ACLs and policies.
|
2015-06-18 23:18:10 +00:00
|
|
|
type aclCache struct {
|
|
|
|
config *Config
|
|
|
|
logger *log.Logger
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// acls is a non-authoritative ACL cache.
|
2016-08-09 18:00:22 +00:00
|
|
|
acls *lru.TwoQueueCache
|
2015-06-18 23:18:10 +00:00
|
|
|
|
2017-09-14 19:31:01 +00:00
|
|
|
// sentinel is the code engine (can be nil).
|
|
|
|
sentinel sentinel.Evaluator
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// aclPolicyCache is a non-authoritative policy cache.
|
2016-08-09 18:00:22 +00:00
|
|
|
policies *lru.TwoQueueCache
|
2015-06-18 23:18:10 +00:00
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// rpc is a function used to talk to the client/server.
|
2015-06-18 23:18:10 +00:00
|
|
|
rpc rpcFn
|
2016-08-04 00:01:32 +00:00
|
|
|
|
|
|
|
// local is a function used to look for an ACL locally if replication is
|
|
|
|
// enabled. This will be nil if replication isn't enabled.
|
|
|
|
local acl.FaultFunc
|
2015-06-18 23:18:10 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 00:02:42 +00:00
|
|
|
// newACLCache returns a new non-authoritative cache for ACLs. This is used for
|
2016-08-04 00:01:32 +00:00
|
|
|
// performance, and is used inside the ACL datacenter on non-leader servers, and
|
|
|
|
// outside the ACL datacenter everywhere.
|
2017-09-14 19:31:01 +00:00
|
|
|
func newACLCache(conf *Config, logger *log.Logger, rpc rpcFn, local acl.FaultFunc, sentinel sentinel.Evaluator) (*aclCache, error) {
|
2015-06-18 23:18:10 +00:00
|
|
|
var err error
|
|
|
|
cache := &aclCache{
|
2017-09-14 19:31:01 +00:00
|
|
|
config: conf,
|
|
|
|
logger: logger,
|
|
|
|
rpc: rpc,
|
|
|
|
local: local,
|
|
|
|
sentinel: sentinel,
|
2015-06-18 23:18:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the non-authoritative ACL cache
|
2016-08-09 18:00:22 +00:00
|
|
|
cache.acls, err = lru.New2Q(aclCacheSize)
|
2015-06-18 23:18:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to create ACL cache: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the ACL policy cache
|
2016-08-09 18:00:22 +00:00
|
|
|
cache.policies, err = lru.New2Q(aclCacheSize)
|
2015-06-18 23:18:10 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-08-04 00:01:32 +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) {
|
2016-08-04 00:01:32 +00:00
|
|
|
// Check the cache for the ACL.
|
2014-08-08 22:32:43 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// Check for live cache.
|
2014-08-08 22:32:43 +00:00
|
|
|
if cached != nil && time.Now().Before(cached.Expires) {
|
2017-10-04 23:43:27 +00:00
|
|
|
metrics.IncrCounter([]string{"acl", "cache_hit"}, 1)
|
2014-08-08 22:32:43 +00:00
|
|
|
return cached.ACL, nil
|
|
|
|
}
|
2017-10-04 23:43:27 +00:00
|
|
|
metrics.IncrCounter([]string{"acl", "cache_miss"}, 1)
|
2014-08-08 22:32:43 +00:00
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// Attempt to refresh the policy from the ACL datacenter via an RPC.
|
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
|
|
|
|
}
|
2016-08-04 00:01:32 +00:00
|
|
|
var reply structs.ACLPolicy
|
|
|
|
err := c.rpc("ACL.GetPolicy", &args, &reply)
|
2014-08-08 22:52:52 +00:00
|
|
|
if err == nil {
|
2016-08-04 00:01:32 +00:00
|
|
|
return c.useACLPolicy(id, authDC, cached, &reply)
|
2014-08-08 22:52:52 +00:00
|
|
|
}
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// Check for not-found, which will cause us to bail immediately. For any
|
|
|
|
// other error we report it in the logs but can continue.
|
2017-08-23 14:52:48 +00:00
|
|
|
if acl.IsErrNotFound(err) {
|
|
|
|
return nil, acl.ErrNotFound
|
2016-08-04 00:01:32 +00:00
|
|
|
}
|
2017-04-21 01:59:42 +00:00
|
|
|
c.logger.Printf("[ERR] consul.acl: Failed to get policy from ACL datacenter: %v", err)
|
2016-08-04 00:01:32 +00:00
|
|
|
|
2016-08-04 23:33:40 +00:00
|
|
|
// TODO (slackpad) - We could do a similar thing *within* the ACL
|
|
|
|
// datacenter if the leader isn't available. We have a local state
|
|
|
|
// store of the ACLs, so by populating the local member in this cache,
|
|
|
|
// it would fall back to the state store if there was a leader loss and
|
|
|
|
// the extend-cache policy was true. This feels subtle to explain and
|
|
|
|
// configure, and leader blips should be paved over by cache already, so
|
|
|
|
// we won't do this for now but should consider for the future. This is
|
|
|
|
// a lot different than the replication story where you might be cut off
|
|
|
|
// from the ACL datacenter for an extended period of time and need to
|
|
|
|
// carry on operating with the full set of ACLs as they were known
|
|
|
|
// before the partition.
|
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
// At this point we might have an expired cache entry and we know that
|
|
|
|
// there was a problem getting the ACL from the ACL datacenter. If a
|
|
|
|
// local ACL fault function is registered to query replicated ACL data,
|
|
|
|
// and the user's policy allows it, we will try locally before we give
|
|
|
|
// up.
|
|
|
|
if c.local != nil && c.config.ACLDownPolicy == "extend-cache" {
|
|
|
|
parent, rules, err := c.local(id)
|
|
|
|
if err != nil {
|
|
|
|
// We don't make an exception here for ACLs that aren't
|
|
|
|
// found locally. It seems more robust to use an expired
|
|
|
|
// cached entry (if we have one) rather than ignore it
|
|
|
|
// for the case that replication was a bit behind and
|
|
|
|
// didn't have the ACL yet.
|
|
|
|
c.logger.Printf("[DEBUG] consul.acl: Failed to get policy from replicated ACLs: %v", err)
|
|
|
|
goto ACL_DOWN
|
|
|
|
}
|
|
|
|
|
2017-09-14 19:31:01 +00:00
|
|
|
policy, err := acl.Parse(rules, c.sentinel)
|
2016-08-04 00:01:32 +00:00
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[DEBUG] consul.acl: Failed to parse policy for replicated ACL: %v", err)
|
|
|
|
goto ACL_DOWN
|
2016-03-15 21:16:25 +00:00
|
|
|
}
|
2016-08-04 00:01:32 +00:00
|
|
|
policy.ID = acl.RuleID(rules)
|
|
|
|
|
|
|
|
// Fake up an ACL datacenter reply and inject it into the cache.
|
|
|
|
// Note we use the local TTL here, so this'll be used for that
|
|
|
|
// amount of time even once the ACL datacenter becomes available.
|
2017-10-04 23:43:27 +00:00
|
|
|
metrics.IncrCounter([]string{"acl", "replication_hit"}, 1)
|
2016-08-04 00:01:32 +00:00
|
|
|
reply.ETag = makeACLETag(parent, policy)
|
|
|
|
reply.TTL = c.config.ACLTTL
|
|
|
|
reply.Parent = parent
|
|
|
|
reply.Policy = policy
|
|
|
|
return c.useACLPolicy(id, authDC, cached, &reply)
|
2014-08-08 22:52:52 +00:00
|
|
|
}
|
2014-08-08 22:32:43 +00:00
|
|
|
|
2016-08-04 00:01:32 +00:00
|
|
|
ACL_DOWN:
|
|
|
|
// 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 {
|
2016-12-14 07:21:14 +00:00
|
|
|
// TODO (slackpad) - This seems like it's an unsafe
|
|
|
|
// write.
|
2014-08-09 00:38:39 +00:00
|
|
|
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
|
2017-09-14 19:31:01 +00:00
|
|
|
acl, err := acl.New(parent, p.Policy, c.sentinel)
|
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 {
|
2016-12-11 00:00:11 +00:00
|
|
|
acl acl.ACL
|
|
|
|
logger *log.Logger
|
|
|
|
enforceVersion8 bool
|
2015-06-11 19:08:21 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 00:02:42 +00:00
|
|
|
// newACLFilter constructs a new aclFilter.
|
|
|
|
func newACLFilter(acl acl.ACL, logger *log.Logger, enforceVersion8 bool) *aclFilter {
|
2015-06-11 21:14:43 +00:00
|
|
|
if logger == nil {
|
2017-06-29 11:09:12 +00:00
|
|
|
logger = log.New(os.Stderr, "", log.LstdFlags)
|
2015-06-11 21:14:43 +00:00
|
|
|
}
|
2016-12-11 00:00:11 +00:00
|
|
|
return &aclFilter{
|
|
|
|
acl: acl,
|
|
|
|
logger: logger,
|
|
|
|
enforceVersion8: enforceVersion8,
|
|
|
|
}
|
2015-06-11 21:14:43 +00:00
|
|
|
}
|
|
|
|
|
2016-12-11 21:22:14 +00:00
|
|
|
// allowNode is used to determine if a node is accessible for an ACL.
|
|
|
|
func (f *aclFilter) allowNode(node string) bool {
|
|
|
|
if !f.enforceVersion8 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return f.acl.NodeRead(node)
|
|
|
|
}
|
|
|
|
|
|
|
|
// allowService is used to determine if a service is accessible for an ACL.
|
|
|
|
func (f *aclFilter) allowService(service string) bool {
|
2017-03-23 23:10:50 +00:00
|
|
|
if service == "" {
|
2015-06-11 19:08:21 +00:00
|
|
|
return true
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
2017-03-23 23:10:50 +00:00
|
|
|
|
2017-07-14 05:33:47 +00:00
|
|
|
if !f.enforceVersion8 && service == structs.ConsulServiceID {
|
2017-03-23 23:10:50 +00:00
|
|
|
return true
|
|
|
|
}
|
2015-06-11 19:08:21 +00:00
|
|
|
return f.acl.ServiceRead(service)
|
|
|
|
}
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2016-12-13 05:59:22 +00:00
|
|
|
// allowSession is used to determine if a session for a node is accessible for
|
|
|
|
// an ACL.
|
|
|
|
func (f *aclFilter) allowSession(node string) bool {
|
|
|
|
if !f.enforceVersion8 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return f.acl.SessionRead(node)
|
|
|
|
}
|
|
|
|
|
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]
|
2016-12-13 00:28:52 +00:00
|
|
|
if f.allowNode(check.Node) && f.allowService(check.ServiceName) {
|
2015-06-11 19:08:21 +00:00
|
|
|
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) {
|
2017-04-20 18:42:22 +00:00
|
|
|
for svc := range services {
|
2016-12-11 21:22:14 +00:00
|
|
|
if f.allowService(svc) {
|
2015-06-11 19:08:21 +00:00
|
|
|
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]
|
2016-12-11 21:22:14 +00:00
|
|
|
if f.allowNode(node.Node) && f.allowService(node.ServiceName) {
|
2015-06-11 19:08:21 +00:00
|
|
|
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.
|
2016-12-13 00:53:31 +00:00
|
|
|
func (f *aclFilter) filterNodeServices(services **structs.NodeServices) {
|
|
|
|
if *services == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !f.allowNode((*services).Node.Node) {
|
|
|
|
*services = nil
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-04-20 18:42:22 +00:00
|
|
|
for svc := range (*services).Services {
|
2016-12-11 21:22:14 +00:00
|
|
|
if f.allowService(svc) {
|
2015-06-11 19:08:21 +00:00
|
|
|
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)
|
2016-12-13 00:53:31 +00:00
|
|
|
delete((*services).Services, svc)
|
2015-06-11 19:08:21 +00:00
|
|
|
}
|
|
|
|
}
|
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]
|
2016-12-13 00:28:52 +00:00
|
|
|
if f.allowNode(node.Node.Node) && f.allowService(node.Service.Service) {
|
2015-06-11 19:08:21 +00:00
|
|
|
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
|
|
|
|
2016-12-13 05:59:22 +00:00
|
|
|
// filterSessions is used to filter a set of sessions based on ACLs.
|
|
|
|
func (f *aclFilter) filterSessions(sessions *structs.Sessions) {
|
|
|
|
s := *sessions
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
session := s[i]
|
|
|
|
if f.allowSession(session.Node) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
f.logger.Printf("[DEBUG] consul: dropping session %q from result due to ACLs", session.ID)
|
|
|
|
s = append(s[:i], s[i+1:]...)
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
*sessions = s
|
|
|
|
}
|
|
|
|
|
2016-12-12 19:58:31 +00:00
|
|
|
// filterCoordinates is used to filter nodes in a coordinate dump based on ACL
|
|
|
|
// rules.
|
|
|
|
func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) {
|
|
|
|
c := *coords
|
|
|
|
for i := 0; i < len(c); i++ {
|
|
|
|
node := c[i].Node
|
|
|
|
if f.allowNode(node) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
|
|
|
c = append(c[:i], c[i+1:]...)
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
*coords = c
|
|
|
|
}
|
|
|
|
|
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]
|
|
|
|
|
2016-12-13 02:21:00 +00:00
|
|
|
// Filter nodes
|
|
|
|
if node := info.Node; !f.allowNode(node) {
|
|
|
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
|
|
|
nd = append(nd[:i], nd[i+1:]...)
|
|
|
|
i--
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// Filter services
|
2016-12-13 02:21:00 +00:00
|
|
|
for j := 0; j < len(info.Services); j++ {
|
|
|
|
svc := info.Services[j].Service
|
2016-12-11 21:22:14 +00:00
|
|
|
if f.allowService(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)
|
2016-12-13 02:21:00 +00:00
|
|
|
info.Services = append(info.Services[:j], info.Services[j+1:]...)
|
|
|
|
j--
|
2015-06-09 19:36:25 +00:00
|
|
|
}
|
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
// Filter checks
|
2016-12-13 02:21:00 +00:00
|
|
|
for j := 0; j < len(info.Checks); j++ {
|
|
|
|
chk := info.Checks[j]
|
2016-12-11 21:22:14 +00:00
|
|
|
if f.allowService(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)
|
2016-12-13 02:21:00 +00:00
|
|
|
info.Checks = append(info.Checks[:j], info.Checks[j+1:]...)
|
|
|
|
j--
|
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-12-11 00:00:11 +00:00
|
|
|
// filterNodes is used to filter through all parts of a node list and remove
|
|
|
|
// elements the provided ACL token cannot access.
|
|
|
|
func (f *aclFilter) filterNodes(nodes *structs.Nodes) {
|
|
|
|
n := *nodes
|
|
|
|
for i := 0; i < len(n); i++ {
|
|
|
|
node := n[i].Node
|
2016-12-12 19:58:31 +00:00
|
|
|
if f.allowNode(node) {
|
2016-12-11 00:00:11 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
|
|
|
|
n = append(n[:i], n[i+1:]...)
|
|
|
|
i--
|
|
|
|
}
|
|
|
|
*nodes = n
|
|
|
|
}
|
|
|
|
|
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
|
2017-09-14 19:31:01 +00:00
|
|
|
// rules configured for the provided token.
|
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
|
2017-04-21 00:02:42 +00:00
|
|
|
filt := newACLFilter(acl, s.logger, s.config.ACLEnforceVersion8)
|
2015-06-11 19:08:21 +00:00
|
|
|
|
|
|
|
switch v := subj.(type) {
|
2016-12-11 00:00:11 +00:00
|
|
|
case *structs.CheckServiceNodes:
|
|
|
|
filt.filterCheckServiceNodes(v)
|
|
|
|
|
|
|
|
case *structs.IndexedCheckServiceNodes:
|
|
|
|
filt.filterCheckServiceNodes(&v.Nodes)
|
|
|
|
|
2016-12-12 19:58:31 +00:00
|
|
|
case *structs.IndexedCoordinates:
|
|
|
|
filt.filterCoordinates(&v.Coordinates)
|
|
|
|
|
2015-06-11 19:08:21 +00:00
|
|
|
case *structs.IndexedHealthChecks:
|
|
|
|
filt.filterHealthChecks(&v.HealthChecks)
|
|
|
|
|
2016-12-11 00:00:11 +00:00
|
|
|
case *structs.IndexedNodeDump:
|
|
|
|
filt.filterNodeDump(&v.Dump)
|
2015-06-11 19:08:21 +00:00
|
|
|
|
2016-12-11 00:00:11 +00:00
|
|
|
case *structs.IndexedNodes:
|
|
|
|
filt.filterNodes(&v.Nodes)
|
2015-06-11 19:08:21 +00:00
|
|
|
|
|
|
|
case *structs.IndexedNodeServices:
|
2016-12-13 00:53:31 +00:00
|
|
|
filt.filterNodeServices(&v.NodeServices)
|
2015-06-09 19:36:25 +00:00
|
|
|
|
2016-12-11 00:00:11 +00:00
|
|
|
case *structs.IndexedServiceNodes:
|
|
|
|
filt.filterServiceNodes(&v.ServiceNodes)
|
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
|
|
|
|
2016-12-11 00:00:11 +00:00
|
|
|
case *structs.IndexedServices:
|
|
|
|
filt.filterServices(v.Services)
|
2015-06-11 23:46:15 +00:00
|
|
|
|
2016-12-13 05:59:22 +00:00
|
|
|
case *structs.IndexedSessions:
|
|
|
|
filt.filterSessions(&v.Sessions)
|
|
|
|
|
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
|
|
|
|
}
|
2016-12-09 00:01:01 +00:00
|
|
|
|
|
|
|
// vetRegisterWithACL applies the given ACL's policy to the catalog update and
|
|
|
|
// determines if it is allowed. Since the catalog register request is so
|
|
|
|
// dynamic, this is a pretty complex algorithm and was worth breaking out of the
|
2016-12-10 03:15:44 +00:00
|
|
|
// endpoint. The NodeServices record for the node must be supplied, and can be
|
|
|
|
// nil.
|
2016-12-09 00:01:01 +00:00
|
|
|
//
|
|
|
|
// This is a bit racy because we have to check the state store outside of a
|
|
|
|
// transaction. It's the best we can do because we don't want to flow ACL
|
|
|
|
// checking down there. The node information doesn't change in practice, so this
|
|
|
|
// will be fine. If we expose ways to change node addresses in a later version,
|
|
|
|
// then we should split the catalog API at the node and service level so we can
|
|
|
|
// address this race better (even then it would be super rare, and would at
|
|
|
|
// worst let a service update revert a recent node update, so it doesn't open up
|
|
|
|
// too much abuse).
|
2017-08-23 14:52:48 +00:00
|
|
|
func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
|
2016-12-10 03:15:44 +00:00
|
|
|
ns *structs.NodeServices) error {
|
2016-12-09 00:01:01 +00:00
|
|
|
// Fast path if ACLs are not enabled.
|
2017-08-23 14:52:48 +00:00
|
|
|
if rule == nil {
|
2016-12-09 00:01:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-09-14 19:31:01 +00:00
|
|
|
// This gets called potentially from a few spots so we save it and
|
|
|
|
// return the structure we made if we have it.
|
|
|
|
var memo map[string]interface{}
|
|
|
|
scope := func() map[string]interface{} {
|
|
|
|
if memo != nil {
|
|
|
|
return memo
|
|
|
|
}
|
|
|
|
|
|
|
|
node := &api.Node{
|
|
|
|
ID: string(subj.ID),
|
|
|
|
Node: subj.Node,
|
|
|
|
Address: subj.Address,
|
|
|
|
Datacenter: subj.Datacenter,
|
|
|
|
TaggedAddresses: subj.TaggedAddresses,
|
|
|
|
Meta: subj.NodeMeta,
|
|
|
|
}
|
|
|
|
|
|
|
|
var service *api.AgentService
|
|
|
|
if subj.Service != nil {
|
|
|
|
service = &api.AgentService{
|
|
|
|
ID: subj.Service.ID,
|
|
|
|
Service: subj.Service.Service,
|
|
|
|
Tags: subj.Service.Tags,
|
2018-04-21 15:34:29 +00:00
|
|
|
Meta: subj.Service.Meta,
|
2017-09-14 19:31:01 +00:00
|
|
|
Address: subj.Service.Address,
|
|
|
|
Port: subj.Service.Port,
|
|
|
|
EnableTagOverride: subj.Service.EnableTagOverride,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memo = sentinel.ScopeCatalogUpsert(node, service)
|
|
|
|
return memo
|
|
|
|
}
|
|
|
|
|
2016-12-09 00:01:01 +00:00
|
|
|
// Vet the node info. This allows service updates to re-post the required
|
|
|
|
// node info for each request without having to have node "write"
|
|
|
|
// privileges.
|
|
|
|
needsNode := ns == nil || subj.ChangesNode(ns.Node)
|
2017-09-14 19:31:01 +00:00
|
|
|
|
|
|
|
if needsNode && !rule.NodeWrite(subj.Node, scope) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-09 00:01:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Vet the service change. This includes making sure they can register
|
|
|
|
// the given service, and that we can write to any existing service that
|
|
|
|
// is being modified by id (if any).
|
|
|
|
if subj.Service != nil {
|
2017-09-14 19:31:01 +00:00
|
|
|
if !rule.ServiceWrite(subj.Service.Service, scope) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-09 00:01:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ns != nil {
|
|
|
|
other, ok := ns.Services[subj.Service.ID]
|
2017-09-14 19:31:01 +00:00
|
|
|
|
|
|
|
// This is effectively a delete, so we DO NOT apply the
|
|
|
|
// sentinel scope to the service we are overwriting, just
|
|
|
|
// the regular ACL policy.
|
|
|
|
if ok && !rule.ServiceWrite(other.Service, nil) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-09 00:01:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that the member was flattened before we got there. This
|
|
|
|
// keeps us from having to verify this check as well.
|
|
|
|
if subj.Check != nil {
|
|
|
|
return fmt.Errorf("check member must be nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vet the checks. Node-level checks require node write, and
|
|
|
|
// service-level checks require service write.
|
|
|
|
for _, check := range subj.Checks {
|
|
|
|
// Make sure that the node matches - we don't allow you to mix
|
|
|
|
// checks from other nodes because we'd have to pull a bunch
|
|
|
|
// more state store data to check this. If ACLs are enabled then
|
|
|
|
// we simply require them to match in a given request. There's a
|
|
|
|
// note in state_store.go to ban this down there in Consul 0.8,
|
|
|
|
// but it's good to leave this here because it's required for
|
|
|
|
// correctness wrt. ACLs.
|
|
|
|
if check.Node != subj.Node {
|
|
|
|
return fmt.Errorf("Node '%s' for check '%s' doesn't match register request node '%s'",
|
|
|
|
check.Node, check.CheckID, subj.Node)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Node-level check.
|
|
|
|
if check.ServiceID == "" {
|
2017-09-14 19:31:01 +00:00
|
|
|
if !rule.NodeWrite(subj.Node, scope) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-09 00:01:01 +00:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Service-level check, check the common case where it
|
|
|
|
// matches the service part of this request, which has
|
|
|
|
// already been vetted above, and might be being registered
|
|
|
|
// along with its checks.
|
|
|
|
if subj.Service != nil && subj.Service.ID == check.ServiceID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Service-level check for some other service. Make sure they've
|
|
|
|
// got write permissions for that service.
|
|
|
|
if ns == nil {
|
2017-04-21 01:59:42 +00:00
|
|
|
return fmt.Errorf("Unknown service '%s' for check '%s'", check.ServiceID, check.CheckID)
|
|
|
|
}
|
|
|
|
|
|
|
|
other, ok := ns.Services[check.ServiceID]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Unknown service '%s' for check '%s'", check.ServiceID, check.CheckID)
|
|
|
|
}
|
|
|
|
|
2017-09-14 19:31:01 +00:00
|
|
|
// We are only adding a check here, so we don't add the scope,
|
|
|
|
// since the sentinel policy doesn't apply to adding checks at
|
|
|
|
// this time.
|
|
|
|
if !rule.ServiceWrite(other.Service, nil) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-09 00:01:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2016-12-10 03:15:44 +00:00
|
|
|
|
|
|
|
// vetDeregisterWithACL applies the given ACL's policy to the catalog update and
|
|
|
|
// determines if it is allowed. Since the catalog deregister request is so
|
|
|
|
// dynamic, this is a pretty complex algorithm and was worth breaking out of the
|
|
|
|
// endpoint. The NodeService for the referenced service must be supplied, and can
|
|
|
|
// be nil; similar for the HealthCheck for the referenced health check.
|
2017-08-23 14:52:48 +00:00
|
|
|
func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
|
2016-12-10 03:15:44 +00:00
|
|
|
ns *structs.NodeService, nc *structs.HealthCheck) error {
|
2017-09-14 19:31:01 +00:00
|
|
|
|
2016-12-10 03:15:44 +00:00
|
|
|
// Fast path if ACLs are not enabled.
|
2017-08-23 14:52:48 +00:00
|
|
|
if rule == nil {
|
2016-12-10 03:15:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-09-14 19:31:01 +00:00
|
|
|
// We don't apply sentinel in this path, since at this time sentinel
|
|
|
|
// only applies to create and update operations.
|
|
|
|
|
2016-12-10 03:15:44 +00:00
|
|
|
// This order must match the code in applyRegister() in fsm.go since it
|
|
|
|
// also evaluates things in this order, and will ignore fields based on
|
|
|
|
// this precedence. This lets us also ignore them from an ACL perspective.
|
|
|
|
if subj.ServiceID != "" {
|
|
|
|
if ns == nil {
|
|
|
|
return fmt.Errorf("Unknown service '%s'", subj.ServiceID)
|
|
|
|
}
|
2017-09-14 19:31:01 +00:00
|
|
|
if !rule.ServiceWrite(ns.Service, nil) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-10 03:15:44 +00:00
|
|
|
}
|
|
|
|
} else if subj.CheckID != "" {
|
|
|
|
if nc == nil {
|
|
|
|
return fmt.Errorf("Unknown check '%s'", subj.CheckID)
|
|
|
|
}
|
|
|
|
if nc.ServiceID != "" {
|
2017-09-14 19:31:01 +00:00
|
|
|
if !rule.ServiceWrite(nc.ServiceName, nil) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-10 03:15:44 +00:00
|
|
|
}
|
|
|
|
} else {
|
2017-09-14 19:31:01 +00:00
|
|
|
if !rule.NodeWrite(subj.Node, nil) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-10 03:15:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2017-09-14 19:31:01 +00:00
|
|
|
if !rule.NodeWrite(subj.Node, nil) {
|
2017-08-23 14:52:48 +00:00
|
|
|
return acl.ErrPermissionDenied
|
2016-12-10 03:15:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|