open-consul/acl/acl.go

804 lines
20 KiB
Go

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 ACL
// denyAll is a singleton policy which denies all actions
denyAll ACL
// manageAll is a singleton policy which allows all
// actions, including management
manageAll ACL
)
// 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 = &StaticACL{
allowManage: false,
defaultAllow: true,
}
denyAll = &StaticACL{
allowManage: false,
defaultAllow: false,
}
manageAll = &StaticACL{
allowManage: true,
defaultAllow: true,
}
}
// ACL is the interface for policy enforcement.
type ACL interface {
// ACLList checks for permission to list all the ACLs
ACLList() bool
// ACLModify checks for permission to manipulate ACLs
ACLModify() 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
}
// StaticACL 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 StaticACL struct {
allowManage bool
defaultAllow bool
}
func (s *StaticACL) ACLList() bool {
return s.allowManage
}
func (s *StaticACL) ACLModify() bool {
return s.allowManage
}
func (s *StaticACL) AgentRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) AgentWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) EventRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) EventWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) IntentionDefaultAllow() bool {
return s.defaultAllow
}
func (s *StaticACL) IntentionRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) IntentionWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) KeyRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) KeyList(string) bool {
return s.defaultAllow
}
func (s *StaticACL) KeyWrite(string, sentinel.ScopeFn) bool {
return s.defaultAllow
}
func (s *StaticACL) KeyWritePrefix(string) bool {
return s.defaultAllow
}
func (s *StaticACL) KeyringRead() bool {
return s.defaultAllow
}
func (s *StaticACL) KeyringWrite() bool {
return s.defaultAllow
}
func (s *StaticACL) NodeRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) NodeWrite(string, sentinel.ScopeFn) bool {
return s.defaultAllow
}
func (s *StaticACL) OperatorRead() bool {
return s.defaultAllow
}
func (s *StaticACL) OperatorWrite() bool {
return s.defaultAllow
}
func (s *StaticACL) PreparedQueryRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) PreparedQueryWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) ServiceRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) ServiceWrite(string, sentinel.ScopeFn) bool {
return s.defaultAllow
}
func (s *StaticACL) SessionRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) SessionWrite(string) bool {
return s.defaultAllow
}
func (s *StaticACL) Snapshot() bool {
return s.allowManage
}
// AllowAll returns an ACL rule that allows all operations
func AllowAll() ACL {
return allowAll
}
// DenyAll returns an ACL rule that denies all operations
func DenyAll() ACL {
return denyAll
}
// ManageAll returns an ACL rule that can manage all resources
func ManageAll() ACL {
return manageAll
}
// RootACL returns a possible ACL if the ID matches a root policy
func RootACL(id string) ACL {
switch id {
case "allow":
return allowAll
case "deny":
return denyAll
case "manage":
return manageAll
default:
return nil
}
}
// PolicyRule binds a regular ACL policy along with an optional piece of
// code to execute.
type PolicyRule struct {
// aclPolicy is used for simple acl rules(allow/deny/manage)
aclPolicy string
// sentinelPolicy has the code part of a policy
sentinelPolicy Sentinel
}
// PolicyACL is used to wrap a set of ACL policies to provide
// the ACL interface.
type PolicyACL struct {
// parent is used to resolve policy if we have
// no matching rule.
parent ACL
// sentinel is an interface for validating and executing sentinel code
// policies.
sentinel sentinel.Evaluator
// agentRules contains the agent policies
agentRules *radix.Tree
// intentionRules contains the service intention policies
intentionRules *radix.Tree
// keyRules contains the key policies
keyRules *radix.Tree
// nodeRules contains the node policies
nodeRules *radix.Tree
// serviceRules contains the service policies
serviceRules *radix.Tree
// sessionRules contains the session policies
sessionRules *radix.Tree
// eventRules contains the user event policies
eventRules *radix.Tree
// preparedQueryRules contains the prepared query 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
}
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
}
}
// New is used to construct a policy based ACL from a set of policies
// and a parent policy to resolve missing cases.
func New(parent ACL, policy *Policy, sentinel sentinel.Evaluator) (*PolicyACL, error) {
p := &PolicyACL{
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,
}
// Load the agent policy
for _, ap := range policy.Agents {
p.agentRules.Insert(ap.Node, ap.Policy)
}
// Load the key policy
for _, kp := range policy.Keys {
policyRule := PolicyRule{
aclPolicy: kp.Policy,
sentinelPolicy: kp.Sentinel,
}
p.keyRules.Insert(kp.Prefix, policyRule)
}
// Load the node policy
for _, np := range policy.Nodes {
policyRule := PolicyRule{
aclPolicy: np.Policy,
sentinelPolicy: np.Sentinel,
}
p.nodeRules.Insert(np.Name, policyRule)
}
// Load the service policy
for _, sp := range policy.Services {
policyRule := PolicyRule{
aclPolicy: sp.Policy,
sentinelPolicy: sp.Sentinel,
}
p.serviceRules.Insert(sp.Name, policyRule)
// Determine the intention. The intention could be blank (not set).
// If the intention is not set, the value depends on the value of
// the service policy.
intention := sp.Intentions
if intention == "" {
switch sp.Policy {
case PolicyRead, PolicyWrite:
intention = PolicyRead
default:
intention = PolicyDeny
}
}
policyRule = PolicyRule{
aclPolicy: intention,
sentinelPolicy: sp.Sentinel,
}
p.intentionRules.Insert(sp.Name, policyRule)
}
// Load the session policy
for _, sp := range policy.Sessions {
p.sessionRules.Insert(sp.Node, sp.Policy)
}
// Load the event policy
for _, ep := range policy.Events {
p.eventRules.Insert(ep.Event, ep.Policy)
}
// Load the prepared query policy
for _, pq := range policy.PreparedQueries {
p.preparedQueryRules.Insert(pq.Prefix, pq.Policy)
}
// Load the keyring policy
p.keyringRule = policy.Keyring
// Load the operator policy
p.operatorRule = policy.Operator
return p, nil
}
// ACLList checks if listing of ACLs is allowed
func (p *PolicyACL) ACLList() bool {
return p.parent.ACLList()
}
// ACLModify checks if modification of ACLs is allowed
func (p *PolicyACL) ACLModify() bool {
return p.parent.ACLModify()
}
// AgentRead checks for permission to read from agent endpoints for a given
// node.
func (p *PolicyACL) AgentRead(node string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.agentRules.LongestPrefix(node); 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 *PolicyACL) AgentWrite(node string) bool {
// Check for an exact rule or catch-all
_, rule, ok := p.agentRules.LongestPrefix(node)
if 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 *PolicyACL) Snapshot() bool {
return p.parent.Snapshot()
}
// EventRead is used to determine if the policy allows for a
// specific user event to be read.
func (p *PolicyACL) EventRead(name string) bool {
// Longest-prefix match on event names
if _, rule, ok := p.eventRules.LongestPrefix(name); 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 *PolicyACL) EventWrite(name string) bool {
// Longest-prefix match event names
if _, rule, ok := p.eventRules.LongestPrefix(name); 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 *PolicyACL) 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 *PolicyACL) IntentionRead(prefix string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.intentionRules.LongestPrefix(prefix); ok {
pr := rule.(PolicyRule)
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 *PolicyACL) IntentionWrite(prefix string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.intentionRules.LongestPrefix(prefix); ok {
pr := rule.(PolicyRule)
if allow, recurse := enforce(pr.aclPolicy, PolicyWrite); !recurse {
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 *PolicyACL) KeyRead(key string) bool {
// Look for a matching rule
if _, rule, ok := p.keyRules.LongestPrefix(key); ok {
pr := rule.(PolicyRule)
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 *PolicyACL) KeyList(key string) bool {
// Look for a matching rule
if _, rule, ok := p.keyRules.LongestPrefix(key); ok {
pr := rule.(PolicyRule)
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 *PolicyACL) KeyWrite(key string, scope sentinel.ScopeFn) bool {
// Look for a matching rule
if _, rule, ok := p.keyRules.LongestPrefix(key); ok {
pr := rule.(PolicyRule)
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
func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
// Look for a matching rule that denies
_, rule, ok := p.keyRules.LongestPrefix(prefix)
if ok && rule.(PolicyRule).aclPolicy != PolicyWrite {
return false
}
// Look if any of our children have a deny policy
deny := false
p.keyRules.WalkPrefix(prefix, func(path string, rule interface{}) bool {
// We have a rule to prevent a write in a sub-directory!
if rule.(PolicyRule).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 ok {
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 *PolicyACL) 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 *PolicyACL) 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 *PolicyACL) 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 *PolicyACL) 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 *PolicyACL) NodeRead(name string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.nodeRules.LongestPrefix(name); ok {
pr := rule.(PolicyRule)
if allow, recurse := enforce(pr.aclPolicy, PolicyRead); !recurse {
return allow
}
}
// No matching rule, use the parent.
return p.parent.NodeRead(name)
}
// NodeWrite checks if writing (registering) a node is allowed
func (p *PolicyACL) NodeWrite(name string, scope sentinel.ScopeFn) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.nodeRules.LongestPrefix(name); ok {
pr := rule.(PolicyRule)
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 *PolicyACL) PreparedQueryRead(prefix string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.preparedQueryRules.LongestPrefix(prefix); 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 *PolicyACL) PreparedQueryWrite(prefix string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.preparedQueryRules.LongestPrefix(prefix); 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 *PolicyACL) ServiceRead(name string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.serviceRules.LongestPrefix(name); ok {
pr := rule.(PolicyRule)
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 *PolicyACL) ServiceWrite(name string, scope sentinel.ScopeFn) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.serviceRules.LongestPrefix(name); ok {
pr := rule.(PolicyRule)
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 *PolicyACL) SessionRead(node string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.sessionRules.LongestPrefix(node); 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 *PolicyACL) SessionWrite(node string) bool {
// Check for an exact rule or catch-all
if _, rule, ok := p.sessionRules.LongestPrefix(node); 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 *PolicyACL) 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())
}