package acl
import (
"github.com/armon/go-radix"
)
type policyAuthorizer struct {
// aclRule contains the acl management policy.
aclRule * policyAuthorizerRule
// 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 * policyAuthorizerRule
// operatorRule contains the operator policies.
operatorRule * policyAuthorizerRule
// embedded enterprise policy authorizer
enterprisePolicyAuthorizer
}
// policyAuthorizerRule is a struct to hold an ACL policy decision along
// with extra Consul Enterprise specific policy
type policyAuthorizerRule struct {
// decision is the enforcement decision for this rule
access AccessLevel
// Embedded Consul Enterprise specific policy
EnterpriseRule
}
// policyAuthorizerRadixLeaf is used as the main
// structure for storing in the radix.Tree's within the
// PolicyAuthorizer
type policyAuthorizerRadixLeaf struct {
exact * policyAuthorizerRule
prefix * policyAuthorizerRule
}
// 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 * policyAuthorizerRule , 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
}
// insertPolicyIntoRadix will insert or update part of the leaf node within the radix tree corresponding to the
// given segment. To update only one of the exact match or prefix match policy, set the value you want to leave alone
// to nil when calling the function.
func insertPolicyIntoRadix ( segment string , policy string , ent * EnterpriseRule , tree * radix . Tree , prefix bool ) error {
al , err := AccessLevelFromString ( policy )
if err != nil {
return err
}
policyRule := policyAuthorizerRule {
access : al ,
}
if ent != nil {
policyRule . EnterpriseRule = * ent
}
var policyLeaf * policyAuthorizerRadixLeaf
leaf , found := tree . Get ( segment )
if found {
policyLeaf = leaf . ( * policyAuthorizerRadixLeaf )
} else {
policyLeaf = & policyAuthorizerRadixLeaf { }
tree . Insert ( segment , policyLeaf )
}
if prefix {
policyLeaf . prefix = & policyRule
} else {
policyLeaf . exact = & policyRule
}
return nil
}
// enforce is a convenience function to
func enforce ( rule AccessLevel , requiredPermission AccessLevel ) EnforcementDecision {
switch rule {
case AccessWrite :
// grants read, list and write permissions
return Allow
case AccessList :
// grants read and list permissions
if requiredPermission == AccessList || requiredPermission == AccessRead {
return Allow
} else {
return Deny
}
case AccessRead :
// grants just read permissions
if requiredPermission == AccessRead {
return Allow
} else {
return Deny
}
case AccessDeny :
// explicit denial - do not recurse
return Deny
default :
// need to recurse as there was no specific access level set
return Default
}
}
func defaultIsAllow ( decision EnforcementDecision ) EnforcementDecision {
switch decision {
case Allow , Default :
return Allow
default :
return Deny
}
}
func ( p * policyAuthorizer ) loadRules ( policy * PolicyRules ) error {
// Load the agent policy (exact matches)
for _ , ap := range policy . Agents {
if err := insertPolicyIntoRadix ( ap . Node , ap . Policy , nil , p . agentRules , false ) ; err != nil {
return err
}
}
// Load the agent policy (prefix matches)
for _ , ap := range policy . AgentPrefixes {
if err := insertPolicyIntoRadix ( ap . Node , ap . Policy , nil , p . agentRules , true ) ; err != nil {
return err
}
}
// Load the key policy (exact matches)
for _ , kp := range policy . Keys {
if err := insertPolicyIntoRadix ( kp . Prefix , kp . Policy , & kp . EnterpriseRule , p . keyRules , false ) ; err != nil {
return err
}
}
// Load the key policy (prefix matches)
for _ , kp := range policy . KeyPrefixes {
if err := insertPolicyIntoRadix ( kp . Prefix , kp . Policy , & kp . EnterpriseRule , p . keyRules , true ) ; err != nil {
return err
}
}
// Load the node policy (exact matches)
for _ , np := range policy . Nodes {
if err := insertPolicyIntoRadix ( np . Name , np . Policy , & np . EnterpriseRule , p . nodeRules , false ) ; err != nil {
return err
}
}
// Load the node policy (prefix matches)
for _ , np := range policy . NodePrefixes {
if err := insertPolicyIntoRadix ( np . Name , np . Policy , & np . EnterpriseRule , p . nodeRules , true ) ; err != nil {
return err
}
}
// Load the service policy (exact matches)
for _ , sp := range policy . Services {
if err := insertPolicyIntoRadix ( sp . Name , sp . Policy , & sp . EnterpriseRule , p . serviceRules , false ) ; err != nil {
return err
}
intention := sp . Intentions
if intention == "" {
switch sp . Policy {
case PolicyRead , PolicyWrite :
intention = PolicyRead
default :
intention = PolicyDeny
}
}
if err := insertPolicyIntoRadix ( sp . Name , intention , & sp . EnterpriseRule , p . intentionRules , false ) ; err != nil {
return err
}
}
// Load the service policy (prefix matches)
for _ , sp := range policy . ServicePrefixes {
if err := insertPolicyIntoRadix ( sp . Name , sp . Policy , & sp . EnterpriseRule , p . serviceRules , true ) ; err != nil {
return err
}
intention := sp . Intentions
if intention == "" {
switch sp . Policy {
case PolicyRead , PolicyWrite :
intention = PolicyRead
default :
intention = PolicyDeny
}
}
if err := insertPolicyIntoRadix ( sp . Name , intention , & sp . EnterpriseRule , p . intentionRules , true ) ; err != nil {
return err
}
}
// Load the session policy (exact matches)
for _ , sp := range policy . Sessions {
if err := insertPolicyIntoRadix ( sp . Node , sp . Policy , nil , p . sessionRules , false ) ; err != nil {
return err
}
}
// Load the session policy (prefix matches)
for _ , sp := range policy . SessionPrefixes {
if err := insertPolicyIntoRadix ( sp . Node , sp . Policy , nil , p . sessionRules , true ) ; err != nil {
return err
}
}
// Load the event policy (exact matches)
for _ , ep := range policy . Events {
if err := insertPolicyIntoRadix ( ep . Event , ep . Policy , nil , p . eventRules , false ) ; err != nil {
return err
}
}
// Load the event policy (prefix matches)
for _ , ep := range policy . EventPrefixes {
if err := insertPolicyIntoRadix ( ep . Event , ep . Policy , nil , p . eventRules , true ) ; err != nil {
return err
}
}
// Load the prepared query policy (exact matches)
for _ , qp := range policy . PreparedQueries {
if err := insertPolicyIntoRadix ( qp . Prefix , qp . Policy , nil , p . preparedQueryRules , false ) ; err != nil {
return err
}
}
// Load the prepared query policy (prefix matches)
for _ , qp := range policy . PreparedQueryPrefixes {
if err := insertPolicyIntoRadix ( qp . Prefix , qp . Policy , nil , p . preparedQueryRules , true ) ; err != nil {
return err
}
}
// Load the acl policy
if policy . ACL != "" {
access , err := AccessLevelFromString ( policy . ACL )
if err != nil {
return err
}
p . aclRule = & policyAuthorizerRule { access : access }
}
// Load the keyring policy
if policy . Keyring != "" {
access , err := AccessLevelFromString ( policy . Keyring )
if err != nil {
return err
}
p . keyringRule = & policyAuthorizerRule { access : access }
}
// Load the operator policy
if policy . Operator != "" {
access , err := AccessLevelFromString ( policy . Operator )
if err != nil {
return err
}
p . operatorRule = & policyAuthorizerRule { access : access }
}
return nil
}
func newPolicyAuthorizer ( policies [ ] * Policy , ent * Config ) ( Authorizer , error ) {
policy := MergePolicies ( policies )
return newPolicyAuthorizerFromRules ( & policy . PolicyRules , ent )
}
func newPolicyAuthorizerFromRules ( rules * PolicyRules , ent * Config ) ( Authorizer , error ) {
p := & policyAuthorizer {
agentRules : radix . New ( ) ,
intentionRules : radix . New ( ) ,
keyRules : radix . New ( ) ,
nodeRules : radix . New ( ) ,
serviceRules : radix . New ( ) ,
sessionRules : radix . New ( ) ,
eventRules : radix . New ( ) ,
preparedQueryRules : radix . New ( ) ,
}
p . enterprisePolicyAuthorizer . init ( ent )
if err := p . loadRules ( rules ) ; err != nil {
return nil , err
}
return p , nil
}
// enforceCallbacks are to be passed to anyAllowed or allAllowed. The interface{}
// parameter will be a value stored in the radix.Tree passed to those functions.
// prefixOnly indicates that only we only want to consider the prefix matching rule
// if any. The return value indicates whether this one leaf node in the tree would
// allow, deny or make no decision regarding some authorization.
type enforceCallback func ( raw interface { } , prefixOnly bool ) EnforcementDecision
func anyAllowed ( tree * radix . Tree , enforceFn enforceCallback ) EnforcementDecision {
decision := Default
// special case for handling a catch-all prefix rule. If the rule woul Deny access then our default decision
// should be to Deny, but this decision should still be overridable with other more specific rules.
if raw , found := tree . Get ( "" ) ; found {
decision = enforceFn ( raw , true )
if decision == Allow {
return Allow
}
}
tree . Walk ( func ( path string , raw interface { } ) bool {
if enforceFn ( raw , false ) == Allow {
decision = Allow
return true
}
return false
} )
return decision
}
func allAllowed ( tree * radix . Tree , enforceFn enforceCallback ) EnforcementDecision {
decision := Default
// look for a "" prefix rule
if raw , found := tree . Get ( "" ) ; found {
// ensure that the empty prefix rule would allow the access
// if it does allow it we still must check all the other rules to ensure
// nothing overrides the top level grant with a different access level
// if not we can return early
decision = enforceFn ( raw , true )
// the top level prefix rule denied access so we can return early.
if decision == Deny {
return Deny
}
}
tree . Walk ( func ( path string , raw interface { } ) bool {
if enforceFn ( raw , false ) == Deny {
decision = Deny
return true
}
return false
} )
return decision
}
func ( authz * policyAuthorizer ) anyAllowed ( tree * radix . Tree , requiredPermission AccessLevel ) EnforcementDecision {
return anyAllowed ( tree , func ( raw interface { } , prefixOnly bool ) EnforcementDecision {
leaf := raw . ( * policyAuthorizerRadixLeaf )
decision := Default
if leaf . prefix != nil {
decision = enforce ( leaf . prefix . access , requiredPermission )
}
if prefixOnly || decision == Allow || leaf . exact == nil {
return decision
}
return enforce ( leaf . exact . access , requiredPermission )
} )
}
func ( authz * policyAuthorizer ) allAllowed ( tree * radix . Tree , requiredPermission AccessLevel ) EnforcementDecision {
return allAllowed ( tree , func ( raw interface { } , prefixOnly bool ) EnforcementDecision {
leaf := raw . ( * policyAuthorizerRadixLeaf )
prefixDecision := Default
if leaf . prefix != nil {
prefixDecision = enforce ( leaf . prefix . access , requiredPermission )
}
if prefixOnly || prefixDecision == Deny || leaf . exact == nil {
return prefixDecision
}
decision := enforce ( leaf . exact . access , requiredPermission )
if decision == Default {
// basically this means defer to the prefix decision as the
// authorizer rule made no decision with an exact match rule
return prefixDecision
}
return decision
} )
}
// ACLRead checks if listing of ACLs is allowed
func ( p * policyAuthorizer ) ACLRead ( * AuthorizerContext ) EnforcementDecision {
if p . aclRule != nil {
return enforce ( p . aclRule . access , AccessRead )
}
return Default
}
// ACLWrite checks if modification of ACLs is allowed
func ( p * policyAuthorizer ) ACLWrite ( * AuthorizerContext ) EnforcementDecision {
if p . aclRule != nil {
return enforce ( p . aclRule . access , AccessWrite )
}
return Default
}
// AgentRead checks for permission to read from agent endpoints for a given
// node.
func ( p * policyAuthorizer ) AgentRead ( node string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( node , p . agentRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// AgentWrite checks for permission to make changes via agent endpoints for a
// given node.
func ( p * policyAuthorizer ) AgentWrite ( node string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( node , p . agentRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}
// Snapshot checks if taking and restoring snapshots is allowed.
func ( p * policyAuthorizer ) Snapshot ( _ * AuthorizerContext ) EnforcementDecision {
if p . aclRule != nil {
return enforce ( p . aclRule . access , AccessWrite )
}
return Default
}
// EventRead is used to determine if the policy allows for a
// specific user event to be read.
func ( p * policyAuthorizer ) EventRead ( name string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( name , p . eventRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// EventWrite is used to determine if new events can be created
// (fired) by the policy.
func ( p * policyAuthorizer ) EventWrite ( name string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( name , p . eventRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}
// IntentionDefaultAllow returns whether the default behavior when there are
// no matching intentions is to allow or deny.
func ( p * policyAuthorizer ) IntentionDefaultAllow ( _ * AuthorizerContext ) EnforcementDecision {
// We always go up, this can't be determined by a policy.
return Default
}
// IntentionRead checks if writing (creating, updating, or deleting) of an
// intention is allowed.
func ( p * policyAuthorizer ) IntentionRead ( prefix string , _ * AuthorizerContext ) EnforcementDecision {
if prefix == "*" {
return p . anyAllowed ( p . intentionRules , AccessRead )
}
if rule , ok := getPolicy ( prefix , p . intentionRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// IntentionWrite checks if writing (creating, updating, or deleting) of an
// intention is allowed.
func ( p * policyAuthorizer ) IntentionWrite ( prefix string , _ * AuthorizerContext ) EnforcementDecision {
if prefix == "*" {
return p . allAllowed ( p . intentionRules , AccessWrite )
}
if rule , ok := getPolicy ( prefix , p . intentionRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}
// KeyRead returns if a key is allowed to be read
func ( p * policyAuthorizer ) KeyRead ( key string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( key , p . keyRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// KeyList returns if a key is allowed to be listed
func ( p * policyAuthorizer ) KeyList ( key string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( key , p . keyRules ) ; ok {
return enforce ( rule . access , AccessList )
}
return Default
}
// KeyWrite returns if a key is allowed to be written
func ( p * policyAuthorizer ) KeyWrite ( key string , entCtx * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( key , p . keyRules ) ; ok {
decision := enforce ( rule . access , AccessWrite )
if decision == Allow {
return defaultIsAllow ( p . enterprisePolicyAuthorizer . enforce ( & rule . EnterpriseRule , entCtx ) )
}
return decision
}
return Default
}
// 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 , _ * AuthorizerContext ) EnforcementDecision {
// Conditions for Allow:
// * The longest prefix match rule that would apply to the given prefix
// grants AccessWrite
// AND
// * There are no rules (exact or prefix match) within/under the given prefix
// that would NOT grant AccessWrite.
//
// Conditions for Deny:
// * The longest prefix match rule that would apply to the given prefix
// does not grant AccessWrite.
// OR
// * There is 1+ rules (exact or prefix match) within/under the given prefix
// that do NOT grant AccessWrite.
//
// Conditions for Default:
// * There is no prefix match rule that would appy to the given prefix.
// AND
// * There are no rules (exact or prefix match) within/under the given prefix
// that would NOT grant AccessWrite.
baseAccess := Default
// 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 {
if rule . prefix . access != AccessWrite {
baseAccess = Deny
} else {
baseAccess = Allow
}
}
return false
} )
// baseAccess will be Deny only when a prefix rule was found and it didn't
// grant AccessWrite. Otherwise the access level will be Default or Allow
// neither of which should be returned right now.
if baseAccess == Deny {
return baseAccess
}
// Look if any of our children do not allow write access. This loop takes
// into account both prefix and exact match rules.
withinPrefixAccess := Default
p . keyRules . WalkPrefix ( prefix , func ( path string , leaf interface { } ) bool {
rule := leaf . ( * policyAuthorizerRadixLeaf )
if rule . prefix != nil && rule . prefix . access != AccessWrite {
withinPrefixAccess = Deny
return true
}
if rule . exact != nil && rule . exact . access != AccessWrite {
withinPrefixAccess = Deny
return true
}
return false
} )
// Deny the write if any sub-rules may be violated. If none are violated then
// we can defer to the baseAccess.
if withinPrefixAccess == Deny {
return Deny
}
// either Default or Allow at this point. Allow if there was a prefix rule
// that was applicable and it granted write access. Default if there was
// no applicable rule.
return baseAccess
}
// KeyringRead is used to determine if the keyring can be
// read by the current ACL token.
func ( p * policyAuthorizer ) KeyringRead ( * AuthorizerContext ) EnforcementDecision {
if p . keyringRule != nil {
return enforce ( p . keyringRule . access , AccessRead )
}
return Default
}
// KeyringWrite determines if the keyring can be manipulated.
func ( p * policyAuthorizer ) KeyringWrite ( * AuthorizerContext ) EnforcementDecision {
if p . keyringRule != nil {
return enforce ( p . keyringRule . access , AccessWrite )
}
return Default
}
// OperatorRead determines if the read-only operator functions are allowed.
func ( p * policyAuthorizer ) OperatorRead ( * AuthorizerContext ) EnforcementDecision {
if p . operatorRule != nil {
return enforce ( p . operatorRule . access , AccessRead )
}
return Default
}
// OperatorWrite determines if the state-changing operator functions are
// allowed.
func ( p * policyAuthorizer ) OperatorWrite ( * AuthorizerContext ) EnforcementDecision {
if p . operatorRule != nil {
return enforce ( p . operatorRule . access , AccessWrite )
}
return Default
}
// NodeRead checks if reading (discovery) of a node is allowed
func ( p * policyAuthorizer ) NodeRead ( name string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( name , p . nodeRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// NodeWrite checks if writing (registering) a node is allowed
func ( p * policyAuthorizer ) NodeWrite ( name string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( name , p . nodeRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}
// 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 , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( prefix , p . preparedQueryRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// PreparedQueryWrite checks if writing (creating, updating, or deleting) of a
// prepared query is allowed.
func ( p * policyAuthorizer ) PreparedQueryWrite ( prefix string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( prefix , p . preparedQueryRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}
// ServiceRead checks if reading (discovery) of a service is allowed
func ( p * policyAuthorizer ) ServiceRead ( name string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( name , p . serviceRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// ServiceWrite checks if writing (registering) a service is allowed
func ( p * policyAuthorizer ) ServiceWrite ( name string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( name , p . serviceRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}
// SessionRead checks for permission to read sessions for a given node.
func ( p * policyAuthorizer ) SessionRead ( node string , _ * AuthorizerContext ) EnforcementDecision {
if rule , ok := getPolicy ( node , p . sessionRules ) ; ok {
return enforce ( rule . access , AccessRead )
}
return Default
}
// SessionWrite checks for permission to create sessions for a given node.
func ( p * policyAuthorizer ) SessionWrite ( node string , _ * AuthorizerContext ) EnforcementDecision {
// Check for an exact rule or catch-all
if rule , ok := getPolicy ( node , p . sessionRules ) ; ok {
return enforce ( rule . access , AccessWrite )
}
return Default
}