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
pull/6626/head
Matt Keeler 2019-10-15 16:58:50 -04:00 committed by GitHub
parent 6d645fe53c
commit 973341a592
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 4492 additions and 3030 deletions

View File

@ -293,7 +293,7 @@ jobs:
# make dev build of nomad # make dev build of nomad
- run: - run:
command: make dev command: make pkg/linux_amd64/nomad
working_directory: *NOMAD_WORKING_DIR working_directory: *NOMAD_WORKING_DIR
# update gotestsum # update gotestsum

1010
acl/acl.go

File diff suppressed because it is too large Load Diff

6
acl/acl_oss.go Normal file
View File

@ -0,0 +1,6 @@
// +build !consulent
package acl
// EnterpriseACLConfig stub
type EnterpriseACLConfig struct{}

File diff suppressed because it is too large Load Diff

128
acl/authorizer.go Normal file
View File

@ -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
}

9
acl/authorizer_oss.go Normal file
View File

@ -0,0 +1,9 @@
// +build !consulent
package acl
// EnterpriseAuthorizerContext stub
type EnterpriseAuthorizerContext struct{}
// EnterpriseAuthorizer stub interface
type EnterpriseAuthorizer interface{}

226
acl/chained_authorizer.go Normal file
View File

@ -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)
})
}

View File

@ -0,0 +1,3 @@
// +build !consulent
package acl

View File

@ -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)
})
}

View File

@ -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
}

File diff suppressed because it is too large Load Diff

637
acl/policy_authorizer.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}
})
}
}

364
acl/policy_merger.go Normal file
View File

@ -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()
}

17
acl/policy_merger_oss.go Normal file
View File

@ -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
}

19
acl/policy_oss.go Normal file
View File

@ -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
}

File diff suppressed because it is too large Load Diff

244
acl/static_authorizer.go Normal file
View File

@ -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
}
}

View File

@ -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)
})
}

View File

@ -41,20 +41,22 @@ func (a *Agent) initializeACLs() error {
// only. This used to allow a prefix match on agent names but that seems // only. This used to allow a prefix match on agent names but that seems
// entirely unnecessary so it is now using an exact match. // entirely unnecessary so it is now using an exact match.
policy := &acl.Policy{ policy := &acl.Policy{
Agents: []*acl.AgentPolicy{ PolicyRules: acl.PolicyRules{
&acl.AgentPolicy{ Agents: []*acl.AgentRule{
Node: a.config.NodeName, &acl.AgentRule{
Policy: acl.PolicyWrite, Node: a.config.NodeName,
Policy: acl.PolicyWrite,
},
}, },
}, NodePrefixes: []*acl.NodeRule{
NodePrefixes: []*acl.NodePolicy{ &acl.NodeRule{
&acl.NodePolicy{ Name: "",
Name: "", Policy: acl.PolicyRead,
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 { if err != nil {
return err return err
} }
@ -75,14 +77,16 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e
} }
// Vet the service itself. // 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 return acl.ErrPermissionDenied
} }
// Vet any service that might be getting overwritten. // Vet any service that might be getting overwritten.
services := a.State.Services() services := a.State.Services()
if existing, ok := services[service.ID]; ok { 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 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 // 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. // since it can be discovered as an instance of that service.
if service.Kind == structs.ServiceKindConnectProxy { 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 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. // Vet any changes based on the existing services's info.
services := a.State.Services() services := a.State.Services()
if existing, ok := services[serviceID]; ok { 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 return acl.ErrPermissionDenied
} }
} else { } else {
@ -137,11 +143,13 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
// Vet the check itself. // Vet the check itself.
if len(check.ServiceName) > 0 { 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 return acl.ErrPermissionDenied
} }
} else { } 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 return acl.ErrPermissionDenied
} }
} }
@ -150,11 +158,13 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
checks := a.State.Checks() checks := a.State.Checks()
if existing, ok := checks[check.CheckID]; ok { if existing, ok := checks[check.CheckID]; ok {
if len(existing.ServiceName) > 0 { 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 return acl.ErrPermissionDenied
} }
} else { } 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 return acl.ErrPermissionDenied
} }
} }
@ -178,11 +188,13 @@ func (a *Agent) vetCheckUpdate(token string, checkID types.CheckID) error {
checks := a.State.Checks() checks := a.State.Checks()
if existing, ok := checks[checkID]; ok { if existing, ok := checks[checkID]; ok {
if len(existing.ServiceName) > 0 { 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 return acl.ErrPermissionDenied
} }
} else { } 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 return acl.ErrPermissionDenied
} }
} }
@ -208,7 +220,8 @@ func (a *Agent) filterMembers(token string, members *[]serf.Member) error {
m := *members m := *members
for i := 0; i < len(m); i++ { for i := 0; i < len(m); i++ {
node := m[i].Name 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 continue
} }
a.logger.Printf("[DEBUG] agent: dropping node %q from result due to ACLs", node) 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. // Filter out services based on the service policy.
for id, service := range *services { 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 continue
} }
a.logger.Printf("[DEBUG] agent: dropping service %q from result due to ACLs", id) 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. // Filter out checks based on the node or service policy.
for id, check := range *checks { for id, check := range *checks {
if len(check.ServiceName) > 0 { 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 continue
} }
} else { } 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 continue
} }
} }

View File

@ -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 // 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 // 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 return nil, acl.ErrPermissionDenied
} }

View File

@ -195,10 +195,10 @@ func TestACL_AgentMasterToken(t *testing.T) {
require.NotNil(t, authz) require.NotNil(t, authz)
require.Nil(t, err) require.Nil(t, err)
require.True(t, authz.AgentRead(a.config.NodeName)) require.Equal(t, acl.Allow, authz.AgentRead(a.config.NodeName, nil))
require.True(t, authz.AgentWrite(a.config.NodeName)) require.Equal(t, acl.Allow, authz.AgentWrite(a.config.NodeName, nil))
require.True(t, authz.NodeRead("foobarbaz")) require.Equal(t, acl.Allow, authz.NodeRead("foobarbaz", nil))
require.False(t, authz.NodeWrite("foobarbaz", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foobarbaz", nil))
} }
func TestACL_RootAuthorizersDenied(t *testing.T) { func TestACL_RootAuthorizersDenied(t *testing.T) {
@ -225,7 +225,7 @@ func TestACL_RootAuthorizersDenied(t *testing.T) {
} }
func authzFromPolicy(policy *acl.Policy) (acl.Authorizer, error) { 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 // catalogPolicy supplies some standard policies to help with testing the
@ -235,32 +235,42 @@ func catalogPolicy(token string) (acl.Authorizer, error) {
case "node-ro": case "node-ro":
return authzFromPolicy(&acl.Policy{ return authzFromPolicy(&acl.Policy{
NodePrefixes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{Name: "Node", Policy: "read"}, NodePrefixes: []*acl.NodeRule{
&acl.NodeRule{Name: "Node", Policy: "read"},
},
}, },
}) })
case "node-rw": case "node-rw":
return authzFromPolicy(&acl.Policy{ return authzFromPolicy(&acl.Policy{
NodePrefixes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{Name: "Node", Policy: "write"}, NodePrefixes: []*acl.NodeRule{
&acl.NodeRule{Name: "Node", Policy: "write"},
},
}, },
}) })
case "service-ro": case "service-ro":
return authzFromPolicy(&acl.Policy{ return authzFromPolicy(&acl.Policy{
ServicePrefixes: []*acl.ServicePolicy{ PolicyRules: acl.PolicyRules{
&acl.ServicePolicy{Name: "service", Policy: "read"}, ServicePrefixes: []*acl.ServiceRule{
&acl.ServiceRule{Name: "service", Policy: "read"},
},
}, },
}) })
case "service-rw": case "service-rw":
return authzFromPolicy(&acl.Policy{ return authzFromPolicy(&acl.Policy{
ServicePrefixes: []*acl.ServicePolicy{ PolicyRules: acl.PolicyRules{
&acl.ServicePolicy{Name: "service", Policy: "write"}, ServicePrefixes: []*acl.ServiceRule{
&acl.ServiceRule{Name: "service", Policy: "write"},
},
}, },
}) })
case "other-rw": case "other-rw":
return authzFromPolicy(&acl.Policy{ return authzFromPolicy(&acl.Policy{
ServicePrefixes: []*acl.ServicePolicy{ PolicyRules: acl.PolicyRules{
&acl.ServicePolicy{Name: "other", Policy: "write"}, ServicePrefixes: []*acl.ServiceRule{
&acl.ServiceRule{Name: "other", Policy: "write"},
},
}, },
}) })
default: default:

View File

