package acl import ( "github.com/armon/go-radix" "github.com/hashicorp/consul/sentinel" ) var ( // allowAll is a singleton policy which allows all // non-management actions allowAll Authorizer // denyAll is a singleton policy which denies all actions denyAll Authorizer // manageAll is a singleton policy which allows all // actions, including management manageAll Authorizer ) // DefaultPolicyEnforcementLevel will be used if the user leaves the level // blank when configuring an ACL. const DefaultPolicyEnforcementLevel = "hard-mandatory" func init() { // Setup the singletons allowAll = &StaticAuthorizer{ allowManage: false, defaultAllow: true, } denyAll = &StaticAuthorizer{ allowManage: false, defaultAllow: false, } manageAll = &StaticAuthorizer{ allowManage: true, defaultAllow: true, } } // Authorizer is the interface for policy enforcement. type Authorizer interface { // ACLRead checks for permission to list all the ACLs ACLRead() bool // ACLWrite checks for permission to manipulate ACLs ACLWrite() bool // AgentRead checks for permission to read from agent endpoints for a // given node. AgentRead(string) bool // AgentWrite checks for permission to make changes via agent endpoints // for a given node. AgentWrite(string) bool // EventRead determines if a specific event can be queried. EventRead(string) bool // EventWrite determines if a specific event may be fired. EventWrite(string) bool // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. IntentionDefaultAllow() bool // IntentionRead determines if a specific intention can be read. IntentionRead(string) bool // IntentionWrite determines if a specific intention can be // created, modified, or deleted. IntentionWrite(string) bool // KeyList checks for permission to list keys under a prefix KeyList(string) bool // KeyRead checks for permission to read a given key KeyRead(string) bool // KeyWrite checks for permission to write a given key KeyWrite(string, sentinel.ScopeFn) bool // KeyWritePrefix checks for permission to write to an // entire key prefix. This means there must be no sub-policies // that deny a write. KeyWritePrefix(string) bool // KeyringRead determines if the encryption keyring used in // the gossip layer can be read. KeyringRead() bool // KeyringWrite determines if the keyring can be manipulated KeyringWrite() bool // NodeRead checks for permission to read (discover) a given node. NodeRead(string) bool // NodeWrite checks for permission to create or update (register) a // given node. NodeWrite(string, sentinel.ScopeFn) bool // OperatorRead determines if the read-only Consul operator functions // can be used. OperatorRead() bool // OperatorWrite determines if the state-changing Consul operator // functions can be used. OperatorWrite() bool // PreparedQueryRead determines if a specific prepared query can be read // to show its contents (this is not used for execution). PreparedQueryRead(string) bool // PreparedQueryWrite determines if a specific prepared query can be // created, modified, or deleted. PreparedQueryWrite(string) bool // ServiceRead checks for permission to read a given service ServiceRead(string) bool // ServiceWrite checks for permission to create or update a given // service ServiceWrite(string, sentinel.ScopeFn) bool // SessionRead checks for permission to read sessions for a given node. SessionRead(string) bool // SessionWrite checks for permission to create sessions for a given // node. SessionWrite(string) bool // Snapshot checks for permission to take and restore snapshots. Snapshot() bool } // StaticAuthorizer is used to implement a base ACL policy. It either // allows or denies all requests. This can be used as a parent // ACL to act in a blacklist or whitelist mode. type StaticAuthorizer struct { allowManage bool defaultAllow bool } func (s *StaticAuthorizer) ACLRead() bool { return s.allowManage } func (s *StaticAuthorizer) ACLWrite() bool { return s.allowManage } func (s *StaticAuthorizer) AgentRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) AgentWrite(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) EventRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) EventWrite(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) IntentionDefaultAllow() bool { return s.defaultAllow } func (s *StaticAuthorizer) IntentionRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) IntentionWrite(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) KeyRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) KeyList(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) KeyWrite(string, sentinel.ScopeFn) bool { return s.defaultAllow } func (s *StaticAuthorizer) KeyWritePrefix(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) KeyringRead() bool { return s.defaultAllow } func (s *StaticAuthorizer) KeyringWrite() bool { return s.defaultAllow } func (s *StaticAuthorizer) NodeRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) NodeWrite(string, sentinel.ScopeFn) bool { return s.defaultAllow } func (s *StaticAuthorizer) OperatorRead() bool { return s.defaultAllow } func (s *StaticAuthorizer) OperatorWrite() bool { return s.defaultAllow } func (s *StaticAuthorizer) PreparedQueryRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) PreparedQueryWrite(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) ServiceRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) ServiceWrite(string, sentinel.ScopeFn) bool { return s.defaultAllow } func (s *StaticAuthorizer) SessionRead(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) SessionWrite(string) bool { return s.defaultAllow } func (s *StaticAuthorizer) Snapshot() bool { return s.allowManage } // AllowAll returns an Authorizer that allows all operations func AllowAll() Authorizer { return allowAll } // DenyAll returns an Authorizer that denies all operations func DenyAll() Authorizer { return denyAll } // ManageAll returns an Authorizer that can manage all resources func ManageAll() Authorizer { return manageAll } // RootAuthorizer returns a possible Authorizer if the ID matches a root policy func RootAuthorizer(id string) Authorizer { switch id { case "allow": return allowAll case "deny": return denyAll case "manage": return manageAll default: return nil } } // RulePolicy binds a regular ACL policy along with an optional piece of // code to execute. type RulePolicy struct { // aclPolicy is used for simple acl rules(allow/deny/manage) aclPolicy string // sentinelPolicy has the code part of a policy sentinelPolicy Sentinel } // PolicyAuthorizer is used to wrap a set of ACL policies to provide // the Authorizer interface. // type PolicyAuthorizer struct { // parent is used to resolve policy if we have // no matching rule. parent Authorizer // sentinel is an interface for validating and executing sentinel code // policies. sentinel sentinel.Evaluator // aclRule contains the acl management policy. aclRule string // agentRules contain the exact-match agent policies agentRules *radix.Tree // intentionRules contains the service intention exact-match policies intentionRules *radix.Tree // keyRules contains the key exact-match policies keyRules *radix.Tree // nodeRules contains the node exact-match policies nodeRules *radix.Tree // serviceRules contains the service exact-match policies serviceRules *radix.Tree // sessionRules contains the session exact-match policies sessionRules *radix.Tree // eventRules contains the user event exact-match policies eventRules *radix.Tree // preparedQueryRules contains the prepared query exact-match policies preparedQueryRules *radix.Tree // keyringRule contains the keyring policies. The keyring has // a very simple yes/no without prefix matching, so here we // don't need to use a radix tree. keyringRule string // operatorRule contains the operator policies. operatorRule string } // policyAuthorizerRadixLeaf is used as the main // structure for storing in the radix.Tree's within the // PolicyAuthorizer type policyAuthorizerRadixLeaf struct { exact interface{} prefix interface{} } // getPolicy first attempts to get an exact match for the segment from the "exact" tree and then falls // back to getting the policy for the longest prefix from the "prefix" tree func getPolicy(segment string, tree *radix.Tree) (policy interface{}, found bool) { found = false tree.WalkPath(segment, func(path string, leaf interface{}) bool { policies := leaf.(*policyAuthorizerRadixLeaf) if policies.exact != nil && path == segment { found = true policy = policies.exact return true } if policies.prefix != nil { found = true policy = policies.prefix } return false }) return } func insertPolicyIntoRadix(segment string, tree *radix.Tree, exactPolicy interface{}, prefixPolicy interface{}) { leaf, found := tree.Get(segment) if found { policy := leaf.(*policyAuthorizerRadixLeaf) if exactPolicy != nil { policy.exact = exactPolicy } if prefixPolicy != nil { policy.prefix = prefixPolicy } } else { policy := &policyAuthorizerRadixLeaf{exact: exactPolicy, prefix: prefixPolicy} tree.Insert(segment, policy) } } func enforce(rule string, requiredPermission string) (allow, recurse bool) { switch rule { case PolicyWrite: // grants read, list and write permissions return true, false case PolicyList: // grants read and list permissions if requiredPermission == PolicyList || requiredPermission == PolicyRead { return true, false } else { return false, false } case PolicyRead: // grants just read permissions if requiredPermission == PolicyRead { return true, false } else { return false, false } case PolicyDeny: // explicit denial - do not recurse return false, false default: // need to recurse as there was no specific policy set return false, true } } // NewPolicyAuthorizer is used to construct a policy based ACL from a set of policies // and a parent policy to resolve missing cases. func NewPolicyAuthorizer(parent Authorizer, policies []*Policy, sentinel sentinel.Evaluator) (*PolicyAuthorizer, error) { p := &PolicyAuthorizer{ parent: parent, agentRules: radix.New(), intentionRules: radix.New(), keyRules: radix.New(), nodeRules: radix.New(), serviceRules: radix.New(), sessionRules: radix.New(), eventRules: radix.New(), preparedQueryRules: radix.New(), sentinel: sentinel, } policy := MergePolicies(policies) // Load the agent policy (exact matches) for _, ap := range policy.Agents { insertPolicyIntoRadix(ap.Node, p.agentRules, ap.Policy, nil) } // Load the agent policy (prefix matches) for _, ap := range policy.AgentPrefixes { insertPolicyIntoRadix(ap.Node, p.agentRules, nil, ap.Policy) } // Load the key policy (exact matches) for _, kp := range policy.Keys { policyRule := RulePolicy{ aclPolicy: kp.Policy, sentinelPolicy: kp.Sentinel, } insertPolicyIntoRadix(kp.Prefix, p.keyRules, policyRule, nil) } // Load the key policy (prefix matches) for _, kp := range policy.KeyPrefixes { policyRule := RulePolicy{ aclPolicy: kp.Policy, sentinelPolicy: kp.Sentinel, } insertPolicyIntoRadix(kp.Prefix, p.keyRules, nil, policyRule) } // Load the node policy (exact matches) for _, np := range policy.Nodes { policyRule := RulePolicy{ aclPolicy: np.Policy, sentinelPolicy: np.Sentinel, } insertPolicyIntoRadix(np.Name, p.nodeRules, policyRule, nil) } // Load the node policy (prefix matches) for _, np := range policy.NodePrefixes { policyRule := RulePolicy{ aclPolicy: np.Policy, sentinelPolicy: np.Sentinel, } insertPolicyIntoRadix(np.Name, p.nodeRules, nil, policyRule) } // Load the service policy (exact matches) for _, sp := range policy.Services { policyRule := RulePolicy{ aclPolicy: sp.Policy, sentinelPolicy: sp.Sentinel, } insertPolicyIntoRadix(sp.Name, p.serviceRules, policyRule, nil) intention := sp.Intentions if intention == "" { switch sp.Policy { case PolicyRead, PolicyWrite: intention = PolicyRead default: intention = PolicyDeny } } policyRule = RulePolicy{ aclPolicy: intention, sentinelPolicy: sp.Sentinel, } insertPolicyIntoRadix(sp.Name, p.intentionRules, policyRule, nil) } // Load the service policy (prefix matches) for _, sp := range policy.ServicePrefixes { policyRule := RulePolicy{ aclPolicy: sp.Policy, sentinelPolicy: sp.Sentinel, } insertPolicyIntoRadix(sp.Name, p.serviceRules, nil, policyRule) intention := sp.Intentions if intention == "" { switch sp.Policy { case PolicyRead, PolicyWrite: intention = PolicyRead default: intention = PolicyDeny } } policyRule = RulePolicy{ aclPolicy: intention, sentinelPolicy: sp.Sentinel, } insertPolicyIntoRadix(sp.Name, p.intentionRules, nil, policyRule) } // Load the session policy (exact matches) for _, sp := range policy.Sessions { insertPolicyIntoRadix(sp.Node, p.sessionRules, sp.Policy, nil) } // Load the session policy (prefix matches) for _, sp := range policy.SessionPrefixes { insertPolicyIntoRadix(sp.Node, p.sessionRules, nil, sp.Policy) } // Load the event policy (exact matches) for _, ep := range policy.Events { insertPolicyIntoRadix(ep.Event, p.eventRules, ep.Policy, nil) } // Load the event policy (prefix matches) for _, ep := range policy.EventPrefixes { insertPolicyIntoRadix(ep.Event, p.eventRules, nil, ep.Policy) } // Load the prepared query policy (exact matches) for _, qp := range policy.PreparedQueries { insertPolicyIntoRadix(qp.Prefix, p.preparedQueryRules, qp.Policy, nil) } // Load the prepared query policy (prefix matches) for _, qp := range policy.PreparedQueryPrefixes { insertPolicyIntoRadix(qp.Prefix, p.preparedQueryRules, nil, qp.Policy) } // Load the acl policy p.aclRule = policy.ACL // Load the keyring policy p.keyringRule = policy.Keyring // Load the operator policy p.operatorRule = policy.Operator return p, nil } // ACLRead checks if listing of ACLs is allowed func (p *PolicyAuthorizer) ACLRead() bool { if allow, recurse := enforce(p.aclRule, PolicyRead); !recurse { return allow } return p.parent.ACLRead() } // ACLWrite checks if modification of ACLs is allowed func (p *PolicyAuthorizer) ACLWrite() bool { if allow, recurse := enforce(p.aclRule, PolicyWrite); !recurse { return allow } return p.parent.ACLWrite() } // AgentRead checks for permission to read from agent endpoints for a given // node. func (p *PolicyAuthorizer) AgentRead(node string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(node, p.agentRules); ok { if allow, recurse := enforce(rule.(string), PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.AgentRead(node) } // AgentWrite checks for permission to make changes via agent endpoints for a // given node. func (p *PolicyAuthorizer) AgentWrite(node string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(node, p.agentRules); ok { if allow, recurse := enforce(rule.(string), PolicyWrite); !recurse { return allow } } // No matching rule, use the parent. return p.parent.AgentWrite(node) } // Snapshot checks if taking and restoring snapshots is allowed. func (p *PolicyAuthorizer) Snapshot() bool { if allow, recurse := enforce(p.aclRule, PolicyWrite); !recurse { return allow } return p.parent.Snapshot() } // EventRead is used to determine if the policy allows for a // specific user event to be read. func (p *PolicyAuthorizer) EventRead(name string) bool { // Longest-prefix match on event names if rule, ok := getPolicy(name, p.eventRules); ok { if allow, recurse := enforce(rule.(string), PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.EventRead(name) } // EventWrite is used to determine if new events can be created // (fired) by the policy. func (p *PolicyAuthorizer) EventWrite(name string) bool { // Longest-prefix match event names if rule, ok := getPolicy(name, p.eventRules); ok { if allow, recurse := enforce(rule.(string), PolicyWrite); !recurse { return allow } } // No match, use parent return p.parent.EventWrite(name) } // IntentionDefaultAllow returns whether the default behavior when there are // no matching intentions is to allow or deny. func (p *PolicyAuthorizer) IntentionDefaultAllow() bool { // We always go up, this can't be determined by a policy. return p.parent.IntentionDefaultAllow() } // IntentionRead checks if writing (creating, updating, or deleting) of an // intention is allowed. func (p *PolicyAuthorizer) IntentionRead(prefix string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(prefix, p.intentionRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.IntentionRead(prefix) } // IntentionWrite checks if writing (creating, updating, or deleting) of an // intention is allowed. func (p *PolicyAuthorizer) IntentionWrite(prefix string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(prefix, p.intentionRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyWrite); !recurse { // TODO (ACL-V2) - should we do sentinel enforcement here return allow } } // No matching rule, use the parent. return p.parent.IntentionWrite(prefix) } // KeyRead returns if a key is allowed to be read func (p *PolicyAuthorizer) KeyRead(key string) bool { // Look for a matching rule if rule, ok := getPolicy(key, p.keyRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.KeyRead(key) } // KeyList returns if a key is allowed to be listed func (p *PolicyAuthorizer) KeyList(key string) bool { // Look for a matching rule if rule, ok := getPolicy(key, p.keyRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyList); !recurse { return allow } } // No matching rule, use the parent. return p.parent.KeyList(key) } // KeyWrite returns if a key is allowed to be written func (p *PolicyAuthorizer) KeyWrite(key string, scope sentinel.ScopeFn) bool { // Look for a matching rule if rule, ok := getPolicy(key, p.keyRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyWrite); !recurse { if allow { return p.executeCodePolicy(&pr.sentinelPolicy, scope) } return false } } // No matching rule, use the parent. return p.parent.KeyWrite(key, scope) } // KeyWritePrefix returns if a prefix is allowed to be written // // This is mainly used to detect whether a whole tree within // the KV can be removed. For that reason we must be able to // delete everything under the prefix. First we must have "write" // on the prefix itself func (p *PolicyAuthorizer) KeyWritePrefix(prefix string) bool { // Look for a matching rule that denies prefixAllowed := true found := false // Look for a prefix rule that would apply to the prefix we are checking // WalkPath starts at the root and walks down to the given prefix. // Therefore the last prefix rule we see is the one that matters p.keyRules.WalkPath(prefix, func(path string, leaf interface{}) bool { rule := leaf.(*policyAuthorizerRadixLeaf) if rule.prefix != nil { found = true if rule.prefix.(RulePolicy).aclPolicy != PolicyWrite { prefixAllowed = false } else { prefixAllowed = true } } return false }) if !prefixAllowed { return false } // Look if any of our children do not allow write access. This loop takes // into account both prefix and exact match rules. deny := false p.keyRules.WalkPrefix(prefix, func(path string, leaf interface{}) bool { found = true rule := leaf.(*policyAuthorizerRadixLeaf) if rule.prefix != nil && rule.prefix.(RulePolicy).aclPolicy != PolicyWrite { deny = true return true } if rule.exact != nil && rule.exact.(RulePolicy).aclPolicy != PolicyWrite { deny = true return true } return false }) // Deny the write if any sub-rules may be violated if deny { return false } // If we had a matching rule, done if found { return true } // No matching rule, use the parent. return p.parent.KeyWritePrefix(prefix) } // KeyringRead is used to determine if the keyring can be // read by the current ACL token. func (p *PolicyAuthorizer) KeyringRead() bool { if allow, recurse := enforce(p.keyringRule, PolicyRead); !recurse { return allow } return p.parent.KeyringRead() } // KeyringWrite determines if the keyring can be manipulated. func (p *PolicyAuthorizer) KeyringWrite() bool { if allow, recurse := enforce(p.keyringRule, PolicyWrite); !recurse { return allow } return p.parent.KeyringWrite() } // OperatorRead determines if the read-only operator functions are allowed. func (p *PolicyAuthorizer) OperatorRead() bool { if allow, recurse := enforce(p.operatorRule, PolicyRead); !recurse { return allow } return p.parent.OperatorRead() } // OperatorWrite determines if the state-changing operator functions are // allowed. func (p *PolicyAuthorizer) OperatorWrite() bool { if allow, recurse := enforce(p.operatorRule, PolicyWrite); !recurse { return allow } return p.parent.OperatorWrite() } // NodeRead checks if reading (discovery) of a node is allowed func (p *PolicyAuthorizer) NodeRead(name string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(name, p.nodeRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyRead); !recurse { // TODO (ACL-V2) - Should we do sentinel enforcement here return allow } } // No matching rule, use the parent. return p.parent.NodeRead(name) } // NodeWrite checks if writing (registering) a node is allowed func (p *PolicyAuthorizer) NodeWrite(name string, scope sentinel.ScopeFn) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(name, p.nodeRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyWrite); !recurse { return allow } } // No matching rule, use the parent. return p.parent.NodeWrite(name, scope) } // PreparedQueryRead checks if reading (listing) of a prepared query is // allowed - this isn't execution, just listing its contents. func (p *PolicyAuthorizer) PreparedQueryRead(prefix string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(prefix, p.preparedQueryRules); ok { if allow, recurse := enforce(rule.(string), PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.PreparedQueryRead(prefix) } // PreparedQueryWrite checks if writing (creating, updating, or deleting) of a // prepared query is allowed. func (p *PolicyAuthorizer) PreparedQueryWrite(prefix string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(prefix, p.preparedQueryRules); ok { if allow, recurse := enforce(rule.(string), PolicyWrite); !recurse { return allow } } // No matching rule, use the parent. return p.parent.PreparedQueryWrite(prefix) } // ServiceRead checks if reading (discovery) of a service is allowed func (p *PolicyAuthorizer) ServiceRead(name string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(name, p.serviceRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.ServiceRead(name) } // ServiceWrite checks if writing (registering) a service is allowed func (p *PolicyAuthorizer) ServiceWrite(name string, scope sentinel.ScopeFn) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(name, p.serviceRules); ok { pr := rule.(RulePolicy) if allow, recurse := enforce(pr.aclPolicy, PolicyWrite); !recurse { return allow } } // No matching rule, use the parent. return p.parent.ServiceWrite(name, scope) } // SessionRead checks for permission to read sessions for a given node. func (p *PolicyAuthorizer) SessionRead(node string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(node, p.sessionRules); ok { if allow, recurse := enforce(rule.(string), PolicyRead); !recurse { return allow } } // No matching rule, use the parent. return p.parent.SessionRead(node) } // SessionWrite checks for permission to create sessions for a given node. func (p *PolicyAuthorizer) SessionWrite(node string) bool { // Check for an exact rule or catch-all if rule, ok := getPolicy(node, p.sessionRules); ok { if allow, recurse := enforce(rule.(string), PolicyWrite); !recurse { return allow } } // No matching rule, use the parent. return p.parent.SessionWrite(node) } // executeCodePolicy will run the associated code policy if code policies are // enabled. func (p *PolicyAuthorizer) executeCodePolicy(policy *Sentinel, scope sentinel.ScopeFn) bool { if p.sentinel == nil { return true } if policy.Code == "" || scope == nil { return true } enforcement := policy.EnforcementLevel if enforcement == "" { enforcement = DefaultPolicyEnforcementLevel } return p.sentinel.Execute(policy.Code, enforcement, scope()) }