From 973341a5926679c548ff99809eb6d6ee62c2c1c6 Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Tue, 15 Oct 2019 16:58:50 -0400 Subject: [PATCH] ACL Authorizer overhaul (#6620) * ACL Authorizer overhaul To account for upcoming features every Authorization function can now take an extra *acl.EnterpriseAuthorizerContext. These are unused in OSS and will always be nil. Additionally the acl package has received some thorough refactoring to enable all of the extra Consul Enterprise specific authorizations including moving sentinel enforcement into the stubbed structs. The Authorizer funcs now return an acl.EnforcementDecision instead of a boolean. This improves the overall interface as it makes multiple Authorizers easily chainable as they now indicate whether they had an authoritative decision or should use some other defaults. A ChainedAuthorizer was added to handle this Authorizer enforcement chain and will never itself return a non-authoritative decision. * Include stub for extra enterprise rules in the global management policy * Allow for an upgrade of the global-management policy --- .circleci/config.yml | 2 +- acl/acl.go | 1010 +---------- acl/acl_oss.go | 6 + acl/acl_test.go | 1570 +++++++++-------- acl/authorizer.go | 128 ++ acl/authorizer_oss.go | 9 + acl/chained_authorizer.go | 226 +++ acl/chained_authorizer_oss.go | 3 + acl/chained_authorizer_test.go | 247 +++ acl/errors.go | 72 - acl/policy.go | 835 +++------ acl/policy_authorizer.go | 637 +++++++ acl/policy_authorizer_oss.go | 30 + acl/policy_authorizer_test.go | 371 ++++ acl/policy_merger.go | 364 ++++ acl/policy_merger_oss.go | 17 + acl/policy_oss.go | 19 + acl/policy_test.go | 566 +++--- acl/static_authorizer.go | 244 +++ acl/static_authorizer_test.go | 103 ++ agent/acl.go | 64 +- agent/acl_endpoint.go | 3 +- agent/acl_test.go | 40 +- agent/agent_endpoint.go | 25 +- agent/connect_auth.go | 6 +- agent/consul/acl.go | 281 +-- agent/consul/acl_endpoint.go | 78 +- agent/consul/acl_endpoint_legacy.go | 13 +- agent/consul/acl_endpoint_test.go | 4 +- agent/consul/acl_oss.go | 13 + agent/consul/acl_test.go | 272 +-- agent/consul/catalog_endpoint.go | 9 +- agent/consul/client.go | 12 +- agent/consul/config_endpoint.go | 3 +- agent/consul/connect_ca_endpoint.go | 12 +- agent/consul/coordinate_endpoint.go | 6 +- agent/consul/discovery_chain_endpoint.go | 3 +- agent/consul/filter.go | 16 +- agent/consul/filter_test.go | 6 +- agent/consul/health_endpoint.go | 3 +- agent/consul/intention_endpoint.go | 10 +- agent/consul/internal_endpoint.go | 6 +- agent/consul/kvs_endpoint.go | 18 +- agent/consul/leader.go | 15 +- agent/consul/operator_autopilot_endpoint.go | 6 +- agent/consul/operator_raft_endpoint.go | 6 +- agent/consul/prepared_query_endpoint.go | 4 +- agent/consul/server.go | 21 +- agent/consul/session_endpoint.go | 9 +- agent/consul/snapshot_endpoint.go | 2 +- agent/consul/state/acl.go | 8 +- agent/event_endpoint.go | 6 +- agent/http.go | 2 +- agent/structs/acl.go | 17 +- agent/structs/acl_oss.go | 7 + agent/structs/acl_test.go | 30 +- agent/structs/config_entry.go | 6 +- agent/structs/config_entry_discoverychain.go | 6 +- .../config_entry_discoverychain_test.go | 2 +- agent/xds/server.go | 7 +- agent/xds/server_test.go | 6 +- 61 files changed, 4492 insertions(+), 3030 deletions(-) create mode 100644 acl/acl_oss.go create mode 100644 acl/authorizer.go create mode 100644 acl/authorizer_oss.go create mode 100644 acl/chained_authorizer.go create mode 100644 acl/chained_authorizer_oss.go create mode 100644 acl/chained_authorizer_test.go delete mode 100644 acl/errors.go create mode 100644 acl/policy_authorizer.go create mode 100644 acl/policy_authorizer_oss.go create mode 100644 acl/policy_authorizer_test.go create mode 100644 acl/policy_merger.go create mode 100644 acl/policy_merger_oss.go create mode 100644 acl/policy_oss.go create mode 100644 acl/static_authorizer.go create mode 100644 acl/static_authorizer_test.go create mode 100644 agent/consul/acl_oss.go create mode 100644 agent/structs/acl_oss.go diff --git a/.circleci/config.yml b/.circleci/config.yml index cead6b2be8..b22468c93f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -293,7 +293,7 @@ jobs: # make dev build of nomad - run: - command: make dev + command: make pkg/linux_amd64/nomad working_directory: *NOMAD_WORKING_DIR # update gotestsum diff --git a/acl/acl.go b/acl/acl.go index cd3b6df053..65c5cc7ab6 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -1,964 +1,72 @@ package acl import ( - "github.com/armon/go-radix" - "github.com/hashicorp/consul/sentinel" + "errors" + "strings" +) + +// These error constants define the standard ACL error types. The values +// must not be changed since the error values are sent via RPC calls +// from older clients and may not have the correct type. +const ( + errNotFound = "ACL not found" + errRootDenied = "Cannot resolve root ACL" + errDisabled = "ACL support disabled" + errPermissionDenied = "Permission denied" + errInvalidParent = "Invalid Parent" ) var ( - // allowAll is a singleton policy which allows all - // non-management actions - allowAll Authorizer + // ErrNotFound indicates there is no matching ACL. + ErrNotFound = errors.New(errNotFound) - // denyAll is a singleton policy which denies all actions - denyAll Authorizer + // ErrRootDenied is returned when attempting to resolve a root ACL. + ErrRootDenied = errors.New(errRootDenied) - // manageAll is a singleton policy which allows all - // actions, including management - manageAll Authorizer + // ErrDisabled is returned when ACL changes are not permitted since + // they are disabled. + ErrDisabled = errors.New(errDisabled) + + // ErrPermissionDenied is returned when an ACL based rejection + // happens. + ErrPermissionDenied = PermissionDeniedError{} + + // ErrInvalidParent is returned when a remotely resolve ACL + // token claims to have a non-root parent + ErrInvalidParent = errors.New(errInvalidParent) ) -// DefaultPolicyEnforcementLevel will be used if the user leaves the level -// blank when configuring an ACL. -const DefaultPolicyEnforcementLevel = "hard-mandatory" +// IsErrNotFound checks if the given error message is comparable to +// ErrNotFound. +func IsErrNotFound(err error) bool { + return err != nil && strings.Contains(err.Error(), errNotFound) +} -func init() { - // Setup the singletons - allowAll = &StaticAuthorizer{ - allowManage: false, - defaultAllow: true, +// IsErrRootDenied checks if the given error message is comparable to +// ErrRootDenied. +func IsErrRootDenied(err error) bool { + return err != nil && strings.Contains(err.Error(), errRootDenied) +} + +// IsErrDisabled checks if the given error message is comparable to +// ErrDisabled. +func IsErrDisabled(err error) bool { + return err != nil && strings.Contains(err.Error(), errDisabled) +} + +// IsErrPermissionDenied checks if the given error message is comparable +// to ErrPermissionDenied. +func IsErrPermissionDenied(err error) bool { + return err != nil && strings.Contains(err.Error(), errPermissionDenied) +} + +type PermissionDeniedError struct { + Cause string +} + +func (e PermissionDeniedError) Error() string { + if e.Cause != "" { + return errPermissionDenied + ": " + e.Cause } - 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 { - parentAllows := p.parent.KeyWritePrefix(prefix) - - // Look for a matching rule that denies - prefixAllowed := parentAllows - 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 - }) - - // This will be false if we had a prefix that didn't allow write or if - // there was no prefix rule and the parent policy would deny access. - 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 { - 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 prefix rule and it allowed writes, then we can allow the access - if found { - return true - } - - // No matching rule, use the parent policy. - return parentAllows -} - -// 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()) + return errPermissionDenied } diff --git a/acl/acl_oss.go b/acl/acl_oss.go new file mode 100644 index 0000000000..667b300d38 --- /dev/null +++ b/acl/acl_oss.go @@ -0,0 +1,6 @@ +// +build !consulent + +package acl + +// EnterpriseACLConfig stub +type EnterpriseACLConfig struct{} diff --git a/acl/acl_test.go b/acl/acl_test.go index 800c3744d9..815c869163 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -9,22 +9,24 @@ import ( func legacyPolicy(policy *Policy) *Policy { return &Policy{ - Agents: policy.Agents, - AgentPrefixes: policy.Agents, - Nodes: policy.Nodes, - NodePrefixes: policy.Nodes, - Keys: policy.Keys, - KeyPrefixes: policy.Keys, - Services: policy.Services, - ServicePrefixes: policy.Services, - Sessions: policy.Sessions, - SessionPrefixes: policy.Sessions, - Events: policy.Events, - EventPrefixes: policy.Events, - PreparedQueries: policy.PreparedQueries, - PreparedQueryPrefixes: policy.PreparedQueries, - Keyring: policy.Keyring, - Operator: policy.Operator, + PolicyRules: PolicyRules{ + Agents: policy.Agents, + AgentPrefixes: policy.Agents, + Nodes: policy.Nodes, + NodePrefixes: policy.Nodes, + Keys: policy.Keys, + KeyPrefixes: policy.Keys, + Services: policy.Services, + ServicePrefixes: policy.Services, + Sessions: policy.Sessions, + SessionPrefixes: policy.Sessions, + Events: policy.Events, + EventPrefixes: policy.Events, + PreparedQueries: policy.PreparedQueries, + PreparedQueryPrefixes: policy.PreparedQueries, + Keyring: policy.Keyring, + Operator: policy.Operator, + }, } } @@ -34,219 +36,323 @@ func legacyPolicy(policy *Policy) *Policy { // nicer in the embedded struct within TestACL // -func checkAllowACLRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.ACLRead()) +func checkAllowACLRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.ACLRead(entCtx)) } -func checkAllowACLWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.ACLWrite()) +func checkAllowACLWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.ACLWrite(entCtx)) } -func checkAllowAgentRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.AgentRead(prefix)) +func checkAllowAgentRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.AgentRead(prefix, entCtx)) } -func checkAllowAgentWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.AgentWrite(prefix)) +func checkAllowAgentWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.AgentWrite(prefix, entCtx)) } -func checkAllowEventRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.EventRead(prefix)) +func checkAllowEventRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.EventRead(prefix, entCtx)) } -func checkAllowEventWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.EventWrite(prefix)) +func checkAllowEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.EventWrite(prefix, entCtx)) } -func checkAllowIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.IntentionDefaultAllow()) +func checkAllowIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.IntentionDefaultAllow(entCtx)) } -func checkAllowIntentionRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.IntentionRead(prefix)) +func checkAllowIntentionRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.IntentionRead(prefix, entCtx)) } -func checkAllowIntentionWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.IntentionWrite(prefix)) +func checkAllowIntentionWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.IntentionWrite(prefix, entCtx)) } -func checkAllowKeyRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.KeyRead(prefix)) +func checkAllowKeyRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.KeyRead(prefix, entCtx)) } -func checkAllowKeyList(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.KeyList(prefix)) +func checkAllowKeyList(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.KeyList(prefix, entCtx)) } -func checkAllowKeyringRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.KeyringRead()) +func checkAllowKeyringRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.KeyringRead(entCtx)) } -func checkAllowKeyringWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.KeyringWrite()) +func checkAllowKeyringWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.KeyringWrite(entCtx)) } -func checkAllowKeyWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.KeyWrite(prefix, nil)) +func checkAllowKeyWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.KeyWrite(prefix, entCtx)) } -func checkAllowKeyWritePrefix(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.KeyWritePrefix(prefix)) +func checkAllowKeyWritePrefix(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.KeyWritePrefix(prefix, entCtx)) } -func checkAllowNodeRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.NodeRead(prefix)) +func checkAllowNodeRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.NodeRead(prefix, entCtx)) } -func checkAllowNodeWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.NodeWrite(prefix, nil)) +func checkAllowNodeWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.NodeWrite(prefix, entCtx)) } -func checkAllowOperatorRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.OperatorRead()) +func checkAllowOperatorRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.OperatorRead(entCtx)) } -func checkAllowOperatorWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.OperatorWrite()) +func checkAllowOperatorWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.OperatorWrite(entCtx)) } -func checkAllowPreparedQueryRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.PreparedQueryRead(prefix)) +func checkAllowPreparedQueryRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.PreparedQueryRead(prefix, entCtx)) } -func checkAllowPreparedQueryWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.PreparedQueryWrite(prefix)) +func checkAllowPreparedQueryWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.PreparedQueryWrite(prefix, entCtx)) } -func checkAllowServiceRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.ServiceRead(prefix)) +func checkAllowServiceRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.ServiceRead(prefix, entCtx)) } -func checkAllowServiceWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.ServiceWrite(prefix, nil)) +func checkAllowServiceWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.ServiceWrite(prefix, entCtx)) } -func checkAllowSessionRead(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.SessionRead(prefix)) +func checkAllowSessionRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.SessionRead(prefix, entCtx)) } -func checkAllowSessionWrite(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.SessionWrite(prefix)) +func checkAllowSessionWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.SessionWrite(prefix, entCtx)) } -func checkAllowSnapshot(t *testing.T, authz Authorizer, prefix string) { - require.True(t, authz.Snapshot()) +func checkAllowSnapshot(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Allow, authz.Snapshot(entCtx)) } -func checkDenyACLRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.ACLRead()) +func checkDenyACLRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.ACLRead(entCtx)) } -func checkDenyACLWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.ACLWrite()) +func checkDenyACLWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.ACLWrite(entCtx)) } -func checkDenyAgentRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.AgentRead(prefix)) +func checkDenyAgentRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.AgentRead(prefix, entCtx)) } -func checkDenyAgentWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.AgentWrite(prefix)) +func checkDenyAgentWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.AgentWrite(prefix, entCtx)) } -func checkDenyEventRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.EventRead(prefix)) +func checkDenyEventRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.EventRead(prefix, entCtx)) } -func checkDenyEventWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.EventWrite(prefix)) +func checkDenyEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.EventWrite(prefix, entCtx)) } -func checkDenyIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.IntentionDefaultAllow()) +func checkDenyIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.IntentionDefaultAllow(entCtx)) } -func checkDenyIntentionRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.IntentionRead(prefix)) +func checkDenyIntentionRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.IntentionRead(prefix, entCtx)) } -func checkDenyIntentionWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.IntentionWrite(prefix)) +func checkDenyIntentionWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.IntentionWrite(prefix, entCtx)) } -func checkDenyKeyRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.KeyRead(prefix)) +func checkDenyKeyRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.KeyRead(prefix, entCtx)) } -func checkDenyKeyList(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.KeyList(prefix)) +func checkDenyKeyList(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.KeyList(prefix, entCtx)) } -func checkDenyKeyringRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.KeyringRead()) +func checkDenyKeyringRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.KeyringRead(entCtx)) } -func checkDenyKeyringWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.KeyringWrite()) +func checkDenyKeyringWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.KeyringWrite(entCtx)) } -func checkDenyKeyWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.KeyWrite(prefix, nil)) +func checkDenyKeyWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.KeyWrite(prefix, entCtx)) } -func checkDenyKeyWritePrefix(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.KeyWritePrefix(prefix)) +func checkDenyKeyWritePrefix(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.KeyWritePrefix(prefix, entCtx)) } -func checkDenyNodeRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.NodeRead(prefix)) +func checkDenyNodeRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.NodeRead(prefix, entCtx)) } -func checkDenyNodeWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.NodeWrite(prefix, nil)) +func checkDenyNodeWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.NodeWrite(prefix, entCtx)) } -func checkDenyOperatorRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.OperatorRead()) +func checkDenyOperatorRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.OperatorRead(entCtx)) } -func checkDenyOperatorWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.OperatorWrite()) +func checkDenyOperatorWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.OperatorWrite(entCtx)) } -func checkDenyPreparedQueryRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.PreparedQueryRead(prefix)) +func checkDenyPreparedQueryRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.PreparedQueryRead(prefix, entCtx)) } -func checkDenyPreparedQueryWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.PreparedQueryWrite(prefix)) +func checkDenyPreparedQueryWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.PreparedQueryWrite(prefix, entCtx)) } -func checkDenyServiceRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.ServiceRead(prefix)) +func checkDenyServiceRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.ServiceRead(prefix, entCtx)) } -func checkDenyServiceWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.ServiceWrite(prefix, nil)) +func checkDenyServiceWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.ServiceWrite(prefix, entCtx)) } -func checkDenySessionRead(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.SessionRead(prefix)) +func checkDenySessionRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.SessionRead(prefix, entCtx)) } -func checkDenySessionWrite(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.SessionWrite(prefix)) +func checkDenySessionWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.SessionWrite(prefix, entCtx)) } -func checkDenySnapshot(t *testing.T, authz Authorizer, prefix string) { - require.False(t, authz.Snapshot()) +func checkDenySnapshot(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Deny, authz.Snapshot(entCtx)) +} + +func checkDefaultACLRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.ACLRead(entCtx)) +} + +func checkDefaultACLWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.ACLWrite(entCtx)) +} + +func checkDefaultAgentRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.AgentRead(prefix, entCtx)) +} + +func checkDefaultAgentWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.AgentWrite(prefix, entCtx)) +} + +func checkDefaultEventRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.EventRead(prefix, entCtx)) +} + +func checkDefaultEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.EventWrite(prefix, entCtx)) +} + +func checkDefaultIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.IntentionDefaultAllow(entCtx)) +} + +func checkDefaultIntentionRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.IntentionRead(prefix, entCtx)) +} + +func checkDefaultIntentionWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.IntentionWrite(prefix, entCtx)) +} + +func checkDefaultKeyRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.KeyRead(prefix, entCtx)) +} + +func checkDefaultKeyList(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.KeyList(prefix, entCtx)) +} + +func checkDefaultKeyringRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.KeyringRead(entCtx)) +} + +func checkDefaultKeyringWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.KeyringWrite(entCtx)) +} + +func checkDefaultKeyWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.KeyWrite(prefix, entCtx)) +} + +func checkDefaultKeyWritePrefix(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.KeyWritePrefix(prefix, entCtx)) +} + +func checkDefaultNodeRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.NodeRead(prefix, entCtx)) +} + +func checkDefaultNodeWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.NodeWrite(prefix, entCtx)) +} + +func checkDefaultOperatorRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.OperatorRead(entCtx)) +} + +func checkDefaultOperatorWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.OperatorWrite(entCtx)) +} + +func checkDefaultPreparedQueryRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.PreparedQueryRead(prefix, entCtx)) +} + +func checkDefaultPreparedQueryWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.PreparedQueryWrite(prefix, entCtx)) +} + +func checkDefaultServiceRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.ServiceRead(prefix, entCtx)) +} + +func checkDefaultServiceWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.ServiceWrite(prefix, entCtx)) +} + +func checkDefaultSessionRead(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.SessionRead(prefix, entCtx)) +} + +func checkDefaultSessionWrite(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.SessionWrite(prefix, entCtx)) +} + +func checkDefaultSnapshot(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) { + require.Equal(t, Default, authz.Snapshot(entCtx)) } func TestACL(t *testing.T) { type aclCheck struct { name string prefix string - check func(t *testing.T, authz Authorizer, prefix string) + check func(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) } type aclTest struct { @@ -352,18 +458,20 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "root", - Policy: PolicyRead, - }, - &AgentPolicy{ - Node: "root-nope", - Policy: PolicyDeny, - }, - &AgentPolicy{ - Node: "root-rw", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "root", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &AgentRule{ + Node: "root-rw", + Policy: PolicyWrite, + }, }, }, }), @@ -390,18 +498,20 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "root", - Policy: PolicyRead, - }, - &AgentPolicy{ - Node: "root-nope", - Policy: PolicyDeny, - }, - &AgentPolicy{ - Node: "root-rw", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "root", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &AgentRule{ + Node: "root-rw", + Policy: PolicyWrite, + }, }, }, }), @@ -428,10 +538,12 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ - Prefix: "other", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "other", + Policy: PolicyDeny, + }, }, }, }), @@ -450,42 +562,46 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "root-nope", - Policy: PolicyDeny, - }, - &AgentPolicy{ - Node: "root-ro", - Policy: PolicyRead, - }, - &AgentPolicy{ - Node: "root-rw", - Policy: PolicyWrite, - }, - &AgentPolicy{ - Node: "override", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &AgentRule{ + Node: "root-ro", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "root-rw", + Policy: PolicyWrite, + }, + &AgentRule{ + Node: "override", + Policy: PolicyDeny, + }, }, }, }), legacyPolicy(&Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "child-nope", - Policy: PolicyDeny, - }, - &AgentPolicy{ - Node: "child-ro", - Policy: PolicyRead, - }, - &AgentPolicy{ - Node: "child-rw", - Policy: PolicyWrite, - }, - &AgentPolicy{ - Node: "override", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "child-nope", + Policy: PolicyDeny, + }, + &AgentRule{ + Node: "child-ro", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "child-rw", + Policy: PolicyWrite, + }, + &AgentRule{ + Node: "override", + Policy: PolicyWrite, + }, }, }, }), @@ -526,42 +642,46 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "root-nope", - Policy: PolicyDeny, - }, - &AgentPolicy{ - Node: "root-ro", - Policy: PolicyRead, - }, - &AgentPolicy{ - Node: "root-rw", - Policy: PolicyWrite, - }, - &AgentPolicy{ - Node: "override", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &AgentRule{ + Node: "root-ro", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "root-rw", + Policy: PolicyWrite, + }, + &AgentRule{ + Node: "override", + Policy: PolicyDeny, + }, }, }, }), legacyPolicy(&Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "child-nope", - Policy: PolicyDeny, - }, - &AgentPolicy{ - Node: "child-ro", - Policy: PolicyRead, - }, - &AgentPolicy{ - Node: "child-rw", - Policy: PolicyWrite, - }, - &AgentPolicy{ - Node: "override", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "child-nope", + Policy: PolicyDeny, + }, + &AgentRule{ + Node: "child-ro", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "child-rw", + Policy: PolicyWrite, + }, + &AgentRule{ + Node: "override", + Policy: PolicyWrite, + }, }, }, }), @@ -602,7 +722,9 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Keyring: PolicyDeny, + PolicyRules: PolicyRules{ + Keyring: PolicyDeny, + }, }, }, checks: []aclCheck{ @@ -616,7 +738,9 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Keyring: PolicyRead, + PolicyRules: PolicyRules{ + Keyring: PolicyRead, + }, }, }, checks: []aclCheck{ @@ -630,7 +754,9 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Keyring: PolicyWrite, + PolicyRules: PolicyRules{ + Keyring: PolicyWrite, + }, }, }, checks: []aclCheck{ @@ -654,7 +780,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Keyring: PolicyDeny, + PolicyRules: PolicyRules{ + Keyring: PolicyDeny, + }, }, }, checks: []aclCheck{ @@ -667,7 +795,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Keyring: PolicyRead, + PolicyRules: PolicyRules{ + Keyring: PolicyRead, + }, }, }, checks: []aclCheck{ @@ -680,7 +810,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Keyring: PolicyWrite, + PolicyRules: PolicyRules{ + Keyring: PolicyWrite, + }, }, }, checks: []aclCheck{ @@ -704,7 +836,9 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Operator: PolicyDeny, + PolicyRules: PolicyRules{ + Operator: PolicyDeny, + }, }, }, checks: []aclCheck{ @@ -718,7 +852,9 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Operator: PolicyRead, + PolicyRules: PolicyRules{ + Operator: PolicyRead, + }, }, }, checks: []aclCheck{ @@ -732,7 +868,9 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Operator: PolicyWrite, + PolicyRules: PolicyRules{ + Operator: PolicyWrite, + }, }, }, checks: []aclCheck{ @@ -756,7 +894,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Operator: PolicyDeny, + PolicyRules: PolicyRules{ + Operator: PolicyDeny, + }, }, }, checks: []aclCheck{ @@ -769,7 +909,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Operator: PolicyRead, + PolicyRules: PolicyRules{ + Operator: PolicyRead, + }, }, }, checks: []aclCheck{ @@ -782,7 +924,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Operator: PolicyWrite, + PolicyRules: PolicyRules{ + Operator: PolicyWrite, + }, }, }, checks: []aclCheck{ @@ -806,42 +950,46 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ - Name: "root-nope", - Policy: PolicyDeny, - }, - &NodePolicy{ - Name: "root-ro", - Policy: PolicyRead, - }, - &NodePolicy{ - Name: "root-rw", - Policy: PolicyWrite, - }, - &NodePolicy{ - Name: "override", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ + Name: "root-nope", + Policy: PolicyDeny, + }, + &NodeRule{ + Name: "root-ro", + Policy: PolicyRead, + }, + &NodeRule{ + Name: "root-rw", + Policy: PolicyWrite, + }, + &NodeRule{ + Name: "override", + Policy: PolicyDeny, + }, }, }, }), legacyPolicy(&Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ - Name: "child-nope", - Policy: PolicyDeny, - }, - &NodePolicy{ - Name: "child-ro", - Policy: PolicyRead, - }, - &NodePolicy{ - Name: "child-rw", - Policy: PolicyWrite, - }, - &NodePolicy{ - Name: "override", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ + Name: "child-nope", + Policy: PolicyDeny, + }, + &NodeRule{ + Name: "child-ro", + Policy: PolicyRead, + }, + &NodeRule{ + Name: "child-rw", + Policy: PolicyWrite, + }, + &NodeRule{ + Name: "override", + Policy: PolicyWrite, + }, }, }, }), @@ -882,42 +1030,46 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ - Name: "root-nope", - Policy: PolicyDeny, - }, - &NodePolicy{ - Name: "root-ro", - Policy: PolicyRead, - }, - &NodePolicy{ - Name: "root-rw", - Policy: PolicyWrite, - }, - &NodePolicy{ - Name: "override", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ + Name: "root-nope", + Policy: PolicyDeny, + }, + &NodeRule{ + Name: "root-ro", + Policy: PolicyRead, + }, + &NodeRule{ + Name: "root-rw", + Policy: PolicyWrite, + }, + &NodeRule{ + Name: "override", + Policy: PolicyDeny, + }, }, }, }), legacyPolicy(&Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ - Name: "child-nope", - Policy: PolicyDeny, - }, - &NodePolicy{ - Name: "child-ro", - Policy: PolicyRead, - }, - &NodePolicy{ - Name: "child-rw", - Policy: PolicyWrite, - }, - &NodePolicy{ - Name: "override", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ + Name: "child-nope", + Policy: PolicyDeny, + }, + &NodeRule{ + Name: "child-ro", + Policy: PolicyRead, + }, + &NodeRule{ + Name: "child-rw", + Policy: PolicyWrite, + }, + &NodeRule{ + Name: "override", + Policy: PolicyWrite, + }, }, }, }), @@ -958,42 +1110,46 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ - Node: "root-nope", - Policy: PolicyDeny, - }, - &SessionPolicy{ - Node: "root-ro", - Policy: PolicyRead, - }, - &SessionPolicy{ - Node: "root-rw", - Policy: PolicyWrite, - }, - &SessionPolicy{ - Node: "override", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &SessionRule{ + Node: "root-ro", + Policy: PolicyRead, + }, + &SessionRule{ + Node: "root-rw", + Policy: PolicyWrite, + }, + &SessionRule{ + Node: "override", + Policy: PolicyDeny, + }, }, }, }), legacyPolicy(&Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ - Node: "child-nope", - Policy: PolicyDeny, - }, - &SessionPolicy{ - Node: "child-ro", - Policy: PolicyRead, - }, - &SessionPolicy{ - Node: "child-rw", - Policy: PolicyWrite, - }, - &SessionPolicy{ - Node: "override", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ + Node: "child-nope", + Policy: PolicyDeny, + }, + &SessionRule{ + Node: "child-ro", + Policy: PolicyRead, + }, + &SessionRule{ + Node: "child-rw", + Policy: PolicyWrite, + }, + &SessionRule{ + Node: "override", + Policy: PolicyWrite, + }, }, }, }), @@ -1034,42 +1190,46 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ - Node: "root-nope", - Policy: PolicyDeny, - }, - &SessionPolicy{ - Node: "root-ro", - Policy: PolicyRead, - }, - &SessionPolicy{ - Node: "root-rw", - Policy: PolicyWrite, - }, - &SessionPolicy{ - Node: "override", - Policy: PolicyDeny, + PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ + Node: "root-nope", + Policy: PolicyDeny, + }, + &SessionRule{ + Node: "root-ro", + Policy: PolicyRead, + }, + &SessionRule{ + Node: "root-rw", + Policy: PolicyWrite, + }, + &SessionRule{ + Node: "override", + Policy: PolicyDeny, + }, }, }, }), legacyPolicy(&Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ - Node: "child-nope", - Policy: PolicyDeny, - }, - &SessionPolicy{ - Node: "child-ro", - Policy: PolicyRead, - }, - &SessionPolicy{ - Node: "child-rw", - Policy: PolicyWrite, - }, - &SessionPolicy{ - Node: "override", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ + Node: "child-nope", + Policy: PolicyDeny, + }, + &SessionRule{ + Node: "child-ro", + Policy: PolicyRead, + }, + &SessionRule{ + Node: "child-rw", + Policy: PolicyWrite, + }, + &SessionRule{ + Node: "override", + Policy: PolicyWrite, + }, }, }, }), @@ -1110,62 +1270,66 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Keys: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foo/", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo/", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "bar/", + Policy: PolicyRead, + }, }, - &KeyPolicy{ - Prefix: "bar/", - Policy: PolicyRead, + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "other", + Policy: PolicyWrite, + }, + &PreparedQueryRule{ + Prefix: "foo", + Policy: PolicyRead, + }, }, - }, - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ - Prefix: "other", - Policy: PolicyWrite, - }, - &PreparedQueryPolicy{ - Prefix: "foo", - Policy: PolicyRead, - }, - }, - Services: []*ServicePolicy{ - &ServicePolicy{ - Name: "other", - Policy: PolicyWrite, - }, - &ServicePolicy{ - Name: "foo", - Policy: PolicyRead, + Services: []*ServiceRule{ + &ServiceRule{ + Name: "other", + Policy: PolicyWrite, + }, + &ServiceRule{ + Name: "foo", + Policy: PolicyRead, + }, }, }, }), legacyPolicy(&Policy{ - Keys: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foo/priv/", - Policy: PolicyRead, + PolicyRules: PolicyRules{ + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo/priv/", + Policy: PolicyRead, + }, + &KeyRule{ + Prefix: "bar/", + Policy: PolicyDeny, + }, + &KeyRule{ + Prefix: "zip/", + Policy: PolicyRead, + }, }, - &KeyPolicy{ - Prefix: "bar/", - Policy: PolicyDeny, + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "bar", + Policy: PolicyDeny, + }, }, - &KeyPolicy{ - Prefix: "zip/", - Policy: PolicyRead, - }, - }, - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ - Prefix: "bar", - Policy: PolicyDeny, - }, - }, - Services: []*ServicePolicy{ - &ServicePolicy{ - Name: "bar", - Policy: PolicyDeny, + Services: []*ServiceRule{ + &ServiceRule{ + Name: "bar", + Policy: PolicyDeny, + }, }, }, }), @@ -1217,82 +1381,84 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ legacyPolicy(&Policy{ - Events: []*EventPolicy{ - &EventPolicy{ - Event: "", - Policy: PolicyRead, + PolicyRules: PolicyRules{ + Events: []*EventRule{ + &EventRule{ + Event: "", + Policy: PolicyRead, + }, + &EventRule{ + Event: "foo", + Policy: PolicyWrite, + }, + &EventRule{ + Event: "bar", + Policy: PolicyDeny, + }, }, - &EventPolicy{ - Event: "foo", - Policy: PolicyWrite, + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo/", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "foo/priv/", + Policy: PolicyDeny, + }, + &KeyRule{ + Prefix: "bar/", + Policy: PolicyDeny, + }, + &KeyRule{ + Prefix: "zip/", + Policy: PolicyRead, + }, + &KeyRule{ + Prefix: "zap/", + Policy: PolicyList, + }, }, - &EventPolicy{ - Event: "bar", - Policy: PolicyDeny, + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "", + Policy: PolicyRead, + }, + &PreparedQueryRule{ + Prefix: "foo", + Policy: PolicyWrite, + }, + &PreparedQueryRule{ + Prefix: "bar", + Policy: PolicyDeny, + }, + &PreparedQueryRule{ + Prefix: "zoo", + Policy: PolicyWrite, + }, }, - }, - Keys: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foo/", - Policy: PolicyWrite, - }, - &KeyPolicy{ - Prefix: "foo/priv/", - Policy: PolicyDeny, - }, - &KeyPolicy{ - Prefix: "bar/", - Policy: PolicyDeny, - }, - &KeyPolicy{ - Prefix: "zip/", - Policy: PolicyRead, - }, - &KeyPolicy{ - Prefix: "zap/", - Policy: PolicyList, - }, - }, - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ - Prefix: "", - Policy: PolicyRead, - }, - &PreparedQueryPolicy{ - Prefix: "foo", - Policy: PolicyWrite, - }, - &PreparedQueryPolicy{ - Prefix: "bar", - Policy: PolicyDeny, - }, - &PreparedQueryPolicy{ - Prefix: "zoo", - Policy: PolicyWrite, - }, - }, - Services: []*ServicePolicy{ - &ServicePolicy{ - Name: "", - Policy: PolicyWrite, - }, - &ServicePolicy{ - Name: "foo", - Policy: PolicyRead, - }, - &ServicePolicy{ - Name: "bar", - Policy: PolicyDeny, - }, - &ServicePolicy{ - Name: "barfoo", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - &ServicePolicy{ - Name: "intbaz", - Policy: PolicyWrite, - Intentions: PolicyDeny, + Services: []*ServiceRule{ + &ServiceRule{ + Name: "", + Policy: PolicyWrite, + }, + &ServiceRule{ + Name: "foo", + Policy: PolicyRead, + }, + &ServiceRule{ + Name: "bar", + Policy: PolicyDeny, + }, + &ServiceRule{ + Name: "barfoo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + &ServiceRule{ + Name: "intbaz", + Policy: PolicyWrite, + Intentions: PolicyDeny, + }, }, }, }), @@ -1394,147 +1560,149 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ - Node: "foo", - Policy: PolicyWrite, + PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "foo", + Policy: PolicyWrite, + }, + &AgentRule{ + Node: "football", + Policy: PolicyDeny, + }, }, - &AgentPolicy{ - Node: "football", - Policy: PolicyDeny, + AgentPrefixes: []*AgentRule{ + &AgentRule{ + Node: "foot", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "fo", + Policy: PolicyRead, + }, }, - }, - AgentPrefixes: []*AgentPolicy{ - &AgentPolicy{ - Node: "foot", - Policy: PolicyRead, + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "football", + Policy: PolicyDeny, + }, }, - &AgentPolicy{ - Node: "fo", - Policy: PolicyRead, + KeyPrefixes: []*KeyRule{ + &KeyRule{ + Prefix: "foot", + Policy: PolicyRead, + }, + &KeyRule{ + Prefix: "fo", + Policy: PolicyRead, + }, }, - }, - Keys: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foo", - Policy: PolicyWrite, + Nodes: []*NodeRule{ + &NodeRule{ + Name: "foo", + Policy: PolicyWrite, + }, + &NodeRule{ + Name: "football", + Policy: PolicyDeny, + }, }, - &KeyPolicy{ - Prefix: "football", - Policy: PolicyDeny, + NodePrefixes: []*NodeRule{ + &NodeRule{ + Name: "foot", + Policy: PolicyRead, + }, + &NodeRule{ + Name: "fo", + Policy: PolicyRead, + }, }, - }, - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foot", - Policy: PolicyRead, + Services: []*ServiceRule{ + &ServiceRule{ + Name: "foo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + &ServiceRule{ + Name: "football", + Policy: PolicyDeny, + }, }, - &KeyPolicy{ - Prefix: "fo", - Policy: PolicyRead, + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ + Name: "foot", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + &ServiceRule{ + Name: "fo", + Policy: PolicyRead, + Intentions: PolicyRead, + }, }, - }, - Nodes: []*NodePolicy{ - &NodePolicy{ - Name: "foo", - Policy: PolicyWrite, + Sessions: []*SessionRule{ + &SessionRule{ + Node: "foo", + Policy: PolicyWrite, + }, + &SessionRule{ + Node: "football", + Policy: PolicyDeny, + }, }, - &NodePolicy{ - Name: "football", - Policy: PolicyDeny, + SessionPrefixes: []*SessionRule{ + &SessionRule{ + Node: "foot", + Policy: PolicyRead, + }, + &SessionRule{ + Node: "fo", + Policy: PolicyRead, + }, }, - }, - NodePrefixes: []*NodePolicy{ - &NodePolicy{ - Name: "foot", - Policy: PolicyRead, + Events: []*EventRule{ + &EventRule{ + Event: "foo", + Policy: PolicyWrite, + }, + &EventRule{ + Event: "football", + Policy: PolicyDeny, + }, }, - &NodePolicy{ - Name: "fo", - Policy: PolicyRead, + EventPrefixes: []*EventRule{ + &EventRule{ + Event: "foot", + Policy: PolicyRead, + }, + &EventRule{ + Event: "fo", + Policy: PolicyRead, + }, }, - }, - Services: []*ServicePolicy{ - &ServicePolicy{ - Name: "foo", - Policy: PolicyWrite, - Intentions: PolicyWrite, + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "foo", + Policy: PolicyWrite, + }, + &PreparedQueryRule{ + Prefix: "football", + Policy: PolicyDeny, + }, }, - &ServicePolicy{ - Name: "football", - Policy: PolicyDeny, - }, - }, - ServicePrefixes: []*ServicePolicy{ - &ServicePolicy{ - Name: "foot", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - &ServicePolicy{ - Name: "fo", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - }, - Sessions: []*SessionPolicy{ - &SessionPolicy{ - Node: "foo", - Policy: PolicyWrite, - }, - &SessionPolicy{ - Node: "football", - Policy: PolicyDeny, - }, - }, - SessionPrefixes: []*SessionPolicy{ - &SessionPolicy{ - Node: "foot", - Policy: PolicyRead, - }, - &SessionPolicy{ - Node: "fo", - Policy: PolicyRead, - }, - }, - Events: []*EventPolicy{ - &EventPolicy{ - Event: "foo", - Policy: PolicyWrite, - }, - &EventPolicy{ - Event: "football", - Policy: PolicyDeny, - }, - }, - EventPrefixes: []*EventPolicy{ - &EventPolicy{ - Event: "foot", - Policy: PolicyRead, - }, - &EventPolicy{ - Event: "fo", - Policy: PolicyRead, - }, - }, - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ - Prefix: "foo", - Policy: PolicyWrite, - }, - &PreparedQueryPolicy{ - Prefix: "football", - Policy: PolicyDeny, - }, - }, - PreparedQueryPrefixes: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ - Prefix: "foot", - Policy: PolicyRead, - }, - &PreparedQueryPolicy{ - Prefix: "fo", - Policy: PolicyRead, + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "foot", + Policy: PolicyRead, + }, + &PreparedQueryRule{ + Prefix: "fo", + Policy: PolicyRead, + }, }, }, }, @@ -1681,7 +1849,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - ACL: PolicyRead, + PolicyRules: PolicyRules{ + ACL: PolicyRead, + }, }, }, checks: []aclCheck{ @@ -1695,7 +1865,9 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - ACL: PolicyWrite, + PolicyRules: PolicyRules{ + ACL: PolicyWrite, + }, }, }, checks: []aclCheck{ @@ -1709,36 +1881,38 @@ func TestACL(t *testing.T) { defaultPolicy: DenyAll(), policyStack: []*Policy{ &Policy{ - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "fo", - Policy: PolicyRead, + PolicyRules: PolicyRules{ + KeyPrefixes: []*KeyRule{ + &KeyRule{ + Prefix: "fo", + Policy: PolicyRead, + }, + &KeyRule{ + Prefix: "foo/", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "bar/", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "baz/", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "test/", + Policy: PolicyWrite, + }, }, - &KeyPolicy{ - Prefix: "foo/", - Policy: PolicyWrite, - }, - &KeyPolicy{ - Prefix: "bar/", - Policy: PolicyWrite, - }, - &KeyPolicy{ - Prefix: "baz/", - Policy: PolicyWrite, - }, - &KeyPolicy{ - Prefix: "test/", - Policy: PolicyWrite, - }, - }, - Keys: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foo/bar", - Policy: PolicyWrite, - }, - &KeyPolicy{ - Prefix: "bar/baz", - Policy: PolicyRead, + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo/bar", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "bar/baz", + Policy: PolicyRead, + }, }, }, }, @@ -1770,10 +1944,12 @@ func TestACL(t *testing.T) { defaultPolicy: AllowAll(), policyStack: []*Policy{ &Policy{ - Keys: []*KeyPolicy{ - &KeyPolicy{ - Prefix: "foo/bar", - Policy: PolicyRead, + PolicyRules: PolicyRules{ + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo/bar", + Policy: PolicyRead, + }, }, }, }, @@ -1793,7 +1969,7 @@ func TestACL(t *testing.T) { t.Run(tcase.name, func(t *testing.T) { acl := tcase.defaultPolicy for _, policy := range tcase.policyStack { - newACL, err := NewPolicyAuthorizer(acl, []*Policy{policy}, nil) + newACL, err := NewPolicyAuthorizerWithDefaults(acl, []*Policy{policy}, nil) require.NoError(t, err) acl = newACL } @@ -1804,7 +1980,7 @@ func TestACL(t *testing.T) { checkName = fmt.Sprintf("%s.Prefix(%s)", checkName, check.prefix) } t.Run(checkName, func(t *testing.T) { - check.check(t, acl, check.prefix) + check.check(t, acl, check.prefix, nil) }) } }) @@ -1821,125 +1997,107 @@ func TestRootAuthorizer(t *testing.T) { func TestACLEnforce(t *testing.T) { type enforceTest struct { name string - rule string - required string - allow bool - recurse bool + rule AccessLevel + required AccessLevel + expected EnforcementDecision } tests := []enforceTest{ { name: "RuleNoneRequireRead", - rule: "", - required: PolicyRead, - allow: false, - recurse: true, + rule: AccessUnknown, + required: AccessRead, + expected: Default, }, { name: "RuleNoneRequireWrite", - rule: "", - required: PolicyWrite, - allow: false, - recurse: true, + rule: AccessUnknown, + required: AccessWrite, + expected: Default, }, { name: "RuleNoneRequireList", - rule: "", - required: PolicyList, - allow: false, - recurse: true, + rule: AccessUnknown, + required: AccessList, + expected: Default, }, { name: "RuleReadRequireRead", - rule: PolicyRead, - required: PolicyRead, - allow: true, - recurse: false, + rule: AccessRead, + required: AccessRead, + expected: Allow, }, { name: "RuleReadRequireWrite", - rule: PolicyRead, - required: PolicyWrite, - allow: false, - recurse: false, + rule: AccessRead, + required: AccessWrite, + expected: Deny, }, { name: "RuleReadRequireList", - rule: PolicyRead, - required: PolicyList, - allow: false, - recurse: false, + rule: AccessRead, + required: AccessList, + expected: Deny, }, { name: "RuleListRequireRead", - rule: PolicyList, - required: PolicyRead, - allow: true, - recurse: false, + rule: AccessList, + required: AccessRead, + expected: Allow, }, { name: "RuleListRequireWrite", - rule: PolicyList, - required: PolicyWrite, - allow: false, - recurse: false, + rule: AccessList, + required: AccessWrite, + expected: Deny, }, { name: "RuleListRequireList", - rule: PolicyList, - required: PolicyList, - allow: true, - recurse: false, + rule: AccessList, + required: AccessList, + expected: Allow, }, { name: "RuleWritetRequireRead", - rule: PolicyWrite, - required: PolicyRead, - allow: true, - recurse: false, + rule: AccessWrite, + required: AccessRead, + expected: Allow, }, { name: "RuleWritetRequireWrite", - rule: PolicyWrite, - required: PolicyWrite, - allow: true, - recurse: false, + rule: AccessWrite, + required: AccessWrite, + expected: Allow, }, { name: "RuleWritetRequireList", - rule: PolicyWrite, - required: PolicyList, - allow: true, - recurse: false, + rule: AccessWrite, + required: AccessList, + expected: Allow, }, { name: "RuleDenyRequireRead", - rule: PolicyDeny, - required: PolicyRead, - allow: false, - recurse: false, + rule: AccessDeny, + required: AccessRead, + expected: Deny, }, { name: "RuleDenyRequireWrite", - rule: PolicyDeny, - required: PolicyWrite, - allow: false, - recurse: false, + rule: AccessDeny, + required: AccessWrite, + expected: Deny, }, { name: "RuleDenyRequireList", - rule: PolicyDeny, - required: PolicyList, - allow: false, - recurse: false, + rule: AccessDeny, + required: AccessList, + expected: Deny, }, } for _, tcase := range tests { t.Run(tcase.name, func(t *testing.T) { - allow, recurse := enforce(tcase.rule, tcase.required) - require.Equal(t, tcase.allow, allow) - require.Equal(t, tcase.recurse, recurse) + require.Equal(t, tcase.expected, enforce(tcase.rule, tcase.required)) }) } } diff --git a/acl/authorizer.go b/acl/authorizer.go new file mode 100644 index 0000000000..a4d28be75c --- /dev/null +++ b/acl/authorizer.go @@ -0,0 +1,128 @@ +package acl + +type EnforcementDecision int + +const ( + // Deny returned from an Authorizer enforcement method indicates + // that a corresponding rule was found and that access should be denied + Deny EnforcementDecision = iota + // Allow returned from an Authorizer enforcement method indicates + // that a corresponding rule was found and that access should be allowed + Allow + // Default returned from an Authorizer enforcement method indicates + // that a corresponding rule was not found and that whether access + // should be granted or denied should be deferred to the default + // access level + Default +) + +func (d EnforcementDecision) String() string { + switch d { + case Allow: + return "Allow" + case Deny: + return "Deny" + case Default: + return "Default" + default: + return "Unknown" + } +} + +// Authorizer is the interface for policy enforcement. +type Authorizer interface { + // ACLRead checks for permission to list all the ACLs + ACLRead(*EnterpriseAuthorizerContext) EnforcementDecision + + // ACLWrite checks for permission to manipulate ACLs + ACLWrite(*EnterpriseAuthorizerContext) EnforcementDecision + + // AgentRead checks for permission to read from agent endpoints for a + // given node. + AgentRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // AgentWrite checks for permission to make changes via agent endpoints + // for a given node. + AgentWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // EventRead determines if a specific event can be queried. + EventRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // EventWrite determines if a specific event may be fired. + EventWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // IntentionDefaultAllow determines the default authorized behavior + // when no intentions match a Connect request. + IntentionDefaultAllow(*EnterpriseAuthorizerContext) EnforcementDecision + + // IntentionRead determines if a specific intention can be read. + IntentionRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // IntentionWrite determines if a specific intention can be + // created, modified, or deleted. + IntentionWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // KeyList checks for permission to list keys under a prefix + KeyList(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // KeyRead checks for permission to read a given key + KeyRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // KeyWrite checks for permission to write a given key + KeyWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // 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, *EnterpriseAuthorizerContext) EnforcementDecision + + // KeyringRead determines if the encryption keyring used in + // the gossip layer can be read. + KeyringRead(*EnterpriseAuthorizerContext) EnforcementDecision + + // KeyringWrite determines if the keyring can be manipulated + KeyringWrite(*EnterpriseAuthorizerContext) EnforcementDecision + + // NodeRead checks for permission to read (discover) a given node. + NodeRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // NodeWrite checks for permission to create or update (register) a + // given node. + NodeWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // OperatorRead determines if the read-only Consul operator functions + // can be used. + OperatorRead(*EnterpriseAuthorizerContext) EnforcementDecision + + // OperatorWrite determines if the state-changing Consul operator + // functions can be used. + OperatorWrite(*EnterpriseAuthorizerContext) EnforcementDecision + + // PreparedQueryRead determines if a specific prepared query can be read + // to show its contents (this is not used for execution). + PreparedQueryRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // PreparedQueryWrite determines if a specific prepared query can be + // created, modified, or deleted. + PreparedQueryWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // ServiceRead checks for permission to read a given service + ServiceRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // ServiceWrite checks for permission to create or update a given + // service + ServiceWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // SessionRead checks for permission to read sessions for a given node. + SessionRead(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // SessionWrite checks for permission to create sessions for a given + // node. + SessionWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision + + // Snapshot checks for permission to take and restore snapshots. + Snapshot(*EnterpriseAuthorizerContext) EnforcementDecision + + // Embedded Interface for Consul Enterprise specific ACL enforcement + EnterpriseAuthorizer +} diff --git a/acl/authorizer_oss.go b/acl/authorizer_oss.go new file mode 100644 index 0000000000..3731f9eae5 --- /dev/null +++ b/acl/authorizer_oss.go @@ -0,0 +1,9 @@ +// +build !consulent + +package acl + +// EnterpriseAuthorizerContext stub +type EnterpriseAuthorizerContext struct{} + +// EnterpriseAuthorizer stub interface +type EnterpriseAuthorizer interface{} diff --git a/acl/chained_authorizer.go b/acl/chained_authorizer.go new file mode 100644 index 0000000000..3d80f8be10 --- /dev/null +++ b/acl/chained_authorizer.go @@ -0,0 +1,226 @@ +package acl + +// ChainedAuthorizer can combine multiple Authorizers into one. +// Each Authorizer in the chain is asked (in order) for an +// enforcement decision. The first non-Default decision that +// is rendered by an Authorizer in the chain will be used +// as the overall decision of the ChainedAuthorizer +type ChainedAuthorizer struct { + chain []Authorizer +} + +// NewChainedAuthorizer creates a ChainedAuthorizer with the provided +// chain of Authorizers. The slice provided should be in the order of +// most precedent Authorizer at the beginning and least precedent +// Authorizer at the end. +func NewChainedAuthorizer(chain []Authorizer) *ChainedAuthorizer { + return &ChainedAuthorizer{ + chain: chain, + } +} + +func (c *ChainedAuthorizer) executeChain(enforce func(authz Authorizer) EnforcementDecision) EnforcementDecision { + for _, authz := range c.chain { + decision := enforce(authz) + if decision != Default { + return decision + } + } + return Deny +} + +// ACLRead checks for permission to list all the ACLs +func (c *ChainedAuthorizer) ACLRead(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.ACLRead(entCtx) + }) +} + +// ACLWrite checks for permission to manipulate ACLs +func (c *ChainedAuthorizer) ACLWrite(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.ACLWrite(entCtx) + }) +} + +// AgentRead checks for permission to read from agent endpoints for a +// given node. +func (c *ChainedAuthorizer) AgentRead(node string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.AgentRead(node, entCtx) + }) +} + +// AgentWrite checks for permission to make changes via agent endpoints +// for a given node. +func (c *ChainedAuthorizer) AgentWrite(node string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.AgentWrite(node, entCtx) + }) +} + +// EventRead determines if a specific event can be queried. +func (c *ChainedAuthorizer) EventRead(name string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.EventRead(name, entCtx) + }) +} + +// EventWrite determines if a specific event may be fired. +func (c *ChainedAuthorizer) EventWrite(name string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.EventWrite(name, entCtx) + }) +} + +// IntentionDefaultAllow determines the default authorized behavior +// when no intentions match a Connect request. +func (c *ChainedAuthorizer) IntentionDefaultAllow(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IntentionDefaultAllow(entCtx) + }) +} + +// IntentionRead determines if a specific intention can be read. +func (c *ChainedAuthorizer) IntentionRead(prefix string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IntentionRead(prefix, entCtx) + }) +} + +// IntentionWrite determines if a specific intention can be +// created, modified, or deleted. +func (c *ChainedAuthorizer) IntentionWrite(prefix string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.IntentionWrite(prefix, entCtx) + }) +} + +// KeyList checks for permission to list keys under a prefix +func (c *ChainedAuthorizer) KeyList(keyPrefix string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.KeyList(keyPrefix, entCtx) + }) +} + +// KeyRead checks for permission to read a given key +func (c *ChainedAuthorizer) KeyRead(key string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.KeyRead(key, entCtx) + }) +} + +// KeyWrite checks for permission to write a given key +func (c *ChainedAuthorizer) KeyWrite(key string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.KeyWrite(key, entCtx) + }) +} + +// KeyWritePrefix checks for permission to write to an +// entire key prefix. This means there must be no sub-policies +// that deny a write. +func (c *ChainedAuthorizer) KeyWritePrefix(keyPrefix string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.KeyWritePrefix(keyPrefix, entCtx) + }) +} + +// KeyringRead determines if the encryption keyring used in +// the gossip layer can be read. +func (c *ChainedAuthorizer) KeyringRead(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.KeyringRead(entCtx) + }) +} + +// KeyringWrite determines if the keyring can be manipulated +func (c *ChainedAuthorizer) KeyringWrite(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.KeyringWrite(entCtx) + }) +} + +// NodeRead checks for permission to read (discover) a given node. +func (c *ChainedAuthorizer) NodeRead(node string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.NodeRead(node, entCtx) + }) +} + +// NodeWrite checks for permission to create or update (register) a +// given node. +func (c *ChainedAuthorizer) NodeWrite(node string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.NodeWrite(node, entCtx) + }) +} + +// OperatorRead determines if the read-only Consul operator functions +// can be used. +func (c *ChainedAuthorizer) OperatorRead(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.OperatorRead(entCtx) + }) +} + +// OperatorWrite determines if the state-changing Consul operator +// functions can be used. +func (c *ChainedAuthorizer) OperatorWrite(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.OperatorWrite(entCtx) + }) +} + +// PreparedQueryRead determines if a specific prepared query can be read +// to show its contents (this is not used for execution). +func (c *ChainedAuthorizer) PreparedQueryRead(query string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.PreparedQueryRead(query, entCtx) + }) +} + +// PreparedQueryWrite determines if a specific prepared query can be +// created, modified, or deleted. +func (c *ChainedAuthorizer) PreparedQueryWrite(query string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.PreparedQueryWrite(query, entCtx) + }) +} + +// ServiceRead checks for permission to read a given service +func (c *ChainedAuthorizer) ServiceRead(name string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.ServiceRead(name, entCtx) + }) +} + +// ServiceWrite checks for permission to create or update a given +// service +func (c *ChainedAuthorizer) ServiceWrite(name string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.ServiceWrite(name, entCtx) + }) +} + +// SessionRead checks for permission to read sessions for a given node. +func (c *ChainedAuthorizer) SessionRead(node string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.SessionRead(node, entCtx) + }) +} + +// SessionWrite checks for permission to create sessions for a given +// node. +func (c *ChainedAuthorizer) SessionWrite(node string, entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.SessionWrite(node, entCtx) + }) +} + +// Snapshot checks for permission to take and restore snapshots. +func (c *ChainedAuthorizer) Snapshot(entCtx *EnterpriseAuthorizerContext) EnforcementDecision { + return c.executeChain(func(authz Authorizer) EnforcementDecision { + return authz.Snapshot(entCtx) + }) +} diff --git a/acl/chained_authorizer_oss.go b/acl/chained_authorizer_oss.go new file mode 100644 index 0000000000..fd5160ee6d --- /dev/null +++ b/acl/chained_authorizer_oss.go @@ -0,0 +1,3 @@ +// +build !consulent + +package acl diff --git a/acl/chained_authorizer_test.go b/acl/chained_authorizer_test.go new file mode 100644 index 0000000000..f6473fbe94 --- /dev/null +++ b/acl/chained_authorizer_test.go @@ -0,0 +1,247 @@ +package acl + +import ( + "testing" +) + +type testAuthorizer EnforcementDecision + +func (authz testAuthorizer) ACLRead(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) ACLWrite(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) AgentRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) AgentWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) EventRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) EventWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) IntentionDefaultAllow(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) IntentionRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) IntentionWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) KeyList(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) KeyRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) KeyWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) KeyWritePrefix(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) KeyringRead(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) KeyringWrite(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) NodeRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) NodeWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) OperatorRead(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) OperatorWrite(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) PreparedQueryRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) PreparedQueryWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) ServiceRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) ServiceWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) SessionRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) SessionWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} +func (authz testAuthorizer) Snapshot(*EnterpriseAuthorizerContext) EnforcementDecision { + return EnforcementDecision(authz) +} + +func TestChainedAuthorizer(t *testing.T) { + t.Parallel() + + t.Run("No Authorizers", func(t *testing.T) { + t.Parallel() + + authz := NewChainedAuthorizer([]Authorizer{}) + checkDenyACLRead(t, authz, "foo", nil) + checkDenyACLWrite(t, authz, "foo", nil) + checkDenyAgentRead(t, authz, "foo", nil) + checkDenyAgentWrite(t, authz, "foo", nil) + checkDenyEventRead(t, authz, "foo", nil) + checkDenyEventWrite(t, authz, "foo", nil) + checkDenyIntentionDefaultAllow(t, authz, "foo", nil) + checkDenyIntentionRead(t, authz, "foo", nil) + checkDenyIntentionWrite(t, authz, "foo", nil) + checkDenyKeyRead(t, authz, "foo", nil) + checkDenyKeyList(t, authz, "foo", nil) + checkDenyKeyringRead(t, authz, "foo", nil) + checkDenyKeyringWrite(t, authz, "foo", nil) + checkDenyKeyWrite(t, authz, "foo", nil) + checkDenyKeyWritePrefix(t, authz, "foo", nil) + checkDenyNodeRead(t, authz, "foo", nil) + checkDenyNodeWrite(t, authz, "foo", nil) + checkDenyOperatorRead(t, authz, "foo", nil) + checkDenyOperatorWrite(t, authz, "foo", nil) + checkDenyPreparedQueryRead(t, authz, "foo", nil) + checkDenyPreparedQueryWrite(t, authz, "foo", nil) + checkDenyServiceRead(t, authz, "foo", nil) + checkDenyServiceWrite(t, authz, "foo", nil) + checkDenySessionRead(t, authz, "foo", nil) + checkDenySessionWrite(t, authz, "foo", nil) + checkDenySnapshot(t, authz, "foo", nil) + }) + + t.Run("Authorizer Defaults", func(t *testing.T) { + t.Parallel() + + authz := NewChainedAuthorizer([]Authorizer{testAuthorizer(Default)}) + checkDenyACLRead(t, authz, "foo", nil) + checkDenyACLWrite(t, authz, "foo", nil) + checkDenyAgentRead(t, authz, "foo", nil) + checkDenyAgentWrite(t, authz, "foo", nil) + checkDenyEventRead(t, authz, "foo", nil) + checkDenyEventWrite(t, authz, "foo", nil) + checkDenyIntentionDefaultAllow(t, authz, "foo", nil) + checkDenyIntentionRead(t, authz, "foo", nil) + checkDenyIntentionWrite(t, authz, "foo", nil) + checkDenyKeyRead(t, authz, "foo", nil) + checkDenyKeyList(t, authz, "foo", nil) + checkDenyKeyringRead(t, authz, "foo", nil) + checkDenyKeyringWrite(t, authz, "foo", nil) + checkDenyKeyWrite(t, authz, "foo", nil) + checkDenyKeyWritePrefix(t, authz, "foo", nil) + checkDenyNodeRead(t, authz, "foo", nil) + checkDenyNodeWrite(t, authz, "foo", nil) + checkDenyOperatorRead(t, authz, "foo", nil) + checkDenyOperatorWrite(t, authz, "foo", nil) + checkDenyPreparedQueryRead(t, authz, "foo", nil) + checkDenyPreparedQueryWrite(t, authz, "foo", nil) + checkDenyServiceRead(t, authz, "foo", nil) + checkDenyServiceWrite(t, authz, "foo", nil) + checkDenySessionRead(t, authz, "foo", nil) + checkDenySessionWrite(t, authz, "foo", nil) + checkDenySnapshot(t, authz, "foo", nil) + }) + + t.Run("Authorizer No Defaults", func(t *testing.T) { + t.Parallel() + + authz := NewChainedAuthorizer([]Authorizer{testAuthorizer(Allow)}) + checkAllowACLRead(t, authz, "foo", nil) + checkAllowACLWrite(t, authz, "foo", nil) + checkAllowAgentRead(t, authz, "foo", nil) + checkAllowAgentWrite(t, authz, "foo", nil) + checkAllowEventRead(t, authz, "foo", nil) + checkAllowEventWrite(t, authz, "foo", nil) + checkAllowIntentionDefaultAllow(t, authz, "foo", nil) + checkAllowIntentionRead(t, authz, "foo", nil) + checkAllowIntentionWrite(t, authz, "foo", nil) + checkAllowKeyRead(t, authz, "foo", nil) + checkAllowKeyList(t, authz, "foo", nil) + checkAllowKeyringRead(t, authz, "foo", nil) + checkAllowKeyringWrite(t, authz, "foo", nil) + checkAllowKeyWrite(t, authz, "foo", nil) + checkAllowKeyWritePrefix(t, authz, "foo", nil) + checkAllowNodeRead(t, authz, "foo", nil) + checkAllowNodeWrite(t, authz, "foo", nil) + checkAllowOperatorRead(t, authz, "foo", nil) + checkAllowOperatorWrite(t, authz, "foo", nil) + checkAllowPreparedQueryRead(t, authz, "foo", nil) + checkAllowPreparedQueryWrite(t, authz, "foo", nil) + checkAllowServiceRead(t, authz, "foo", nil) + checkAllowServiceWrite(t, authz, "foo", nil) + checkAllowSessionRead(t, authz, "foo", nil) + checkAllowSessionWrite(t, authz, "foo", nil) + checkAllowSnapshot(t, authz, "foo", nil) + }) + + t.Run("First Found", func(t *testing.T) { + t.Parallel() + + authz := NewChainedAuthorizer([]Authorizer{testAuthorizer(Deny), testAuthorizer(Allow)}) + checkDenyACLRead(t, authz, "foo", nil) + checkDenyACLWrite(t, authz, "foo", nil) + checkDenyAgentRead(t, authz, "foo", nil) + checkDenyAgentWrite(t, authz, "foo", nil) + checkDenyEventRead(t, authz, "foo", nil) + checkDenyEventWrite(t, authz, "foo", nil) + checkDenyIntentionDefaultAllow(t, authz, "foo", nil) + checkDenyIntentionRead(t, authz, "foo", nil) + checkDenyIntentionWrite(t, authz, "foo", nil) + checkDenyKeyRead(t, authz, "foo", nil) + checkDenyKeyList(t, authz, "foo", nil) + checkDenyKeyringRead(t, authz, "foo", nil) + checkDenyKeyringWrite(t, authz, "foo", nil) + checkDenyKeyWrite(t, authz, "foo", nil) + checkDenyKeyWritePrefix(t, authz, "foo", nil) + checkDenyNodeRead(t, authz, "foo", nil) + checkDenyNodeWrite(t, authz, "foo", nil) + checkDenyOperatorRead(t, authz, "foo", nil) + checkDenyOperatorWrite(t, authz, "foo", nil) + checkDenyPreparedQueryRead(t, authz, "foo", nil) + checkDenyPreparedQueryWrite(t, authz, "foo", nil) + checkDenyServiceRead(t, authz, "foo", nil) + checkDenyServiceWrite(t, authz, "foo", nil) + checkDenySessionRead(t, authz, "foo", nil) + checkDenySessionWrite(t, authz, "foo", nil) + checkDenySnapshot(t, authz, "foo", nil) + + authz = NewChainedAuthorizer([]Authorizer{testAuthorizer(Default), testAuthorizer(Allow)}) + checkAllowACLRead(t, authz, "foo", nil) + checkAllowACLWrite(t, authz, "foo", nil) + checkAllowAgentRead(t, authz, "foo", nil) + checkAllowAgentWrite(t, authz, "foo", nil) + checkAllowEventRead(t, authz, "foo", nil) + checkAllowEventWrite(t, authz, "foo", nil) + checkAllowIntentionDefaultAllow(t, authz, "foo", nil) + checkAllowIntentionRead(t, authz, "foo", nil) + checkAllowIntentionWrite(t, authz, "foo", nil) + checkAllowKeyRead(t, authz, "foo", nil) + checkAllowKeyList(t, authz, "foo", nil) + checkAllowKeyringRead(t, authz, "foo", nil) + checkAllowKeyringWrite(t, authz, "foo", nil) + checkAllowKeyWrite(t, authz, "foo", nil) + checkAllowKeyWritePrefix(t, authz, "foo", nil) + checkAllowNodeRead(t, authz, "foo", nil) + checkAllowNodeWrite(t, authz, "foo", nil) + checkAllowOperatorRead(t, authz, "foo", nil) + checkAllowOperatorWrite(t, authz, "foo", nil) + checkAllowPreparedQueryRead(t, authz, "foo", nil) + checkAllowPreparedQueryWrite(t, authz, "foo", nil) + checkAllowServiceRead(t, authz, "foo", nil) + checkAllowServiceWrite(t, authz, "foo", nil) + checkAllowSessionRead(t, authz, "foo", nil) + checkAllowSessionWrite(t, authz, "foo", nil) + checkAllowSnapshot(t, authz, "foo", nil) + }) + +} diff --git a/acl/errors.go b/acl/errors.go deleted file mode 100644 index 65c5cc7ab6..0000000000 --- a/acl/errors.go +++ /dev/null @@ -1,72 +0,0 @@ -package acl - -import ( - "errors" - "strings" -) - -// These error constants define the standard ACL error types. The values -// must not be changed since the error values are sent via RPC calls -// from older clients and may not have the correct type. -const ( - errNotFound = "ACL not found" - errRootDenied = "Cannot resolve root ACL" - errDisabled = "ACL support disabled" - errPermissionDenied = "Permission denied" - errInvalidParent = "Invalid Parent" -) - -var ( - // ErrNotFound indicates there is no matching ACL. - ErrNotFound = errors.New(errNotFound) - - // ErrRootDenied is returned when attempting to resolve a root ACL. - ErrRootDenied = errors.New(errRootDenied) - - // ErrDisabled is returned when ACL changes are not permitted since - // they are disabled. - ErrDisabled = errors.New(errDisabled) - - // ErrPermissionDenied is returned when an ACL based rejection - // happens. - ErrPermissionDenied = PermissionDeniedError{} - - // ErrInvalidParent is returned when a remotely resolve ACL - // token claims to have a non-root parent - ErrInvalidParent = errors.New(errInvalidParent) -) - -// IsErrNotFound checks if the given error message is comparable to -// ErrNotFound. -func IsErrNotFound(err error) bool { - return err != nil && strings.Contains(err.Error(), errNotFound) -} - -// IsErrRootDenied checks if the given error message is comparable to -// ErrRootDenied. -func IsErrRootDenied(err error) bool { - return err != nil && strings.Contains(err.Error(), errRootDenied) -} - -// IsErrDisabled checks if the given error message is comparable to -// ErrDisabled. -func IsErrDisabled(err error) bool { - return err != nil && strings.Contains(err.Error(), errDisabled) -} - -// IsErrPermissionDenied checks if the given error message is comparable -// to ErrPermissionDenied. -func IsErrPermissionDenied(err error) bool { - return err != nil && strings.Contains(err.Error(), errPermissionDenied) -} - -type PermissionDeniedError struct { - Cause string -} - -func (e PermissionDeniedError) Error() string { - if e.Cause != "" { - return errPermissionDenied + ": " + e.Cause - } - return errPermissionDenied -} diff --git a/acl/policy.go b/acl/policy.go index 180676f1e2..3016869bdb 100644 --- a/acl/policy.go +++ b/acl/policy.go @@ -2,16 +2,14 @@ package acl import ( "bytes" - "encoding/binary" "fmt" "strconv" + "strings" - "github.com/hashicorp/consul/sentinel" "github.com/hashicorp/hcl" "github.com/hashicorp/hcl/hcl/ast" hclprinter "github.com/hashicorp/hcl/hcl/printer" "github.com/hashicorp/hcl/hcl/token" - "golang.org/x/crypto/blake2b" ) type SyntaxVersion int @@ -24,306 +22,303 @@ const ( const ( PolicyDeny = "deny" PolicyRead = "read" - PolicyWrite = "write" PolicyList = "list" + PolicyWrite = "write" ) -// Policy is used to represent the policy specified by -// an ACL configuration. +type AccessLevel int + +const ( + AccessUnknown AccessLevel = iota + AccessDeny + AccessRead + AccessList + AccessWrite +) + +func (l AccessLevel) String() string { + switch l { + case AccessDeny: + return PolicyDeny + case AccessRead: + return PolicyRead + case AccessList: + return PolicyList + case AccessWrite: + return PolicyWrite + default: + return "unknown" + } +} + +func AccessLevelFromString(level string) (AccessLevel, error) { + switch strings.ToLower(level) { + case PolicyDeny: + return AccessDeny, nil + case PolicyRead: + return AccessRead, nil + case PolicyList: + return AccessList, nil + case PolicyWrite: + return AccessWrite, nil + default: + return AccessUnknown, fmt.Errorf("%q is not a valid access level", level) + } +} + +type PolicyRules struct { + ACL string `hcl:"acl,expand"` + Agents []*AgentRule `hcl:"agent,expand"` + AgentPrefixes []*AgentRule `hcl:"agent_prefix,expand"` + Keys []*KeyRule `hcl:"key,expand"` + KeyPrefixes []*KeyRule `hcl:"key_prefix,expand"` + Nodes []*NodeRule `hcl:"node,expand"` + NodePrefixes []*NodeRule `hcl:"node_prefix,expand"` + Services []*ServiceRule `hcl:"service,expand"` + ServicePrefixes []*ServiceRule `hcl:"service_prefix,expand"` + Sessions []*SessionRule `hcl:"session,expand"` + SessionPrefixes []*SessionRule `hcl:"session_prefix,expand"` + Events []*EventRule `hcl:"event,expand"` + EventPrefixes []*EventRule `hcl:"event_prefix,expand"` + PreparedQueries []*PreparedQueryRule `hcl:"query,expand"` + PreparedQueryPrefixes []*PreparedQueryRule `hcl:"query_prefix,expand"` + Keyring string `hcl:"keyring"` + Operator string `hcl:"operator"` +} + +// Policy is used to represent the policy specified by an ACL configuration. type Policy struct { - ID string `hcl:"id"` - Revision uint64 `hcl:"revision"` - ACL string `hcl:"acl,expand"` - Agents []*AgentPolicy `hcl:"agent,expand"` - AgentPrefixes []*AgentPolicy `hcl:"agent_prefix,expand"` - Keys []*KeyPolicy `hcl:"key,expand"` - KeyPrefixes []*KeyPolicy `hcl:"key_prefix,expand"` - Nodes []*NodePolicy `hcl:"node,expand"` - NodePrefixes []*NodePolicy `hcl:"node_prefix,expand"` - Services []*ServicePolicy `hcl:"service,expand"` - ServicePrefixes []*ServicePolicy `hcl:"service_prefix,expand"` - Sessions []*SessionPolicy `hcl:"session,expand"` - SessionPrefixes []*SessionPolicy `hcl:"session_prefix,expand"` - Events []*EventPolicy `hcl:"event,expand"` - EventPrefixes []*EventPolicy `hcl:"event_prefix,expand"` - PreparedQueries []*PreparedQueryPolicy `hcl:"query,expand"` - PreparedQueryPrefixes []*PreparedQueryPolicy `hcl:"query_prefix,expand"` - Keyring string `hcl:"keyring"` - Operator string `hcl:"operator"` + ID string `hcl:"id"` + Revision uint64 `hcl:"revision"` + PolicyRules `hcl:",squash"` + EnterprisePolicyRules `hcl:",squash"` } -// Sentinel defines a snippet of Sentinel code that can be attached to a policy. -type Sentinel struct { - Code string - EnforcementLevel string -} - -// AgentPolicy represents a policy for working with agent endpoints on nodes +// AgentRule represents a rule for working with agent endpoints on nodes // with specific name prefixes. -type AgentPolicy struct { +type AgentRule struct { Node string `hcl:",key"` Policy string } -func (a *AgentPolicy) GoString() string { - return fmt.Sprintf("%#v", *a) +// KeyRule represents a rule for a key +type KeyRule struct { + Prefix string `hcl:",key"` + Policy string + + EnterpriseRule `hcl:",squash"` } -// KeyPolicy represents a policy for a key -type KeyPolicy struct { - Prefix string `hcl:",key"` - Policy string - Sentinel Sentinel +// NodeRule represents a rule for a node +type NodeRule struct { + Name string `hcl:",key"` + Policy string + + EnterpriseRule `hcl:",squash"` } -func (k *KeyPolicy) GoString() string { - return fmt.Sprintf("%#v", *k) -} - -// NodePolicy represents a policy for a node -type NodePolicy struct { - Name string `hcl:",key"` - Policy string - Sentinel Sentinel -} - -func (n *NodePolicy) GoString() string { - return fmt.Sprintf("%#v", *n) -} - -// ServicePolicy represents a policy for a service -type ServicePolicy struct { - Name string `hcl:",key"` - Policy string - Sentinel Sentinel +// ServiceRule represents a policy for a service +type ServiceRule struct { + Name string `hcl:",key"` + Policy string // Intentions is the policy for intentions where this service is the // destination. This may be empty, in which case the Policy determines // the intentions policy. Intentions string + + EnterpriseRule `hcl:",squash"` } -func (s *ServicePolicy) GoString() string { - return fmt.Sprintf("%#v", *s) -} - -// SessionPolicy represents a policy for making sessions tied to specific node +// SessionRule represents a rule for making sessions tied to specific node // name prefixes. -type SessionPolicy struct { +type SessionRule struct { Node string `hcl:",key"` Policy string } -func (s *SessionPolicy) GoString() string { - return fmt.Sprintf("%#v", *s) -} - -// EventPolicy represents a user event policy. -type EventPolicy struct { +// EventRule represents a user event rule. +type EventRule struct { Event string `hcl:",key"` Policy string } -func (e *EventPolicy) GoString() string { - return fmt.Sprintf("%#v", *e) -} - -// PreparedQueryPolicy represents a prepared query policy. -type PreparedQueryPolicy struct { +// PreparedQueryRule represents a prepared query rule. +type PreparedQueryRule struct { Prefix string `hcl:",key"` Policy string } -func (p *PreparedQueryPolicy) GoString() string { - return fmt.Sprintf("%#v", *p) -} - // isPolicyValid makes sure the given string matches one of the valid policies. -func isPolicyValid(policy string) bool { - switch policy { - case PolicyDeny: - return true - case PolicyRead: - return true - case PolicyWrite: - return true - default: +func isPolicyValid(policy string, allowList bool) bool { + access, err := AccessLevelFromString(policy) + if err != nil { return false } + if access == AccessList && !allowList { + return false + } + return true } -// isSentinelValid makes sure the given sentinel block is valid, and will skip -// out if the evaluator is nil. -func isSentinelValid(sentinel sentinel.Evaluator, basicPolicy string, sp Sentinel) error { - // Sentinel not enabled at all, or for this policy. - if sentinel == nil { - return nil - } - if sp.Code == "" { - return nil +func (pr *PolicyRules) Validate(conf *EnterpriseACLConfig) error { + // Validate the acl policy - this one is allowed to be empty + if pr.ACL != "" && !isPolicyValid(pr.ACL, false) { + return fmt.Errorf("Invalid acl policy: %#v", pr.ACL) } - // We only allow sentinel code on write policies at this time. - if basicPolicy != PolicyWrite { - return fmt.Errorf("code is only allowed for write policies") + // Validate the agent policy + for _, ap := range pr.Agents { + if !isPolicyValid(ap.Policy, false) { + return fmt.Errorf("Invalid agent policy: %#v", ap) + } + } + for _, ap := range pr.AgentPrefixes { + if !isPolicyValid(ap.Policy, false) { + return fmt.Errorf("Invalid agent_prefix policy: %#v", ap) + } } - // Validate the sentinel parts. - switch sp.EnforcementLevel { - case "", "soft-mandatory", "hard-mandatory": - // OK - default: - return fmt.Errorf("unsupported enforcement level %q", sp.EnforcementLevel) + // Validate the key policy + for _, kp := range pr.Keys { + if !isPolicyValid(kp.Policy, true) { + return fmt.Errorf("Invalid key policy: %#v", kp) + } + if err := kp.EnterpriseRule.Validate(kp.Policy, conf); err != nil { + return fmt.Errorf("Invalid key enterprise policy: %#v, got error: %v", kp, err) + } } - return sentinel.Compile(sp.Code) + for _, kp := range pr.KeyPrefixes { + if !isPolicyValid(kp.Policy, true) { + return fmt.Errorf("Invalid key_prefix policy: %#v", kp) + } + if err := kp.EnterpriseRule.Validate(kp.Policy, conf); err != nil { + return fmt.Errorf("Invalid key_prefix enterprise policy: %#v, got error: %v", kp, err) + } + } + + // Validate the node policies + for _, np := range pr.Nodes { + if !isPolicyValid(np.Policy, false) { + return fmt.Errorf("Invalid node policy: %#v", np) + } + if err := np.EnterpriseRule.Validate(np.Policy, conf); err != nil { + return fmt.Errorf("Invalid node enterprise policy: %#v, got error: %v", np, err) + } + } + for _, np := range pr.NodePrefixes { + if !isPolicyValid(np.Policy, false) { + return fmt.Errorf("Invalid node_prefix policy: %#v", np) + } + if err := np.EnterpriseRule.Validate(np.Policy, conf); err != nil { + return fmt.Errorf("Invalid node_prefix enterprise policy: %#v, got error: %v", np, err) + } + } + + // Validate the service policies + for _, sp := range pr.Services { + if !isPolicyValid(sp.Policy, false) { + return fmt.Errorf("Invalid service policy: %#v", sp) + } + if sp.Intentions != "" && !isPolicyValid(sp.Intentions, false) { + return fmt.Errorf("Invalid service intentions policy: %#v", sp) + } + if err := sp.EnterpriseRule.Validate(sp.Policy, conf); err != nil { + return fmt.Errorf("Invalid service enterprise policy: %#v, got error: %v", sp, err) + } + } + for _, sp := range pr.ServicePrefixes { + if !isPolicyValid(sp.Policy, false) { + return fmt.Errorf("Invalid service_prefix policy: %#v", sp) + } + if sp.Intentions != "" && !isPolicyValid(sp.Intentions, false) { + return fmt.Errorf("Invalid service_prefix intentions policy: %#v", sp) + } + if err := sp.EnterpriseRule.Validate(sp.Policy, conf); err != nil { + return fmt.Errorf("Invalid service_prefix enterprise policy: %#v, got error: %v", sp, err) + } + } + + // Validate the session policies + for _, sp := range pr.Sessions { + if !isPolicyValid(sp.Policy, false) { + return fmt.Errorf("Invalid session policy: %#v", sp) + } + } + for _, sp := range pr.SessionPrefixes { + if !isPolicyValid(sp.Policy, false) { + return fmt.Errorf("Invalid session_prefix policy: %#v", sp) + } + } + + // Validate the user event policies + for _, ep := range pr.Events { + if !isPolicyValid(ep.Policy, false) { + return fmt.Errorf("Invalid event policy: %#v", ep) + } + } + for _, ep := range pr.EventPrefixes { + if !isPolicyValid(ep.Policy, false) { + return fmt.Errorf("Invalid event_prefix policy: %#v", ep) + } + } + + // Validate the prepared query policies + for _, pq := range pr.PreparedQueries { + if !isPolicyValid(pq.Policy, false) { + return fmt.Errorf("Invalid query policy: %#v", pq) + } + } + for _, pq := range pr.PreparedQueryPrefixes { + if !isPolicyValid(pq.Policy, false) { + return fmt.Errorf("Invalid query_prefix policy: %#v", pq) + } + } + + // Validate the keyring policy - this one is allowed to be empty + if pr.Keyring != "" && !isPolicyValid(pr.Keyring, false) { + return fmt.Errorf("Invalid keyring policy: %#v", pr.Keyring) + } + + // Validate the operator policy - this one is allowed to be empty + if pr.Operator != "" && !isPolicyValid(pr.Operator, false) { + return fmt.Errorf("Invalid operator policy: %#v", pr.Operator) + } + + return nil } -func parseCurrent(rules string, sentinel sentinel.Evaluator) (*Policy, error) { +func parseCurrent(rules string, conf *EnterpriseACLConfig) (*Policy, error) { p := &Policy{} if err := hcl.Decode(p, rules); err != nil { return nil, fmt.Errorf("Failed to parse ACL rules: %v", err) } - // Validate the acl policy - if p.ACL != "" && !isPolicyValid(p.ACL) { - return nil, fmt.Errorf("Invalid acl policy: %#v", p.ACL) + if err := p.PolicyRules.Validate(conf); err != nil { + return nil, err } - // Validate the agent policy - for _, ap := range p.Agents { - if !isPolicyValid(ap.Policy) { - return nil, fmt.Errorf("Invalid agent policy: %#v", ap) - } - } - for _, ap := range p.AgentPrefixes { - if !isPolicyValid(ap.Policy) { - return nil, fmt.Errorf("Invalid agent_prefix policy: %#v", ap) - } - } - - // Validate the key policy - for _, kp := range p.Keys { - if kp.Policy != PolicyList && !isPolicyValid(kp.Policy) { - return nil, fmt.Errorf("Invalid key policy: %#v", kp) - } - if err := isSentinelValid(sentinel, kp.Policy, kp.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid key Sentinel policy: %#v, got error:%v", kp, err) - } - } - for _, kp := range p.KeyPrefixes { - if kp.Policy != PolicyList && !isPolicyValid(kp.Policy) { - return nil, fmt.Errorf("Invalid key_prefix policy: %#v", kp) - } - if err := isSentinelValid(sentinel, kp.Policy, kp.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid key_prefix Sentinel policy: %#v, got error:%v", kp, err) - } - } - - // Validate the node policies - for _, np := range p.Nodes { - if !isPolicyValid(np.Policy) { - return nil, fmt.Errorf("Invalid node policy: %#v", np) - } - if err := isSentinelValid(sentinel, np.Policy, np.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid node Sentinel policy: %#v, got error:%v", np, err) - } - } - for _, np := range p.NodePrefixes { - if !isPolicyValid(np.Policy) { - return nil, fmt.Errorf("Invalid node_prefix policy: %#v", np) - } - if err := isSentinelValid(sentinel, np.Policy, np.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid node_prefix Sentinel policy: %#v, got error:%v", np, err) - } - } - - // Validate the service policies - for _, sp := range p.Services { - if !isPolicyValid(sp.Policy) { - return nil, fmt.Errorf("Invalid service policy: %#v", sp) - } - if sp.Intentions != "" && !isPolicyValid(sp.Intentions) { - return nil, fmt.Errorf("Invalid service intentions policy: %#v", sp) - } - if err := isSentinelValid(sentinel, sp.Policy, sp.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid service Sentinel policy: %#v, got error:%v", sp, err) - } - } - for _, sp := range p.ServicePrefixes { - if !isPolicyValid(sp.Policy) { - return nil, fmt.Errorf("Invalid service_prefix policy: %#v", sp) - } - if sp.Intentions != "" && !isPolicyValid(sp.Intentions) { - return nil, fmt.Errorf("Invalid service_prefix intentions policy: %#v", sp) - } - if err := isSentinelValid(sentinel, sp.Policy, sp.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid service_prefix Sentinel policy: %#v, got error:%v", sp, err) - } - } - - // Validate the session policies - for _, sp := range p.Sessions { - if !isPolicyValid(sp.Policy) { - return nil, fmt.Errorf("Invalid session policy: %#v", sp) - } - } - for _, sp := range p.SessionPrefixes { - if !isPolicyValid(sp.Policy) { - return nil, fmt.Errorf("Invalid session_prefix policy: %#v", sp) - } - } - - // Validate the user event policies - for _, ep := range p.Events { - if !isPolicyValid(ep.Policy) { - return nil, fmt.Errorf("Invalid event policy: %#v", ep) - } - } - for _, ep := range p.EventPrefixes { - if !isPolicyValid(ep.Policy) { - return nil, fmt.Errorf("Invalid event_prefix policy: %#v", ep) - } - } - - // Validate the prepared query policies - for _, pq := range p.PreparedQueries { - if !isPolicyValid(pq.Policy) { - return nil, fmt.Errorf("Invalid query policy: %#v", pq) - } - } - for _, pq := range p.PreparedQueryPrefixes { - if !isPolicyValid(pq.Policy) { - return nil, fmt.Errorf("Invalid query_prefix policy: %#v", pq) - } - } - - // Validate the keyring policy - this one is allowed to be empty - if p.Keyring != "" && !isPolicyValid(p.Keyring) { - return nil, fmt.Errorf("Invalid keyring policy: %#v", p.Keyring) - } - - // Validate the operator policy - this one is allowed to be empty - if p.Operator != "" && !isPolicyValid(p.Operator) { - return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator) + if err := p.EnterprisePolicyRules.Validate(conf); err != nil { + return nil, fmt.Errorf("Invalidate enterprise rules: %v", err) } return p, nil } -func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { +func parseLegacy(rules string, conf *EnterpriseACLConfig) (*Policy, error) { p := &Policy{} type LegacyPolicy struct { - Agents []*AgentPolicy `hcl:"agent,expand"` - Keys []*KeyPolicy `hcl:"key,expand"` - Nodes []*NodePolicy `hcl:"node,expand"` - Services []*ServicePolicy `hcl:"service,expand"` - Sessions []*SessionPolicy `hcl:"session,expand"` - Events []*EventPolicy `hcl:"event,expand"` - PreparedQueries []*PreparedQueryPolicy `hcl:"query,expand"` - Keyring string `hcl:"keyring"` - Operator string `hcl:"operator"` + Agents []*AgentRule `hcl:"agent,expand"` + Keys []*KeyRule `hcl:"key,expand"` + Nodes []*NodeRule `hcl:"node,expand"` + Services []*ServiceRule `hcl:"service,expand"` + Sessions []*SessionRule `hcl:"session,expand"` + Events []*EventRule `hcl:"event,expand"` + PreparedQueries []*PreparedQueryRule `hcl:"query,expand"` + Keyring string `hcl:"keyring"` + Operator string `hcl:"operator"` } lp := &LegacyPolicy{} @@ -334,7 +329,7 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the agent policy for _, ap := range lp.Agents { - if !isPolicyValid(ap.Policy) { + if !isPolicyValid(ap.Policy, false) { return nil, fmt.Errorf("Invalid agent policy: %#v", ap) } @@ -343,11 +338,12 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the key policy for _, kp := range lp.Keys { - if kp.Policy != PolicyList && !isPolicyValid(kp.Policy) { + if !isPolicyValid(kp.Policy, true) { return nil, fmt.Errorf("Invalid key policy: %#v", kp) } - if err := isSentinelValid(sentinel, kp.Policy, kp.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid key Sentinel policy: %#v, got error:%v", kp, err) + + if err := kp.EnterpriseRule.Validate(kp.Policy, conf); err != nil { + return nil, fmt.Errorf("Invalid key enterprise policy: %#v, got error: %v", kp, err) } p.KeyPrefixes = append(p.KeyPrefixes, kp) @@ -355,11 +351,11 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the node policies for _, np := range lp.Nodes { - if !isPolicyValid(np.Policy) { + if !isPolicyValid(np.Policy, false) { return nil, fmt.Errorf("Invalid node policy: %#v", np) } - if err := isSentinelValid(sentinel, np.Policy, np.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid node Sentinel policy: %#v, got error:%v", np, err) + if err := np.EnterpriseRule.Validate(np.Policy, conf); err != nil { + return nil, fmt.Errorf("Invalid node enterprise policy: %#v, got error: %v", np, err) } p.NodePrefixes = append(p.NodePrefixes, np) @@ -367,14 +363,14 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the service policies for _, sp := range lp.Services { - if !isPolicyValid(sp.Policy) { + if !isPolicyValid(sp.Policy, false) { return nil, fmt.Errorf("Invalid service policy: %#v", sp) } - if sp.Intentions != "" && !isPolicyValid(sp.Intentions) { + if sp.Intentions != "" && !isPolicyValid(sp.Intentions, false) { return nil, fmt.Errorf("Invalid service intentions policy: %#v", sp) } - if err := isSentinelValid(sentinel, sp.Policy, sp.Sentinel); err != nil { - return nil, fmt.Errorf("Invalid service Sentinel policy: %#v, got error:%v", sp, err) + if err := sp.EnterpriseRule.Validate(sp.Policy, conf); err != nil { + return nil, fmt.Errorf("Invalid service enterprise policy: %#v, got error: %v", sp, err) } p.ServicePrefixes = append(p.ServicePrefixes, sp) @@ -382,7 +378,7 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the session policies for _, sp := range lp.Sessions { - if !isPolicyValid(sp.Policy) { + if !isPolicyValid(sp.Policy, false) { return nil, fmt.Errorf("Invalid session policy: %#v", sp) } @@ -391,7 +387,7 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the user event policies for _, ep := range lp.Events { - if !isPolicyValid(ep.Policy) { + if !isPolicyValid(ep.Policy, false) { return nil, fmt.Errorf("Invalid event policy: %#v", ep) } @@ -400,7 +396,7 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // Validate the prepared query policies for _, pq := range lp.PreparedQueries { - if !isPolicyValid(pq.Policy) { + if !isPolicyValid(pq.Policy, false) { return nil, fmt.Errorf("Invalid query policy: %#v", pq) } @@ -408,14 +404,14 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { } // Validate the keyring policy - this one is allowed to be empty - if lp.Keyring != "" && !isPolicyValid(lp.Keyring) { + if lp.Keyring != "" && !isPolicyValid(lp.Keyring, false) { return nil, fmt.Errorf("Invalid keyring policy: %#v", lp.Keyring) } else { p.Keyring = lp.Keyring } // Validate the operator policy - this one is allowed to be empty - if lp.Operator != "" && !isPolicyValid(lp.Operator) { + if lp.Operator != "" && !isPolicyValid(lp.Operator, false) { return nil, fmt.Errorf("Invalid operator policy: %#v", lp.Operator) } else { p.Operator = lp.Operator @@ -427,7 +423,7 @@ func parseLegacy(rules string, sentinel sentinel.Evaluator) (*Policy, error) { // NewPolicyFromSource is used to parse the specified ACL rules into an // intermediary set of policies, before being compiled into // the ACL -func NewPolicyFromSource(id string, revision uint64, rules string, syntax SyntaxVersion, sentinel sentinel.Evaluator) (*Policy, error) { +func NewPolicyFromSource(id string, revision uint64, rules string, syntax SyntaxVersion, conf *EnterpriseACLConfig) (*Policy, error) { if rules == "" { // Hot path for empty source return &Policy{ID: id, Revision: revision}, nil @@ -437,9 +433,9 @@ func NewPolicyFromSource(id string, revision uint64, rules string, syntax Syntax var err error switch syntax { case SyntaxLegacy: - policy, err = parseLegacy(rules, sentinel) + policy, err = parseLegacy(rules, conf) case SyntaxCurrent: - policy, err = parseCurrent(rules, sentinel) + policy, err = parseCurrent(rules, conf) default: return nil, fmt.Errorf("Invalid rules version: %d", syntax) } @@ -455,9 +451,11 @@ func (policy *Policy) ConvertToLegacy() *Policy { converted := &Policy{ ID: policy.ID, Revision: policy.Revision, - ACL: policy.ACL, - Keyring: policy.Keyring, - Operator: policy.Operator, + PolicyRules: PolicyRules{ + ACL: policy.ACL, + Keyring: policy.Keyring, + Operator: policy.Operator, + }, } converted.Agents = append(converted.Agents, policy.Agents...) @@ -479,17 +477,19 @@ func (policy *Policy) ConvertToLegacy() *Policy { func (policy *Policy) ConvertFromLegacy() *Policy { return &Policy{ - ID: policy.ID, - Revision: policy.Revision, - AgentPrefixes: policy.Agents, - KeyPrefixes: policy.Keys, - NodePrefixes: policy.Nodes, - ServicePrefixes: policy.Services, - SessionPrefixes: policy.Sessions, - EventPrefixes: policy.Events, - PreparedQueryPrefixes: policy.PreparedQueries, - Keyring: policy.Keyring, - Operator: policy.Operator, + ID: policy.ID, + Revision: policy.Revision, + PolicyRules: PolicyRules{ + AgentPrefixes: policy.Agents, + KeyPrefixes: policy.Keys, + NodePrefixes: policy.Nodes, + ServicePrefixes: policy.Services, + SessionPrefixes: policy.Sessions, + EventPrefixes: policy.Events, + PreparedQueryPrefixes: policy.PreparedQueries, + Keyring: policy.Keyring, + Operator: policy.Operator, + }, } } @@ -523,295 +523,6 @@ func takesPrecedenceOver(a, b string) bool { return false } -func multiPolicyID(policies []*Policy) []byte { - cacheKeyHash, err := blake2b.New256(nil) - if err != nil { - panic(err) - } - for _, policy := range policies { - cacheKeyHash.Write([]byte(policy.ID)) - binary.Write(cacheKeyHash, binary.BigEndian, policy.Revision) - } - return cacheKeyHash.Sum(nil) -} - -// MergePolicies merges multiple ACL policies into one policy -// This function will not set either the ID or the Scope fields -// of the resulting policy as its up to the caller to determine -// what the merged value is. -func MergePolicies(policies []*Policy) *Policy { - // maps are used here so that we can lookup each policy by - // the segment that the rule applies to during the policy - // merge. Otherwise we could do a linear search through a slice - // and replace it inline - aclPolicy := "" - agentPolicies := make(map[string]*AgentPolicy) - agentPrefixPolicies := make(map[string]*AgentPolicy) - eventPolicies := make(map[string]*EventPolicy) - eventPrefixPolicies := make(map[string]*EventPolicy) - keyringPolicy := "" - keyPolicies := make(map[string]*KeyPolicy) - keyPrefixPolicies := make(map[string]*KeyPolicy) - nodePolicies := make(map[string]*NodePolicy) - nodePrefixPolicies := make(map[string]*NodePolicy) - operatorPolicy := "" - preparedQueryPolicies := make(map[string]*PreparedQueryPolicy) - preparedQueryPrefixPolicies := make(map[string]*PreparedQueryPolicy) - servicePolicies := make(map[string]*ServicePolicy) - servicePrefixPolicies := make(map[string]*ServicePolicy) - sessionPolicies := make(map[string]*SessionPolicy) - sessionPrefixPolicies := make(map[string]*SessionPolicy) - - // Parse all the individual rule sets - for _, policy := range policies { - if takesPrecedenceOver(policy.ACL, aclPolicy) { - aclPolicy = policy.ACL - } - - for _, ap := range policy.Agents { - update := true - if permission, found := agentPolicies[ap.Node]; found { - update = takesPrecedenceOver(ap.Policy, permission.Policy) - } - - if update { - - agentPolicies[ap.Node] = ap - } - } - - for _, ap := range policy.AgentPrefixes { - update := true - if permission, found := agentPrefixPolicies[ap.Node]; found { - update = takesPrecedenceOver(ap.Policy, permission.Policy) - } - - if update { - agentPrefixPolicies[ap.Node] = ap - } - } - - for _, ep := range policy.Events { - update := true - if permission, found := eventPolicies[ep.Event]; found { - update = takesPrecedenceOver(ep.Policy, permission.Policy) - } - - if update { - eventPolicies[ep.Event] = ep - } - } - - for _, ep := range policy.EventPrefixes { - update := true - if permission, found := eventPrefixPolicies[ep.Event]; found { - update = takesPrecedenceOver(ep.Policy, permission.Policy) - } - - if update { - eventPrefixPolicies[ep.Event] = ep - } - } - - if takesPrecedenceOver(policy.Keyring, keyringPolicy) { - keyringPolicy = policy.Keyring - } - - for _, kp := range policy.Keys { - update := true - if permission, found := keyPolicies[kp.Prefix]; found { - update = takesPrecedenceOver(kp.Policy, permission.Policy) - } - - if update { - keyPolicies[kp.Prefix] = kp - } - } - - for _, kp := range policy.KeyPrefixes { - update := true - if permission, found := keyPrefixPolicies[kp.Prefix]; found { - update = takesPrecedenceOver(kp.Policy, permission.Policy) - } - - if update { - keyPrefixPolicies[kp.Prefix] = kp - } - } - - for _, np := range policy.Nodes { - update := true - if permission, found := nodePolicies[np.Name]; found { - update = takesPrecedenceOver(np.Policy, permission.Policy) - } - - if update { - nodePolicies[np.Name] = np - } - } - - for _, np := range policy.NodePrefixes { - update := true - if permission, found := nodePrefixPolicies[np.Name]; found { - update = takesPrecedenceOver(np.Policy, permission.Policy) - } - - if update { - nodePrefixPolicies[np.Name] = np - } - } - - if takesPrecedenceOver(policy.Operator, operatorPolicy) { - operatorPolicy = policy.Operator - } - - for _, qp := range policy.PreparedQueries { - update := true - if permission, found := preparedQueryPolicies[qp.Prefix]; found { - update = takesPrecedenceOver(qp.Policy, permission.Policy) - } - - if update { - preparedQueryPolicies[qp.Prefix] = qp - } - } - - for _, qp := range policy.PreparedQueryPrefixes { - update := true - if permission, found := preparedQueryPrefixPolicies[qp.Prefix]; found { - update = takesPrecedenceOver(qp.Policy, permission.Policy) - } - - if update { - preparedQueryPrefixPolicies[qp.Prefix] = qp - } - } - - for _, sp := range policy.Services { - existing, found := servicePolicies[sp.Name] - - if !found { - servicePolicies[sp.Name] = sp - continue - } - - if takesPrecedenceOver(sp.Policy, existing.Policy) { - existing.Policy = sp.Policy - existing.Sentinel = sp.Sentinel - } - - if takesPrecedenceOver(sp.Intentions, existing.Intentions) { - existing.Intentions = sp.Intentions - } - } - - for _, sp := range policy.ServicePrefixes { - existing, found := servicePrefixPolicies[sp.Name] - - if !found { - servicePrefixPolicies[sp.Name] = sp - continue - } - - if takesPrecedenceOver(sp.Policy, existing.Policy) { - existing.Policy = sp.Policy - existing.Sentinel = sp.Sentinel - } - - if takesPrecedenceOver(sp.Intentions, existing.Intentions) { - existing.Intentions = sp.Intentions - } - } - - for _, sp := range policy.Sessions { - update := true - if permission, found := sessionPolicies[sp.Node]; found { - update = takesPrecedenceOver(sp.Policy, permission.Policy) - } - - if update { - sessionPolicies[sp.Node] = sp - } - } - - for _, sp := range policy.SessionPrefixes { - update := true - if permission, found := sessionPrefixPolicies[sp.Node]; found { - update = takesPrecedenceOver(sp.Policy, permission.Policy) - } - - if update { - sessionPrefixPolicies[sp.Node] = sp - } - } - } - - merged := &Policy{ACL: aclPolicy, Keyring: keyringPolicy, Operator: operatorPolicy} - - // All the for loop appends are ugly but Go doesn't have a way to get - // a slice of all values within a map so this is necessary - - for _, policy := range agentPolicies { - merged.Agents = append(merged.Agents, policy) - } - - for _, policy := range agentPrefixPolicies { - merged.AgentPrefixes = append(merged.AgentPrefixes, policy) - } - - for _, policy := range eventPolicies { - merged.Events = append(merged.Events, policy) - } - - for _, policy := range eventPrefixPolicies { - merged.EventPrefixes = append(merged.EventPrefixes, policy) - } - - for _, policy := range keyPolicies { - merged.Keys = append(merged.Keys, policy) - } - - for _, policy := range keyPrefixPolicies { - merged.KeyPrefixes = append(merged.KeyPrefixes, policy) - } - - for _, policy := range nodePolicies { - merged.Nodes = append(merged.Nodes, policy) - } - - for _, policy := range nodePrefixPolicies { - merged.NodePrefixes = append(merged.NodePrefixes, policy) - } - - for _, policy := range preparedQueryPolicies { - merged.PreparedQueries = append(merged.PreparedQueries, policy) - } - - for _, policy := range preparedQueryPrefixPolicies { - merged.PreparedQueryPrefixes = append(merged.PreparedQueryPrefixes, policy) - } - - for _, policy := range servicePolicies { - merged.Services = append(merged.Services, policy) - } - - for _, policy := range servicePrefixPolicies { - merged.ServicePrefixes = append(merged.ServicePrefixes, policy) - } - - for _, policy := range sessionPolicies { - merged.Sessions = append(merged.Sessions, policy) - } - - for _, policy := range sessionPrefixPolicies { - merged.SessionPrefixes = append(merged.SessionPrefixes, policy) - } - - merged.ID = fmt.Sprintf("%x", multiPolicyID(policies)) - - return merged -} - func TranslateLegacyRules(policyBytes []byte) ([]byte, error) { parsed, err := hcl.ParseBytes(policyBytes) if err != nil { diff --git a/acl/policy_authorizer.go b/acl/policy_authorizer.go new file mode 100644 index 0000000000..0ce500b007 --- /dev/null +++ b/acl/policy_authorizer.go @@ -0,0 +1,637 @@ +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 *EnterpriseACLConfig) (Authorizer, error) { + policy := MergePolicies(policies) + + return newPolicyAuthorizerFromRules(&policy.PolicyRules, ent) +} + +func newPolicyAuthorizerFromRules(rules *PolicyRules, ent *EnterpriseACLConfig) (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 +} + +// ACLRead checks if listing of ACLs is allowed +func (p *policyAuthorizer) ACLRead(*EnterpriseAuthorizerContext) 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(*EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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(_ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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(_ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) EnforcementDecision { + 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, _ *EnterpriseAuthorizerContext) EnforcementDecision { + 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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 *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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(*EnterpriseAuthorizerContext) 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(*EnterpriseAuthorizerContext) 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(*EnterpriseAuthorizerContext) 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(*EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) 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, _ *EnterpriseAuthorizerContext) EnforcementDecision { + // Check for an exact rule or catch-all + if rule, ok := getPolicy(node, p.sessionRules); ok { + return enforce(rule.access, AccessWrite) + } + return Default +} diff --git a/acl/policy_authorizer_oss.go b/acl/policy_authorizer_oss.go new file mode 100644 index 0000000000..15ac4eec61 --- /dev/null +++ b/acl/policy_authorizer_oss.go @@ -0,0 +1,30 @@ +// +build !consulent + +package acl + +// enterprisePolicyAuthorizer stub +type enterprisePolicyAuthorizer struct{} + +func (authz *enterprisePolicyAuthorizer) init(*EnterpriseACLConfig) { + // nothing to do +} + +func (authz *enterprisePolicyAuthorizer) enforce(_ *EnterpriseRule, _ *EnterpriseAuthorizerContext) EnforcementDecision { + return Default +} + +// NewPolicyAuthorizer merges the policies and returns an Authorizer that will enforce them +func NewPolicyAuthorizer(policies []*Policy, entConfig *EnterpriseACLConfig) (Authorizer, error) { + return newPolicyAuthorizer(policies, entConfig) +} + +// NewPolicyAuthorizerWithDefaults will actually created a ChainedAuthorizer with +// the policies compiled into one Authorizer and the backup policy of the defaultAuthz +func NewPolicyAuthorizerWithDefaults(defaultAuthz Authorizer, policies []*Policy, entConfig *EnterpriseACLConfig) (Authorizer, error) { + authz, err := newPolicyAuthorizer(policies, entConfig) + if err != nil { + return nil, err + } + + return NewChainedAuthorizer([]Authorizer{authz, defaultAuthz}), nil +} diff --git a/acl/policy_authorizer_test.go b/acl/policy_authorizer_test.go new file mode 100644 index 0000000000..9ca84367f1 --- /dev/null +++ b/acl/policy_authorizer_test.go @@ -0,0 +1,371 @@ +package acl + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +// Note that many of the policy authorizer tests still live in acl_test.go. These utilize a default policy or layer +// up multiple authorizers just like before the latest overhaul of the ACL package. To reduce the code diff and to +// ensure compatibility from version to version those tests have been only minimally altered. The tests in this +// file are specific to the newer functionality. +func TestPolicyAuthorizer(t *testing.T) { + t.Parallel() + + type aclCheck struct { + name string + prefix string + check func(t *testing.T, authz Authorizer, prefix string, entCtx *EnterpriseAuthorizerContext) + } + + type aclTest struct { + policy *Policy + checks []aclCheck + } + + cases := map[string]aclTest{ + // This test ensures that if the policy doesn't define a rule then the policy authorizer will + // return no concrete enforcement decision. This allows deferring to some defaults in another + // authorizer including usage of a default overall policy of "deny" + "Defaults": aclTest{ + policy: &Policy{}, + checks: []aclCheck{ + {name: "DefaultACLRead", prefix: "foo", check: checkDefaultACLRead}, + {name: "DefaultACLWrite", prefix: "foo", check: checkDefaultACLWrite}, + {name: "DefaultAgentRead", prefix: "foo", check: checkDefaultAgentRead}, + {name: "DefaultAgentWrite", prefix: "foo", check: checkDefaultAgentWrite}, + {name: "DefaultEventRead", prefix: "foo", check: checkDefaultEventRead}, + {name: "DefaultEventWrite", prefix: "foo", check: checkDefaultEventWrite}, + {name: "DefaultIntentionDefaultAllow", prefix: "foo", check: checkDefaultIntentionDefaultAllow}, + {name: "DefaultIntentionRead", prefix: "foo", check: checkDefaultIntentionRead}, + {name: "DefaultIntentionWrite", prefix: "foo", check: checkDefaultIntentionWrite}, + {name: "DefaultKeyRead", prefix: "foo", check: checkDefaultKeyRead}, + {name: "DefaultKeyList", prefix: "foo", check: checkDefaultKeyList}, + {name: "DefaultKeyringRead", prefix: "foo", check: checkDefaultKeyringRead}, + {name: "DefaultKeyringWrite", prefix: "foo", check: checkDefaultKeyringWrite}, + {name: "DefaultKeyWrite", prefix: "foo", check: checkDefaultKeyWrite}, + {name: "DefaultKeyWritePrefix", prefix: "foo", check: checkDefaultKeyWritePrefix}, + {name: "DefaultNodeRead", prefix: "foo", check: checkDefaultNodeRead}, + {name: "DefaultNodeWrite", prefix: "foo", check: checkDefaultNodeWrite}, + {name: "DefaultOperatorRead", prefix: "foo", check: checkDefaultOperatorRead}, + {name: "DefaultOperatorWrite", prefix: "foo", check: checkDefaultOperatorWrite}, + {name: "DefaultPreparedQueryRead", prefix: "foo", check: checkDefaultPreparedQueryRead}, + {name: "DefaultPreparedQueryWrite", prefix: "foo", check: checkDefaultPreparedQueryWrite}, + {name: "DefaultServiceRead", prefix: "foo", check: checkDefaultServiceRead}, + {name: "DefaultServiceWrite", prefix: "foo", check: checkDefaultServiceWrite}, + {name: "DefaultSessionRead", prefix: "foo", check: checkDefaultSessionRead}, + {name: "DefaultSessionWrite", prefix: "foo", check: checkDefaultSessionWrite}, + {name: "DefaultSnapshot", prefix: "foo", check: checkDefaultSnapshot}, + }, + }, + "Prefer Exact Matches": aclTest{ + policy: &Policy{PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ + Node: "foo", + Policy: PolicyWrite, + }, + &AgentRule{ + Node: "football", + Policy: PolicyDeny, + }, + }, + AgentPrefixes: []*AgentRule{ + &AgentRule{ + Node: "foot", + Policy: PolicyRead, + }, + &AgentRule{ + Node: "fo", + Policy: PolicyRead, + }, + }, + Keys: []*KeyRule{ + &KeyRule{ + Prefix: "foo", + Policy: PolicyWrite, + }, + &KeyRule{ + Prefix: "football", + Policy: PolicyDeny, + }, + }, + KeyPrefixes: []*KeyRule{ + &KeyRule{ + Prefix: "foot", + Policy: PolicyRead, + }, + &KeyRule{ + Prefix: "fo", + Policy: PolicyRead, + }, + }, + Nodes: []*NodeRule{ + &NodeRule{ + Name: "foo", + Policy: PolicyWrite, + }, + &NodeRule{ + Name: "football", + Policy: PolicyDeny, + }, + }, + NodePrefixes: []*NodeRule{ + &NodeRule{ + Name: "foot", + Policy: PolicyRead, + }, + &NodeRule{ + Name: "fo", + Policy: PolicyRead, + }, + }, + Services: []*ServiceRule{ + &ServiceRule{ + Name: "foo", + Policy: PolicyWrite, + Intentions: PolicyWrite, + }, + &ServiceRule{ + Name: "football", + Policy: PolicyDeny, + }, + }, + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ + Name: "foot", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + &ServiceRule{ + Name: "fo", + Policy: PolicyRead, + Intentions: PolicyRead, + }, + }, + Sessions: []*SessionRule{ + &SessionRule{ + Node: "foo", + Policy: PolicyWrite, + }, + &SessionRule{ + Node: "football", + Policy: PolicyDeny, + }, + }, + SessionPrefixes: []*SessionRule{ + &SessionRule{ + Node: "foot", + Policy: PolicyRead, + }, + &SessionRule{ + Node: "fo", + Policy: PolicyRead, + }, + }, + Events: []*EventRule{ + &EventRule{ + Event: "foo", + Policy: PolicyWrite, + }, + &EventRule{ + Event: "football", + Policy: PolicyDeny, + }, + }, + EventPrefixes: []*EventRule{ + &EventRule{ + Event: "foot", + Policy: PolicyRead, + }, + &EventRule{ + Event: "fo", + Policy: PolicyRead, + }, + }, + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "foo", + Policy: PolicyWrite, + }, + &PreparedQueryRule{ + Prefix: "football", + Policy: PolicyDeny, + }, + }, + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ + Prefix: "foot", + Policy: PolicyRead, + }, + &PreparedQueryRule{ + Prefix: "fo", + Policy: PolicyRead, + }, + }, + }}, + checks: []aclCheck{ + {name: "AgentReadPrefixAllowed", prefix: "fo", check: checkAllowAgentRead}, + {name: "AgentWritePrefixDenied", prefix: "fo", check: checkDenyAgentWrite}, + {name: "AgentReadPrefixAllowed", prefix: "for", check: checkAllowAgentRead}, + {name: "AgentWritePrefixDenied", prefix: "for", check: checkDenyAgentWrite}, + {name: "AgentReadAllowed", prefix: "foo", check: checkAllowAgentRead}, + {name: "AgentWriteAllowed", prefix: "foo", check: checkAllowAgentWrite}, + {name: "AgentReadPrefixAllowed", prefix: "foot", check: checkAllowAgentRead}, + {name: "AgentWritePrefixDenied", prefix: "foot", check: checkDenyAgentWrite}, + {name: "AgentReadPrefixAllowed", prefix: "foot2", check: checkAllowAgentRead}, + {name: "AgentWritePrefixDenied", prefix: "foot2", check: checkDenyAgentWrite}, + {name: "AgentReadPrefixAllowed", prefix: "food", check: checkAllowAgentRead}, + {name: "AgentWritePrefixDenied", prefix: "food", check: checkDenyAgentWrite}, + {name: "AgentReadDenied", prefix: "football", check: checkDenyAgentRead}, + {name: "AgentWriteDenied", prefix: "football", check: checkDenyAgentWrite}, + + {name: "KeyReadPrefixAllowed", prefix: "fo", check: checkAllowKeyRead}, + {name: "KeyWritePrefixDenied", prefix: "fo", check: checkDenyKeyWrite}, + {name: "KeyReadPrefixAllowed", prefix: "for", check: checkAllowKeyRead}, + {name: "KeyWritePrefixDenied", prefix: "for", check: checkDenyKeyWrite}, + {name: "KeyReadAllowed", prefix: "foo", check: checkAllowKeyRead}, + {name: "KeyWriteAllowed", prefix: "foo", check: checkAllowKeyWrite}, + {name: "KeyReadPrefixAllowed", prefix: "foot", check: checkAllowKeyRead}, + {name: "KeyWritePrefixDenied", prefix: "foot", check: checkDenyKeyWrite}, + {name: "KeyReadPrefixAllowed", prefix: "foot2", check: checkAllowKeyRead}, + {name: "KeyWritePrefixDenied", prefix: "foot2", check: checkDenyKeyWrite}, + {name: "KeyReadPrefixAllowed", prefix: "food", check: checkAllowKeyRead}, + {name: "KeyWritePrefixDenied", prefix: "food", check: checkDenyKeyWrite}, + {name: "KeyReadDenied", prefix: "football", check: checkDenyKeyRead}, + {name: "KeyWriteDenied", prefix: "football", check: checkDenyKeyWrite}, + + {name: "NodeReadPrefixAllowed", prefix: "fo", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "fo", check: checkDenyNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "for", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "for", check: checkDenyNodeWrite}, + {name: "NodeReadAllowed", prefix: "foo", check: checkAllowNodeRead}, + {name: "NodeWriteAllowed", prefix: "foo", check: checkAllowNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "foot", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "foot", check: checkDenyNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "foot2", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "foot2", check: checkDenyNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "food", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "food", check: checkDenyNodeWrite}, + {name: "NodeReadDenied", prefix: "football", check: checkDenyNodeRead}, + {name: "NodeWriteDenied", prefix: "football", check: checkDenyNodeWrite}, + + {name: "ServiceReadPrefixAllowed", prefix: "fo", check: checkAllowServiceRead}, + {name: "ServiceWritePrefixDenied", prefix: "fo", check: checkDenyServiceWrite}, + {name: "ServiceReadPrefixAllowed", prefix: "for", check: checkAllowServiceRead}, + {name: "ServiceWritePrefixDenied", prefix: "for", check: checkDenyServiceWrite}, + {name: "ServiceReadAllowed", prefix: "foo", check: checkAllowServiceRead}, + {name: "ServiceWriteAllowed", prefix: "foo", check: checkAllowServiceWrite}, + {name: "ServiceReadPrefixAllowed", prefix: "foot", check: checkAllowServiceRead}, + {name: "ServiceWritePrefixDenied", prefix: "foot", check: checkDenyServiceWrite}, + {name: "ServiceReadPrefixAllowed", prefix: "foot2", check: checkAllowServiceRead}, + {name: "ServiceWritePrefixDenied", prefix: "foot2", check: checkDenyServiceWrite}, + {name: "ServiceReadPrefixAllowed", prefix: "food", check: checkAllowServiceRead}, + {name: "ServiceWritePrefixDenied", prefix: "food", check: checkDenyServiceWrite}, + {name: "ServiceReadDenied", prefix: "football", check: checkDenyServiceRead}, + {name: "ServiceWriteDenied", prefix: "football", check: checkDenyServiceWrite}, + + {name: "NodeReadPrefixAllowed", prefix: "fo", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "fo", check: checkDenyNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "for", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "for", check: checkDenyNodeWrite}, + {name: "NodeReadAllowed", prefix: "foo", check: checkAllowNodeRead}, + {name: "NodeWriteAllowed", prefix: "foo", check: checkAllowNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "foot", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "foot", check: checkDenyNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "foot2", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "foot2", check: checkDenyNodeWrite}, + {name: "NodeReadPrefixAllowed", prefix: "food", check: checkAllowNodeRead}, + {name: "NodeWritePrefixDenied", prefix: "food", check: checkDenyNodeWrite}, + {name: "NodeReadDenied", prefix: "football", check: checkDenyNodeRead}, + {name: "NodeWriteDenied", prefix: "football", check: checkDenyNodeWrite}, + + {name: "IntentionReadPrefixAllowed", prefix: "fo", check: checkAllowIntentionRead}, + {name: "IntentionWritePrefixDenied", prefix: "fo", check: checkDenyIntentionWrite}, + {name: "IntentionReadPrefixAllowed", prefix: "for", check: checkAllowIntentionRead}, + {name: "IntentionWritePrefixDenied", prefix: "for", check: checkDenyIntentionWrite}, + {name: "IntentionReadAllowed", prefix: "foo", check: checkAllowIntentionRead}, + {name: "IntentionWriteAllowed", prefix: "foo", check: checkAllowIntentionWrite}, + {name: "IntentionReadPrefixAllowed", prefix: "foot", check: checkAllowIntentionRead}, + {name: "IntentionWritePrefixDenied", prefix: "foot", check: checkDenyIntentionWrite}, + {name: "IntentionReadPrefixAllowed", prefix: "foot2", check: checkAllowIntentionRead}, + {name: "IntentionWritePrefixDenied", prefix: "foot2", check: checkDenyIntentionWrite}, + {name: "IntentionReadPrefixAllowed", prefix: "food", check: checkAllowIntentionRead}, + {name: "IntentionWritePrefixDenied", prefix: "food", check: checkDenyIntentionWrite}, + {name: "IntentionReadDenied", prefix: "football", check: checkDenyIntentionRead}, + {name: "IntentionWriteDenied", prefix: "football", check: checkDenyIntentionWrite}, + + {name: "SessionReadPrefixAllowed", prefix: "fo", check: checkAllowSessionRead}, + {name: "SessionWritePrefixDenied", prefix: "fo", check: checkDenySessionWrite}, + {name: "SessionReadPrefixAllowed", prefix: "for", check: checkAllowSessionRead}, + {name: "SessionWritePrefixDenied", prefix: "for", check: checkDenySessionWrite}, + {name: "SessionReadAllowed", prefix: "foo", check: checkAllowSessionRead}, + {name: "SessionWriteAllowed", prefix: "foo", check: checkAllowSessionWrite}, + {name: "SessionReadPrefixAllowed", prefix: "foot", check: checkAllowSessionRead}, + {name: "SessionWritePrefixDenied", prefix: "foot", check: checkDenySessionWrite}, + {name: "SessionReadPrefixAllowed", prefix: "foot2", check: checkAllowSessionRead}, + {name: "SessionWritePrefixDenied", prefix: "foot2", check: checkDenySessionWrite}, + {name: "SessionReadPrefixAllowed", prefix: "food", check: checkAllowSessionRead}, + {name: "SessionWritePrefixDenied", prefix: "food", check: checkDenySessionWrite}, + {name: "SessionReadDenied", prefix: "football", check: checkDenySessionRead}, + {name: "SessionWriteDenied", prefix: "football", check: checkDenySessionWrite}, + + {name: "EventReadPrefixAllowed", prefix: "fo", check: checkAllowEventRead}, + {name: "EventWritePrefixDenied", prefix: "fo", check: checkDenyEventWrite}, + {name: "EventReadPrefixAllowed", prefix: "for", check: checkAllowEventRead}, + {name: "EventWritePrefixDenied", prefix: "for", check: checkDenyEventWrite}, + {name: "EventReadAllowed", prefix: "foo", check: checkAllowEventRead}, + {name: "EventWriteAllowed", prefix: "foo", check: checkAllowEventWrite}, + {name: "EventReadPrefixAllowed", prefix: "foot", check: checkAllowEventRead}, + {name: "EventWritePrefixDenied", prefix: "foot", check: checkDenyEventWrite}, + {name: "EventReadPrefixAllowed", prefix: "foot2", check: checkAllowEventRead}, + {name: "EventWritePrefixDenied", prefix: "foot2", check: checkDenyEventWrite}, + {name: "EventReadPrefixAllowed", prefix: "food", check: checkAllowEventRead}, + {name: "EventWritePrefixDenied", prefix: "food", check: checkDenyEventWrite}, + {name: "EventReadDenied", prefix: "football", check: checkDenyEventRead}, + {name: "EventWriteDenied", prefix: "football", check: checkDenyEventWrite}, + + {name: "PreparedQueryReadPrefixAllowed", prefix: "fo", check: checkAllowPreparedQueryRead}, + {name: "PreparedQueryWritePrefixDenied", prefix: "fo", check: checkDenyPreparedQueryWrite}, + {name: "PreparedQueryReadPrefixAllowed", prefix: "for", check: checkAllowPreparedQueryRead}, + {name: "PreparedQueryWritePrefixDenied", prefix: "for", check: checkDenyPreparedQueryWrite}, + {name: "PreparedQueryReadAllowed", prefix: "foo", check: checkAllowPreparedQueryRead}, + {name: "PreparedQueryWriteAllowed", prefix: "foo", check: checkAllowPreparedQueryWrite}, + {name: "PreparedQueryReadPrefixAllowed", prefix: "foot", check: checkAllowPreparedQueryRead}, + {name: "PreparedQueryWritePrefixDenied", prefix: "foot", check: checkDenyPreparedQueryWrite}, + {name: "PreparedQueryReadPrefixAllowed", prefix: "foot2", check: checkAllowPreparedQueryRead}, + {name: "PreparedQueryWritePrefixDenied", prefix: "foot2", check: checkDenyPreparedQueryWrite}, + {name: "PreparedQueryReadPrefixAllowed", prefix: "food", check: checkAllowPreparedQueryRead}, + {name: "PreparedQueryWritePrefixDenied", prefix: "food", check: checkDenyPreparedQueryWrite}, + {name: "PreparedQueryReadDenied", prefix: "football", check: checkDenyPreparedQueryRead}, + {name: "PreparedQueryWriteDenied", prefix: "football", check: checkDenyPreparedQueryWrite}, + }, + }, + } + + for name, tcase := range cases { + name := name + tcase := tcase + t.Run(name, func(t *testing.T) { + t.Parallel() + + authz, err := NewPolicyAuthorizer([]*Policy{tcase.policy}, nil) + require.NoError(t, err) + + for _, check := range tcase.checks { + checkName := check.name + if check.prefix != "" { + checkName = fmt.Sprintf("%s.Prefix(%s)", checkName, check.prefix) + } + t.Run(checkName, func(t *testing.T) { + check := check + t.Parallel() + + check.check(t, authz, check.prefix, nil) + }) + } + }) + } +} diff --git a/acl/policy_merger.go b/acl/policy_merger.go new file mode 100644 index 0000000000..c21cc8e39b --- /dev/null +++ b/acl/policy_merger.go @@ -0,0 +1,364 @@ +package acl + +import ( + "encoding/binary" + "fmt" + "hash" + + "golang.org/x/crypto/blake2b" +) + +type policyRulesMergeContext struct { + aclRule string + agentRules map[string]*AgentRule + agentPrefixRules map[string]*AgentRule + eventRules map[string]*EventRule + eventPrefixRules map[string]*EventRule + keyringRule string + keyRules map[string]*KeyRule + keyPrefixRules map[string]*KeyRule + nodeRules map[string]*NodeRule + nodePrefixRules map[string]*NodeRule + operatorRule string + preparedQueryRules map[string]*PreparedQueryRule + preparedQueryPrefixRules map[string]*PreparedQueryRule + serviceRules map[string]*ServiceRule + servicePrefixRules map[string]*ServiceRule + sessionRules map[string]*SessionRule + sessionPrefixRules map[string]*SessionRule +} + +func (p *policyRulesMergeContext) init() { + p.aclRule = "" + p.agentRules = make(map[string]*AgentRule) + p.agentPrefixRules = make(map[string]*AgentRule) + p.eventRules = make(map[string]*EventRule) + p.eventPrefixRules = make(map[string]*EventRule) + p.keyringRule = "" + p.keyRules = make(map[string]*KeyRule) + p.keyPrefixRules = make(map[string]*KeyRule) + p.nodeRules = make(map[string]*NodeRule) + p.nodePrefixRules = make(map[string]*NodeRule) + p.operatorRule = "" + p.preparedQueryRules = make(map[string]*PreparedQueryRule) + p.preparedQueryPrefixRules = make(map[string]*PreparedQueryRule) + p.serviceRules = make(map[string]*ServiceRule) + p.servicePrefixRules = make(map[string]*ServiceRule) + p.sessionRules = make(map[string]*SessionRule) + p.sessionPrefixRules = make(map[string]*SessionRule) +} + +func (p *policyRulesMergeContext) merge(policy *PolicyRules) { + if takesPrecedenceOver(policy.ACL, p.aclRule) { + p.aclRule = policy.ACL + } + + for _, ap := range policy.Agents { + update := true + if permission, found := p.agentRules[ap.Node]; found { + update = takesPrecedenceOver(ap.Policy, permission.Policy) + } + + if update { + p.agentRules[ap.Node] = ap + } + } + + for _, ap := range policy.AgentPrefixes { + update := true + if permission, found := p.agentPrefixRules[ap.Node]; found { + update = takesPrecedenceOver(ap.Policy, permission.Policy) + } + + if update { + p.agentPrefixRules[ap.Node] = ap + } + } + + for _, ep := range policy.Events { + update := true + if permission, found := p.eventRules[ep.Event]; found { + update = takesPrecedenceOver(ep.Policy, permission.Policy) + } + + if update { + p.eventRules[ep.Event] = ep + } + } + + for _, ep := range policy.EventPrefixes { + update := true + if permission, found := p.eventPrefixRules[ep.Event]; found { + update = takesPrecedenceOver(ep.Policy, permission.Policy) + } + + if update { + p.eventPrefixRules[ep.Event] = ep + } + } + + if takesPrecedenceOver(policy.Keyring, p.keyringRule) { + p.keyringRule = policy.Keyring + } + + for _, kp := range policy.Keys { + update := true + if permission, found := p.keyRules[kp.Prefix]; found { + update = takesPrecedenceOver(kp.Policy, permission.Policy) + } + + if update { + p.keyRules[kp.Prefix] = kp + } + } + + for _, kp := range policy.KeyPrefixes { + update := true + if permission, found := p.keyPrefixRules[kp.Prefix]; found { + update = takesPrecedenceOver(kp.Policy, permission.Policy) + } + + if update { + p.keyPrefixRules[kp.Prefix] = kp + } + } + + for _, np := range policy.Nodes { + update := true + if permission, found := p.nodeRules[np.Name]; found { + update = takesPrecedenceOver(np.Policy, permission.Policy) + } + + if update { + p.nodeRules[np.Name] = np + } + } + + for _, np := range policy.NodePrefixes { + update := true + if permission, found := p.nodePrefixRules[np.Name]; found { + update = takesPrecedenceOver(np.Policy, permission.Policy) + } + + if update { + p.nodePrefixRules[np.Name] = np + } + } + + if takesPrecedenceOver(policy.Operator, p.operatorRule) { + p.operatorRule = policy.Operator + } + + for _, qp := range policy.PreparedQueries { + update := true + if permission, found := p.preparedQueryRules[qp.Prefix]; found { + update = takesPrecedenceOver(qp.Policy, permission.Policy) + } + + if update { + p.preparedQueryRules[qp.Prefix] = qp + } + } + + for _, qp := range policy.PreparedQueryPrefixes { + update := true + if permission, found := p.preparedQueryPrefixRules[qp.Prefix]; found { + update = takesPrecedenceOver(qp.Policy, permission.Policy) + } + + if update { + p.preparedQueryPrefixRules[qp.Prefix] = qp + } + } + + for _, sp := range policy.Services { + existing, found := p.serviceRules[sp.Name] + + if !found { + p.serviceRules[sp.Name] = sp + continue + } + + if takesPrecedenceOver(sp.Policy, existing.Policy) { + existing.Policy = sp.Policy + existing.EnterpriseRule = sp.EnterpriseRule + } + + if takesPrecedenceOver(sp.Intentions, existing.Intentions) { + existing.Intentions = sp.Intentions + } + } + + for _, sp := range policy.ServicePrefixes { + existing, found := p.servicePrefixRules[sp.Name] + + if !found { + p.servicePrefixRules[sp.Name] = sp + continue + } + + if takesPrecedenceOver(sp.Policy, existing.Policy) { + existing.Policy = sp.Policy + existing.EnterpriseRule = sp.EnterpriseRule + } + + if takesPrecedenceOver(sp.Intentions, existing.Intentions) { + existing.Intentions = sp.Intentions + } + } + + for _, sp := range policy.Sessions { + update := true + if permission, found := p.sessionRules[sp.Node]; found { + update = takesPrecedenceOver(sp.Policy, permission.Policy) + } + + if update { + p.sessionRules[sp.Node] = sp + } + } + + for _, sp := range policy.SessionPrefixes { + update := true + if permission, found := p.sessionPrefixRules[sp.Node]; found { + update = takesPrecedenceOver(sp.Policy, permission.Policy) + } + + if update { + p.sessionPrefixRules[sp.Node] = sp + } + } +} + +func (p *policyRulesMergeContext) update(merged *PolicyRules) { + merged.ACL = p.aclRule + merged.Keyring = p.keyringRule + merged.Operator = p.operatorRule + + // All the for loop appends are ugly but Go doesn't have a way to get + // a slice of all values within a map so this is necessary + + merged.Agents = []*AgentRule{} + for _, policy := range p.agentRules { + merged.Agents = append(merged.Agents, policy) + } + + merged.AgentPrefixes = []*AgentRule{} + for _, policy := range p.agentPrefixRules { + merged.AgentPrefixes = append(merged.AgentPrefixes, policy) + } + + merged.Events = []*EventRule{} + for _, policy := range p.eventRules { + merged.Events = append(merged.Events, policy) + } + + merged.EventPrefixes = []*EventRule{} + for _, policy := range p.eventPrefixRules { + merged.EventPrefixes = append(merged.EventPrefixes, policy) + } + + merged.Keys = []*KeyRule{} + for _, policy := range p.keyRules { + merged.Keys = append(merged.Keys, policy) + } + + merged.KeyPrefixes = []*KeyRule{} + for _, policy := range p.keyPrefixRules { + merged.KeyPrefixes = append(merged.KeyPrefixes, policy) + } + + merged.Nodes = []*NodeRule{} + for _, policy := range p.nodeRules { + merged.Nodes = append(merged.Nodes, policy) + } + + merged.NodePrefixes = []*NodeRule{} + for _, policy := range p.nodePrefixRules { + merged.NodePrefixes = append(merged.NodePrefixes, policy) + } + + merged.PreparedQueries = []*PreparedQueryRule{} + for _, policy := range p.preparedQueryRules { + merged.PreparedQueries = append(merged.PreparedQueries, policy) + } + + merged.PreparedQueryPrefixes = []*PreparedQueryRule{} + for _, policy := range p.preparedQueryPrefixRules { + merged.PreparedQueryPrefixes = append(merged.PreparedQueryPrefixes, policy) + } + + merged.Services = []*ServiceRule{} + for _, policy := range p.serviceRules { + merged.Services = append(merged.Services, policy) + } + + merged.ServicePrefixes = []*ServiceRule{} + for _, policy := range p.servicePrefixRules { + merged.ServicePrefixes = append(merged.ServicePrefixes, policy) + } + + merged.Sessions = []*SessionRule{} + for _, policy := range p.sessionRules { + merged.Sessions = append(merged.Sessions, policy) + } + + merged.SessionPrefixes = []*SessionRule{} + for _, policy := range p.sessionPrefixRules { + merged.SessionPrefixes = append(merged.SessionPrefixes, policy) + } +} + +type PolicyMerger struct { + idHasher hash.Hash + policyRulesMergeContext + enterprisePolicyRulesMergeContext +} + +func NewPolicyMerger() *PolicyMerger { + merger := &PolicyMerger{} + merger.init() + return merger +} + +func (m *PolicyMerger) init() { + var err error + m.idHasher, err = blake2b.New256(nil) + if err != nil { + panic(err) + } + + m.policyRulesMergeContext.init() + m.enterprisePolicyRulesMergeContext.init() +} + +func (m *PolicyMerger) Merge(policy *Policy) { + // This is part of calculating the merged policies ID + m.idHasher.Write([]byte(policy.ID)) + binary.Write(m.idHasher, binary.BigEndian, policy.Revision) + + m.policyRulesMergeContext.merge(&policy.PolicyRules) + m.enterprisePolicyRulesMergeContext.merge(&policy.EnterprisePolicyRules) +} + +// Policy outputs the merged policy +func (m *PolicyMerger) Policy() *Policy { + merged := &Policy{ + ID: fmt.Sprintf("%x", m.idHasher.Sum(nil)), + } + + m.policyRulesMergeContext.update(&merged.PolicyRules) + m.enterprisePolicyRulesMergeContext.update(&merged.EnterprisePolicyRules) + + return merged +} + +func MergePolicies(policies []*Policy) *Policy { + var merger PolicyMerger + merger.init() + for _, p := range policies { + merger.Merge(p) + } + + return merger.Policy() +} diff --git a/acl/policy_merger_oss.go b/acl/policy_merger_oss.go new file mode 100644 index 0000000000..b97361da44 --- /dev/null +++ b/acl/policy_merger_oss.go @@ -0,0 +1,17 @@ +// +build !consulent + +package acl + +type enterprisePolicyRulesMergeContext struct{} + +func (ctx *enterprisePolicyRulesMergeContext) init() { + // do nothing +} + +func (ctx *enterprisePolicyRulesMergeContext) merge(*EnterprisePolicyRules) { + // do nothing +} + +func (ctx *enterprisePolicyRulesMergeContext) update(*EnterprisePolicyRules) { + // do nothing +} diff --git a/acl/policy_oss.go b/acl/policy_oss.go new file mode 100644 index 0000000000..247e6dcaa8 --- /dev/null +++ b/acl/policy_oss.go @@ -0,0 +1,19 @@ +// +build !consulent + +package acl + +// EnterpriseRule stub +type EnterpriseRule struct{} + +func (r *EnterpriseRule) Validate(string, *EnterpriseACLConfig) error { + // nothing to validate + return nil +} + +// EnterprisePolicyRules stub +type EnterprisePolicyRules struct{} + +func (r *EnterprisePolicyRules) Validate(*EnterpriseACLConfig) error { + // nothing to validate + return nil +} diff --git a/acl/policy_test.go b/acl/policy_test.go index f002a2f905..234506a581 100644 --- a/acl/policy_test.go +++ b/acl/policy_test.go @@ -87,100 +87,100 @@ func TestPolicySourceParse(t *testing.T) { `query "bar" { `, ` policy = "deny" `, `} `), - &Policy{ - AgentPrefixes: []*AgentPolicy{ - &AgentPolicy{ + &Policy{PolicyRules: PolicyRules{ + AgentPrefixes: []*AgentRule{ + &AgentRule{ Node: "foo", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "bar", Policy: PolicyWrite, }, }, - EventPrefixes: []*EventPolicy{ - &EventPolicy{ + EventPrefixes: []*EventRule{ + &EventRule{ Event: "", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "foo", Policy: PolicyWrite, }, - &EventPolicy{ + &EventRule{ Event: "bar", Policy: PolicyDeny, }, }, Keyring: PolicyDeny, - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ + KeyPrefixes: []*KeyRule{ + &KeyRule{ Prefix: "", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "foo/", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "foo/bar/", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "foo/bar/baz", Policy: PolicyDeny, }, }, - NodePrefixes: []*NodePolicy{ - &NodePolicy{ + NodePrefixes: []*NodeRule{ + &NodeRule{ Name: "", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "foo", Policy: PolicyWrite, }, - &NodePolicy{ + &NodeRule{ Name: "bar", Policy: PolicyDeny, }, }, Operator: PolicyDeny, - PreparedQueryPrefixes: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "foo", Policy: PolicyWrite, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "bar", Policy: PolicyDeny, }, }, - ServicePrefixes: []*ServicePolicy{ - &ServicePolicy{ + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ Name: "", Policy: PolicyWrite, }, - &ServicePolicy{ + &ServiceRule{ Name: "foo", Policy: PolicyRead, }, }, - SessionPrefixes: []*SessionPolicy{ - &SessionPolicy{ + SessionPrefixes: []*SessionRule{ + &SessionRule{ Node: "foo", Policy: PolicyWrite, }, - &SessionPolicy{ + &SessionRule{ Node: "bar", Policy: PolicyDeny, }, }, - }, + }}, "", }, { @@ -262,100 +262,100 @@ func TestPolicySourceParse(t *testing.T) { ` } `, ` } `, `} `), - &Policy{ - AgentPrefixes: []*AgentPolicy{ - &AgentPolicy{ + &Policy{PolicyRules: PolicyRules{ + AgentPrefixes: []*AgentRule{ + &AgentRule{ Node: "foo", Policy: PolicyWrite, }, - &AgentPolicy{ + &AgentRule{ Node: "bar", Policy: PolicyDeny, }, }, - EventPrefixes: []*EventPolicy{ - &EventPolicy{ + EventPrefixes: []*EventRule{ + &EventRule{ Event: "", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "foo", Policy: PolicyWrite, }, - &EventPolicy{ + &EventRule{ Event: "bar", Policy: PolicyDeny, }, }, Keyring: PolicyDeny, - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ + KeyPrefixes: []*KeyRule{ + &KeyRule{ Prefix: "", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "foo/", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "foo/bar/", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "foo/bar/baz", Policy: PolicyDeny, }, }, - NodePrefixes: []*NodePolicy{ - &NodePolicy{ + NodePrefixes: []*NodeRule{ + &NodeRule{ Name: "", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "foo", Policy: PolicyWrite, }, - &NodePolicy{ + &NodeRule{ Name: "bar", Policy: PolicyDeny, }, }, Operator: PolicyDeny, - PreparedQueryPrefixes: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "foo", Policy: PolicyWrite, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "bar", Policy: PolicyDeny, }, }, - ServicePrefixes: []*ServicePolicy{ - &ServicePolicy{ + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ Name: "", Policy: PolicyWrite, }, - &ServicePolicy{ + &ServiceRule{ Name: "foo", Policy: PolicyRead, }, }, - SessionPrefixes: []*SessionPolicy{ - &SessionPolicy{ + SessionPrefixes: []*SessionRule{ + &SessionRule{ Node: "foo", Policy: PolicyWrite, }, - &SessionPolicy{ + &SessionRule{ Node: "bar", Policy: PolicyDeny, }, }, - }, + }}, "", }, { @@ -365,14 +365,14 @@ func TestPolicySourceParse(t *testing.T) { `service "foo" { `, ` policy = "write"`, `} `), - &Policy{ - ServicePrefixes: []*ServicePolicy{ + &Policy{PolicyRules: PolicyRules{ + ServicePrefixes: []*ServiceRule{ { Name: "foo", Policy: "write", }, }, - }, + }}, "", }, { @@ -383,15 +383,15 @@ func TestPolicySourceParse(t *testing.T) { ` policy = "write" `, ` intentions = "read"`, `} `), - &Policy{ - ServicePrefixes: []*ServicePolicy{ + &Policy{PolicyRules: PolicyRules{ + ServicePrefixes: []*ServiceRule{ { Name: "foo", Policy: "write", Intentions: "read", }, }, - }, + }}, "", }, { @@ -409,7 +409,7 @@ func TestPolicySourceParse(t *testing.T) { "Bad Policy - ACL", SyntaxCurrent, - `acl = "nope"`, + `acl = "list"`, // there is no list policy but this helps to exercise another check in isPolicyValid nil, "Invalid acl policy", }, @@ -529,14 +529,14 @@ func TestPolicySourceParse(t *testing.T) { "Keyring Empty", SyntaxCurrent, `keyring = ""`, - &Policy{Keyring: ""}, + &Policy{PolicyRules: PolicyRules{Keyring: ""}}, "", }, { "Operator Empty", SyntaxCurrent, `operator = ""`, - &Policy{Operator: ""}, + &Policy{PolicyRules: PolicyRules{Operator: ""}}, "", }, } @@ -565,707 +565,707 @@ func TestMergePolicies(t *testing.T) { { name: "Agents", input: []*Policy{ - &Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ + &Policy{PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ Node: "foo", Policy: PolicyWrite, }, - &AgentPolicy{ + &AgentRule{ Node: "bar", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "baz", Policy: PolicyWrite, }, }, - AgentPrefixes: []*AgentPolicy{ - &AgentPolicy{ + AgentPrefixes: []*AgentRule{ + &AgentRule{ Node: "000", Policy: PolicyWrite, }, - &AgentPolicy{ + &AgentRule{ Node: "111", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "222", Policy: PolicyWrite, }, }, - }, - &Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ Node: "foo", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "baz", Policy: PolicyDeny, }, }, - AgentPrefixes: []*AgentPolicy{ - &AgentPolicy{ + AgentPrefixes: []*AgentRule{ + &AgentRule{ Node: "000", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "222", Policy: PolicyDeny, }, }, }, - }, - expected: &Policy{ - Agents: []*AgentPolicy{ - &AgentPolicy{ + }}, + expected: &Policy{PolicyRules: PolicyRules{ + Agents: []*AgentRule{ + &AgentRule{ Node: "foo", Policy: PolicyWrite, }, - &AgentPolicy{ + &AgentRule{ Node: "bar", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "baz", Policy: PolicyDeny, }, }, - AgentPrefixes: []*AgentPolicy{ - &AgentPolicy{ + AgentPrefixes: []*AgentRule{ + &AgentRule{ Node: "000", Policy: PolicyWrite, }, - &AgentPolicy{ + &AgentRule{ Node: "111", Policy: PolicyRead, }, - &AgentPolicy{ + &AgentRule{ Node: "222", Policy: PolicyDeny, }, }, - }, + }}, }, { name: "Events", input: []*Policy{ - &Policy{ - Events: []*EventPolicy{ - &EventPolicy{ + &Policy{PolicyRules: PolicyRules{ + Events: []*EventRule{ + &EventRule{ Event: "foo", Policy: PolicyWrite, }, - &EventPolicy{ + &EventRule{ Event: "bar", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "baz", Policy: PolicyWrite, }, }, - EventPrefixes: []*EventPolicy{ - &EventPolicy{ + EventPrefixes: []*EventRule{ + &EventRule{ Event: "000", Policy: PolicyWrite, }, - &EventPolicy{ + &EventRule{ Event: "111", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "222", Policy: PolicyWrite, }, }, - }, - &Policy{ - Events: []*EventPolicy{ - &EventPolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + Events: []*EventRule{ + &EventRule{ Event: "foo", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "baz", Policy: PolicyDeny, }, }, - EventPrefixes: []*EventPolicy{ - &EventPolicy{ + EventPrefixes: []*EventRule{ + &EventRule{ Event: "000", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "222", Policy: PolicyDeny, }, }, - }, + }}, }, - expected: &Policy{ - Events: []*EventPolicy{ - &EventPolicy{ + expected: &Policy{PolicyRules: PolicyRules{ + Events: []*EventRule{ + &EventRule{ Event: "foo", Policy: PolicyWrite, }, - &EventPolicy{ + &EventRule{ Event: "bar", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "baz", Policy: PolicyDeny, }, }, - EventPrefixes: []*EventPolicy{ - &EventPolicy{ + EventPrefixes: []*EventRule{ + &EventRule{ Event: "000", Policy: PolicyWrite, }, - &EventPolicy{ + &EventRule{ Event: "111", Policy: PolicyRead, }, - &EventPolicy{ + &EventRule{ Event: "222", Policy: PolicyDeny, }, }, - }, + }}, }, { name: "Node", input: []*Policy{ - &Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ + &Policy{PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ Name: "foo", Policy: PolicyWrite, }, - &NodePolicy{ + &NodeRule{ Name: "bar", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "baz", Policy: PolicyWrite, }, }, - NodePrefixes: []*NodePolicy{ - &NodePolicy{ + NodePrefixes: []*NodeRule{ + &NodeRule{ Name: "000", Policy: PolicyWrite, }, - &NodePolicy{ + &NodeRule{ Name: "111", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "222", Policy: PolicyWrite, }, }, - }, - &Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ Name: "foo", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "baz", Policy: PolicyDeny, }, }, - NodePrefixes: []*NodePolicy{ - &NodePolicy{ + NodePrefixes: []*NodeRule{ + &NodeRule{ Name: "000", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "222", Policy: PolicyDeny, }, }, }, - }, - expected: &Policy{ - Nodes: []*NodePolicy{ - &NodePolicy{ + }}, + expected: &Policy{PolicyRules: PolicyRules{ + Nodes: []*NodeRule{ + &NodeRule{ Name: "foo", Policy: PolicyWrite, }, - &NodePolicy{ + &NodeRule{ Name: "bar", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "baz", Policy: PolicyDeny, }, }, - NodePrefixes: []*NodePolicy{ - &NodePolicy{ + NodePrefixes: []*NodeRule{ + &NodeRule{ Name: "000", Policy: PolicyWrite, }, - &NodePolicy{ + &NodeRule{ Name: "111", Policy: PolicyRead, }, - &NodePolicy{ + &NodeRule{ Name: "222", Policy: PolicyDeny, }, }, - }, + }}, }, { name: "Keys", input: []*Policy{ - &Policy{ - Keys: []*KeyPolicy{ - &KeyPolicy{ + &Policy{PolicyRules: PolicyRules{ + Keys: []*KeyRule{ + &KeyRule{ Prefix: "foo", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "bar", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "baz", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "zoo", Policy: PolicyList, }, }, - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ + KeyPrefixes: []*KeyRule{ + &KeyRule{ Prefix: "000", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "111", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "222", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "333", Policy: PolicyList, }, }, - }, - &Policy{ - Keys: []*KeyPolicy{ - &KeyPolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + Keys: []*KeyRule{ + &KeyRule{ Prefix: "foo", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "baz", Policy: PolicyDeny, }, - &KeyPolicy{ + &KeyRule{ Prefix: "zoo", Policy: PolicyRead, }, }, - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ + KeyPrefixes: []*KeyRule{ + &KeyRule{ Prefix: "000", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "222", Policy: PolicyDeny, }, - &KeyPolicy{ + &KeyRule{ Prefix: "333", Policy: PolicyRead, }, }, - }, + }}, }, - expected: &Policy{ - Keys: []*KeyPolicy{ - &KeyPolicy{ + expected: &Policy{PolicyRules: PolicyRules{ + Keys: []*KeyRule{ + &KeyRule{ Prefix: "foo", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "bar", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "baz", Policy: PolicyDeny, }, - &KeyPolicy{ + &KeyRule{ Prefix: "zoo", Policy: PolicyList, }, }, - KeyPrefixes: []*KeyPolicy{ - &KeyPolicy{ + KeyPrefixes: []*KeyRule{ + &KeyRule{ Prefix: "000", Policy: PolicyWrite, }, - &KeyPolicy{ + &KeyRule{ Prefix: "111", Policy: PolicyRead, }, - &KeyPolicy{ + &KeyRule{ Prefix: "222", Policy: PolicyDeny, }, - &KeyPolicy{ + &KeyRule{ Prefix: "333", Policy: PolicyList, }, }, - }, + }}, }, { name: "Services", input: []*Policy{ - &Policy{ - Services: []*ServicePolicy{ - &ServicePolicy{ + &Policy{PolicyRules: PolicyRules{ + Services: []*ServiceRule{ + &ServiceRule{ Name: "foo", Policy: PolicyWrite, Intentions: PolicyWrite, }, - &ServicePolicy{ + &ServiceRule{ Name: "bar", Policy: PolicyRead, Intentions: PolicyRead, }, - &ServicePolicy{ + &ServiceRule{ Name: "baz", Policy: PolicyWrite, Intentions: PolicyWrite, }, }, - ServicePrefixes: []*ServicePolicy{ - &ServicePolicy{ + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ Name: "000", Policy: PolicyWrite, Intentions: PolicyWrite, }, - &ServicePolicy{ + &ServiceRule{ Name: "111", Policy: PolicyRead, Intentions: PolicyRead, }, - &ServicePolicy{ + &ServiceRule{ Name: "222", Policy: PolicyWrite, Intentions: PolicyWrite, }, }, - }, - &Policy{ - Services: []*ServicePolicy{ - &ServicePolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + Services: []*ServiceRule{ + &ServiceRule{ Name: "foo", Policy: PolicyRead, Intentions: PolicyRead, }, - &ServicePolicy{ + &ServiceRule{ Name: "baz", Policy: PolicyDeny, Intentions: PolicyDeny, }, }, - ServicePrefixes: []*ServicePolicy{ - &ServicePolicy{ + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ Name: "000", Policy: PolicyRead, Intentions: PolicyRead, }, - &ServicePolicy{ + &ServiceRule{ Name: "222", Policy: PolicyDeny, Intentions: PolicyDeny, }, }, - }, + }}, }, - expected: &Policy{ - Services: []*ServicePolicy{ - &ServicePolicy{ + expected: &Policy{PolicyRules: PolicyRules{ + Services: []*ServiceRule{ + &ServiceRule{ Name: "foo", Policy: PolicyWrite, Intentions: PolicyWrite, }, - &ServicePolicy{ + &ServiceRule{ Name: "bar", Policy: PolicyRead, Intentions: PolicyRead, }, - &ServicePolicy{ + &ServiceRule{ Name: "baz", Policy: PolicyDeny, Intentions: PolicyDeny, }, }, - ServicePrefixes: []*ServicePolicy{ - &ServicePolicy{ + ServicePrefixes: []*ServiceRule{ + &ServiceRule{ Name: "000", Policy: PolicyWrite, Intentions: PolicyWrite, }, - &ServicePolicy{ + &ServiceRule{ Name: "111", Policy: PolicyRead, Intentions: PolicyRead, }, - &ServicePolicy{ + &ServiceRule{ Name: "222", Policy: PolicyDeny, Intentions: PolicyDeny, }, }, - }, + }}, }, { name: "Sessions", input: []*Policy{ - &Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ + &Policy{PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ Node: "foo", Policy: PolicyWrite, }, - &SessionPolicy{ + &SessionRule{ Node: "bar", Policy: PolicyRead, }, - &SessionPolicy{ + &SessionRule{ Node: "baz", Policy: PolicyWrite, }, }, - SessionPrefixes: []*SessionPolicy{ - &SessionPolicy{ + SessionPrefixes: []*SessionRule{ + &SessionRule{ Node: "000", Policy: PolicyWrite, }, - &SessionPolicy{ + &SessionRule{ Node: "111", Policy: PolicyRead, }, - &SessionPolicy{ + &SessionRule{ Node: "222", Policy: PolicyWrite, }, }, - }, - &Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ Node: "foo", Policy: PolicyRead, }, - &SessionPolicy{ + &SessionRule{ Node: "baz", Policy: PolicyDeny, }, }, - SessionPrefixes: []*SessionPolicy{ - &SessionPolicy{ + SessionPrefixes: []*SessionRule{ + &SessionRule{ Node: "000", Policy: PolicyRead, }, - &SessionPolicy{ + &SessionRule{ Node: "222", Policy: PolicyDeny, }, }, - }, + }}, }, - expected: &Policy{ - Sessions: []*SessionPolicy{ - &SessionPolicy{ + expected: &Policy{PolicyRules: PolicyRules{ + Sessions: []*SessionRule{ + &SessionRule{ Node: "foo", Policy: PolicyWrite, }, - &SessionPolicy{ + &SessionRule{ Node: "bar", Policy: PolicyRead, }, - &SessionPolicy{ + &SessionRule{ Node: "baz", Policy: PolicyDeny, }, }, - SessionPrefixes: []*SessionPolicy{ - &SessionPolicy{ + SessionPrefixes: []*SessionRule{ + &SessionRule{ Node: "000", Policy: PolicyWrite, }, - &SessionPolicy{ + &SessionRule{ Node: "111", Policy: PolicyRead, }, - &SessionPolicy{ + &SessionRule{ Node: "222", Policy: PolicyDeny, }, }, - }, + }}, }, { name: "Prepared Queries", input: []*Policy{ - &Policy{ - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + &Policy{PolicyRules: PolicyRules{ + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "foo", Policy: PolicyWrite, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "bar", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "baz", Policy: PolicyWrite, }, }, - PreparedQueryPrefixes: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "000", Policy: PolicyWrite, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "111", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "222", Policy: PolicyWrite, }, }, - }, - &Policy{ - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + }}, + &Policy{PolicyRules: PolicyRules{ + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "foo", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "baz", Policy: PolicyDeny, }, }, - PreparedQueryPrefixes: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "000", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "222", Policy: PolicyDeny, }, }, - }, + }}, }, - expected: &Policy{ - PreparedQueries: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + expected: &Policy{PolicyRules: PolicyRules{ + PreparedQueries: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "foo", Policy: PolicyWrite, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "bar", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "baz", Policy: PolicyDeny, }, }, - PreparedQueryPrefixes: []*PreparedQueryPolicy{ - &PreparedQueryPolicy{ + PreparedQueryPrefixes: []*PreparedQueryRule{ + &PreparedQueryRule{ Prefix: "000", Policy: PolicyWrite, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "111", Policy: PolicyRead, }, - &PreparedQueryPolicy{ + &PreparedQueryRule{ Prefix: "222", Policy: PolicyDeny, }, }, - }, + }}, }, { name: "Write Precedence", input: []*Policy{ - &Policy{ + &Policy{PolicyRules: PolicyRules{ ACL: PolicyRead, Keyring: PolicyRead, Operator: PolicyRead, - }, - &Policy{ + }}, + &Policy{PolicyRules: PolicyRules{ ACL: PolicyWrite, Keyring: PolicyWrite, Operator: PolicyWrite, - }, + }}, }, - expected: &Policy{ + expected: &Policy{PolicyRules: PolicyRules{ ACL: PolicyWrite, Keyring: PolicyWrite, Operator: PolicyWrite, - }, + }}, }, { name: "Deny Precedence", input: []*Policy{ - &Policy{ + &Policy{PolicyRules: PolicyRules{ ACL: PolicyWrite, Keyring: PolicyWrite, Operator: PolicyWrite, - }, - &Policy{ + }}, + &Policy{PolicyRules: PolicyRules{ ACL: PolicyDeny, Keyring: PolicyDeny, Operator: PolicyDeny, - }, + }}, }, - expected: &Policy{ + expected: &Policy{PolicyRules: PolicyRules{ ACL: PolicyDeny, Keyring: PolicyDeny, Operator: PolicyDeny, - }, + }}, }, { name: "Read Precedence", input: []*Policy{ - &Policy{ + &Policy{PolicyRules: PolicyRules{ ACL: PolicyRead, Keyring: PolicyRead, Operator: PolicyRead, - }, + }}, &Policy{}, }, - expected: &Policy{ + expected: &Policy{PolicyRules: PolicyRules{ ACL: PolicyRead, Keyring: PolicyRead, Operator: PolicyRead, - }, + }}, }, } diff --git a/acl/static_authorizer.go b/acl/static_authorizer.go new file mode 100644 index 0000000000..aaf7089416 --- /dev/null +++ b/acl/static_authorizer.go @@ -0,0 +1,244 @@ +package acl + +var ( + // allowAll is a singleton policy which allows all + // non-management actions + allowAll Authorizer = &StaticAuthorizer{ + allowManage: false, + defaultAllow: true, + } + + // denyAll is a singleton policy which denies all actions + denyAll Authorizer = &StaticAuthorizer{ + allowManage: false, + defaultAllow: false, + } + + // manageAll is a singleton policy which allows all + // actions, including management + // TODO (acls) - Do we need to keep this around? Our config parsing doesn't allow + // specifying a default "manage" policy so I believe nothing will every use this. + manageAll Authorizer = &StaticAuthorizer{ + allowManage: true, + defaultAllow: true, + } +) + +// 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(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.allowManage { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) ACLWrite(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.allowManage { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) AgentRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) AgentWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) EventRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) EventWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) IntentionDefaultAllow(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) IntentionRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) IntentionWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) KeyRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) KeyList(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) KeyWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) KeyWritePrefix(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) KeyringRead(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) KeyringWrite(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) NodeRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) NodeWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) OperatorRead(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) OperatorWrite(*EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) PreparedQueryRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) PreparedQueryWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) ServiceRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) ServiceWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) SessionRead(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) SessionWrite(string, *EnterpriseAuthorizerContext) EnforcementDecision { + if s.defaultAllow { + return Allow + } + return Deny +} + +func (s *StaticAuthorizer) Snapshot(_ *EnterpriseAuthorizerContext) EnforcementDecision { + if s.allowManage { + return Allow + } + return Deny +} + +// 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 + } +} diff --git a/acl/static_authorizer_test.go b/acl/static_authorizer_test.go new file mode 100644 index 0000000000..a2865754ee --- /dev/null +++ b/acl/static_authorizer_test.go @@ -0,0 +1,103 @@ +package acl + +import ( + "testing" +) + +func TestStaticAuthorizer(t *testing.T) { + t.Parallel() + + t.Run("AllowAll", func(t *testing.T) { + t.Parallel() + + authz := AllowAll() + checkDenyACLRead(t, authz, "foo", nil) + checkDenyACLWrite(t, authz, "foo", nil) + checkAllowAgentRead(t, authz, "foo", nil) + checkAllowAgentWrite(t, authz, "foo", nil) + checkAllowEventRead(t, authz, "foo", nil) + checkAllowEventWrite(t, authz, "foo", nil) + checkAllowIntentionDefaultAllow(t, authz, "foo", nil) + checkAllowIntentionRead(t, authz, "foo", nil) + checkAllowIntentionWrite(t, authz, "foo", nil) + checkAllowKeyRead(t, authz, "foo", nil) + checkAllowKeyList(t, authz, "foo", nil) + checkAllowKeyringRead(t, authz, "foo", nil) + checkAllowKeyringWrite(t, authz, "foo", nil) + checkAllowKeyWrite(t, authz, "foo", nil) + checkAllowKeyWritePrefix(t, authz, "foo", nil) + checkAllowNodeRead(t, authz, "foo", nil) + checkAllowNodeWrite(t, authz, "foo", nil) + checkAllowOperatorRead(t, authz, "foo", nil) + checkAllowOperatorWrite(t, authz, "foo", nil) + checkAllowPreparedQueryRead(t, authz, "foo", nil) + checkAllowPreparedQueryWrite(t, authz, "foo", nil) + checkAllowServiceRead(t, authz, "foo", nil) + checkAllowServiceWrite(t, authz, "foo", nil) + checkAllowSessionRead(t, authz, "foo", nil) + checkAllowSessionWrite(t, authz, "foo", nil) + checkDenySnapshot(t, authz, "foo", nil) + }) + + t.Run("DenyAll", func(t *testing.T) { + t.Parallel() + authz := DenyAll() + checkDenyACLRead(t, authz, "foo", nil) + checkDenyACLWrite(t, authz, "foo", nil) + checkDenyAgentRead(t, authz, "foo", nil) + checkDenyAgentWrite(t, authz, "foo", nil) + checkDenyEventRead(t, authz, "foo", nil) + checkDenyEventWrite(t, authz, "foo", nil) + checkDenyIntentionDefaultAllow(t, authz, "foo", nil) + checkDenyIntentionRead(t, authz, "foo", nil) + checkDenyIntentionWrite(t, authz, "foo", nil) + checkDenyKeyRead(t, authz, "foo", nil) + checkDenyKeyList(t, authz, "foo", nil) + checkDenyKeyringRead(t, authz, "foo", nil) + checkDenyKeyringWrite(t, authz, "foo", nil) + checkDenyKeyWrite(t, authz, "foo", nil) + checkDenyKeyWritePrefix(t, authz, "foo", nil) + checkDenyNodeRead(t, authz, "foo", nil) + checkDenyNodeWrite(t, authz, "foo", nil) + checkDenyOperatorRead(t, authz, "foo", nil) + checkDenyOperatorWrite(t, authz, "foo", nil) + checkDenyPreparedQueryRead(t, authz, "foo", nil) + checkDenyPreparedQueryWrite(t, authz, "foo", nil) + checkDenyServiceRead(t, authz, "foo", nil) + checkDenyServiceWrite(t, authz, "foo", nil) + checkDenySessionRead(t, authz, "foo", nil) + checkDenySessionWrite(t, authz, "foo", nil) + checkDenySnapshot(t, authz, "foo", nil) + }) + + t.Run("ManageAll", func(t *testing.T) { + t.Parallel() + authz := ManageAll() + checkAllowACLRead(t, authz, "foo", nil) + checkAllowACLWrite(t, authz, "foo", nil) + checkAllowAgentRead(t, authz, "foo", nil) + checkAllowAgentWrite(t, authz, "foo", nil) + checkAllowEventRead(t, authz, "foo", nil) + checkAllowEventWrite(t, authz, "foo", nil) + checkAllowIntentionDefaultAllow(t, authz, "foo", nil) + checkAllowIntentionRead(t, authz, "foo", nil) + checkAllowIntentionWrite(t, authz, "foo", nil) + checkAllowKeyRead(t, authz, "foo", nil) + checkAllowKeyList(t, authz, "foo", nil) + checkAllowKeyringRead(t, authz, "foo", nil) + checkAllowKeyringWrite(t, authz, "foo", nil) + checkAllowKeyWrite(t, authz, "foo", nil) + checkAllowKeyWritePrefix(t, authz, "foo", nil) + checkAllowNodeRead(t, authz, "foo", nil) + checkAllowNodeWrite(t, authz, "foo", nil) + checkAllowOperatorRead(t, authz, "foo", nil) + checkAllowOperatorWrite(t, authz, "foo", nil) + checkAllowPreparedQueryRead(t, authz, "foo", nil) + checkAllowPreparedQueryWrite(t, authz, "foo", nil) + checkAllowServiceRead(t, authz, "foo", nil) + checkAllowServiceWrite(t, authz, "foo", nil) + checkAllowSessionRead(t, authz, "foo", nil) + checkAllowSessionWrite(t, authz, "foo", nil) + checkAllowSnapshot(t, authz, "foo", nil) + }) +} diff --git a/agent/acl.go b/agent/acl.go index 0111af08e0..403639ec5b 100644 --- a/agent/acl.go +++ b/agent/acl.go @@ -41,20 +41,22 @@ func (a *Agent) initializeACLs() error { // only. This used to allow a prefix match on agent names but that seems // entirely unnecessary so it is now using an exact match. policy := &acl.Policy{ - Agents: []*acl.AgentPolicy{ - &acl.AgentPolicy{ - Node: a.config.NodeName, - Policy: acl.PolicyWrite, + PolicyRules: acl.PolicyRules{ + Agents: []*acl.AgentRule{ + &acl.AgentRule{ + Node: a.config.NodeName, + Policy: acl.PolicyWrite, + }, }, - }, - NodePrefixes: []*acl.NodePolicy{ - &acl.NodePolicy{ - Name: "", - Policy: acl.PolicyRead, + NodePrefixes: []*acl.NodeRule{ + &acl.NodeRule{ + Name: "", + Policy: acl.PolicyRead, + }, }, }, } - master, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + master, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { return err } @@ -75,14 +77,16 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e } // Vet the service itself. - if !rule.ServiceWrite(service.Service, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(service.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } // Vet any service that might be getting overwritten. services := a.State.Services() if existing, ok := services[service.ID]; ok { - if !rule.ServiceWrite(existing.Service, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(existing.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -90,7 +94,8 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e // If the service is a proxy, ensure that it has write on the destination too // since it can be discovered as an instance of that service. if service.Kind == structs.ServiceKindConnectProxy { - if !rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -113,7 +118,8 @@ func (a *Agent) vetServiceUpdate(token string, serviceID string) error { // Vet any changes based on the existing services's info. services := a.State.Services() if existing, ok := services[serviceID]; ok { - if !rule.ServiceWrite(existing.Service, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(existing.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } } else { @@ -137,11 +143,13 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error // Vet the check itself. if len(check.ServiceName) > 0 { - if !rule.ServiceWrite(check.ServiceName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(check.ServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } else { - if !rule.NodeWrite(a.config.NodeName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.NodeWrite(a.config.NodeName, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -150,11 +158,13 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error checks := a.State.Checks() if existing, ok := checks[check.CheckID]; ok { if len(existing.ServiceName) > 0 { - if !rule.ServiceWrite(existing.ServiceName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(existing.ServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } else { - if !rule.NodeWrite(a.config.NodeName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.NodeWrite(a.config.NodeName, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -178,11 +188,13 @@ func (a *Agent) vetCheckUpdate(token string, checkID types.CheckID) error { checks := a.State.Checks() if existing, ok := checks[checkID]; ok { if len(existing.ServiceName) > 0 { - if !rule.ServiceWrite(existing.ServiceName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceWrite(existing.ServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } else { - if !rule.NodeWrite(a.config.NodeName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.NodeWrite(a.config.NodeName, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -208,7 +220,8 @@ func (a *Agent) filterMembers(token string, members *[]serf.Member) error { m := *members for i := 0; i < len(m); i++ { node := m[i].Name - if rule.NodeRead(node) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.NodeRead(node, nil) == acl.Allow { continue } a.logger.Printf("[DEBUG] agent: dropping node %q from result due to ACLs", node) @@ -232,7 +245,8 @@ func (a *Agent) filterServices(token string, services *map[string]*structs.NodeS // Filter out services based on the service policy. for id, service := range *services { - if rule.ServiceRead(service.Service) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceRead(service.Service, nil) == acl.Allow { continue } a.logger.Printf("[DEBUG] agent: dropping service %q from result due to ACLs", id) @@ -255,11 +269,13 @@ func (a *Agent) filterChecks(token string, checks *map[types.CheckID]*structs.He // Filter out checks based on the node or service policy. for id, check := range *checks { if len(check.ServiceName) > 0 { - if rule.ServiceRead(check.ServiceName) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.ServiceRead(check.ServiceName, nil) == acl.Allow { continue } } else { - if rule.NodeRead(a.config.NodeName) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.NodeRead(a.config.NodeName, nil) == acl.Allow { continue } } diff --git a/agent/acl_endpoint.go b/agent/acl_endpoint.go index db456ff715..22f26e3307 100644 --- a/agent/acl_endpoint.go +++ b/agent/acl_endpoint.go @@ -110,7 +110,8 @@ func (s *HTTPServer) ACLRulesTranslate(resp http.ResponseWriter, req *http.Reque } // Should this require lesser permissions? Really the only reason to require authorization at all is // to prevent external entities from DoS Consul with repeated rule translation requests - if rule != nil && !rule.ACLRead() { + // TODO (namespaces) - pass through a real ent authz ctx + if rule != nil && rule.ACLRead(nil) != acl.Allow { return nil, acl.ErrPermissionDenied } diff --git a/agent/acl_test.go b/agent/acl_test.go index ffc7e00ac9..ae46d8e905 100644 --- a/agent/acl_test.go +++ b/agent/acl_test.go @@ -195,10 +195,10 @@ func TestACL_AgentMasterToken(t *testing.T) { require.NotNil(t, authz) require.Nil(t, err) - require.True(t, authz.AgentRead(a.config.NodeName)) - require.True(t, authz.AgentWrite(a.config.NodeName)) - require.True(t, authz.NodeRead("foobarbaz")) - require.False(t, authz.NodeWrite("foobarbaz", nil)) + require.Equal(t, acl.Allow, authz.AgentRead(a.config.NodeName, nil)) + require.Equal(t, acl.Allow, authz.AgentWrite(a.config.NodeName, nil)) + require.Equal(t, acl.Allow, authz.NodeRead("foobarbaz", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foobarbaz", nil)) } func TestACL_RootAuthorizersDenied(t *testing.T) { @@ -225,7 +225,7 @@ func TestACL_RootAuthorizersDenied(t *testing.T) { } func authzFromPolicy(policy *acl.Policy) (acl.Authorizer, error) { - return acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + return acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) } // catalogPolicy supplies some standard policies to help with testing the @@ -235,32 +235,42 @@ func catalogPolicy(token string) (acl.Authorizer, error) { case "node-ro": return authzFromPolicy(&acl.Policy{ - NodePrefixes: []*acl.NodePolicy{ - &acl.NodePolicy{Name: "Node", Policy: "read"}, + PolicyRules: acl.PolicyRules{ + NodePrefixes: []*acl.NodeRule{ + &acl.NodeRule{Name: "Node", Policy: "read"}, + }, }, }) case "node-rw": return authzFromPolicy(&acl.Policy{ - NodePrefixes: []*acl.NodePolicy{ - &acl.NodePolicy{Name: "Node", Policy: "write"}, + PolicyRules: acl.PolicyRules{ + NodePrefixes: []*acl.NodeRule{ + &acl.NodeRule{Name: "Node", Policy: "write"}, + }, }, }) case "service-ro": return authzFromPolicy(&acl.Policy{ - ServicePrefixes: []*acl.ServicePolicy{ - &acl.ServicePolicy{Name: "service", Policy: "read"}, + PolicyRules: acl.PolicyRules{ + ServicePrefixes: []*acl.ServiceRule{ + &acl.ServiceRule{Name: "service", Policy: "read"}, + }, }, }) case "service-rw": return authzFromPolicy(&acl.Policy{ - ServicePrefixes: []*acl.ServicePolicy{ - &acl.ServicePolicy{Name: "service", Policy: "write"}, + PolicyRules: acl.PolicyRules{ + ServicePrefixes: []*acl.ServiceRule{ + &acl.ServiceRule{Name: "service", Policy: "write"}, + }, }, }) case "other-rw": return authzFromPolicy(&acl.Policy{ - ServicePrefixes: []*acl.ServicePolicy{ - &acl.ServicePolicy{Name: "other", Policy: "write"}, + PolicyRules: acl.PolicyRules{ + ServicePrefixes: []*acl.ServiceRule{ + &acl.ServiceRule{Name: "other", Policy: "write"}, + }, }, }) default: diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 69f5271618..23fbe38e98 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -48,7 +48,7 @@ func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (int if err != nil { return nil, err } - if rule != nil && !rule.AgentRead(s.agent.config.NodeName) { + if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -101,7 +101,7 @@ func (s *HTTPServer) AgentMetrics(resp http.ResponseWriter, req *http.Request) ( if err != nil { return nil, err } - if rule != nil && !rule.AgentRead(s.agent.config.NodeName) { + if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } if enablePrometheusOutput(req) { @@ -130,7 +130,7 @@ func (s *HTTPServer) AgentReload(resp http.ResponseWriter, req *http.Request) (i if err != nil { return nil, err } - if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) { + if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -281,7 +281,8 @@ func (s *HTTPServer) AgentService(resp http.ResponseWriter, req *http.Request) ( if err != nil { return "", nil, err } - if rule != nil && !rule.ServiceRead(svc.Service) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule != nil && rule.ServiceRead(svc.Service, nil) != acl.Allow { return "", nil, acl.ErrPermissionDenied } @@ -388,7 +389,7 @@ func (s *HTTPServer) AgentJoin(resp http.ResponseWriter, req *http.Request) (int if err != nil { return nil, err } - if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) { + if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -416,7 +417,7 @@ func (s *HTTPServer) AgentLeave(resp http.ResponseWriter, req *http.Request) (in if err != nil { return nil, err } - if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) { + if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -434,7 +435,7 @@ func (s *HTTPServer) AgentForceLeave(resp http.ResponseWriter, req *http.Request if err != nil { return nil, err } - if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) { + if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -1049,7 +1050,8 @@ func (s *HTTPServer) AgentNodeMaintenance(resp http.ResponseWriter, req *http.Re if err != nil { return nil, err } - if rule != nil && !rule.NodeWrite(s.agent.config.NodeName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx? + if rule != nil && rule.NodeWrite(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -1070,7 +1072,7 @@ func (s *HTTPServer) AgentMonitor(resp http.ResponseWriter, req *http.Request) ( if err != nil { return nil, err } - if rule != nil && !rule.AgentRead(s.agent.config.NodeName) { + if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -1165,7 +1167,7 @@ func (s *HTTPServer) AgentToken(resp http.ResponseWriter, req *http.Request) (in if err != nil { return nil, err } - if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) { + if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow { return nil, acl.ErrPermissionDenied } @@ -1370,7 +1372,8 @@ func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (int return nil, err } - if rule != nil && !rule.OperatorRead() { + // TODO (namespaces) - pass through a real ent authz ctx + if rule != nil && rule.OperatorRead(nil) != acl.Allow { return nil, acl.ErrPermissionDenied } diff --git a/agent/connect_auth.go b/agent/connect_auth.go index bda5446afe..fc07fe60fb 100644 --- a/agent/connect_auth.go +++ b/agent/connect_auth.go @@ -57,7 +57,8 @@ func (a *Agent) ConnectAuthorize(token string, if err != nil { return returnErr(err) } - if rule != nil && !rule.ServiceWrite(req.Target, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule != nil && rule.ServiceWrite(req.Target, nil) != acl.Allow { return returnErr(acl.ErrPermissionDenied) } @@ -115,5 +116,6 @@ func (a *Agent) ConnectAuthorize(token string, return true, "ACLs disabled, access is allowed by default", &meta, nil } reason = "Default behavior configured by ACLs" - return rule.IntentionDefaultAllow(), reason, &meta, nil + // TODO (namespaces) - pass through a real ent authz ctx + return rule.IntentionDefaultAllow(nil) == acl.Allow, reason, &meta, nil } diff --git a/agent/consul/acl.go b/agent/consul/acl.go index 0a7ca3c16c..ae3fefb8b8 100644 --- a/agent/consul/acl.go +++ b/agent/consul/acl.go @@ -11,8 +11,6 @@ import ( metrics "github.com/armon/go-metrics" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sentinel" "golang.org/x/sync/singleflight" "golang.org/x/time/rate" ) @@ -126,7 +124,8 @@ type ACLResolverConfig struct { // so that it can detect when the servers have gotten ACLs enabled. AutoDisable bool - Sentinel sentinel.Evaluator + // EnterpriseACLConfig contains Consul Enterprise specific ACL configuration + EnterpriseConfig *acl.EnterpriseACLConfig } // ACLResolver is the type to handle all your token and policy resolution needs. @@ -159,7 +158,7 @@ type ACLResolver struct { logger *log.Logger delegate ACLResolverDelegate - sentinel sentinel.Evaluator + entConf *acl.EnterpriseACLConfig cache *structs.ACLCaches identityGroup singleflight.Group @@ -212,7 +211,7 @@ func NewACLResolver(config *ACLResolverConfig) (*ACLResolver, error) { config: config.Config, logger: config.Logger, delegate: config.Delegate, - sentinel: config.Sentinel, + entConf: config.EnterpriseConfig, cache: cache, autoDisable: config.AutoDisable, down: down, @@ -249,7 +248,7 @@ func (r *ACLResolver) fetchAndCacheTokenLegacy(token string, cached *structs.Aut policies = append(policies, policy.ConvertFromLegacy()) } - authorizer, err := acl.NewPolicyAuthorizer(parent, policies, r.sentinel) + authorizer, err := acl.NewPolicyAuthorizerWithDefaults(parent, policies, r.entConf) r.cache.PutAuthorizerWithTTL(token, authorizer, reply.TTL) return authorizer, err @@ -292,7 +291,7 @@ func (r *ACLResolver) resolveTokenLegacy(token string) (acl.Authorizer, error) { return nil, err } - return policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.sentinel) + return policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.entConf) } return nil, err @@ -1005,7 +1004,7 @@ func (r *ACLResolver) ResolveToken(token string) (acl.Authorizer, error) { } // Build the Authorizer - authorizer, err := policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.sentinel) + authorizer, err := policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.entConf) return authorizer, err } @@ -1035,7 +1034,7 @@ func (r *ACLResolver) GetMergedPolicyForToken(token string) (*acl.Policy, error) return nil, acl.ErrNotFound } - return policies.Merge(r.cache, r.sentinel) + return policies.Merge(r.cache, r.entConf) } // aclFilter is used to filter results from our state store based on ACL rules @@ -1059,15 +1058,15 @@ func newACLFilter(authorizer acl.Authorizer, logger *log.Logger, enforceVersion8 } // allowNode is used to determine if a node is accessible for an ACL. -func (f *aclFilter) allowNode(node string) bool { +func (f *aclFilter) allowNode(node string, ent *acl.EnterpriseAuthorizerContext) bool { if !f.enforceVersion8 { return true } - return f.authorizer.NodeRead(node) + return f.authorizer.NodeRead(node, ent) == acl.Allow } // allowService is used to determine if a service is accessible for an ACL. -func (f *aclFilter) allowService(service string) bool { +func (f *aclFilter) allowService(service string, ent *acl.EnterpriseAuthorizerContext) bool { if service == "" { return true } @@ -1075,16 +1074,16 @@ func (f *aclFilter) allowService(service string) bool { if !f.enforceVersion8 && service == structs.ConsulServiceID { return true } - return f.authorizer.ServiceRead(service) + return f.authorizer.ServiceRead(service, ent) == acl.Allow } // allowSession is used to determine if a session for a node is accessible for // an ACL. -func (f *aclFilter) allowSession(node string) bool { +func (f *aclFilter) allowSession(node string, ent *acl.EnterpriseAuthorizerContext) bool { if !f.enforceVersion8 { return true } - return f.authorizer.SessionRead(node) + return f.authorizer.SessionRead(node, ent) == acl.Allow } // filterHealthChecks is used to filter a set of health checks down based on @@ -1093,7 +1092,8 @@ func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) { hc := *checks for i := 0; i < len(hc); i++ { check := hc[i] - if f.allowNode(check.Node) && f.allowService(check.ServiceName) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowNode(check.Node, nil) && f.allowService(check.ServiceName, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", check.CheckID) @@ -1106,7 +1106,8 @@ func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) { // filterServices is used to filter a set of services based on ACLs. func (f *aclFilter) filterServices(services structs.Services) { for svc := range services { - if f.allowService(svc) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowService(svc, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc) @@ -1120,7 +1121,8 @@ func (f *aclFilter) filterServiceNodes(nodes *structs.ServiceNodes) { sn := *nodes for i := 0; i < len(sn); i++ { node := sn[i] - if f.allowNode(node.Node) && f.allowService(node.ServiceName) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowNode(node.Node, nil) && f.allowService(node.ServiceName, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node) @@ -1136,13 +1138,15 @@ func (f *aclFilter) filterNodeServices(services **structs.NodeServices) { return } - if !f.allowNode((*services).Node.Node) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if !f.allowNode((*services).Node.Node, nil) { *services = nil return } for svc := range (*services).Services { - if f.allowService(svc) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowService(svc, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc) @@ -1155,7 +1159,8 @@ func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) { csn := *nodes for i := 0; i < len(csn); i++ { node := csn[i] - if f.allowNode(node.Node.Node) && f.allowService(node.Service.Service) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowNode(node.Node.Node, nil) && f.allowService(node.Service.Service, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node.Node) @@ -1170,7 +1175,8 @@ func (f *aclFilter) filterSessions(sessions *structs.Sessions) { s := *sessions for i := 0; i < len(s); i++ { session := s[i] - if f.allowSession(session.Node) { + // TODO (namespaces) update to call with an actual ent authz context once sessions supports ns + if f.allowSession(session.Node, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping session %q from result due to ACLs", session.ID) @@ -1186,7 +1192,8 @@ func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) { c := *coords for i := 0; i < len(c); i++ { node := c[i].Node - if f.allowNode(node) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowNode(node, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node) @@ -1201,7 +1208,8 @@ func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) { // if the user doesn't have a management token. func (f *aclFilter) filterIntentions(ixns *structs.Intentions) { // Management tokens can see everything with no filtering. - if f.authorizer.ACLRead() { + // TODO (namespaces) update to call with an actual ent authz context once acls support it + if f.authorizer.ACLRead(nil) == acl.Allow { return } @@ -1212,7 +1220,8 @@ func (f *aclFilter) filterIntentions(ixns *structs.Intentions) { // we know at this point the user doesn't have a management // token, otherwise see what the policy says. prefix, ok := ixn.GetACLPrefix() - if !ok || !f.authorizer.IntentionRead(prefix) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if !ok || f.authorizer.IntentionRead(prefix, nil) != acl.Allow { f.logger.Printf("[DEBUG] consul: dropping intention %q from result due to ACLs", ixn.ID) continue } @@ -1231,7 +1240,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) { info := nd[i] // Filter nodes - if node := info.Node; !f.allowNode(node) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if node := info.Node; !f.allowNode(node, nil) { f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node) nd = append(nd[:i], nd[i+1:]...) i-- @@ -1241,7 +1251,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) { // Filter services for j := 0; j < len(info.Services); j++ { svc := info.Services[j].Service - if f.allowService(svc) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowService(svc, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc) @@ -1252,7 +1263,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) { // Filter checks for j := 0; j < len(info.Checks); j++ { chk := info.Checks[j] - if f.allowService(chk.ServiceName) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowService(chk.ServiceName, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", chk.CheckID) @@ -1269,7 +1281,8 @@ func (f *aclFilter) filterNodes(nodes *structs.Nodes) { n := *nodes for i := 0; i < len(n); i++ { node := n[i].Node - if f.allowNode(node) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if f.allowNode(node, nil) { continue } f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node) @@ -1287,7 +1300,8 @@ func (f *aclFilter) filterNodes(nodes *structs.Nodes) { // captured tokens, but they can at least see whether or not a token is set. func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) { // Management tokens can see everything with no filtering. - if f.authorizer.ACLWrite() { + // TODO (namespaces) update to call with an actual ent authz context once acls support it + if f.authorizer.ACLWrite(nil) == acl.Allow { return } @@ -1312,7 +1326,11 @@ func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) { // if the user doesn't have a management token. func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) { // Management tokens can see everything with no filtering. - if f.authorizer.ACLWrite() { + // TODO (namespaces) update to call with an actual ent authz context once acls support it + // TODO (namespaces) is this check even necessary - this looks like a search replace from + // the 1.4 ACL rewrite. The global-management token will provide unrestricted query privileges + // so asking for ACLWrite should be unnecessary. + if f.authorizer.ACLWrite(nil) == acl.Allow { return } @@ -1323,7 +1341,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) { // we know at this point the user doesn't have a management // token, otherwise see what the policy says. prefix, ok := query.GetACLPrefix() - if !ok || !f.authorizer.PreparedQueryRead(prefix) { + if !ok || f.authorizer.PreparedQueryRead(prefix, nil) != acl.Allow { f.logger.Printf("[DEBUG] consul: dropping prepared query %q from result due to ACLs", query.ID) continue } @@ -1338,7 +1356,8 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) { } func (f *aclFilter) redactTokenSecret(token **structs.ACLToken) { - if token == nil || *token == nil || f == nil || f.authorizer.ACLWrite() { + // TODO (namespaces) update to call with an actual ent authz context once acls support it + if token == nil || *token == nil || f == nil || f.authorizer.ACLWrite(nil) == acl.Allow { return } clone := *(*token) @@ -1454,46 +1473,49 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest, return nil } + // TODO (namespaces) update to create a sentinel scope - technically we never check this + // scope but we used to set it so we probably should continue? // This gets called potentially from a few spots so we save it and // return the structure we made if we have it. - var memo map[string]interface{} - scope := func() map[string]interface{} { - if memo != nil { - return memo - } + // var memo map[string]interface{} + // scope := func() map[string]interface{} { + // if memo != nil { + // return memo + // } - node := &api.Node{ - ID: string(subj.ID), - Node: subj.Node, - Address: subj.Address, - Datacenter: subj.Datacenter, - TaggedAddresses: subj.TaggedAddresses, - Meta: subj.NodeMeta, - } + // node := &api.Node{ + // ID: string(subj.ID), + // Node: subj.Node, + // Address: subj.Address, + // Datacenter: subj.Datacenter, + // TaggedAddresses: subj.TaggedAddresses, + // Meta: subj.NodeMeta, + // } - var service *api.AgentService - if subj.Service != nil { - service = &api.AgentService{ - ID: subj.Service.ID, - Service: subj.Service.Service, - Tags: subj.Service.Tags, - Meta: subj.Service.Meta, - Address: subj.Service.Address, - Port: subj.Service.Port, - EnableTagOverride: subj.Service.EnableTagOverride, - } - } + // var service *api.AgentService + // if subj.Service != nil { + // service = &api.AgentService{ + // ID: subj.Service.ID, + // Service: subj.Service.Service, + // Tags: subj.Service.Tags, + // Meta: subj.Service.Meta, + // Address: subj.Service.Address, + // Port: subj.Service.Port, + // EnableTagOverride: subj.Service.EnableTagOverride, + // } + // } - memo = sentinel.ScopeCatalogUpsert(node, service) - return memo - } + // memo = sentinel.ScopeCatalogUpsert(node, service) + // return memo + // } // Vet the node info. This allows service updates to re-post the required // node info for each request without having to have node "write" // privileges. needsNode := ns == nil || subj.ChangesNode(ns.Node) - if needsNode && !rule.NodeWrite(subj.Node, scope) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if needsNode && rule.NodeWrite(subj.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1501,7 +1523,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest, // the given service, and that we can write to any existing service that // is being modified by id (if any). if subj.Service != nil { - if !rule.ServiceWrite(subj.Service.Service, scope) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.ServiceWrite(subj.Service.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1511,7 +1534,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest, // This is effectively a delete, so we DO NOT apply the // sentinel scope to the service we are overwriting, just // the regular ACL policy. - if ok && !rule.ServiceWrite(other.Service, nil) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if ok && rule.ServiceWrite(other.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -1540,7 +1564,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest, // Node-level check. if check.ServiceID == "" { - if !rule.NodeWrite(subj.Node, scope) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.NodeWrite(subj.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } continue @@ -1568,7 +1593,7 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest, // We are only adding a check here, so we don't add the scope, // since the sentinel policy doesn't apply to adding checks at // this time. - if !rule.ServiceWrite(other.Service, nil) { + if rule.ServiceWrite(other.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -1595,7 +1620,8 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest, // Allow service deregistration if the token has write permission for the node. // This accounts for cases where the agent no longer has a token with write permission // on the service to deregister it. - if rule.NodeWrite(subj.Node, nil) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.NodeWrite(subj.Node, nil) == acl.Allow { return nil } @@ -1607,7 +1633,8 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest, if ns == nil { return fmt.Errorf("Unknown service '%s'", subj.ServiceID) } - if !rule.ServiceWrite(ns.Service, nil) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.ServiceWrite(ns.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } } else if subj.CheckID != "" { @@ -1615,11 +1642,13 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest, return fmt.Errorf("Unknown check '%s'", subj.CheckID) } if nc.ServiceID != "" { - if !rule.ServiceWrite(nc.ServiceName, nil) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.ServiceWrite(nc.ServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } else { - if !rule.NodeWrite(subj.Node, nil) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.NodeWrite(subj.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -1641,24 +1670,27 @@ func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error { node := op.Node - n := &api.Node{ - Node: node.Node, - ID: string(node.ID), - Address: node.Address, - Datacenter: node.Datacenter, - TaggedAddresses: node.TaggedAddresses, - Meta: node.Meta, - } + // TODO (namespaces) uncomment once we bring back sentinel scope creation in the authz ctx + // n := &api.Node{ + // Node: node.Node, + // ID: string(node.ID), + // Address: node.Address, + // Datacenter: node.Datacenter, + // TaggedAddresses: node.TaggedAddresses, + // Meta: node.Meta, + // } + // TODO (namespaces) update to create a authz context with a scope once the catalog supports it // Sentinel doesn't apply to deletes, only creates/updates, so we don't need the scopeFn. - var scope func() map[string]interface{} - if op.Verb != api.NodeDelete && op.Verb != api.NodeDeleteCAS { - scope = func() map[string]interface{} { - return sentinel.ScopeCatalogUpsert(n, nil) - } - } + // var scope func() map[string]interface{} + // if op.Verb != api.NodeDelete && op.Verb != api.NodeDeleteCAS { + // scope = func() map[string]interface{} { + // return sentinel.ScopeCatalogUpsert(n, nil) + // } + // } - if rule != nil && !rule.NodeWrite(node.Node, scope) { + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule != nil && rule.NodeWrite(node.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1674,23 +1706,25 @@ func vetServiceTxnOp(op *structs.TxnServiceOp, rule acl.Authorizer) error { service := op.Service - n := &api.Node{Node: op.Node} - svc := &api.AgentService{ - ID: service.ID, - Service: service.Service, - Tags: service.Tags, - Meta: service.Meta, - Address: service.Address, - Port: service.Port, - EnableTagOverride: service.EnableTagOverride, - } - var scope func() map[string]interface{} - if op.Verb != api.ServiceDelete && op.Verb != api.ServiceDeleteCAS { - scope = func() map[string]interface{} { - return sentinel.ScopeCatalogUpsert(n, svc) - } - } - if !rule.ServiceWrite(service.Service, scope) { + // TODO (namespaces) update to create authz context with the sentinel scope + // n := &api.Node{Node: op.Node} + // svc := &api.AgentService{ + // ID: service.ID, + // Service: service.Service, + // Tags: service.Tags, + // Meta: service.Meta, + // Address: service.Address, + // Port: service.Port, + // EnableTagOverride: service.EnableTagOverride, + // } + // var scope func() map[string]interface{} + // if op.Verb != api.ServiceDelete && op.Verb != api.ServiceDeleteCAS { + // scope = func() map[string]interface{} { + // return sentinel.ScopeCatalogUpsert(n, svc) + // } + // } + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.ServiceWrite(service.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1704,31 +1738,36 @@ func vetCheckTxnOp(op *structs.TxnCheckOp, rule acl.Authorizer) error { return nil } - n := &api.Node{Node: op.Check.Node} - svc := &api.AgentService{ - ID: op.Check.ServiceID, - Service: op.Check.ServiceID, - Tags: op.Check.ServiceTags, - } - var scope func() map[string]interface{} + // TODO (namespaces) uncomment once these are used for sentinel scope creation + // n := &api.Node{Node: op.Check.Node} + // svc := &api.AgentService{ + // ID: op.Check.ServiceID, + // Service: op.Check.ServiceID, + // Tags: op.Check.ServiceTags, + // } + // var scope func() map[string]interface{} if op.Check.ServiceID == "" { // Node-level check. - if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS { - scope = func() map[string]interface{} { - return sentinel.ScopeCatalogUpsert(n, svc) - } - } - if !rule.NodeWrite(op.Check.Node, scope) { + // TODO (namespaces) update to create authz with sentinel scope + // if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS { + // scope = func() map[string]interface{} { + // return sentinel.ScopeCatalogUpsert(n, svc) + // } + // } + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.NodeWrite(op.Check.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } } else { // Service-level check. - if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS { - scope = func() map[string]interface{} { - return sentinel.ScopeCatalogUpsert(n, svc) - } - } - if !rule.ServiceWrite(op.Check.ServiceName, scope) { + // TODO (namespaces) update to create authz with sentinel scope + // if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS { + // scope = func() map[string]interface{} { + // return sentinel.ScopeCatalogUpsert(n, svc) + // } + // } + // TODO (namespaces) update to call with an actual ent authz context once the catalog supports it + if rule.ServiceWrite(op.Check.ServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index fda4b302a9..0ba4b02ab7 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -206,9 +206,10 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke // Only ACLRead privileges are required to list tokens // However if you do not have ACLWrite as well the token // secrets will be redacted + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err = a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -223,7 +224,8 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke index, token, err = state.ACLTokenGetByAccessor(ws, args.TokenID) if token != nil { a.srv.filterACLWithAuthorizer(rule, &token) - if !rule.ACLWrite() { + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it + if rule.ACLWrite(nil) != acl.Allow { reply.Redacted = true } } @@ -261,9 +263,10 @@ func (a *ACL) TokenClone(args *structs.ACLTokenSetRequest, reply *structs.ACLTok defer metrics.MeasureSince([]string{"acl", "token", "clone"}, time.Now()) + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -324,9 +327,10 @@ func (a *ACL) TokenSet(args *structs.ACLTokenSetRequest, reply *structs.ACLToken defer metrics.MeasureSince([]string{"acl", "token", "upsert"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -683,9 +687,10 @@ func (a *ACL) TokenDelete(args *structs.ACLTokenDeleteRequest, reply *string) er defer metrics.MeasureSince([]string{"acl", "token", "delete"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -760,9 +765,10 @@ func (a *ACL) TokenList(args *structs.ACLTokenListRequest, reply *structs.ACLTok } rule, err := a.srv.ResolveToken(args.Token) + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -801,9 +807,10 @@ func (a *ACL) TokenBatchRead(args *structs.ACLTokenBatchGetRequest, reply *struc } rule, err := a.srv.ResolveToken(args.Token) + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -819,7 +826,8 @@ func (a *ACL) TokenBatchRead(args *structs.ACLTokenBatchGetRequest, reply *struc a.srv.filterACLWithAuthorizer(rule, &tokens) reply.Index, reply.Tokens = index, tokens - reply.Redacted = !rule.ACLWrite() + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it + reply.Redacted = rule.ACLWrite(nil) != acl.Allow return nil }) } @@ -835,7 +843,7 @@ func (a *ACL) PolicyRead(args *structs.ACLPolicyGetRequest, reply *structs.ACLPo if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -863,7 +871,7 @@ func (a *ACL) PolicyBatchRead(args *structs.ACLPolicyBatchGetRequest, reply *str if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -895,9 +903,10 @@ func (a *ACL) PolicySet(args *structs.ACLPolicySetRequest, reply *structs.ACLPol defer metrics.MeasureSince([]string{"acl", "policy", "upsert"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -971,7 +980,7 @@ func (a *ACL) PolicySet(args *structs.ACLPolicySetRequest, reply *structs.ACLPol } // validate the rules - _, err = acl.NewPolicyFromSource("", 0, policy.Rules, policy.Syntax, a.srv.sentinel) + _, err = acl.NewPolicyFromSource("", 0, policy.Rules, policy.Syntax, a.srv.enterpriseACLConfig) if err != nil { return err } @@ -1018,9 +1027,10 @@ func (a *ACL) PolicyDelete(args *structs.ACLPolicyDeleteRequest, reply *string) defer metrics.MeasureSince([]string{"acl", "policy", "delete"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1068,9 +1078,10 @@ func (a *ACL) PolicyList(args *structs.ACLPolicyListRequest, reply *structs.ACLP return err } + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1213,9 +1224,10 @@ func (a *ACL) RoleRead(args *structs.ACLRoleGetRequest, reply *structs.ACLRoleRe return err } + // TODO (namespaces) update to create and use actual enterprise authorizer context if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1250,9 +1262,10 @@ func (a *ACL) RoleBatchRead(args *structs.ACLRoleBatchGetRequest, reply *structs return err } + // TODO (namespaces) update to create and use actual enterprise authorizer context if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1284,9 +1297,10 @@ func (a *ACL) RoleSet(args *structs.ACLRoleSetRequest, reply *structs.ACLRole) e defer metrics.MeasureSince([]string{"acl", "role", "upsert"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1422,9 +1436,10 @@ func (a *ACL) RoleDelete(args *structs.ACLRoleDeleteRequest, reply *string) erro defer metrics.MeasureSince([]string{"acl", "role", "delete"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1468,9 +1483,10 @@ func (a *ACL) RoleList(args *structs.ACLRoleListRequest, reply *structs.ACLRoleL return err } + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1544,9 +1560,10 @@ func (a *ACL) BindingRuleRead(args *structs.ACLBindingRuleGetRequest, reply *str return err } + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1579,9 +1596,10 @@ func (a *ACL) BindingRuleSet(args *structs.ACLBindingRuleSetRequest, reply *stru defer metrics.MeasureSince([]string{"acl", "bindingrule", "upsert"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1696,9 +1714,10 @@ func (a *ACL) BindingRuleDelete(args *structs.ACLBindingRuleDeleteRequest, reply defer metrics.MeasureSince([]string{"acl", "bindingrule", "delete"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1742,9 +1761,10 @@ func (a *ACL) BindingRuleList(args *structs.ACLBindingRuleListRequest, reply *st return err } + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1773,9 +1793,10 @@ func (a *ACL) AuthMethodRead(args *structs.ACLAuthMethodGetRequest, reply *struc return err } + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1808,9 +1829,10 @@ func (a *ACL) AuthMethodSet(args *structs.ACLAuthMethodSetRequest, reply *struct defer metrics.MeasureSince([]string{"acl", "authmethod", "upsert"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1885,9 +1907,10 @@ func (a *ACL) AuthMethodDelete(args *structs.ACLAuthMethodDeleteRequest, reply * defer metrics.MeasureSince([]string{"acl", "authmethod", "delete"}, time.Now()) // Verify token is permitted to modify ACLs + // TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -1931,9 +1954,10 @@ func (a *ACL) AuthMethodList(args *structs.ACLAuthMethodListRequest, reply *stru return err } + // TODO (namespaces) update to call ACLRead with an authz context once ACLs support it if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLRead() { + } else if rule == nil || rule.ACLRead(nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/acl_endpoint_legacy.go b/agent/consul/acl_endpoint_legacy.go index 16379faa2e..7b18b3d5a0 100644 --- a/agent/consul/acl_endpoint_legacy.go +++ b/agent/consul/acl_endpoint_legacy.go @@ -114,7 +114,7 @@ func aclApplyInternal(srv *Server, args *structs.ACLRequest, reply *string) erro } // Validate the rules compile - _, err := acl.NewPolicyFromSource("", 0, args.ACL.Rules, acl.SyntaxLegacy, srv.sentinel) + _, err := acl.NewPolicyFromSource("", 0, args.ACL.Rules, acl.SyntaxLegacy, srv.enterpriseACLConfig) if err != nil { return fmt.Errorf("ACL rule compilation failed: %v", err) } @@ -160,9 +160,10 @@ func (a *ACL) Apply(args *structs.ACLRequest, reply *string) error { } // Verify token is permitted to modify ACLs + // NOTE: We will not support enterprise authorizer contexts with legacy ACLs if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -198,6 +199,10 @@ func (a *ACL) Get(args *structs.ACLSpecificRequest, return err } + // NOTE: This has no ACL check because legacy ACLs were managed with + // the secrets and therefore the argument to the Get request is + // authorization in and of itself. + // Verify we are allowed to serve this request if !a.srv.ACLsEnabled() { return acl.ErrDisabled @@ -246,9 +251,11 @@ func (a *ACL) List(args *structs.DCSpecificRequest, } // Verify token is permitted to list ACLs + // NOTES: Previously with legacy ACL there was no read-only ACL permissions + // and this check for ACLWrite is basically what it did before. if rule, err := a.srv.ResolveToken(args.Token); err != nil { return err - } else if rule == nil || !rule.ACLWrite() { + } else if rule == nil || rule.ACLWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/acl_endpoint_test.go b/agent/consul/acl_endpoint_test.go index c34197d1a4..3b807e77fb 100644 --- a/agent/consul/acl_endpoint_test.go +++ b/agent/consul/acl_endpoint_test.go @@ -212,7 +212,7 @@ func TestACLEndpoint_Update_PurgeCache(t *testing.T) { if acl1 == nil { t.Fatalf("should not be nil") } - if !acl1.KeyRead("foo") { + if acl1.KeyRead("foo", nil) != acl.Allow { t.Fatalf("should be allowed") } @@ -234,7 +234,7 @@ func TestACLEndpoint_Update_PurgeCache(t *testing.T) { if acl2 == acl1 { t.Fatalf("should not be cached") } - if acl2.KeyRead("foo") { + if acl2.KeyRead("foo", nil) == acl.Allow { t.Fatalf("should not be allowed") } diff --git a/agent/consul/acl_oss.go b/agent/consul/acl_oss.go new file mode 100644 index 0000000000..5f3d4800b7 --- /dev/null +++ b/agent/consul/acl_oss.go @@ -0,0 +1,13 @@ +// +build !consulent + +package consul + +import ( + "log" + + "github.com/hashicorp/consul/acl" +) + +func newEnterpriseACLConfig(*log.Logger) *acl.EnterpriseACLConfig { + return nil +} diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go index 25154dfa48..246b40b7eb 100644 --- a/agent/consul/acl_test.go +++ b/agent/consul/acl_test.go @@ -764,7 +764,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "cached") // from "found" token @@ -774,7 +774,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz2) require.False(t, authz == authz2) - require.False(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz2.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", false, "expired") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", false, "expired") // from "found" token @@ -802,14 +802,14 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) // role cache expired - so we will fail to resolve that role and use the default policy only authz2, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz2) require.False(t, authz == authz2) - require.False(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz2.NodeWrite("foo", nil)) }) t.Run("Extend-Cache-Policy", func(t *testing.T) { @@ -832,7 +832,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requireIdentityCached(t, r, "found", true, "cached") @@ -841,7 +841,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2) - require.True(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) }) t.Run("Extend-Cache-Role", func(t *testing.T) { @@ -864,7 +864,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requireIdentityCached(t, r, "found-role", true, "still cached") @@ -873,7 +873,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2) - require.True(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) }) t.Run("Extend-Cache-Expired-Policy", func(t *testing.T) { @@ -897,7 +897,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "cached") // from "found" token @@ -907,7 +907,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz2) require.True(t, authz == authz2) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "still cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "still cached") // from "found" token @@ -935,14 +935,14 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) // Will just use the policy cache authz2, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz2) require.True(t, authz == authz2) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) }) t.Run("Async-Cache-Expired-Policy", func(t *testing.T) { @@ -968,7 +968,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "cached") // from "found" token @@ -979,7 +979,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "cached") // from "found" token @@ -989,7 +989,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz3, err := r.ResolveToken("found") assert.NoError(t, err) assert.NotNil(t, authz3) - assert.False(t, authz3.NodeWrite("foo", nil)) + assert.Equal(t, acl.Deny, authz3.NodeWrite("foo", nil)) }) requirePolicyCached(t, r, "node-wr", false, "no longer cached") // from "found" token @@ -1020,7 +1020,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) // The identity should have been cached so this should still be valid authz2, err := r.ResolveToken("found-role") @@ -1028,14 +1028,14 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) // the go routine spawned will eventually return with a authz that doesn't have the policy retry.Run(t, func(t *retry.R) { authz3, err := r.ResolveToken("found-role") assert.NoError(t, err) assert.NotNil(t, authz3) - assert.False(t, authz3.NodeWrite("foo", nil)) + assert.Equal(t, acl.Deny, authz3.NodeWrite("foo", nil)) }) }) @@ -1062,7 +1062,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "cached") // from "found" token @@ -1072,7 +1072,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2) - require.True(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) }) t.Run("Extend-Cache-Client-Role", func(t *testing.T) { @@ -1099,7 +1099,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requirePolicyCached(t, r, "node-wr", true, "still cached") // from "found" token requirePolicyCached(t, r, "dc2-key-wr", true, "still cached") // from "found" token @@ -1109,7 +1109,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2, "\n[1]={%+v} != \n[2]={%+v}", authz, authz2) - require.True(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) }) t.Run("Async-Cache", func(t *testing.T) { @@ -1132,7 +1132,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken("found") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) requireIdentityCached(t, r, "found", true, "cached") @@ -1142,7 +1142,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { require.NotNil(t, authz2) // testing pointer equality - these will be the same object because it is cached. require.True(t, authz == authz2) - require.True(t, authz2.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz2.NodeWrite("foo", nil)) requireIdentityCached(t, r, "found", true, "cached") @@ -1205,7 +1205,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken(secretID) require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) // Verify that the caches are setup properly. requireIdentityCached(t, r, secretID, true, "cached") @@ -1267,7 +1267,7 @@ func TestACLResolver_DownPolicy(t *testing.T) { authz, err := r.ResolveToken(secretID) require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) // Verify that the caches are setup properly. requireIdentityCached(t, r, secretID, true, "cached") @@ -1304,9 +1304,9 @@ func TestACLResolver_DatacenterScoping(t *testing.T) { authz, err := r.ResolveToken("found") require.NotNil(t, authz) require.NoError(t, err) - require.False(t, authz.ACLRead()) - require.True(t, authz.NodeWrite("foo", nil)) - require.False(t, authz.KeyWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.KeyWrite("foo", nil)) }) t.Run("dc2", func(t *testing.T) { @@ -1326,9 +1326,9 @@ func TestACLResolver_DatacenterScoping(t *testing.T) { authz, err := r.ResolveToken("found") require.NotNil(t, authz) require.NoError(t, err) - require.False(t, authz.ACLRead()) - require.False(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.KeyWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.KeyWrite("foo", nil)) }) } @@ -1403,8 +1403,8 @@ func TestACLResolver_Client(t *testing.T) { authz, err := r.ResolveToken("a1a54629-5050-4d17-8a4e-560d2423f835") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeWrite("foo", nil)) - require.False(t, authz.ACLRead()) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) require.True(t, modified) require.True(t, deleted) require.Equal(t, int32(2), tokenReads) @@ -1577,49 +1577,49 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega authz, err := r.ResolveToken("missing-policy") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.ACLRead()) - require.False(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) }) runTwiceAndReset("Missing Role", func(t *testing.T) { authz, err := r.ResolveToken("missing-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.ACLRead()) - require.False(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) }) runTwiceAndReset("Missing Policy on Role", func(t *testing.T) { authz, err := r.ResolveToken("missing-policy-on-role") require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.ACLRead()) - require.False(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) }) runTwiceAndReset("Normal with Policy", func(t *testing.T) { authz, err := r.ResolveToken("found") require.NotNil(t, authz) require.NoError(t, err) - require.False(t, authz.ACLRead()) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) }) runTwiceAndReset("Normal with Role", func(t *testing.T) { authz, err := r.ResolveToken("found-role") require.NotNil(t, authz) require.NoError(t, err) - require.False(t, authz.ACLRead()) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) }) runTwiceAndReset("Normal with Policy and Role", func(t *testing.T) { authz, err := r.ResolveToken("found-policy-and-role") require.NotNil(t, authz) require.NoError(t, err) - require.False(t, authz.ACLRead()) - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.ServiceRead("bar")) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("bar", nil)) }) runTwiceAndReset("Synthetic Policies Independently Cache", func(t *testing.T) { @@ -1631,28 +1631,28 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega require.NotNil(t, authz) require.NoError(t, err) // spot check some random perms - require.False(t, authz.ACLRead()) - require.False(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) // ensure we didn't bleed over to the other synthetic policy - require.False(t, authz.ServiceWrite("service2", nil)) + require.Equal(t, acl.Deny, authz.ServiceWrite("service2", nil)) // check our own synthetic policy - require.True(t, authz.ServiceWrite("service1", nil)) - require.True(t, authz.ServiceRead("literally-anything")) - require.True(t, authz.NodeRead("any-node")) + require.Equal(t, acl.Allow, authz.ServiceWrite("service1", nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil)) + require.Equal(t, acl.Allow, authz.NodeRead("any-node", nil)) } { authz, err := r.ResolveToken("found-synthetic-policy-2") require.NotNil(t, authz) require.NoError(t, err) // spot check some random perms - require.False(t, authz.ACLRead()) - require.False(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) // ensure we didn't bleed over to the other synthetic policy - require.False(t, authz.ServiceWrite("service1", nil)) + require.Equal(t, acl.Deny, authz.ServiceWrite("service1", nil)) // check our own synthetic policy - require.True(t, authz.ServiceWrite("service2", nil)) - require.True(t, authz.ServiceRead("literally-anything")) - require.True(t, authz.NodeRead("any-node")) + require.Equal(t, acl.Allow, authz.ServiceWrite("service2", nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil)) + require.Equal(t, acl.Allow, authz.NodeRead("any-node", nil)) } }) @@ -1660,24 +1660,24 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega authz, err := r.ResolveToken("") require.NotNil(t, authz) require.NoError(t, err) - require.False(t, authz.ACLRead()) - require.True(t, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) }) runTwiceAndReset("legacy-management", func(t *testing.T) { authz, err := r.ResolveToken("legacy-management") require.NotNil(t, authz) require.NoError(t, err) - require.True(t, authz.ACLWrite()) - require.True(t, authz.KeyRead("foo")) + require.Equal(t, acl.Allow, authz.ACLWrite(nil)) + require.Equal(t, acl.Allow, authz.KeyRead("foo", nil)) }) runTwiceAndReset("legacy-client", func(t *testing.T) { authz, err := r.ResolveToken("legacy-client") require.NoError(t, err) require.NotNil(t, authz) - require.False(t, authz.OperatorRead()) - require.True(t, authz.ServiceRead("foo")) + require.Equal(t, acl.Deny, authz.OperatorRead(nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil)) }) } @@ -1700,10 +1700,12 @@ func TestACLResolver_Legacy(t *testing.T) { reply.ETag = "nothing" reply.Policy = &acl.Policy{ ID: "not-needed", - Nodes: []*acl.NodePolicy{ - &acl.NodePolicy{ - Name: "foo", - Policy: acl.PolicyWrite, + PolicyRules: acl.PolicyRules{ + Nodes: []*acl.NodeRule{ + &acl.NodeRule{ + Name: "foo", + Policy: acl.PolicyWrite, + }, }, }, } @@ -1719,18 +1721,18 @@ func TestACLResolver_Legacy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) // this should be from the cache authz, err = r.ResolveToken("foo") require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) }) t.Run("Cache-Expiry-Extend", func(t *testing.T) { @@ -1749,10 +1751,12 @@ func TestACLResolver_Legacy(t *testing.T) { reply.ETag = "nothing" reply.Policy = &acl.Policy{ ID: "not-needed", - Nodes: []*acl.NodePolicy{ - &acl.NodePolicy{ - Name: "foo", - Policy: acl.PolicyWrite, + PolicyRules: acl.PolicyRules{ + Nodes: []*acl.NodeRule{ + &acl.NodeRule{ + Name: "foo", + Policy: acl.PolicyWrite, + }, }, }, } @@ -1770,18 +1774,18 @@ func TestACLResolver_Legacy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) // this should be from the cache authz, err = r.ResolveToken("foo") require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) }) t.Run("Cache-Expiry-Allow", func(t *testing.T) { @@ -1800,10 +1804,12 @@ func TestACLResolver_Legacy(t *testing.T) { reply.ETag = "nothing" reply.Policy = &acl.Policy{ ID: "not-needed", - Nodes: []*acl.NodePolicy{ - &acl.NodePolicy{ - Name: "foo", - Policy: acl.PolicyWrite, + PolicyRules: acl.PolicyRules{ + Nodes: []*acl.NodeRule{ + &acl.NodeRule{ + Name: "foo", + Policy: acl.PolicyWrite, + }, }, }, } @@ -1822,18 +1828,18 @@ func TestACLResolver_Legacy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) // this should be from the cache authz, err = r.ResolveToken("foo") require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.True(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("fo", nil)) }) t.Run("Cache-Expiry-Deny", func(t *testing.T) { @@ -1852,10 +1858,12 @@ func TestACLResolver_Legacy(t *testing.T) { reply.ETag = "nothing" reply.Policy = &acl.Policy{ ID: "not-needed", - Nodes: []*acl.NodePolicy{ - &acl.NodePolicy{ - Name: "foo", - Policy: acl.PolicyWrite, + PolicyRules: acl.PolicyRules{ + Nodes: []*acl.NodeRule{ + &acl.NodeRule{ + Name: "foo", + Policy: acl.PolicyWrite, + }, }, }, } @@ -1874,18 +1882,18 @@ func TestACLResolver_Legacy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) // this should be from the cache authz, err = r.ResolveToken("foo") require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.False(t, authz.NodeWrite("foo", nil)) - require.False(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) }) t.Run("Cache-Expiry-Async-Cache", func(t *testing.T) { @@ -1904,10 +1912,12 @@ func TestACLResolver_Legacy(t *testing.T) { reply.ETag = "nothing" reply.Policy = &acl.Policy{ ID: "not-needed", - Nodes: []*acl.NodePolicy{ - &acl.NodePolicy{ - Name: "foo", - Policy: acl.PolicyWrite, + PolicyRules: acl.PolicyRules{ + Nodes: []*acl.NodeRule{ + &acl.NodeRule{ + Name: "foo", + Policy: acl.PolicyWrite, + }, }, }, } @@ -1926,9 +1936,9 @@ func TestACLResolver_Legacy(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz) // there is a bit of translation that happens - require.True(t, authz.NodeWrite("foo", nil)) - require.True(t, authz.NodeWrite("foo/bar", nil)) - require.False(t, authz.NodeWrite("fo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil)) // delivered from the cache authz2, err := r.ResolveToken("foo") @@ -2170,7 +2180,7 @@ service "foo" { if err != nil { t.Fatalf("err %v", err) } - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2204,7 +2214,7 @@ node "node1" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2260,7 +2270,7 @@ service "foo" { } `, acl.SyntaxLegacy, nil) assert.Nil(err) - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) assert.Nil(err) // Filter @@ -2347,7 +2357,7 @@ service "foo" { if err != nil { t.Fatalf("err %v", err) } - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2381,7 +2391,7 @@ node "node1" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2453,7 +2463,7 @@ service "foo" { if err != nil { t.Fatalf("err %v", err) } - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2487,7 +2497,7 @@ node "node1" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2559,7 +2569,7 @@ service "foo" { if err != nil { t.Fatalf("err %v", err) } - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2596,7 +2606,7 @@ node "node1" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2750,7 +2760,7 @@ service "foo" { if err != nil { t.Fatalf("err %v", err) } - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -2790,7 +2800,7 @@ node "node1" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3046,7 +3056,7 @@ node "node" { if err != nil { t.Fatalf("err %v", err) } - perms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3091,7 +3101,7 @@ service "service" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3121,7 +3131,7 @@ service "other" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3195,7 +3205,7 @@ service "other" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3225,7 +3235,7 @@ node "node" { if err != nil { t.Fatalf("err %v", err) } - perms, err = acl.NewPolicyAuthorizer(perms, []*acl.Policy{policy}, nil) + perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3272,7 +3282,7 @@ node "node" { if err != nil { t.Fatalf("err %v", err) } - nodePerms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + nodePerms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3285,7 +3295,7 @@ node "node" { if err != nil { t.Fatalf("err %v", err) } - servicePerms, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + servicePerms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) if err != nil { t.Fatalf("err: %v", err) } @@ -3294,7 +3304,7 @@ node "node" { DeregisterRequest structs.DeregisterRequest Service *structs.NodeService Check *structs.HealthCheck - Perms *acl.PolicyAuthorizer + Perms acl.Authorizer Expected bool Name string }{ diff --git a/agent/consul/catalog_endpoint.go b/agent/consul/catalog_endpoint.go index c2d43263ba..4c8dfb3585 100644 --- a/agent/consul/catalog_endpoint.go +++ b/agent/consul/catalog_endpoint.go @@ -65,14 +65,16 @@ func servicePreApply(service *structs.NodeService, rule acl.Authorizer) error { // later if version 0.8 is enabled, so we can eventually just // delete this and do all the ACL checks down there. if service.Service != structs.ConsulServiceName { - if rule != nil && !rule.ServiceWrite(service.Service, nil) { + // TODO (namespaces) update to send an actual enterprise authorizer context + if rule != nil && rule.ServiceWrite(service.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } } // Proxies must have write permission on their destination if service.Kind == structs.ServiceKindConnectProxy { - if rule != nil && !rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) { + // TODO (namespaces) update to send an actual enterprise authorizer context + if rule != nil && rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -334,7 +336,8 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru return err } - if rule != nil && !rule.ServiceRead(args.ServiceName) { + // TODO (namespaces) update to send an actual enterprise authorizer context + if rule != nil && rule.ServiceRead(args.ServiceName, nil) != acl.Allow { // Just return nil, which will return an empty response (tested) return nil } diff --git a/agent/consul/client.go b/agent/consul/client.go index 14b36373fb..3e84be9cca 100644 --- a/agent/consul/client.go +++ b/agent/consul/client.go @@ -154,12 +154,12 @@ func NewClientLogger(config *Config, logger *log.Logger, tlsConfigurator *tlsuti c.useNewACLs = 0 aclConfig := ACLResolverConfig{ - Config: config, - Delegate: c, - Logger: logger, - AutoDisable: true, - CacheConfig: clientACLCacheConfig, - Sentinel: nil, + Config: config, + Delegate: c, + Logger: logger, + AutoDisable: true, + CacheConfig: clientACLCacheConfig, + EnterpriseConfig: newEnterpriseACLConfig(logger), } var err error if c.acls, err = NewACLResolver(&aclConfig); err != nil { diff --git a/agent/consul/config_endpoint.go b/agent/consul/config_endpoint.go index 3edd82f9c0..5ec9332504 100644 --- a/agent/consul/config_endpoint.go +++ b/agent/consul/config_endpoint.go @@ -231,7 +231,8 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r if err != nil { return err } - if rule != nil && !rule.ServiceRead(args.Name) { + // TODO (namespaces) use actual ent authz context + if rule != nil && rule.ServiceRead(args.Name, nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/connect_ca_endpoint.go b/agent/consul/connect_ca_endpoint.go index 5b5e8e7d20..51d7fdbcd0 100644 --- a/agent/consul/connect_ca_endpoint.go +++ b/agent/consul/connect_ca_endpoint.go @@ -119,7 +119,7 @@ func (s *ConnectCA) ConfigurationGet( if err != nil { return err } - if rule != nil && !rule.OperatorRead() { + if rule != nil && rule.OperatorRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -151,7 +151,7 @@ func (s *ConnectCA) ConfigurationSet( if err != nil { return err } - if rule != nil && !rule.OperatorWrite() { + if rule != nil && rule.OperatorWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -431,7 +431,8 @@ func (s *ConnectCA) Sign( return err } if isService { - if rule != nil && !rule.ServiceWrite(serviceID.Service, nil) { + // TODO (namespaces) use actual ent authz context + if rule != nil && rule.ServiceWrite(serviceID.Service, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -442,7 +443,8 @@ func (s *ConnectCA) Sign( "we are %s", serviceID.Datacenter, s.srv.config.Datacenter) } } else if isAgent { - if rule != nil && !rule.NodeWrite(agentID.Agent, nil) { + // TODO (namespaces) use actual ent authz context + if rule != nil && rule.NodeWrite(agentID.Agent, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -569,7 +571,7 @@ func (s *ConnectCA) SignIntermediate( if err != nil { return err } - if rule != nil && !rule.OperatorWrite() { + if rule != nil && rule.OperatorWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/coordinate_endpoint.go b/agent/consul/coordinate_endpoint.go index 7fd524c4d3..2cced34667 100644 --- a/agent/consul/coordinate_endpoint.go +++ b/agent/consul/coordinate_endpoint.go @@ -139,7 +139,8 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct return err } if rule != nil && c.srv.config.ACLEnforceVersion8 { - if !rule.NodeWrite(args.Node, nil) { + // TODO (namespaces) use actual ent authz context + if rule.NodeWrite(args.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } } @@ -210,7 +211,8 @@ func (c *Coordinate) Node(args *structs.NodeSpecificRequest, reply *structs.Inde return err } if rule != nil && c.srv.config.ACLEnforceVersion8 { - if !rule.NodeRead(args.Node) { + // TODO (namespaces) use actual ent authz context + if rule.NodeRead(args.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } } diff --git a/agent/consul/discovery_chain_endpoint.go b/agent/consul/discovery_chain_endpoint.go index 514427bd3c..97877b6c3e 100644 --- a/agent/consul/discovery_chain_endpoint.go +++ b/agent/consul/discovery_chain_endpoint.go @@ -34,7 +34,8 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs if err != nil { return err } - if rule != nil && !rule.ServiceRead(args.Name) { + // TODO (namespaces) use actual ent authz context + if rule != nil && rule.ServiceRead(args.Name, nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/filter.go b/agent/consul/filter.go index 68b3238ac8..572d4ba1e7 100644 --- a/agent/consul/filter.go +++ b/agent/consul/filter.go @@ -14,7 +14,7 @@ func (d *dirEntFilter) Len() int { return len(d.ent) } func (d *dirEntFilter) Filter(i int) bool { - return !d.authorizer.KeyRead(d.ent[i].Key) + return d.authorizer.KeyRead(d.ent[i].Key, nil) != acl.Allow } func (d *dirEntFilter) Move(dst, src, span int) { copy(d.ent[dst:dst+span], d.ent[src:src+span]) @@ -36,7 +36,8 @@ func (k *keyFilter) Len() int { return len(k.keys) } func (k *keyFilter) Filter(i int) bool { - return !k.authorizer.KeyRead(k.keys[i]) + // TODO (namespaces) use a real ent authz context here + return k.authorizer.KeyRead(k.keys[i], nil) != acl.Allow } func (k *keyFilter) Move(dst, src, span int) { @@ -60,19 +61,20 @@ func (t *txnResultsFilter) Len() int { } func (t *txnResultsFilter) Filter(i int) bool { + // TODO (namespaces) use a real ent authz context for most of these checks result := t.results[i] switch { case result.KV != nil: - return !t.authorizer.KeyRead(result.KV.Key) + return t.authorizer.KeyRead(result.KV.Key, nil) != acl.Allow case result.Node != nil: - return !t.authorizer.NodeRead(result.Node.Node) + return t.authorizer.NodeRead(result.Node.Node, nil) != acl.Allow case result.Service != nil: - return !t.authorizer.ServiceRead(result.Service.Service) + return t.authorizer.ServiceRead(result.Service.Service, nil) != acl.Allow case result.Check != nil: if result.Check.ServiceName != "" { - return !t.authorizer.ServiceRead(result.Check.ServiceName) + return t.authorizer.ServiceRead(result.Check.ServiceName, nil) != acl.Allow } - return !t.authorizer.NodeRead(result.Check.Node) + return t.authorizer.NodeRead(result.Check.Node, nil) != acl.Allow } return false } diff --git a/agent/consul/filter_test.go b/agent/consul/filter_test.go index d7a6e03b5d..5cf4a4c71e 100644 --- a/agent/consul/filter_test.go +++ b/agent/consul/filter_test.go @@ -11,7 +11,7 @@ import ( func TestFilter_DirEnt(t *testing.T) { t.Parallel() policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil) - aclR, _ := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + aclR, _ := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) type tcase struct { in []string @@ -53,7 +53,7 @@ func TestFilter_DirEnt(t *testing.T) { func TestFilter_Keys(t *testing.T) { t.Parallel() policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil) - aclR, _ := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + aclR, _ := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) type tcase struct { in []string @@ -85,7 +85,7 @@ func TestFilter_Keys(t *testing.T) { func TestFilter_TxnResults(t *testing.T) { t.Parallel() policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil) - aclR, _ := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + aclR, _ := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) type tcase struct { in []string diff --git a/agent/consul/health_endpoint.go b/agent/consul/health_endpoint.go index f993385306..42819b75c2 100644 --- a/agent/consul/health_endpoint.go +++ b/agent/consul/health_endpoint.go @@ -5,6 +5,7 @@ import ( "sort" "github.com/armon/go-metrics" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" bexpr "github.com/hashicorp/go-bexpr" @@ -171,7 +172,7 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc return err } - if rule != nil && !rule.ServiceRead(args.ServiceName) { + if rule != nil && rule.ServiceRead(args.ServiceName, nil) != acl.Allow { // Just return nil, which will return an empty response (tested) return nil } diff --git a/agent/consul/intention_endpoint.go b/agent/consul/intention_endpoint.go index fd1a90e06f..8ec7e5905a 100644 --- a/agent/consul/intention_endpoint.go +++ b/agent/consul/intention_endpoint.go @@ -87,7 +87,7 @@ func (s *Intention) Apply( // Perform the ACL check if prefix, ok := args.Intention.GetACLPrefix(); ok { - if rule != nil && !rule.IntentionWrite(prefix) { + if rule != nil && rule.IntentionWrite(prefix, nil) != acl.Allow { s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID) return acl.ErrPermissionDenied } @@ -107,7 +107,7 @@ func (s *Intention) Apply( // Perform the ACL check that we have write to the old prefix too, // which must be true to perform any rename. if prefix, ok := ixn.GetACLPrefix(); ok { - if rule != nil && !rule.IntentionWrite(prefix) { + if rule != nil && rule.IntentionWrite(prefix, nil) != acl.Allow { s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID) return acl.ErrPermissionDenied } @@ -243,7 +243,7 @@ func (s *Intention) Match( // We go through each entry and test the destination to check if it // matches. for _, entry := range args.Match.Entries { - if prefix := entry.Name; prefix != "" && !rule.IntentionRead(prefix) { + if prefix := entry.Name; prefix != "" && rule.IntentionRead(prefix, nil) != acl.Allow { s.srv.logger.Printf("[WARN] consul.intention: Operation on intention prefix '%s' denied due to ACLs", prefix) return acl.ErrPermissionDenied } @@ -309,7 +309,7 @@ func (s *Intention) Check( // NOT IntentionRead because the Check API only returns pass/fail and // returns no other information about the intentions used. if prefix, ok := query.GetACLPrefix(); ok { - if rule != nil && !rule.ServiceRead(prefix) { + if rule != nil && rule.ServiceRead(prefix, nil) != acl.Allow { s.srv.logger.Printf("[WARN] consul.intention: test on intention '%s' denied due to ACLs", prefix) return acl.ErrPermissionDenied } @@ -360,7 +360,7 @@ func (s *Intention) Check( reply.Allowed = true if rule != nil { - reply.Allowed = rule.IntentionDefaultAllow() + reply.Allowed = rule.IntentionDefaultAllow(nil) == acl.Allow } return nil diff --git a/agent/consul/internal_endpoint.go b/agent/consul/internal_endpoint.go index 743ea7c060..533785932b 100644 --- a/agent/consul/internal_endpoint.go +++ b/agent/consul/internal_endpoint.go @@ -125,7 +125,7 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, return err } - if rule != nil && !rule.EventWrite(args.Name) { + if rule != nil && rule.EventWrite(args.Name, nil) != acl.Allow { m.srv.logger.Printf("[WARN] consul: user event %q blocked by ACLs", args.Name) return acl.ErrPermissionDenied } @@ -162,7 +162,7 @@ func (m *Internal) KeyringOperation( if rule != nil { switch args.Operation { case structs.KeyringList: - if !rule.KeyringRead() { + if rule.KeyringRead(nil) != acl.Allow { return fmt.Errorf("Reading keyring denied by ACLs") } case structs.KeyringInstall: @@ -170,7 +170,7 @@ func (m *Internal) KeyringOperation( case structs.KeyringUse: fallthrough case structs.KeyringRemove: - if !rule.KeyringWrite() { + if rule.KeyringWrite(nil) != acl.Allow { return fmt.Errorf("Modifying keyring denied due to ACLs") } default: diff --git a/agent/consul/kvs_endpoint.go b/agent/consul/kvs_endpoint.go index 9fb52437ef..079d2440a0 100644 --- a/agent/consul/kvs_endpoint.go +++ b/agent/consul/kvs_endpoint.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sentinel" "github.com/hashicorp/go-memdb" ) @@ -32,7 +31,8 @@ func kvsPreApply(srv *Server, rule acl.Authorizer, op api.KVOp, dirEnt *structs. if rule != nil { switch op { case api.KVDeleteTree: - if !rule.KeyWritePrefix(dirEnt.Key) { + // TODO (namespaces) use actual ent authz context - ensure we set the Sentinel Scope + if rule.KeyWritePrefix(dirEnt.Key, nil) != acl.Allow { return false, acl.ErrPermissionDenied } @@ -43,15 +43,13 @@ func kvsPreApply(srv *Server, rule acl.Authorizer, op api.KVOp, dirEnt *structs. // These could reveal information based on the outcome // of the transaction, and they operate on individual // keys so we check them here. - if !rule.KeyRead(dirEnt.Key) { + if rule.KeyRead(dirEnt.Key, nil) != acl.Allow { return false, acl.ErrPermissionDenied } default: - scope := func() map[string]interface{} { - return sentinel.ScopeKVUpsert(dirEnt.Key, dirEnt.Value, dirEnt.Flags) - } - if !rule.KeyWrite(dirEnt.Key, scope) { + // TODO (namespaces) use actual ent authz context - ensure we set the Sentinel Scope + if rule.KeyWrite(dirEnt.Key, nil) != acl.Allow { return false, acl.ErrPermissionDenied } } @@ -132,7 +130,7 @@ func (k *KVS) Get(args *structs.KeyRequest, reply *structs.IndexedDirEntries) er if err != nil { return err } - if aclRule != nil && !aclRule.KeyRead(args.Key) { + if aclRule != nil && aclRule.KeyRead(args.Key, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -164,7 +162,7 @@ func (k *KVS) List(args *structs.KeyRequest, reply *structs.IndexedDirEntries) e return err } - if aclToken != nil && k.srv.config.ACLEnableKeyListPolicy && !aclToken.KeyList(args.Key) { + if aclToken != nil && k.srv.config.ACLEnableKeyListPolicy && aclToken.KeyList(args.Key, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -208,7 +206,7 @@ func (k *KVS) ListKeys(args *structs.KeyListRequest, reply *structs.IndexedKeyLi return err } - if aclToken != nil && k.srv.config.ACLEnableKeyListPolicy && !aclToken.KeyList(args.Prefix) { + if aclToken != nil && k.srv.config.ACLEnableKeyListPolicy && aclToken.KeyList(args.Prefix, nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/leader.go b/agent/consul/leader.go index 67ab742838..f14492c53f 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -498,23 +498,28 @@ func (s *Server) initializeACLs(upgrade bool) error { s.logger.Printf("[INFO] acl: initializing acls") - // Create the builtin global-management policy + // Create/Upgrade the builtin global-management policy _, policy, err := s.fsm.State().ACLPolicyGetByID(nil, structs.ACLPolicyGlobalManagementID) if err != nil { return fmt.Errorf("failed to get the builtin global-management policy") } - if policy == nil { - policy := structs.ACLPolicy{ + if policy == nil || policy.Rules != structs.ACLPolicyGlobalManagement { + newPolicy := structs.ACLPolicy{ ID: structs.ACLPolicyGlobalManagementID, Name: "global-management", Description: "Builtin Policy that grants unlimited access", Rules: structs.ACLPolicyGlobalManagement, Syntax: acl.SyntaxCurrent, } - policy.SetHash(true) + if policy != nil { + newPolicy.Name = policy.Name + newPolicy.Description = policy.Description + } + + newPolicy.SetHash(true) req := structs.ACLPolicyBatchSetRequest{ - Policies: structs.ACLPolicies{&policy}, + Policies: structs.ACLPolicies{&newPolicy}, } _, err := s.raftApply(structs.ACLPolicySetRequestType, &req) if err != nil { diff --git a/agent/consul/operator_autopilot_endpoint.go b/agent/consul/operator_autopilot_endpoint.go index c9a328051b..0227e48475 100644 --- a/agent/consul/operator_autopilot_endpoint.go +++ b/agent/consul/operator_autopilot_endpoint.go @@ -19,7 +19,7 @@ func (op *Operator) AutopilotGetConfiguration(args *structs.DCSpecificRequest, r if err != nil { return err } - if rule != nil && !rule.OperatorRead() { + if rule != nil && rule.OperatorRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -48,7 +48,7 @@ func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRe if err != nil { return err } - if rule != nil && !rule.OperatorWrite() { + if rule != nil && rule.OperatorWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -84,7 +84,7 @@ func (op *Operator) ServerHealth(args *structs.DCSpecificRequest, reply *autopil if err != nil { return err } - if rule != nil && !rule.OperatorRead() { + if rule != nil && rule.OperatorRead(nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/operator_raft_endpoint.go b/agent/consul/operator_raft_endpoint.go index 85bdc60bf3..e91311a712 100644 --- a/agent/consul/operator_raft_endpoint.go +++ b/agent/consul/operator_raft_endpoint.go @@ -22,7 +22,7 @@ func (op *Operator) RaftGetConfiguration(args *structs.DCSpecificRequest, reply if err != nil { return err } - if rule != nil && !rule.OperatorRead() { + if rule != nil && rule.OperatorRead(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -84,7 +84,7 @@ func (op *Operator) RaftRemovePeerByAddress(args *structs.RaftRemovePeerRequest, if err != nil { return err } - if rule != nil && !rule.OperatorWrite() { + if rule != nil && rule.OperatorWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -151,7 +151,7 @@ func (op *Operator) RaftRemovePeerByID(args *structs.RaftRemovePeerRequest, repl if err != nil { return err } - if rule != nil && !rule.OperatorWrite() { + if rule != nil && rule.OperatorWrite(nil) != acl.Allow { return acl.ErrPermissionDenied } diff --git a/agent/consul/prepared_query_endpoint.go b/agent/consul/prepared_query_endpoint.go index 097e65e7b1..70ae4e8439 100644 --- a/agent/consul/prepared_query_endpoint.go +++ b/agent/consul/prepared_query_endpoint.go @@ -69,7 +69,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string) // need to make sure they have write access for whatever they are // proposing. if prefix, ok := args.Query.GetACLPrefix(); ok { - if rule != nil && !rule.PreparedQueryWrite(prefix) { + if rule != nil && rule.PreparedQueryWrite(prefix, nil) != acl.Allow { p.srv.logger.Printf("[WARN] consul.prepared_query: Operation on prepared query '%s' denied due to ACLs", args.Query.ID) return acl.ErrPermissionDenied } @@ -89,7 +89,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string) } if prefix, ok := query.GetACLPrefix(); ok { - if rule != nil && !rule.PreparedQueryWrite(prefix) { + if rule != nil && rule.PreparedQueryWrite(prefix, nil) != acl.Allow { p.srv.logger.Printf("[WARN] consul.prepared_query: Operation on prepared query '%s' denied due to ACLs", args.Query.ID) return acl.ErrPermissionDenied } diff --git a/agent/consul/server.go b/agent/consul/server.go index 35c3f93a6d..5cdfeff45f 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -18,6 +18,7 @@ import ( "time" metrics "github.com/armon/go-metrics" + "github.com/hashicorp/consul/acl" ca "github.com/hashicorp/consul/agent/connect/ca" "github.com/hashicorp/consul/agent/consul/autopilot" "github.com/hashicorp/consul/agent/consul/fsm" @@ -28,7 +29,6 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/lib" - "github.com/hashicorp/consul/sentinel" "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" "github.com/hashicorp/go-hclog" @@ -107,8 +107,9 @@ var ( // Server is Consul server which manages the service discovery, // health checking, DC forwarding, Raft, and multiple Serf pools. type Server struct { - // sentinel is the Sentinel code engine (can be nil). - sentinel sentinel.Evaluator + // enterpriseACLConfig is the Consul Enterprise specific items + // necessary for ACLs + enterpriseACLConfig *acl.EnterpriseACLConfig // acls is used to resolve tokens to effective policies acls *ACLResolver @@ -391,15 +392,15 @@ func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store, tl // Initialize the stats fetcher that autopilot will use. s.statsFetcher = NewStatsFetcher(logger, s.connPool, s.config.Datacenter) - s.sentinel = sentinel.New(logger) + s.enterpriseACLConfig = newEnterpriseACLConfig(logger) s.useNewACLs = 0 aclConfig := ACLResolverConfig{ - Config: config, - Delegate: s, - CacheConfig: serverACLCacheConfig, - AutoDisable: false, - Logger: logger, - Sentinel: s.sentinel, + Config: config, + Delegate: s, + CacheConfig: serverACLCacheConfig, + AutoDisable: false, + Logger: logger, + EnterpriseConfig: s.enterpriseACLConfig, } // Initialize the ACL resolver. if s.acls, err = NewACLResolver(&aclConfig); err != nil { diff --git a/agent/consul/session_endpoint.go b/agent/consul/session_endpoint.go index 0ae7db0ed8..072cfce236 100644 --- a/agent/consul/session_endpoint.go +++ b/agent/consul/session_endpoint.go @@ -49,12 +49,14 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error { if existing == nil { return fmt.Errorf("Unknown session %q", args.Session.ID) } - if !rule.SessionWrite(existing.Node) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.SessionWrite(existing.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } case structs.SessionCreate: - if !rule.SessionWrite(args.Session.Node) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.SessionWrite(args.Session.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } @@ -241,7 +243,8 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest, return err } if rule != nil && s.srv.config.ACLEnforceVersion8 { - if !rule.SessionWrite(session.Node) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule.SessionWrite(session.Node, nil) != acl.Allow { return acl.ErrPermissionDenied } } diff --git a/agent/consul/snapshot_endpoint.go b/agent/consul/snapshot_endpoint.go index f00ba1931a..429abba79e 100644 --- a/agent/consul/snapshot_endpoint.go +++ b/agent/consul/snapshot_endpoint.go @@ -61,7 +61,7 @@ func (s *Server) dispatchSnapshotRequest(args *structs.SnapshotRequest, in io.Re // all the ACLs and you could escalate from there. if rule, err := s.ResolveToken(args.Token); err != nil { return nil, err - } else if rule != nil && !rule.Snapshot() { + } else if rule != nil && rule.Snapshot(nil) != acl.Allow { return nil, acl.ErrPermissionDenied } diff --git a/agent/consul/state/acl.go b/agent/consul/state/acl.go index 88cdbeec6a..c0a1d52c68 100644 --- a/agent/consul/state/acl.go +++ b/agent/consul/state/acl.go @@ -1373,11 +1373,13 @@ func (s *Store) aclPolicySetTxn(tx *memdb.Txn, idx uint64, policy *structs.ACLPo } if existing != nil { - policyMatch := existing.(*structs.ACLPolicy) - if policy.ID == structs.ACLPolicyGlobalManagementID { // Only the name and description are modifiable - if policy.Rules != policyMatch.Rules { + // Here we specifically check that the rules on the global management policy + // are identical to the correct policy rules within the binary. This is opposed + // to checking against the current rules to allow us to update the rules during + // upgrades. + if policy.Rules != structs.ACLPolicyGlobalManagement { return fmt.Errorf("Changing the Rules for the builtin global-management policy is not permitted") } diff --git a/agent/event_endpoint.go b/agent/event_endpoint.go index f0acff1920..6d338014a0 100644 --- a/agent/event_endpoint.go +++ b/agent/event_endpoint.go @@ -78,7 +78,7 @@ func (s *HTTPServer) EventList(resp http.ResponseWriter, req *http.Request) (int // Fetch the ACL token, if any. var token string s.parseToken(req, &token) - acl, err := s.agent.resolveToken(token) + authz, err := s.agent.resolveToken(token) if err != nil { return nil, err } @@ -128,10 +128,10 @@ RUN_QUERY: events := s.agent.UserEvents() // Filter the events using the ACL, if present - if acl != nil { + if authz != nil { for i := 0; i < len(events); i++ { name := events[i].Name - if acl.EventRead(name) { + if authz.EventRead(name, nil) == acl.Allow { continue } s.agent.logger.Printf("[DEBUG] agent: dropping event %q from result due to ACLs", name) diff --git a/agent/http.go b/agent/http.go index ce5fe926f4..8a3943c8ef 100644 --- a/agent/http.go +++ b/agent/http.go @@ -279,7 +279,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { // If the token provided does not have the necessary permissions, // write a forbidden response - if rule != nil && !rule.OperatorRead() { + if rule != nil && rule.OperatorRead(nil) != acl.Allow { resp.WriteHeader(http.StatusForbidden) return } diff --git a/agent/structs/acl.go b/agent/structs/acl.go index a9fd9de5ab..c0f3f23247 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -11,7 +11,6 @@ import ( "time" "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/sentinel" "golang.org/x/crypto/blake2b" ) @@ -80,7 +79,7 @@ service_prefix "" { } session_prefix "" { policy = "write" -}` +}` + EnterpriseACLPolicyGlobalManagement // This is the policy ID for anonymous access. This is configurable by the // user. @@ -645,7 +644,7 @@ func (policies ACLPolicyListStubs) Sort() { }) } -func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, sentinel sentinel.Evaluator) ([]*acl.Policy, error) { +func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, entConf *acl.EnterpriseACLConfig) ([]*acl.Policy, error) { // Parse the policies parsed := make([]*acl.Policy, 0, len(policies)) for _, policy := range policies { @@ -658,7 +657,7 @@ func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, sentinel sentinel continue } - p, err := acl.NewPolicyFromSource(policy.ID, policy.ModifyIndex, policy.Rules, policy.Syntax, sentinel) + p, err := acl.NewPolicyFromSource(policy.ID, policy.ModifyIndex, policy.Rules, policy.Syntax, entConf) if err != nil { return nil, fmt.Errorf("failed to parse %q: %v", policy.Name, err) } @@ -670,7 +669,7 @@ func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, sentinel sentinel return parsed, nil } -func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, sentinel sentinel.Evaluator) (acl.Authorizer, error) { +func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, entConf *acl.EnterpriseACLConfig) (acl.Authorizer, error) { // Determine the cache key cacheKey := policies.HashKey() entry := cache.GetAuthorizer(cacheKey) @@ -679,13 +678,13 @@ func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, sen return entry.Authorizer, nil } - parsed, err := policies.resolveWithCache(cache, sentinel) + parsed, err := policies.resolveWithCache(cache, entConf) if err != nil { return nil, fmt.Errorf("failed to parse the ACL policies: %v", err) } // Create the ACL object - authorizer, err := acl.NewPolicyAuthorizer(parent, parsed, sentinel) + authorizer, err := acl.NewPolicyAuthorizerWithDefaults(parent, parsed, entConf) if err != nil { return nil, fmt.Errorf("failed to construct ACL Authorizer: %v", err) } @@ -695,8 +694,8 @@ func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, sen return authorizer, nil } -func (policies ACLPolicies) Merge(cache *ACLCaches, sentinel sentinel.Evaluator) (*acl.Policy, error) { - parsed, err := policies.resolveWithCache(cache, sentinel) +func (policies ACLPolicies) Merge(cache *ACLCaches, entConf *acl.EnterpriseACLConfig) (*acl.Policy, error) { + parsed, err := policies.resolveWithCache(cache, entConf) if err != nil { return nil, err } diff --git a/agent/structs/acl_oss.go b/agent/structs/acl_oss.go new file mode 100644 index 0000000000..f5841c63d1 --- /dev/null +++ b/agent/structs/acl_oss.go @@ -0,0 +1,7 @@ +// +build !consulent + +package structs + +const ( + EnterpriseACLPolicyGlobalManagement = "" +) diff --git a/agent/structs/acl_test.go b/agent/structs/acl_test.go index a7860a49d6..b15a919f9d 100644 --- a/agent/structs/acl_test.go +++ b/agent/structs/acl_test.go @@ -665,11 +665,11 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) { require.NoError(t, err) require.NotNil(t, authz) - require.True(t, authz.NodeRead("foo")) - require.True(t, authz.AgentRead("foo")) - require.True(t, authz.KeyRead("foo")) - require.True(t, authz.ServiceRead("foo")) - require.False(t, authz.ACLRead()) + require.Equal(t, acl.Allow, authz.NodeRead("foo", nil)) + require.Equal(t, acl.Allow, authz.AgentRead("foo", nil)) + require.Equal(t, acl.Allow, authz.KeyRead("foo", nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) }) t.Run("Check Cache", func(t *testing.T) { @@ -678,11 +678,11 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) { authz := entry.Authorizer require.NotNil(t, authz) - require.True(t, authz.NodeRead("foo")) - require.True(t, authz.AgentRead("foo")) - require.True(t, authz.KeyRead("foo")) - require.True(t, authz.ServiceRead("foo")) - require.False(t, authz.ACLRead()) + require.Equal(t, acl.Allow, authz.NodeRead("foo", nil)) + require.Equal(t, acl.Allow, authz.AgentRead("foo", nil)) + require.Equal(t, acl.Allow, authz.KeyRead("foo", nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) // setup the cache for the next test cache.PutAuthorizer(testPolicies.HashKey(), acl.DenyAll()) @@ -694,10 +694,10 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) { require.NotNil(t, authz) // we reset the Authorizer in the cache so now everything should be denied - require.False(t, authz.NodeRead("foo")) - require.False(t, authz.AgentRead("foo")) - require.False(t, authz.KeyRead("foo")) - require.False(t, authz.ServiceRead("foo")) - require.False(t, authz.ACLRead()) + require.Equal(t, acl.Deny, authz.NodeRead("foo", nil)) + require.Equal(t, acl.Deny, authz.AgentRead("foo", nil)) + require.Equal(t, acl.Deny, authz.KeyRead("foo", nil)) + require.Equal(t, acl.Deny, authz.ServiceRead("foo", nil)) + require.Equal(t, acl.Deny, authz.ACLRead(nil)) }) } diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index b48c3407cb..1baef8b4b0 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -92,11 +92,11 @@ func (e *ServiceConfigEntry) Validate() error { } func (e *ServiceConfigEntry) CanRead(rule acl.Authorizer) bool { - return rule.ServiceRead(e.Name) + return rule.ServiceRead(e.Name, nil) == acl.Allow } func (e *ServiceConfigEntry) CanWrite(rule acl.Authorizer) bool { - return rule.ServiceWrite(e.Name, nil) + return rule.ServiceWrite(e.Name, nil) == acl.Allow } func (e *ServiceConfigEntry) GetRaftIndex() *RaftIndex { @@ -162,7 +162,7 @@ func (e *ProxyConfigEntry) CanRead(rule acl.Authorizer) bool { } func (e *ProxyConfigEntry) CanWrite(rule acl.Authorizer) bool { - return rule.OperatorWrite() + return rule.OperatorWrite(nil) == acl.Allow } func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex { diff --git a/agent/structs/config_entry_discoverychain.go b/agent/structs/config_entry_discoverychain.go index ee33d6bb54..7b7ffdf215 100644 --- a/agent/structs/config_entry_discoverychain.go +++ b/agent/structs/config_entry_discoverychain.go @@ -892,13 +892,13 @@ type discoveryChainConfigEntry interface { } func canReadDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer) bool { - return rule.ServiceRead(entry.GetName()) + return rule.ServiceRead(entry.GetName(), nil) == acl.Allow } func canWriteDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer) bool { name := entry.GetName() - if !rule.ServiceWrite(name, nil) { + if rule.ServiceWrite(name, nil) != acl.Allow { return false } @@ -909,7 +909,7 @@ func canWriteDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer // You only need read on related services to redirect traffic flow for // your own service. - if !rule.ServiceRead(svc) { + if rule.ServiceRead(svc, nil) != acl.Allow { return false } } diff --git a/agent/structs/config_entry_discoverychain_test.go b/agent/structs/config_entry_discoverychain_test.go index 602a9d1611..1f140a7178 100644 --- a/agent/structs/config_entry_discoverychain_test.go +++ b/agent/structs/config_entry_discoverychain_test.go @@ -27,7 +27,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) { policy, err := acl.NewPolicyFromSource("", 0, buf.String(), acl.SyntaxCurrent, nil) require.NoError(t, err) - authorizer, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil) + authorizer, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil) require.NoError(t, err) return authorizer } diff --git a/agent/xds/server.go b/agent/xds/server.go index 0e6ae8ea83..cf51cd3dfc 100644 --- a/agent/xds/server.go +++ b/agent/xds/server.go @@ -251,12 +251,13 @@ func (s *Server) process(stream ADSStream, reqCh <-chan *envoy.DiscoveryRequest) switch cfgSnap.Kind { case structs.ServiceKindConnectProxy: - if rule != nil && !rule.ServiceWrite(cfgSnap.Proxy.DestinationServiceName, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule != nil && rule.ServiceWrite(cfgSnap.Proxy.DestinationServiceName, nil) != acl.Allow { return status.Errorf(codes.PermissionDenied, "permission denied") } case structs.ServiceKindMeshGateway: - // TODO (mesh-gateway) - figure out what ACLs to check for the Gateways - if rule != nil && !rule.ServiceWrite(cfgSnap.Service, nil) { + // TODO (namespaces) - pass through a real ent authz ctx + if rule != nil && rule.ServiceWrite(cfgSnap.Service, nil) != acl.Allow { return status.Errorf(codes.PermissionDenied, "permission denied") } default: diff --git a/agent/xds/server_test.go b/agent/xds/server_test.go index 06b96b821b..11ac1552cd 100644 --- a/agent/xds/server_test.go +++ b/agent/xds/server_test.go @@ -450,7 +450,7 @@ func TestServer_StreamAggregatedResources_ACLEnforcement(t *testing.T) { // Parse the ACL and enforce it policy, err := acl.NewPolicyFromSource("", 0, tt.acl, acl.SyntaxLegacy, nil) require.NoError(t, err) - return acl.NewPolicyAuthorizer(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) + return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) } envoy := NewTestEnvoy(t, "web-sidecar-proxy", tt.token) defer envoy.Close() @@ -521,7 +521,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedDuring return nil, acl.ErrNotFound } - return acl.NewPolicyAuthorizer(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) + return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) } envoy := NewTestEnvoy(t, "web-sidecar-proxy", token) defer envoy.Close() @@ -612,7 +612,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedInBack return nil, acl.ErrNotFound } - return acl.NewPolicyAuthorizer(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) + return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil) } envoy := NewTestEnvoy(t, "web-sidecar-proxy", token) defer envoy.Close()