@ -48,7 +48,7 @@ func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (int
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -101,7 +101,7 @@ func (s *HTTPServer) AgentMetrics(resp http.ResponseWriter, req *http.Request) (
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
if enablePrometheusOutput(req) { if enablePrometheusOutput(req) {
@ -130,7 +130,7 @@ func (s *HTTPServer) AgentReload(resp http.ResponseWriter, req *http.Request) (i
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -281,7 +281,8 @@ func (s *HTTPServer) AgentService(resp http.ResponseWriter, req *http.Request) (
if err != nil { if err != nil {
return "", nil, err 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 return "", nil, acl.ErrPermissionDenied
} }
@ -388,7 +389,7 @@ func (s *HTTPServer) AgentJoin(resp http.ResponseWriter, req *http.Request) (int
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -416,7 +417,7 @@ func (s *HTTPServer) AgentLeave(resp http.ResponseWriter, req *http.Request) (in
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -434,7 +435,7 @@ func (s *HTTPServer) AgentForceLeave(resp http.ResponseWriter, req *http.Request
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -1049,7 +1050,8 @@ func (s *HTTPServer) AgentNodeMaintenance(resp http.ResponseWriter, req *http.Re
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -1070,7 +1072,7 @@ func (s *HTTPServer) AgentMonitor(resp http.ResponseWriter, req *http.Request) (
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -1165,7 +1167,7 @@ func (s *HTTPServer) AgentToken(resp http.ResponseWriter, req *http.Request) (in
if err != nil { if err != nil {
return nil, err 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 return nil, acl.ErrPermissionDenied
} }
@ -1370,7 +1372,8 @@ func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (int
return nil, err 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 return nil, acl.ErrPermissionDenied
} }

View File

@ -57,7 +57,8 @@ func (a *Agent) ConnectAuthorize(token string,
if err != nil { if err != nil {
return returnErr(err) 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) 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 return true, "ACLs disabled, access is allowed by default", &meta, nil
} }
reason = "Default behavior configured by ACLs" 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
} }

View File

@ -11,8 +11,6 @@ import (
metrics "github.com/armon/go-metrics" metrics "github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sentinel"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
@ -126,7 +124,8 @@ type ACLResolverConfig struct {
// so that it can detect when the servers have gotten ACLs enabled. // so that it can detect when the servers have gotten ACLs enabled.
AutoDisable bool 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. // ACLResolver is the type to handle all your token and policy resolution needs.
@ -159,7 +158,7 @@ type ACLResolver struct {
logger *log.Logger logger *log.Logger
delegate ACLResolverDelegate delegate ACLResolverDelegate
sentinel sentinel.Evaluator entConf *acl.EnterpriseACLConfig
cache *structs.ACLCaches cache *structs.ACLCaches
identityGroup singleflight.Group identityGroup singleflight.Group
@ -212,7 +211,7 @@ func NewACLResolver(config *ACLResolverConfig) (*ACLResolver, error) {
config: config.Config, config: config.Config,
logger: config.Logger, logger: config.Logger,
delegate: config.Delegate, delegate: config.Delegate,
sentinel: config.Sentinel, entConf: config.EnterpriseConfig,
cache: cache, cache: cache,
autoDisable: config.AutoDisable, autoDisable: config.AutoDisable,
down: down, down: down,
@ -249,7 +248,7 @@ func (r *ACLResolver) fetchAndCacheTokenLegacy(token string, cached *structs.Aut
policies = append(policies, policy.ConvertFromLegacy()) 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) r.cache.PutAuthorizerWithTTL(token, authorizer, reply.TTL)
return authorizer, err return authorizer, err
@ -292,7 +291,7 @@ func (r *ACLResolver) resolveTokenLegacy(token string) (acl.Authorizer, error) {
return nil, err 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 return nil, err
@ -1005,7 +1004,7 @@ func (r *ACLResolver) ResolveToken(token string) (acl.Authorizer, error) {
} }
// Build the Authorizer // 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 return authorizer, err
} }
@ -1035,7 +1034,7 @@ func (r *ACLResolver) GetMergedPolicyForToken(token string) (*acl.Policy, error)
return nil, acl.ErrNotFound 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 // 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. // 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 { if !f.enforceVersion8 {
return true 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. // 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 == "" { if service == "" {
return true return true
} }
@ -1075,16 +1074,16 @@ func (f *aclFilter) allowService(service string) bool {
if !f.enforceVersion8 && service == structs.ConsulServiceID { if !f.enforceVersion8 && service == structs.ConsulServiceID {
return true 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 // allowSession is used to determine if a session for a node is accessible for
// an ACL. // an ACL.
func (f *aclFilter) allowSession(node string) bool { func (f *aclFilter) allowSession(node string, ent *acl.EnterpriseAuthorizerContext) bool {
if !f.enforceVersion8 { if !f.enforceVersion8 {
return true 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 // 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 hc := *checks
for i := 0; i < len(hc); i++ { for i := 0; i < len(hc); i++ {
check := 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", check.CheckID) 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. // filterServices is used to filter a set of services based on ACLs.
func (f *aclFilter) filterServices(services structs.Services) { func (f *aclFilter) filterServices(services structs.Services) {
for svc := range 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc) 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 sn := *nodes
for i := 0; i < len(sn); i++ { for i := 0; i < len(sn); i++ {
node := 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node) 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 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 *services = nil
return return
} }
for svc := range (*services).Services { 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc) 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 csn := *nodes
for i := 0; i < len(csn); i++ { for i := 0; i < len(csn); i++ {
node := 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node.Node) 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 s := *sessions
for i := 0; i < len(s); i++ { for i := 0; i < len(s); i++ {
session := 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping session %q from result due to ACLs", session.ID) 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 c := *coords
for i := 0; i < len(c); i++ { for i := 0; i < len(c); i++ {
node := c[i].Node 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node) 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. // if the user doesn't have a management token.
func (f *aclFilter) filterIntentions(ixns *structs.Intentions) { func (f *aclFilter) filterIntentions(ixns *structs.Intentions) {
// Management tokens can see everything with no filtering. // 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 return
} }
@ -1212,7 +1220,8 @@ func (f *aclFilter) filterIntentions(ixns *structs.Intentions) {
// we know at this point the user doesn't have a management // we know at this point the user doesn't have a management
// token, otherwise see what the policy says. // token, otherwise see what the policy says.
prefix, ok := ixn.GetACLPrefix() 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) f.logger.Printf("[DEBUG] consul: dropping intention %q from result due to ACLs", ixn.ID)
continue continue
} }
@ -1231,7 +1240,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
info := nd[i] info := nd[i]
// Filter nodes // 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) f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
nd = append(nd[:i], nd[i+1:]...) nd = append(nd[:i], nd[i+1:]...)
i-- i--
@ -1241,7 +1251,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
// Filter services // Filter services
for j := 0; j < len(info.Services); j++ { for j := 0; j < len(info.Services); j++ {
svc := info.Services[j].Service 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc) 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 // Filter checks
for j := 0; j < len(info.Checks); j++ { for j := 0; j < len(info.Checks); j++ {
chk := 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", chk.CheckID) 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 n := *nodes
for i := 0; i < len(n); i++ { for i := 0; i < len(n); i++ {
node := n[i].Node 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 continue
} }
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node) 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. // captured tokens, but they can at least see whether or not a token is set.
func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) { func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
// Management tokens can see everything with no filtering. // 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 return
} }
@ -1312,7 +1326,11 @@ func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
// if the user doesn't have a management token. // if the user doesn't have a management token.
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) { func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
// Management tokens can see everything with no filtering. // 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 return
} }
@ -1323,7 +1341,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
// we know at this point the user doesn't have a management // we know at this point the user doesn't have a management
// token, otherwise see what the policy says. // token, otherwise see what the policy says.
prefix, ok := query.GetACLPrefix() 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) f.logger.Printf("[DEBUG] consul: dropping prepared query %q from result due to ACLs", query.ID)
continue continue
} }
@ -1338,7 +1356,8 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
} }
func (f *aclFilter) redactTokenSecret(token **structs.ACLToken) { 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 return
} }
clone := *(*token) clone := *(*token)
@ -1454,46 +1473,49 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
return nil 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 // This gets called potentially from a few spots so we save it and
// return the structure we made if we have it. // return the structure we made if we have it.
var memo map[string]interface{} // var memo map[string]interface{}
scope := func() map[string]interface{} { // scope := func() map[string]interface{} {
if memo != nil { // if memo != nil {
return memo // return memo
} // }
node := &api.Node{ // node := &api.Node{
ID: string(subj.ID), // ID: string(subj.ID),
Node: subj.Node, // Node: subj.Node,
Address: subj.Address, // Address: subj.Address,
Datacenter: subj.Datacenter, // Datacenter: subj.Datacenter,
TaggedAddresses: subj.TaggedAddresses, // TaggedAddresses: subj.TaggedAddresses,
Meta: subj.NodeMeta, // Meta: subj.NodeMeta,
} // }
var service *api.AgentService // var service *api.AgentService
if subj.Service != nil { // if subj.Service != nil {
service = &api.AgentService{ // service = &api.AgentService{
ID: subj.Service.ID, // ID: subj.Service.ID,
Service: subj.Service.Service, // Service: subj.Service.Service,
Tags: subj.Service.Tags, // Tags: subj.Service.Tags,
Meta: subj.Service.Meta, // Meta: subj.Service.Meta,
Address: subj.Service.Address, // Address: subj.Service.Address,
Port: subj.Service.Port, // Port: subj.Service.Port,
EnableTagOverride: subj.Service.EnableTagOverride, // EnableTagOverride: subj.Service.EnableTagOverride,
} // }
} // }
memo = sentinel.ScopeCatalogUpsert(node, service) // memo = sentinel.ScopeCatalogUpsert(node, service)
return memo // return memo
} // }
// Vet the node info. This allows service updates to re-post the required // Vet the node info. This allows service updates to re-post the required
// node info for each request without having to have node "write" // node info for each request without having to have node "write"
// privileges. // privileges.
needsNode := ns == nil || subj.ChangesNode(ns.Node) 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 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 // the given service, and that we can write to any existing service that
// is being modified by id (if any). // is being modified by id (if any).
if subj.Service != nil { 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 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 // This is effectively a delete, so we DO NOT apply the
// sentinel scope to the service we are overwriting, just // sentinel scope to the service we are overwriting, just
// the regular ACL policy. // 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 return acl.ErrPermissionDenied
} }
} }
@ -1540,7 +1564,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
// Node-level check. // Node-level check.
if check.ServiceID == "" { 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 return acl.ErrPermissionDenied
} }
continue 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, // 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 // since the sentinel policy doesn't apply to adding checks at
// this time. // this time.
if !rule.ServiceWrite(other.Service, nil) { if rule.ServiceWrite(other.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied 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. // 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 // This accounts for cases where the agent no longer has a token with write permission
// on the service to deregister it. // 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 return nil
} }
@ -1607,7 +1633,8 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest,
if ns == nil { if ns == nil {
return fmt.Errorf("Unknown service '%s'", subj.ServiceID) 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 return acl.ErrPermissionDenied
} }
} else if subj.CheckID != "" { } else if subj.CheckID != "" {
@ -1615,11 +1642,13 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest,
return fmt.Errorf("Unknown check '%s'", subj.CheckID) return fmt.Errorf("Unknown check '%s'", subj.CheckID)
} }
if nc.ServiceID != "" { 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 return acl.ErrPermissionDenied
} }
} else { } 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 return acl.ErrPermissionDenied
} }
} }
@ -1641,24 +1670,27 @@ func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error {
node := op.Node node := op.Node
n := &api.Node{ // TODO (namespaces) uncomment once we bring back sentinel scope creation in the authz ctx
Node: node.Node, // n := &api.Node{
ID: string(node.ID), // Node: node.Node,
Address: node.Address, // ID: string(node.ID),
Datacenter: node.Datacenter, // Address: node.Address,
TaggedAddresses: node.TaggedAddresses, // Datacenter: node.Datacenter,
Meta: node.Meta, // 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. // Sentinel doesn't apply to deletes, only creates/updates, so we don't need the scopeFn.
var scope func() map[string]interface{} // var scope func() map[string]interface{}
if op.Verb != api.NodeDelete && op.Verb != api.NodeDeleteCAS { // if op.Verb != api.NodeDelete && op.Verb != api.NodeDeleteCAS {
scope = func() map[string]interface{} { // scope = func() map[string]interface{} {
return sentinel.ScopeCatalogUpsert(n, nil) // 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 return acl.ErrPermissionDenied
} }
@ -1674,23 +1706,25 @@ func vetServiceTxnOp(op *structs.TxnServiceOp, rule acl.Authorizer) error {
service := op.Service service := op.Service
n := &api.Node{Node: op.Node} // TODO (namespaces) update to create authz context with the sentinel scope
svc := &api.AgentService{ // n := &api.Node{Node: op.Node}
ID: service.ID, // svc := &api.AgentService{
Service: service.Service, // ID: service.ID,
Tags: service.Tags, // Service: service.Service,
Meta: service.Meta, // Tags: service.Tags,
Address: service.Address, // Meta: service.Meta,
Port: service.Port, // Address: service.Address,
EnableTagOverride: service.EnableTagOverride, // Port: service.Port,
} // EnableTagOverride: service.EnableTagOverride,
var scope func() map[string]interface{} // }
if op.Verb != api.ServiceDelete && op.Verb != api.ServiceDeleteCAS { // var scope func() map[string]interface{}
scope = func() map[string]interface{} { // if op.Verb != api.ServiceDelete && op.Verb != api.ServiceDeleteCAS {
return sentinel.ScopeCatalogUpsert(n, svc) // scope = func() map[string]interface{} {
} // return sentinel.ScopeCatalogUpsert(n, svc)
} // }
if !rule.ServiceWrite(service.Service, scope) { // }
// 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 return acl.ErrPermissionDenied
} }
@ -1704,31 +1738,36 @@ func vetCheckTxnOp(op *structs.TxnCheckOp, rule acl.Authorizer) error {
return nil return nil
} }
n := &api.Node{Node: op.Check.Node} // TODO (namespaces) uncomment once these are used for sentinel scope creation
svc := &api.AgentService{ // n := &api.Node{Node: op.Check.Node}
ID: op.Check.ServiceID, // svc := &api.AgentService{
Service: op.Check.ServiceID, // ID: op.Check.ServiceID,
Tags: op.Check.ServiceTags, // Service: op.Check.ServiceID,
} // Tags: op.Check.ServiceTags,
var scope func() map[string]interface{} // }
// var scope func() map[string]interface{}
if op.Check.ServiceID == "" { if op.Check.ServiceID == "" {
// Node-level check. // Node-level check.
if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS { // TODO (namespaces) update to create authz with sentinel scope
scope = func() map[string]interface{} { // if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS {
return sentinel.ScopeCatalogUpsert(n, svc) // scope = func() map[string]interface{} {
} // return sentinel.ScopeCatalogUpsert(n, svc)
} // }
if !rule.NodeWrite(op.Check.Node, scope) { // }
// 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 return acl.ErrPermissionDenied
} }
} else { } else {
// Service-level check. // Service-level check.
if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS { // TODO (namespaces) update to create authz with sentinel scope
scope = func() map[string]interface{} { // if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS {
return sentinel.ScopeCatalogUpsert(n, svc) // scope = func() map[string]interface{} {
} // return sentinel.ScopeCatalogUpsert(n, svc)
} // }
if !rule.ServiceWrite(op.Check.ServiceName, scope) { // }
// 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 return acl.ErrPermissionDenied
} }
} }

