open-consul/acl/policy_authorizer.go
Daniel Nephin f6d5a85561 acl: move check for Intention.DestinationName into Authorizer
Follow up to https://github.com/hashicorp/consul/pull/10737#discussion_r680134445

Move the check for the Intention.DestinationName into the Authorizer to remove the
need to check what kind of Authorizer is being used.

It sounds like this check is only for legacy ACLs, so is probably just a safeguard
.
2021-08-04 18:06:44 -04:00

758 lines
22 KiB
Go

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 would 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 Deny
}
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
}
func (p *policyAuthorizer) NodeReadAll(_ *AuthorizerContext) EnforcementDecision {
return p.allAllowed(p.nodeRules, AccessRead)
}
// 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
}
func (p *policyAuthorizer) ServiceReadAll(_ *AuthorizerContext) EnforcementDecision {
return p.allAllowed(p.serviceRules, AccessRead)
}
// 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
}