962 lines
25 KiB
Go
962 lines
25 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 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())
|
|
}
|