View File

@ -206,9 +206,10 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke
// Only ACLRead privileges are required to list tokens // Only ACLRead privileges are required to list tokens
// However if you do not have ACLWrite as well the token // However if you do not have ACLWrite as well the token
// secrets will be redacted // 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 { if rule, err = a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied 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) index, token, err = state.ACLTokenGetByAccessor(ws, args.TokenID)
if token != nil { if token != nil {
a.srv.filterACLWithAuthorizer(rule, &token) 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 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()) 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "token", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "token", "delete"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -760,9 +765,10 @@ func (a *ACL) TokenList(args *structs.ACLTokenListRequest, reply *structs.ACLTok
} }
rule, err := a.srv.ResolveToken(args.Token) rule, err := a.srv.ResolveToken(args.Token)
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if err != nil { if err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -801,9 +807,10 @@ func (a *ACL) TokenBatchRead(args *structs.ACLTokenBatchGetRequest, reply *struc
} }
rule, err := a.srv.ResolveToken(args.Token) rule, err := a.srv.ResolveToken(args.Token)
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if err != nil { if err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -819,7 +826,8 @@ func (a *ACL) TokenBatchRead(args *structs.ACLTokenBatchGetRequest, reply *struc
a.srv.filterACLWithAuthorizer(rule, &tokens) a.srv.filterACLWithAuthorizer(rule, &tokens)
reply.Index, reply.Tokens = index, 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 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "policy", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -971,7 +980,7 @@ func (a *ACL) PolicySet(args *structs.ACLPolicySetRequest, reply *structs.ACLPol
} }
// validate the rules // 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 { if err != nil {
return err return err
} }
@ -1018,9 +1027,10 @@ func (a *ACL) PolicyDelete(args *structs.ACLPolicyDeleteRequest, reply *string)
defer metrics.MeasureSince([]string{"acl", "policy", "delete"}, time.Now()) defer metrics.MeasureSince([]string{"acl", "policy", "delete"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1068,9 +1078,10 @@ func (a *ACL) PolicyList(args *structs.ACLPolicyListRequest, reply *structs.ACLP
return err 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1213,9 +1224,10 @@ func (a *ACL) RoleRead(args *structs.ACLRoleGetRequest, reply *structs.ACLRoleRe
return err return err
} }
// TODO (namespaces) update to create and use actual enterprise authorizer context
if rule, err := a.srv.ResolveToken(args.Token); err != nil { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1250,9 +1262,10 @@ func (a *ACL) RoleBatchRead(args *structs.ACLRoleBatchGetRequest, reply *structs
return err return err
} }
// TODO (namespaces) update to create and use actual enterprise authorizer context
if rule, err := a.srv.ResolveToken(args.Token); err != nil { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "role", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "role", "delete"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1468,9 +1483,10 @@ func (a *ACL) RoleList(args *structs.ACLRoleListRequest, reply *structs.ACLRoleL
return err 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1544,9 +1560,10 @@ func (a *ACL) BindingRuleRead(args *structs.ACLBindingRuleGetRequest, reply *str
return err 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "bindingrule", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1696,9 +1714,10 @@ func (a *ACL) BindingRuleDelete(args *structs.ACLBindingRuleDeleteRequest, reply
defer metrics.MeasureSince([]string{"acl", "bindingrule", "delete"}, time.Now()) defer metrics.MeasureSince([]string{"acl", "bindingrule", "delete"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1742,9 +1761,10 @@ func (a *ACL) BindingRuleList(args *structs.ACLBindingRuleListRequest, reply *st
return err 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1773,9 +1793,10 @@ func (a *ACL) AuthMethodRead(args *structs.ACLAuthMethodGetRequest, reply *struc
return err 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied 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()) defer metrics.MeasureSince([]string{"acl", "authmethod", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1885,9 +1907,10 @@ func (a *ACL) AuthMethodDelete(args *structs.ACLAuthMethodDeleteRequest, reply *
defer metrics.MeasureSince([]string{"acl", "authmethod", "delete"}, time.Now()) defer metrics.MeasureSince([]string{"acl", "authmethod", "delete"}, time.Now())
// Verify token is permitted to modify ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -1931,9 +1954,10 @@ func (a *ACL) AuthMethodList(args *structs.ACLAuthMethodListRequest, reply *stru
return err 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLRead() { } else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }

View File

@ -114,7 +114,7 @@ func aclApplyInternal(srv *Server, args *structs.ACLRequest, reply *string) erro
} }
// Validate the rules compile // 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 { if err != nil {
return fmt.Errorf("ACL rule compilation failed: %v", err) 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 // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -198,6 +199,10 @@ func (a *ACL) Get(args *structs.ACLSpecificRequest,
return err 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 // Verify we are allowed to serve this request
if !a.srv.ACLsEnabled() { if !a.srv.ACLsEnabled() {
return acl.ErrDisabled return acl.ErrDisabled
@ -246,9 +251,11 @@ func (a *ACL) List(args *structs.DCSpecificRequest,
} }
// Verify token is permitted to list ACLs // 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 { if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err return err
} else if rule == nil || !rule.ACLWrite() { } else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }

View File

@ -212,7 +212,7 @@ func TestACLEndpoint_Update_PurgeCache(t *testing.T) {
if acl1 == nil { if acl1 == nil {
t.Fatalf("should not be nil") t.Fatalf("should not be nil")
} }
if !acl1.KeyRead("foo") { if acl1.KeyRead("foo", nil) != acl.Allow {
t.Fatalf("should be allowed") t.Fatalf("should be allowed")
} }
@ -234,7 +234,7 @@ func TestACLEndpoint_Update_PurgeCache(t *testing.T) {
if acl2 == acl1 { if acl2 == acl1 {
t.Fatalf("should not be cached") t.Fatalf("should not be cached")
} }
if acl2.KeyRead("foo") { if acl2.KeyRead("foo", nil) == acl.Allow {
t.Fatalf("should not be allowed") t.Fatalf("should not be allowed")
} }

13
agent/consul/acl_oss.go Normal file
View File

@ -0,0 +1,13 @@
// +build !consulent
package consul
import (
"log"
"github.com/hashicorp/consul/acl"
)
func newEnterpriseACLConfig(*log.Logger) *acl.EnterpriseACLConfig {
return nil
}

View File

@ -764,7 +764,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken("found") authz, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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, "node-wr", true, "cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.False(t, authz == 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, "node-wr", false, "expired") // from "found" token
requirePolicyCached(t, r, "dc2-key-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") authz, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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 // role cache expired - so we will fail to resolve that role and use the default policy only
authz2, err := r.ResolveToken("found-role") authz2, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.False(t, authz == 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) { t.Run("Extend-Cache-Policy", func(t *testing.T) {
@ -832,7 +832,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken("found") authz, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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") requireIdentityCached(t, r, "found", true, "cached")
@ -841,7 +841,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) 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) { 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") authz, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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") requireIdentityCached(t, r, "found-role", true, "still cached")
@ -873,7 +873,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) 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) { 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") authz, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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, "node-wr", true, "cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.True(t, authz == 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, "node-wr", true, "still cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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") authz, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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 // Will just use the policy cache
authz2, err := r.ResolveToken("found-role") authz2, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz2) require.NotNil(t, authz2)
require.True(t, authz == 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) { 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") authz, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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, "node-wr", true, "cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == 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, "cached") // from "found" token requirePolicyCached(t, r, "node-wr", true, "cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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") authz3, err := r.ResolveToken("found")
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, authz3) 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 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") authz, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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 // The identity should have been cached so this should still be valid
authz2, err := r.ResolveToken("found-role") authz2, err := r.ResolveToken("found-role")
@ -1028,14 +1028,14 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) 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 // the go routine spawned will eventually return with a authz that doesn't have the policy
retry.Run(t, func(t *retry.R) { retry.Run(t, func(t *retry.R) {
authz3, err := r.ResolveToken("found-role") authz3, err := r.ResolveToken("found-role")
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, authz3) 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") authz, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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, "node-wr", true, "cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) 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) { 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") authz, err := r.ResolveToken("found-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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, "node-wr", true, "still cached") // from "found" token
requirePolicyCached(t, r, "dc2-key-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) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // 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, 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) { t.Run("Async-Cache", func(t *testing.T) {
@ -1132,7 +1132,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken("found") authz, err := r.ResolveToken("found")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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") requireIdentityCached(t, r, "found", true, "cached")
@ -1142,7 +1142,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
require.NotNil(t, authz2) require.NotNil(t, authz2)
// testing pointer equality - these will be the same object because it is cached. // testing pointer equality - these will be the same object because it is cached.
require.True(t, authz == authz2) 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") requireIdentityCached(t, r, "found", true, "cached")
@ -1205,7 +1205,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken(secretID) authz, err := r.ResolveToken(secretID)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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. // Verify that the caches are setup properly.
requireIdentityCached(t, r, secretID, true, "cached") requireIdentityCached(t, r, secretID, true, "cached")
@ -1267,7 +1267,7 @@ func TestACLResolver_DownPolicy(t *testing.T) {
authz, err := r.ResolveToken(secretID) authz, err := r.ResolveToken(secretID)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) 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. // Verify that the caches are setup properly.
requireIdentityCached(t, r, secretID, true, "cached") requireIdentityCached(t, r, secretID, true, "cached")
@ -1304,9 +1304,9 @@ func TestACLResolver_DatacenterScoping(t *testing.T) {
authz, err := r.ResolveToken("found") authz, err := r.ResolveToken("found")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.False(t, authz.KeyWrite("foo", nil)) require.Equal(t, acl.Deny, authz.KeyWrite("foo", nil))
}) })
t.Run("dc2", func(t *testing.T) { t.Run("dc2", func(t *testing.T) {
@ -1326,9 +1326,9 @@ func TestACLResolver_DatacenterScoping(t *testing.T) {
authz, err := r.ResolveToken("found") authz, err := r.ResolveToken("found")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
require.True(t, authz.KeyWrite("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") authz, err := r.ResolveToken("a1a54629-5050-4d17-8a4e-560d2423f835")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.True(t, modified) require.True(t, modified)
require.True(t, deleted) require.True(t, deleted)
require.Equal(t, int32(2), tokenReads) require.Equal(t, int32(2), tokenReads)
@ -1577,49 +1577,49 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega
authz, err := r.ResolveToken("missing-policy") authz, err := r.ResolveToken("missing-policy")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
require.True(t, authz.ACLRead()) require.Equal(t, acl.Allow, authz.ACLRead(nil))
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
}) })
runTwiceAndReset("Missing Role", func(t *testing.T) { runTwiceAndReset("Missing Role", func(t *testing.T) {
authz, err := r.ResolveToken("missing-role") authz, err := r.ResolveToken("missing-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
require.True(t, authz.ACLRead()) require.Equal(t, acl.Allow, authz.ACLRead(nil))
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
}) })
runTwiceAndReset("Missing Policy on Role", func(t *testing.T) { runTwiceAndReset("Missing Policy on Role", func(t *testing.T) {
authz, err := r.ResolveToken("missing-policy-on-role") authz, err := r.ResolveToken("missing-policy-on-role")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
require.True(t, authz.ACLRead()) require.Equal(t, acl.Allow, authz.ACLRead(nil))
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
}) })
runTwiceAndReset("Normal with Policy", func(t *testing.T) { runTwiceAndReset("Normal with Policy", func(t *testing.T) {
authz, err := r.ResolveToken("found") authz, err := r.ResolveToken("found")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
}) })
runTwiceAndReset("Normal with Role", func(t *testing.T) { runTwiceAndReset("Normal with Role", func(t *testing.T) {
authz, err := r.ResolveToken("found-role") authz, err := r.ResolveToken("found-role")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
}) })
runTwiceAndReset("Normal with Policy and Role", func(t *testing.T) { runTwiceAndReset("Normal with Policy and Role", func(t *testing.T) {
authz, err := r.ResolveToken("found-policy-and-role") authz, err := r.ResolveToken("found-policy-and-role")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.ServiceRead("bar")) require.Equal(t, acl.Allow, authz.ServiceRead("bar", nil))
}) })
runTwiceAndReset("Synthetic Policies Independently Cache", func(t *testing.T) { 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.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
// spot check some random perms // spot check some random perms
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
// ensure we didn't bleed over to the other synthetic policy // 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 // check our own synthetic policy
require.True(t, authz.ServiceWrite("service1", nil)) require.Equal(t, acl.Allow, authz.ServiceWrite("service1", nil))
require.True(t, authz.ServiceRead("literally-anything")) require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil))
require.True(t, authz.NodeRead("any-node")) require.Equal(t, acl.Allow, authz.NodeRead("any-node", nil))
} }
{ {
authz, err := r.ResolveToken("found-synthetic-policy-2") authz, err := r.ResolveToken("found-synthetic-policy-2")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
// spot check some random perms // spot check some random perms
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
// ensure we didn't bleed over to the other synthetic policy // 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 // check our own synthetic policy
require.True(t, authz.ServiceWrite("service2", nil)) require.Equal(t, acl.Allow, authz.ServiceWrite("service2", nil))
require.True(t, authz.ServiceRead("literally-anything")) require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil))
require.True(t, authz.NodeRead("any-node")) 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("") authz, err := r.ResolveToken("")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
}) })
runTwiceAndReset("legacy-management", func(t *testing.T) { runTwiceAndReset("legacy-management", func(t *testing.T) {
authz, err := r.ResolveToken("legacy-management") authz, err := r.ResolveToken("legacy-management")
require.NotNil(t, authz) require.NotNil(t, authz)
require.NoError(t, err) require.NoError(t, err)
require.True(t, authz.ACLWrite()) require.Equal(t, acl.Allow, authz.ACLWrite(nil))
require.True(t, authz.KeyRead("foo")) require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
}) })
runTwiceAndReset("legacy-client", func(t *testing.T) { runTwiceAndReset("legacy-client", func(t *testing.T) {
authz, err := r.ResolveToken("legacy-client") authz, err := r.ResolveToken("legacy-client")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
require.False(t, authz.OperatorRead()) require.Equal(t, acl.Deny, authz.OperatorRead(nil))
require.True(t, authz.ServiceRead("foo")) require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
}) })
} }
@ -1700,10 +1700,12 @@ func TestACLResolver_Legacy(t *testing.T) {
reply.ETag = "nothing" reply.ETag = "nothing"
reply.Policy = &acl.Policy{ reply.Policy = &acl.Policy{
ID: "not-needed", ID: "not-needed",
Nodes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{ Nodes: []*acl.NodeRule{
Name: "foo", &acl.NodeRule{
Policy: acl.PolicyWrite, Name: "foo",
Policy: acl.PolicyWrite,
},
}, },
}, },
} }
@ -1719,18 +1721,18 @@ func TestACLResolver_Legacy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
// this should be from the cache // this should be from the cache
authz, err = r.ResolveToken("foo") authz, err = r.ResolveToken("foo")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
}) })
t.Run("Cache-Expiry-Extend", func(t *testing.T) { t.Run("Cache-Expiry-Extend", func(t *testing.T) {
@ -1749,10 +1751,12 @@ func TestACLResolver_Legacy(t *testing.T) {
reply.ETag = "nothing" reply.ETag = "nothing"
reply.Policy = &acl.Policy{ reply.Policy = &acl.Policy{
ID: "not-needed", ID: "not-needed",
Nodes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{ Nodes: []*acl.NodeRule{
Name: "foo", &acl.NodeRule{
Policy: acl.PolicyWrite, Name: "foo",
Policy: acl.PolicyWrite,
},
}, },
}, },
} }
@ -1770,18 +1774,18 @@ func TestACLResolver_Legacy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
// this should be from the cache // this should be from the cache
authz, err = r.ResolveToken("foo") authz, err = r.ResolveToken("foo")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
}) })
t.Run("Cache-Expiry-Allow", func(t *testing.T) { t.Run("Cache-Expiry-Allow", func(t *testing.T) {
@ -1800,10 +1804,12 @@ func TestACLResolver_Legacy(t *testing.T) {
reply.ETag = "nothing" reply.ETag = "nothing"
reply.Policy = &acl.Policy{ reply.Policy = &acl.Policy{
ID: "not-needed", ID: "not-needed",
Nodes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{ Nodes: []*acl.NodeRule{
Name: "foo", &acl.NodeRule{
Policy: acl.PolicyWrite, Name: "foo",
Policy: acl.PolicyWrite,
},
}, },
}, },
} }
@ -1822,18 +1828,18 @@ func TestACLResolver_Legacy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
// this should be from the cache // this should be from the cache
authz, err = r.ResolveToken("foo") authz, err = r.ResolveToken("foo")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.True(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("fo", nil))
}) })
t.Run("Cache-Expiry-Deny", func(t *testing.T) { t.Run("Cache-Expiry-Deny", func(t *testing.T) {
@ -1852,10 +1858,12 @@ func TestACLResolver_Legacy(t *testing.T) {
reply.ETag = "nothing" reply.ETag = "nothing"
reply.Policy = &acl.Policy{ reply.Policy = &acl.Policy{
ID: "not-needed", ID: "not-needed",
Nodes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{ Nodes: []*acl.NodeRule{
Name: "foo", &acl.NodeRule{
Policy: acl.PolicyWrite, Name: "foo",
Policy: acl.PolicyWrite,
},
}, },
}, },
} }
@ -1874,18 +1882,18 @@ func TestACLResolver_Legacy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
// this should be from the cache // this should be from the cache
authz, err = r.ResolveToken("foo") authz, err = r.ResolveToken("foo")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.False(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
require.False(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
}) })
t.Run("Cache-Expiry-Async-Cache", func(t *testing.T) { t.Run("Cache-Expiry-Async-Cache", func(t *testing.T) {
@ -1904,10 +1912,12 @@ func TestACLResolver_Legacy(t *testing.T) {
reply.ETag = "nothing" reply.ETag = "nothing"
reply.Policy = &acl.Policy{ reply.Policy = &acl.Policy{
ID: "not-needed", ID: "not-needed",
Nodes: []*acl.NodePolicy{ PolicyRules: acl.PolicyRules{
&acl.NodePolicy{ Nodes: []*acl.NodeRule{
Name: "foo", &acl.NodeRule{
Policy: acl.PolicyWrite, Name: "foo",
Policy: acl.PolicyWrite,
},
}, },
}, },
} }
@ -1926,9 +1936,9 @@ func TestACLResolver_Legacy(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
// there is a bit of translation that happens // there is a bit of translation that happens
require.True(t, authz.NodeWrite("foo", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo", nil))
require.True(t, authz.NodeWrite("foo/bar", nil)) require.Equal(t, acl.Allow, authz.NodeWrite("foo/bar", nil))
require.False(t, authz.NodeWrite("fo", nil)) require.Equal(t, acl.Deny, authz.NodeWrite("fo", nil))
// delivered from the cache // delivered from the cache
authz2, err := r.ResolveToken("foo") authz2, err := r.ResolveToken("foo")
@ -2170,7 +2180,7 @@ service "foo" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2204,7 +2214,7 @@ node "node1" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2260,7 +2270,7 @@ service "foo" {
} }
`, acl.SyntaxLegacy, nil) `, acl.SyntaxLegacy, nil)
assert.Nil(err) 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) assert.Nil(err)
// Filter // Filter
@ -2347,7 +2357,7 @@ service "foo" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2381,7 +2391,7 @@ node "node1" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2453,7 +2463,7 @@ service "foo" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2487,7 +2497,7 @@ node "node1" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2559,7 +2569,7 @@ service "foo" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2596,7 +2606,7 @@ node "node1" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2750,7 +2760,7 @@ service "foo" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2790,7 +2800,7 @@ node "node1" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3046,7 +3056,7 @@ node "node" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3091,7 +3101,7 @@ service "service" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3121,7 +3131,7 @@ service "other" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3195,7 +3205,7 @@ service "other" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3225,7 +3235,7 @@ node "node" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3272,7 +3282,7 @@ node "node" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3285,7 +3295,7 @@ node "node" {
if err != nil { if err != nil {
t.Fatalf("err %v", err) 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 { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -3294,7 +3304,7 @@ node "node" {
DeregisterRequest structs.DeregisterRequest DeregisterRequest structs.DeregisterRequest
Service *structs.NodeService Service *structs.NodeService
Check *structs.HealthCheck Check *structs.HealthCheck
Perms *acl.PolicyAuthorizer Perms acl.Authorizer
Expected bool Expected bool
Name string Name string
}{ }{

View File

@ -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 // later if version 0.8 is enabled, so we can eventually just
// delete this and do all the ACL checks down there. // delete this and do all the ACL checks down there.
if service.Service != structs.ConsulServiceName { 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 return acl.ErrPermissionDenied
} }
} }
// Proxies must have write permission on their destination // Proxies must have write permission on their destination
if service.Kind == structs.ServiceKindConnectProxy { 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 return acl.ErrPermissionDenied
} }
} }
@ -334,7 +336,8 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
return err 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) // Just return nil, which will return an empty response (tested)
return nil return nil
} }

View File

@ -154,12 +154,12 @@ func NewClientLogger(config *Config, logger *log.Logger, tlsConfigurator *tlsuti
c.useNewACLs = 0 c.useNewACLs = 0
aclConfig := ACLResolverConfig{ aclConfig := ACLResolverConfig{
Config: config, Config: config,
Delegate: c, Delegate: c,
Logger: logger, Logger: logger,
AutoDisable: true, AutoDisable: true,
CacheConfig: clientACLCacheConfig, CacheConfig: clientACLCacheConfig,
Sentinel: nil, EnterpriseConfig: newEnterpriseACLConfig(logger),
} }
var err error var err error
if c.acls, err = NewACLResolver(&aclConfig); err != nil { if c.acls, err = NewACLResolver(&aclConfig); err != nil {

View File

@ -231,7 +231,8 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
if err != nil { if err != nil {
return err 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 return acl.ErrPermissionDenied
} }

View File

@ -119,7 +119,7 @@ func (s *ConnectCA) ConfigurationGet(
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorRead() { if rule != nil && rule.OperatorRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -151,7 +151,7 @@ func (s *ConnectCA) ConfigurationSet(
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorWrite() { if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -431,7 +431,8 @@ func (s *ConnectCA) Sign(
return err return err
} }
if isService { 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 return acl.ErrPermissionDenied
} }
@ -442,7 +443,8 @@ func (s *ConnectCA) Sign(
"we are %s", serviceID.Datacenter, s.srv.config.Datacenter) "we are %s", serviceID.Datacenter, s.srv.config.Datacenter)
} }
} else if isAgent { } 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 return acl.ErrPermissionDenied
} }
} }
@ -569,7 +571,7 @@ func (s *ConnectCA) SignIntermediate(
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorWrite() { if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }

View File

@ -139,7 +139,8 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
return err return err
} }
if rule != nil && c.srv.config.ACLEnforceVersion8 { 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 return acl.ErrPermissionDenied
} }
} }
@ -210,7 +211,8 @@ func (c *Coordinate) Node(args *structs.NodeSpecificRequest, reply *structs.Inde
return err return err
} }
if rule != nil && c.srv.config.ACLEnforceVersion8 { 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 return acl.ErrPermissionDenied
} }
} }

View File

@ -34,7 +34,8 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs
if err != nil { if err != nil {
return err 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 return acl.ErrPermissionDenied
} }

View File

@ -14,7 +14,7 @@ func (d *dirEntFilter) Len() int {
return len(d.ent) return len(d.ent)
} }
func (d *dirEntFilter) Filter(i int) bool { 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) { func (d *dirEntFilter) Move(dst, src, span int) {
copy(d.ent[dst:dst+span], d.ent[src:src+span]) copy(d.ent[dst:dst+span], d.ent[src:src+span])
@ -36,7 +36,8 @@ func (k *keyFilter) Len() int {
return len(k.keys) return len(k.keys)
} }
func (k *keyFilter) Filter(i int) bool { 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) { func (k *keyFilter) Move(dst, src, span int) {
@ -60,19 +61,20 @@ func (t *txnResultsFilter) Len() int {
} }
func (t *txnResultsFilter) Filter(i int) bool { func (t *txnResultsFilter) Filter(i int) bool {
// TODO (namespaces) use a real ent authz context for most of these checks
result := t.results[i] result := t.results[i]
switch { switch {
case result.KV != nil: 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: 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: 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: case result.Check != nil:
if result.Check.ServiceName != "" { 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 return false
} }

View File

@ -11,7 +11,7 @@ import (
func TestFilter_DirEnt(t *testing.T) { func TestFilter_DirEnt(t *testing.T) {
t.Parallel() t.Parallel()
policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil) 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 { type tcase struct {
in []string in []string
@ -53,7 +53,7 @@ func TestFilter_DirEnt(t *testing.T) {
func TestFilter_Keys(t *testing.T) { func TestFilter_Keys(t *testing.T) {
t.Parallel() t.Parallel()
policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil) 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 { type tcase struct {
in []string in []string
@ -85,7 +85,7 @@ func TestFilter_Keys(t *testing.T) {
func TestFilter_TxnResults(t *testing.T) { func TestFilter_TxnResults(t *testing.T) {
t.Parallel() t.Parallel()
policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil) 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 { type tcase struct {
in []string in []string

View File

@ -5,6 +5,7 @@ import (
"sort" "sort"
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
bexpr "github.com/hashicorp/go-bexpr" bexpr "github.com/hashicorp/go-bexpr"
@ -171,7 +172,7 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
return err 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) // Just return nil, which will return an empty response (tested)
return nil return nil
} }

View File

@ -87,7 +87,7 @@ func (s *Intention) Apply(
// Perform the ACL check // Perform the ACL check
if prefix, ok := args.Intention.GetACLPrefix(); ok { 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) s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -107,7 +107,7 @@ func (s *Intention) Apply(
// Perform the ACL check that we have write to the old prefix too, // Perform the ACL check that we have write to the old prefix too,
// which must be true to perform any rename. // which must be true to perform any rename.
if prefix, ok := ixn.GetACLPrefix(); ok { 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) s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -243,7 +243,7 @@ func (s *Intention) Match(
// We go through each entry and test the destination to check if it // We go through each entry and test the destination to check if it
// matches. // matches.
for _, entry := range args.Match.Entries { 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) s.srv.logger.Printf("[WARN] consul.intention: Operation on intention prefix '%s' denied due to ACLs", prefix)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -309,7 +309,7 @@ func (s *Intention) Check(
// NOT IntentionRead because the Check API only returns pass/fail and // NOT IntentionRead because the Check API only returns pass/fail and
// returns no other information about the intentions used. // returns no other information about the intentions used.
if prefix, ok := query.GetACLPrefix(); ok { 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) s.srv.logger.Printf("[WARN] consul.intention: test on intention '%s' denied due to ACLs", prefix)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -360,7 +360,7 @@ func (s *Intention) Check(
reply.Allowed = true reply.Allowed = true
if rule != nil { if rule != nil {
reply.Allowed = rule.IntentionDefaultAllow() reply.Allowed = rule.IntentionDefaultAllow(nil) == acl.Allow
} }
return nil return nil

View File

@ -125,7 +125,7 @@ func (m *Internal) EventFire(args *structs.EventFireRequest,
return err 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) m.srv.logger.Printf("[WARN] consul: user event %q blocked by ACLs", args.Name)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -162,7 +162,7 @@ func (m *Internal) KeyringOperation(
if rule != nil { if rule != nil {
switch args.Operation { switch args.Operation {
case structs.KeyringList: case structs.KeyringList:
if !rule.KeyringRead() { if rule.KeyringRead(nil) != acl.Allow {
return fmt.Errorf("Reading keyring denied by ACLs") return fmt.Errorf("Reading keyring denied by ACLs")
} }
case structs.KeyringInstall: case structs.KeyringInstall:
@ -170,7 +170,7 @@ func (m *Internal) KeyringOperation(
case structs.KeyringUse: case structs.KeyringUse:
fallthrough fallthrough
case structs.KeyringRemove: case structs.KeyringRemove:
if !rule.KeyringWrite() { if rule.KeyringWrite(nil) != acl.Allow {
return fmt.Errorf("Modifying keyring denied due to ACLs") return fmt.Errorf("Modifying keyring denied due to ACLs")
} }
default: default:

View File

@ -9,7 +9,6 @@ import (
"github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
) )
@ -32,7 +31,8 @@ func kvsPreApply(srv *Server, rule acl.Authorizer, op api.KVOp, dirEnt *structs.
if rule != nil { if rule != nil {
switch op { switch op {
case api.KVDeleteTree: 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 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 // These could reveal information based on the outcome
// of the transaction, and they operate on individual // of the transaction, and they operate on individual
// keys so we check them here. // keys so we check them here.
if !rule.KeyRead(dirEnt.Key) { if rule.KeyRead(dirEnt.Key, nil) != acl.Allow {
return false, acl.ErrPermissionDenied return false, acl.ErrPermissionDenied
} }
default: default:
scope := func() map[string]interface{} { // TODO (namespaces) use actual ent authz context - ensure we set the Sentinel Scope
return sentinel.ScopeKVUpsert(dirEnt.Key, dirEnt.Value, dirEnt.Flags) if rule.KeyWrite(dirEnt.Key, nil) != acl.Allow {
}
if !rule.KeyWrite(dirEnt.Key, scope) {
return false, acl.ErrPermissionDenied return false, acl.ErrPermissionDenied
} }
} }
@ -132,7 +130,7 @@ func (k *KVS) Get(args *structs.KeyRequest, reply *structs.IndexedDirEntries) er
if err != nil { if err != nil {
return err return err
} }
if aclRule != nil && !aclRule.KeyRead(args.Key) { if aclRule != nil && aclRule.KeyRead(args.Key, nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -164,7 +162,7 @@ func (k *KVS) List(args *structs.KeyRequest, reply *structs.IndexedDirEntries) e
return err 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 return acl.ErrPermissionDenied
} }
@ -208,7 +206,7 @@ func (k *KVS) ListKeys(args *structs.KeyListRequest, reply *structs.IndexedKeyLi
return err 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 return acl.ErrPermissionDenied
} }

View File

@ -498,23 +498,28 @@ func (s *Server) initializeACLs(upgrade bool) error {
s.logger.Printf("[INFO] acl: initializing acls") 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) _, policy, err := s.fsm.State().ACLPolicyGetByID(nil, structs.ACLPolicyGlobalManagementID)
if err != nil { if err != nil {
return fmt.Errorf("failed to get the builtin global-management policy") return fmt.Errorf("failed to get the builtin global-management policy")
} }
if policy == nil { if policy == nil || policy.Rules != structs.ACLPolicyGlobalManagement {
policy := structs.ACLPolicy{ newPolicy := structs.ACLPolicy{
ID: structs.ACLPolicyGlobalManagementID, ID: structs.ACLPolicyGlobalManagementID,
Name: "global-management", Name: "global-management",
Description: "Builtin Policy that grants unlimited access", Description: "Builtin Policy that grants unlimited access",
Rules: structs.ACLPolicyGlobalManagement, Rules: structs.ACLPolicyGlobalManagement,
Syntax: acl.SyntaxCurrent, Syntax: acl.SyntaxCurrent,
} }
policy.SetHash(true) if policy != nil {
newPolicy.Name = policy.Name
newPolicy.Description = policy.Description
}
newPolicy.SetHash(true)
req := structs.ACLPolicyBatchSetRequest{ req := structs.ACLPolicyBatchSetRequest{
Policies: structs.ACLPolicies{&policy}, Policies: structs.ACLPolicies{&newPolicy},
} }
_, err := s.raftApply(structs.ACLPolicySetRequestType, &req) _, err := s.raftApply(structs.ACLPolicySetRequestType, &req)
if err != nil { if err != nil {

View File

@ -19,7 +19,7 @@ func (op *Operator) AutopilotGetConfiguration(args *structs.DCSpecificRequest, r
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorRead() { if rule != nil && rule.OperatorRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -48,7 +48,7 @@ func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRe
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorWrite() { if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -84,7 +84,7 @@ func (op *Operator) ServerHealth(args *structs.DCSpecificRequest, reply *autopil
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorRead() { if rule != nil && rule.OperatorRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }

View File

@ -22,7 +22,7 @@ func (op *Operator) RaftGetConfiguration(args *structs.DCSpecificRequest, reply
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorRead() { if rule != nil && rule.OperatorRead(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -84,7 +84,7 @@ func (op *Operator) RaftRemovePeerByAddress(args *structs.RaftRemovePeerRequest,
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorWrite() { if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -151,7 +151,7 @@ func (op *Operator) RaftRemovePeerByID(args *structs.RaftRemovePeerRequest, repl
if err != nil { if err != nil {
return err return err
} }
if rule != nil && !rule.OperatorWrite() { if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }

View File

@ -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 // need to make sure they have write access for whatever they are
// proposing. // proposing.
if prefix, ok := args.Query.GetACLPrefix(); ok { 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) p.srv.logger.Printf("[WARN] consul.prepared_query: Operation on prepared query '%s' denied due to ACLs", args.Query.ID)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }
@ -89,7 +89,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string)
} }
if prefix, ok := query.GetACLPrefix(); ok { 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) p.srv.logger.Printf("[WARN] consul.prepared_query: Operation on prepared query '%s' denied due to ACLs", args.Query.ID)
return acl.ErrPermissionDenied return acl.ErrPermissionDenied
} }

View File

@ -18,6 +18,7 @@ import (
"time" "time"
metrics "github.com/armon/go-metrics" metrics "github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
ca "github.com/hashicorp/consul/agent/connect/ca" ca "github.com/hashicorp/consul/agent/connect/ca"
"github.com/hashicorp/consul/agent/consul/autopilot" "github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/consul/agent/consul/fsm" "github.com/hashicorp/consul/agent/consul/fsm"
@ -28,7 +29,6 @@ import (
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/agent/token"
"github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types" "github.com/hashicorp/consul/types"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
@ -107,8 +107,9 @@ var (
// Server is Consul server which manages the service discovery, // Server is Consul server which manages the service discovery,
// health checking, DC forwarding, Raft, and multiple Serf pools. // health checking, DC forwarding, Raft, and multiple Serf pools.
type Server struct { type Server struct {
// sentinel is the Sentinel code engine (can be nil). // enterpriseACLConfig is the Consul Enterprise specific items
sentinel sentinel.Evaluator // necessary for ACLs
enterpriseACLConfig *acl.EnterpriseACLConfig
// acls is used to resolve tokens to effective policies // acls is used to resolve tokens to effective policies
acls *ACLResolver 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. // Initialize the stats fetcher that autopilot will use.
s.statsFetcher = NewStatsFetcher(logger, s.connPool, s.config.Datacenter) s.statsFetcher = NewStatsFetcher(logger, s.connPool, s.config.Datacenter)
s.sentinel = sentinel.New(logger) s.enterpriseACLConfig = newEnterpriseACLConfig(logger)
s.useNewACLs = 0 s.useNewACLs = 0
aclConfig := ACLResolverConfig{ aclConfig := ACLResolverConfig{
Config: config, Config: config,
Delegate: s, Delegate: s,
CacheConfig: serverACLCacheConfig, CacheConfig: serverACLCacheConfig,
AutoDisable: false, AutoDisable: false,
Logger: logger, Logger: logger,
Sentinel: s.sentinel, EnterpriseConfig: s.enterpriseACLConfig,
} }
// Initialize the ACL resolver. // Initialize the ACL resolver.
if s.acls, err = NewACLResolver(&aclConfig); err != nil { if s.acls, err = NewACLResolver(&aclConfig); err != nil {

View File

@ -49,12 +49,14 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
if existing == nil { if existing == nil {
return fmt.Errorf("Unknown session %q", args.Session.ID) 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 return acl.ErrPermissionDenied
} }
case structs.SessionCreate: 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 return acl.ErrPermissionDenied
} }
@ -241,7 +243,8 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest,
return err return err
} }
if rule != nil && s.srv.config.ACLEnforceVersion8 { 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 return acl.ErrPermissionDenied
} }
} }

View File

@ -61,7 +61,7 @@ func (s *Server) dispatchSnapshotRequest(args *structs.SnapshotRequest, in io.Re
// all the ACLs and you could escalate from there. // all the ACLs and you could escalate from there.
if rule, err := s.ResolveToken(args.Token); err != nil { if rule, err := s.ResolveToken(args.Token); err != nil {
return nil, err return nil, err
} else if rule != nil && !rule.Snapshot() { } else if rule != nil && rule.Snapshot(nil) != acl.Allow {
return nil, acl.ErrPermissionDenied return nil, acl.ErrPermissionDenied
} }

View File

@ -1373,11 +1373,13 @@ func (s *Store) aclPolicySetTxn(tx *memdb.Txn, idx uint64, policy *structs.ACLPo
} }
if existing != nil { if existing != nil {
policyMatch := existing.(*structs.ACLPolicy)
if policy.ID == structs.ACLPolicyGlobalManagementID { if policy.ID == structs.ACLPolicyGlobalManagementID {
// Only the name and description are modifiable // 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") return fmt.Errorf("Changing the Rules for the builtin global-management policy is not permitted")
} }

View File

@ -78,7 +78,7 @@ func (s *HTTPServer) EventList(resp http.ResponseWriter, req *http.Request) (int
// Fetch the ACL token, if any. // Fetch the ACL token, if any.
var token string var token string
s.parseToken(req, &token) s.parseToken(req, &token)
acl, err := s.agent.resolveToken(token) authz, err := s.agent.resolveToken(token)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -128,10 +128,10 @@ RUN_QUERY:
events := s.agent.UserEvents() events := s.agent.UserEvents()
// Filter the events using the ACL, if present // Filter the events using the ACL, if present
if acl != nil { if authz != nil {
for i := 0; i < len(events); i++ { for i := 0; i < len(events); i++ {
name := events[i].Name name := events[i].Name
if acl.EventRead(name) { if authz.EventRead(name, nil) == acl.Allow {
continue continue
} }
s.agent.logger.Printf("[DEBUG] agent: dropping event %q from result due to ACLs", name) s.agent.logger.Printf("[DEBUG] agent: dropping event %q from result due to ACLs", name)

View File

@ -279,7 +279,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
// If the token provided does not have the necessary permissions, // If the token provided does not have the necessary permissions,
// write a forbidden response // write a forbidden response
if rule != nil && !rule.OperatorRead() { if rule != nil && rule.OperatorRead(nil) != acl.Allow {
resp.WriteHeader(http.StatusForbidden) resp.WriteHeader(http.StatusForbidden)
return return
} }

View File

@ -11,7 +11,6 @@ import (
"time" "time"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/sentinel"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
) )
@ -80,7 +79,7 @@ service_prefix "" {
} }
session_prefix "" { session_prefix "" {
policy = "write" policy = "write"
}` }` + EnterpriseACLPolicyGlobalManagement
// This is the policy ID for anonymous access. This is configurable by the // This is the policy ID for anonymous access. This is configurable by the
// user. // 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 // Parse the policies
parsed := make([]*acl.Policy, 0, len(policies)) parsed := make([]*acl.Policy, 0, len(policies))
for _, policy := range policies { for _, policy := range policies {
@ -658,7 +657,7 @@ func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, sentinel sentinel
continue 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 { if err != nil {
return nil, fmt.Errorf("failed to parse %q: %v", policy.Name, err) 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 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 // Determine the cache key
cacheKey := policies.HashKey() cacheKey := policies.HashKey()
entry := cache.GetAuthorizer(cacheKey) entry := cache.GetAuthorizer(cacheKey)
@ -679,13 +678,13 @@ func (policies ACLPolicies) Compile(parent acl.Authorizer, cache *ACLCaches, sen
return entry.Authorizer, nil return entry.Authorizer, nil
} }
parsed, err := policies.resolveWithCache(cache, sentinel) parsed, err := policies.resolveWithCache(cache, entConf)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse the ACL policies: %v", err) return nil, fmt.Errorf("failed to parse the ACL policies: %v", err)
} }
// Create the ACL object // Create the ACL object
authorizer, err := acl.NewPolicyAuthorizer(parent, parsed, sentinel) authorizer, err := acl.NewPolicyAuthorizerWithDefaults(parent, parsed, entConf)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to construct ACL Authorizer: %v", err) 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 return authorizer, nil
} }
func (policies ACLPolicies) Merge(cache *ACLCaches, sentinel sentinel.Evaluator) (*acl.Policy, error) { func (policies ACLPolicies) Merge(cache *ACLCaches, entConf *acl.EnterpriseACLConfig) (*acl.Policy, error) {
parsed, err := policies.resolveWithCache(cache, sentinel) parsed, err := policies.resolveWithCache(cache, entConf)
if err != nil { if err != nil {
return nil, err return nil, err
} }

7
agent/structs/acl_oss.go Normal file
View File

@ -0,0 +1,7 @@
// +build !consulent
package structs
const (
EnterpriseACLPolicyGlobalManagement = ""
)

View File

@ -665,11 +665,11 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, authz) require.NotNil(t, authz)
require.True(t, authz.NodeRead("foo")) require.Equal(t, acl.Allow, authz.NodeRead("foo", nil))
require.True(t, authz.AgentRead("foo")) require.Equal(t, acl.Allow, authz.AgentRead("foo", nil))
require.True(t, authz.KeyRead("foo")) require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
require.True(t, authz.ServiceRead("foo")) require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
}) })
t.Run("Check Cache", func(t *testing.T) { t.Run("Check Cache", func(t *testing.T) {
@ -678,11 +678,11 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) {
authz := entry.Authorizer authz := entry.Authorizer
require.NotNil(t, authz) require.NotNil(t, authz)
require.True(t, authz.NodeRead("foo")) require.Equal(t, acl.Allow, authz.NodeRead("foo", nil))
require.True(t, authz.AgentRead("foo")) require.Equal(t, acl.Allow, authz.AgentRead("foo", nil))
require.True(t, authz.KeyRead("foo")) require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
require.True(t, authz.ServiceRead("foo")) require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
// setup the cache for the next test // setup the cache for the next test
cache.PutAuthorizer(testPolicies.HashKey(), acl.DenyAll()) cache.PutAuthorizer(testPolicies.HashKey(), acl.DenyAll())
@ -694,10 +694,10 @@ func TestStructs_ACLPolicies_Compile(t *testing.T) {
require.NotNil(t, authz) require.NotNil(t, authz)
// we reset the Authorizer in the cache so now everything should be denied // we reset the Authorizer in the cache so now everything should be denied
require.False(t, authz.NodeRead("foo")) require.Equal(t, acl.Deny, authz.NodeRead("foo", nil))
require.False(t, authz.AgentRead("foo")) require.Equal(t, acl.Deny, authz.AgentRead("foo", nil))
require.False(t, authz.KeyRead("foo")) require.Equal(t, acl.Deny, authz.KeyRead("foo", nil))
require.False(t, authz.ServiceRead("foo")) require.Equal(t, acl.Deny, authz.ServiceRead("foo", nil))
require.False(t, authz.ACLRead()) require.Equal(t, acl.Deny, authz.ACLRead(nil))
}) })
} }

View File

@ -92,11 +92,11 @@ func (e *ServiceConfigEntry) Validate() error {
} }
func (e *ServiceConfigEntry) CanRead(rule acl.Authorizer) bool { 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 { 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 { 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 { func (e *ProxyConfigEntry) CanWrite(rule acl.Authorizer) bool {
return rule.OperatorWrite() return rule.OperatorWrite(nil) == acl.Allow
} }
func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex { func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {

View File

@ -892,13 +892,13 @@ type discoveryChainConfigEntry interface {
} }
func canReadDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer) bool { 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 { func canWriteDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer) bool {
name := entry.GetName() name := entry.GetName()
if !rule.ServiceWrite(name, nil) { if rule.ServiceWrite(name, nil) != acl.Allow {
return false 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 // You only need read on related services to redirect traffic flow for
// your own service. // your own service.
if !rule.ServiceRead(svc) { if rule.ServiceRead(svc, nil) != acl.Allow {
return false return false
} }
} }

View File

@ -27,7 +27,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
policy, err := acl.NewPolicyFromSource("", 0, buf.String(), acl.SyntaxCurrent, nil) policy, err := acl.NewPolicyFromSource("", 0, buf.String(), acl.SyntaxCurrent, nil)
require.NoError(t, err) 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) require.NoError(t, err)
return authorizer return authorizer
} }

View File

@ -251,12 +251,13 @@ func (s *Server) process(stream ADSStream, reqCh <-chan *envoy.DiscoveryRequest)
switch cfgSnap.Kind { switch cfgSnap.Kind {
case structs.ServiceKindConnectProxy: 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") return status.Errorf(codes.PermissionDenied, "permission denied")
} }
case structs.ServiceKindMeshGateway: case structs.ServiceKindMeshGateway:
// TODO (mesh-gateway) - figure out what ACLs to check for the Gateways // TODO (namespaces) - pass through a real ent authz ctx
if rule != nil && !rule.ServiceWrite(cfgSnap.Service, nil) { if rule != nil && rule.ServiceWrite(cfgSnap.Service, nil) != acl.Allow {
return status.Errorf(codes.PermissionDenied, "permission denied") return status.Errorf(codes.PermissionDenied, "permission denied")
} }
default: default:

View File

@ -450,7 +450,7 @@ func TestServer_StreamAggregatedResources_ACLEnforcement(t *testing.T) {
// Parse the ACL and enforce it // Parse the ACL and enforce it
policy, err := acl.NewPolicyFromSource("", 0, tt.acl, acl.SyntaxLegacy, nil) policy, err := acl.NewPolicyFromSource("", 0, tt.acl, acl.SyntaxLegacy, nil)
require.NoError(t, err) 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) envoy := NewTestEnvoy(t, "web-sidecar-proxy", tt.token)
defer envoy.Close() defer envoy.Close()
@ -521,7 +521,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedDuring
return nil, acl.ErrNotFound 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) envoy := NewTestEnvoy(t, "web-sidecar-proxy", token)
defer envoy.Close() defer envoy.Close()
@ -612,7 +612,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedInBack
return nil, acl.ErrNotFound 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) envoy := NewTestEnvoy(t, "web-sidecar-proxy", token)
defer envoy.Close() defer envoy.Close()