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
This commit is contained in:
Matt Keeler 2019-10-15 16:58:50 -04:00 committed by GitHub
parent d53aae22f5
commit f9a43a1e2d
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
- run:
command: make dev
command: make pkg/linux_amd64/nomad
working_directory: *NOMAD_WORKING_DIR
# 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
// entirely unnecessary so it is now using an exact match.
policy := &acl.Policy{
Agents: []*acl.AgentPolicy{
&acl.AgentPolicy{
Node: a.config.NodeName,
Policy: acl.PolicyWrite,
PolicyRules: acl.PolicyRules{
Agents: []*acl.AgentRule{
&acl.AgentRule{
Node: a.config.NodeName,
Policy: acl.PolicyWrite,
},
},
},
NodePrefixes: []*acl.NodePolicy{
&acl.NodePolicy{
Name: "",
Policy: acl.PolicyRead,
NodePrefixes: []*acl.NodeRule{
&acl.NodeRule{
Name: "",
Policy: acl.PolicyRead,
},
},
},
}
master, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil)
master, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
if err != nil {
return err
}
@ -75,14 +77,16 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e
}
// Vet the service itself.
if !rule.ServiceWrite(service.Service, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(service.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
// Vet any service that might be getting overwritten.
services := a.State.Services()
if existing, ok := services[service.ID]; ok {
if !rule.ServiceWrite(existing.Service, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(existing.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -90,7 +94,8 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e
// If the service is a proxy, ensure that it has write on the destination too
// since it can be discovered as an instance of that service.
if service.Kind == structs.ServiceKindConnectProxy {
if !rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -113,7 +118,8 @@ func (a *Agent) vetServiceUpdate(token string, serviceID string) error {
// Vet any changes based on the existing services's info.
services := a.State.Services()
if existing, ok := services[serviceID]; ok {
if !rule.ServiceWrite(existing.Service, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(existing.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else {
@ -137,11 +143,13 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
// Vet the check itself.
if len(check.ServiceName) > 0 {
if !rule.ServiceWrite(check.ServiceName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(check.ServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(a.config.NodeName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.NodeWrite(a.config.NodeName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -150,11 +158,13 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
checks := a.State.Checks()
if existing, ok := checks[check.CheckID]; ok {
if len(existing.ServiceName) > 0 {
if !rule.ServiceWrite(existing.ServiceName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(existing.ServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(a.config.NodeName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.NodeWrite(a.config.NodeName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -178,11 +188,13 @@ func (a *Agent) vetCheckUpdate(token string, checkID types.CheckID) error {
checks := a.State.Checks()
if existing, ok := checks[checkID]; ok {
if len(existing.ServiceName) > 0 {
if !rule.ServiceWrite(existing.ServiceName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceWrite(existing.ServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(a.config.NodeName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.NodeWrite(a.config.NodeName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -208,7 +220,8 @@ func (a *Agent) filterMembers(token string, members *[]serf.Member) error {
m := *members
for i := 0; i < len(m); i++ {
node := m[i].Name
if rule.NodeRead(node) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.NodeRead(node, nil) == acl.Allow {
continue
}
a.logger.Printf("[DEBUG] agent: dropping node %q from result due to ACLs", node)
@ -232,7 +245,8 @@ func (a *Agent) filterServices(token string, services *map[string]*structs.NodeS
// Filter out services based on the service policy.
for id, service := range *services {
if rule.ServiceRead(service.Service) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceRead(service.Service, nil) == acl.Allow {
continue
}
a.logger.Printf("[DEBUG] agent: dropping service %q from result due to ACLs", id)
@ -255,11 +269,13 @@ func (a *Agent) filterChecks(token string, checks *map[types.CheckID]*structs.He
// Filter out checks based on the node or service policy.
for id, check := range *checks {
if len(check.ServiceName) > 0 {
if rule.ServiceRead(check.ServiceName) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.ServiceRead(check.ServiceName, nil) == acl.Allow {
continue
}
} else {
if rule.NodeRead(a.config.NodeName) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.NodeRead(a.config.NodeName, nil) == acl.Allow {
continue
}
}

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

View File

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

View File

@ -48,7 +48,7 @@ func (s *HTTPServer) AgentSelf(resp http.ResponseWriter, req *http.Request) (int
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentRead(s.agent.config.NodeName) {
if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -101,7 +101,7 @@ func (s *HTTPServer) AgentMetrics(resp http.ResponseWriter, req *http.Request) (
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentRead(s.agent.config.NodeName) {
if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
if enablePrometheusOutput(req) {
@ -130,7 +130,7 @@ func (s *HTTPServer) AgentReload(resp http.ResponseWriter, req *http.Request) (i
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) {
if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -281,7 +281,8 @@ func (s *HTTPServer) AgentService(resp http.ResponseWriter, req *http.Request) (
if err != nil {
return "", nil, err
}
if rule != nil && !rule.ServiceRead(svc.Service) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule != nil && rule.ServiceRead(svc.Service, nil) != acl.Allow {
return "", nil, acl.ErrPermissionDenied
}
@ -388,7 +389,7 @@ func (s *HTTPServer) AgentJoin(resp http.ResponseWriter, req *http.Request) (int
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) {
if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -416,7 +417,7 @@ func (s *HTTPServer) AgentLeave(resp http.ResponseWriter, req *http.Request) (in
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) {
if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -434,7 +435,7 @@ func (s *HTTPServer) AgentForceLeave(resp http.ResponseWriter, req *http.Request
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) {
if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -1049,7 +1050,8 @@ func (s *HTTPServer) AgentNodeMaintenance(resp http.ResponseWriter, req *http.Re
if err != nil {
return nil, err
}
if rule != nil && !rule.NodeWrite(s.agent.config.NodeName, nil) {
// TODO (namespaces) - pass through a real ent authz ctx?
if rule != nil && rule.NodeWrite(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -1070,7 +1072,7 @@ func (s *HTTPServer) AgentMonitor(resp http.ResponseWriter, req *http.Request) (
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentRead(s.agent.config.NodeName) {
if rule != nil && rule.AgentRead(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -1165,7 +1167,7 @@ func (s *HTTPServer) AgentToken(resp http.ResponseWriter, req *http.Request) (in
if err != nil {
return nil, err
}
if rule != nil && !rule.AgentWrite(s.agent.config.NodeName) {
if rule != nil && rule.AgentWrite(s.agent.config.NodeName, nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}
@ -1370,7 +1372,8 @@ func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (int
return nil, err
}
if rule != nil && !rule.OperatorRead() {
// TODO (namespaces) - pass through a real ent authz ctx
if rule != nil && rule.OperatorRead(nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}

View File

@ -57,7 +57,8 @@ func (a *Agent) ConnectAuthorize(token string,
if err != nil {
return returnErr(err)
}
if rule != nil && !rule.ServiceWrite(req.Target, nil) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule != nil && rule.ServiceWrite(req.Target, nil) != acl.Allow {
return returnErr(acl.ErrPermissionDenied)
}
@ -115,5 +116,6 @@ func (a *Agent) ConnectAuthorize(token string,
return true, "ACLs disabled, access is allowed by default", &meta, nil
}
reason = "Default behavior configured by ACLs"
return rule.IntentionDefaultAllow(), reason, &meta, nil
// TODO (namespaces) - pass through a real ent authz ctx
return rule.IntentionDefaultAllow(nil) == acl.Allow, reason, &meta, nil
}

View File

@ -11,8 +11,6 @@ import (
metrics "github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sentinel"
"golang.org/x/sync/singleflight"
"golang.org/x/time/rate"
)
@ -126,7 +124,8 @@ type ACLResolverConfig struct {
// so that it can detect when the servers have gotten ACLs enabled.
AutoDisable bool
Sentinel sentinel.Evaluator
// EnterpriseACLConfig contains Consul Enterprise specific ACL configuration
EnterpriseConfig *acl.EnterpriseACLConfig
}
// ACLResolver is the type to handle all your token and policy resolution needs.
@ -159,7 +158,7 @@ type ACLResolver struct {
logger *log.Logger
delegate ACLResolverDelegate
sentinel sentinel.Evaluator
entConf *acl.EnterpriseACLConfig
cache *structs.ACLCaches
identityGroup singleflight.Group
@ -212,7 +211,7 @@ func NewACLResolver(config *ACLResolverConfig) (*ACLResolver, error) {
config: config.Config,
logger: config.Logger,
delegate: config.Delegate,
sentinel: config.Sentinel,
entConf: config.EnterpriseConfig,
cache: cache,
autoDisable: config.AutoDisable,
down: down,
@ -249,7 +248,7 @@ func (r *ACLResolver) fetchAndCacheTokenLegacy(token string, cached *structs.Aut
policies = append(policies, policy.ConvertFromLegacy())
}
authorizer, err := acl.NewPolicyAuthorizer(parent, policies, r.sentinel)
authorizer, err := acl.NewPolicyAuthorizerWithDefaults(parent, policies, r.entConf)
r.cache.PutAuthorizerWithTTL(token, authorizer, reply.TTL)
return authorizer, err
@ -292,7 +291,7 @@ func (r *ACLResolver) resolveTokenLegacy(token string) (acl.Authorizer, error) {
return nil, err
}
return policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.sentinel)
return policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.entConf)
}
return nil, err
@ -1005,7 +1004,7 @@ func (r *ACLResolver) ResolveToken(token string) (acl.Authorizer, error) {
}
// Build the Authorizer
authorizer, err := policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.sentinel)
authorizer, err := policies.Compile(acl.RootAuthorizer(r.config.ACLDefaultPolicy), r.cache, r.entConf)
return authorizer, err
}
@ -1035,7 +1034,7 @@ func (r *ACLResolver) GetMergedPolicyForToken(token string) (*acl.Policy, error)
return nil, acl.ErrNotFound
}
return policies.Merge(r.cache, r.sentinel)
return policies.Merge(r.cache, r.entConf)
}
// aclFilter is used to filter results from our state store based on ACL rules
@ -1059,15 +1058,15 @@ func newACLFilter(authorizer acl.Authorizer, logger *log.Logger, enforceVersion8
}
// allowNode is used to determine if a node is accessible for an ACL.
func (f *aclFilter) allowNode(node string) bool {
func (f *aclFilter) allowNode(node string, ent *acl.EnterpriseAuthorizerContext) bool {
if !f.enforceVersion8 {
return true
}
return f.authorizer.NodeRead(node)
return f.authorizer.NodeRead(node, ent) == acl.Allow
}
// allowService is used to determine if a service is accessible for an ACL.
func (f *aclFilter) allowService(service string) bool {
func (f *aclFilter) allowService(service string, ent *acl.EnterpriseAuthorizerContext) bool {
if service == "" {
return true
}
@ -1075,16 +1074,16 @@ func (f *aclFilter) allowService(service string) bool {
if !f.enforceVersion8 && service == structs.ConsulServiceID {
return true
}
return f.authorizer.ServiceRead(service)
return f.authorizer.ServiceRead(service, ent) == acl.Allow
}
// allowSession is used to determine if a session for a node is accessible for
// an ACL.
func (f *aclFilter) allowSession(node string) bool {
func (f *aclFilter) allowSession(node string, ent *acl.EnterpriseAuthorizerContext) bool {
if !f.enforceVersion8 {
return true
}
return f.authorizer.SessionRead(node)
return f.authorizer.SessionRead(node, ent) == acl.Allow
}
// filterHealthChecks is used to filter a set of health checks down based on
@ -1093,7 +1092,8 @@ func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) {
hc := *checks
for i := 0; i < len(hc); i++ {
check := hc[i]
if f.allowNode(check.Node) && f.allowService(check.ServiceName) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowNode(check.Node, nil) && f.allowService(check.ServiceName, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", check.CheckID)
@ -1106,7 +1106,8 @@ func (f *aclFilter) filterHealthChecks(checks *structs.HealthChecks) {
// filterServices is used to filter a set of services based on ACLs.
func (f *aclFilter) filterServices(services structs.Services) {
for svc := range services {
if f.allowService(svc) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowService(svc, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
@ -1120,7 +1121,8 @@ func (f *aclFilter) filterServiceNodes(nodes *structs.ServiceNodes) {
sn := *nodes
for i := 0; i < len(sn); i++ {
node := sn[i]
if f.allowNode(node.Node) && f.allowService(node.ServiceName) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowNode(node.Node, nil) && f.allowService(node.ServiceName, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node)
@ -1136,13 +1138,15 @@ func (f *aclFilter) filterNodeServices(services **structs.NodeServices) {
return
}
if !f.allowNode((*services).Node.Node) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if !f.allowNode((*services).Node.Node, nil) {
*services = nil
return
}
for svc := range (*services).Services {
if f.allowService(svc) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowService(svc, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
@ -1155,7 +1159,8 @@ func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) {
csn := *nodes
for i := 0; i < len(csn); i++ {
node := csn[i]
if f.allowNode(node.Node.Node) && f.allowService(node.Service.Service) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowNode(node.Node.Node, nil) && f.allowService(node.Service.Service, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node.Node.Node)
@ -1170,7 +1175,8 @@ func (f *aclFilter) filterSessions(sessions *structs.Sessions) {
s := *sessions
for i := 0; i < len(s); i++ {
session := s[i]
if f.allowSession(session.Node) {
// TODO (namespaces) update to call with an actual ent authz context once sessions supports ns
if f.allowSession(session.Node, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping session %q from result due to ACLs", session.ID)
@ -1186,7 +1192,8 @@ func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) {
c := *coords
for i := 0; i < len(c); i++ {
node := c[i].Node
if f.allowNode(node) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowNode(node, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
@ -1201,7 +1208,8 @@ func (f *aclFilter) filterCoordinates(coords *structs.Coordinates) {
// if the user doesn't have a management token.
func (f *aclFilter) filterIntentions(ixns *structs.Intentions) {
// Management tokens can see everything with no filtering.
if f.authorizer.ACLRead() {
// TODO (namespaces) update to call with an actual ent authz context once acls support it
if f.authorizer.ACLRead(nil) == acl.Allow {
return
}
@ -1212,7 +1220,8 @@ func (f *aclFilter) filterIntentions(ixns *structs.Intentions) {
// we know at this point the user doesn't have a management
// token, otherwise see what the policy says.
prefix, ok := ixn.GetACLPrefix()
if !ok || !f.authorizer.IntentionRead(prefix) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if !ok || f.authorizer.IntentionRead(prefix, nil) != acl.Allow {
f.logger.Printf("[DEBUG] consul: dropping intention %q from result due to ACLs", ixn.ID)
continue
}
@ -1231,7 +1240,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
info := nd[i]
// Filter nodes
if node := info.Node; !f.allowNode(node) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if node := info.Node; !f.allowNode(node, nil) {
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
nd = append(nd[:i], nd[i+1:]...)
i--
@ -1241,7 +1251,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
// Filter services
for j := 0; j < len(info.Services); j++ {
svc := info.Services[j].Service
if f.allowService(svc) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowService(svc, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", svc)
@ -1252,7 +1263,8 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
// Filter checks
for j := 0; j < len(info.Checks); j++ {
chk := info.Checks[j]
if f.allowService(chk.ServiceName) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowService(chk.ServiceName, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping check %q from result due to ACLs", chk.CheckID)
@ -1269,7 +1281,8 @@ func (f *aclFilter) filterNodes(nodes *structs.Nodes) {
n := *nodes
for i := 0; i < len(n); i++ {
node := n[i].Node
if f.allowNode(node) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if f.allowNode(node, nil) {
continue
}
f.logger.Printf("[DEBUG] consul: dropping node %q from result due to ACLs", node)
@ -1287,7 +1300,8 @@ func (f *aclFilter) filterNodes(nodes *structs.Nodes) {
// captured tokens, but they can at least see whether or not a token is set.
func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
// Management tokens can see everything with no filtering.
if f.authorizer.ACLWrite() {
// TODO (namespaces) update to call with an actual ent authz context once acls support it
if f.authorizer.ACLWrite(nil) == acl.Allow {
return
}
@ -1312,7 +1326,11 @@ func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
// if the user doesn't have a management token.
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
// Management tokens can see everything with no filtering.
if f.authorizer.ACLWrite() {
// TODO (namespaces) update to call with an actual ent authz context once acls support it
// TODO (namespaces) is this check even necessary - this looks like a search replace from
// the 1.4 ACL rewrite. The global-management token will provide unrestricted query privileges
// so asking for ACLWrite should be unnecessary.
if f.authorizer.ACLWrite(nil) == acl.Allow {
return
}
@ -1323,7 +1341,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
// we know at this point the user doesn't have a management
// token, otherwise see what the policy says.
prefix, ok := query.GetACLPrefix()
if !ok || !f.authorizer.PreparedQueryRead(prefix) {
if !ok || f.authorizer.PreparedQueryRead(prefix, nil) != acl.Allow {
f.logger.Printf("[DEBUG] consul: dropping prepared query %q from result due to ACLs", query.ID)
continue
}
@ -1338,7 +1356,8 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
}
func (f *aclFilter) redactTokenSecret(token **structs.ACLToken) {
if token == nil || *token == nil || f == nil || f.authorizer.ACLWrite() {
// TODO (namespaces) update to call with an actual ent authz context once acls support it
if token == nil || *token == nil || f == nil || f.authorizer.ACLWrite(nil) == acl.Allow {
return
}
clone := *(*token)
@ -1454,46 +1473,49 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
return nil
}
// TODO (namespaces) update to create a sentinel scope - technically we never check this
// scope but we used to set it so we probably should continue?
// This gets called potentially from a few spots so we save it and
// return the structure we made if we have it.
var memo map[string]interface{}
scope := func() map[string]interface{} {
if memo != nil {
return memo
}
// var memo map[string]interface{}
// scope := func() map[string]interface{} {
// if memo != nil {
// return memo
// }
node := &api.Node{
ID: string(subj.ID),
Node: subj.Node,
Address: subj.Address,
Datacenter: subj.Datacenter,
TaggedAddresses: subj.TaggedAddresses,
Meta: subj.NodeMeta,
}
// node := &api.Node{
// ID: string(subj.ID),
// Node: subj.Node,
// Address: subj.Address,
// Datacenter: subj.Datacenter,
// TaggedAddresses: subj.TaggedAddresses,
// Meta: subj.NodeMeta,
// }
var service *api.AgentService
if subj.Service != nil {
service = &api.AgentService{
ID: subj.Service.ID,
Service: subj.Service.Service,
Tags: subj.Service.Tags,
Meta: subj.Service.Meta,
Address: subj.Service.Address,
Port: subj.Service.Port,
EnableTagOverride: subj.Service.EnableTagOverride,
}
}
// var service *api.AgentService
// if subj.Service != nil {
// service = &api.AgentService{
// ID: subj.Service.ID,
// Service: subj.Service.Service,
// Tags: subj.Service.Tags,
// Meta: subj.Service.Meta,
// Address: subj.Service.Address,
// Port: subj.Service.Port,
// EnableTagOverride: subj.Service.EnableTagOverride,
// }
// }
memo = sentinel.ScopeCatalogUpsert(node, service)
return memo
}
// memo = sentinel.ScopeCatalogUpsert(node, service)
// return memo
// }
// Vet the node info. This allows service updates to re-post the required
// node info for each request without having to have node "write"
// privileges.
needsNode := ns == nil || subj.ChangesNode(ns.Node)
if needsNode && !rule.NodeWrite(subj.Node, scope) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if needsNode && rule.NodeWrite(subj.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1501,7 +1523,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
// the given service, and that we can write to any existing service that
// is being modified by id (if any).
if subj.Service != nil {
if !rule.ServiceWrite(subj.Service.Service, scope) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.ServiceWrite(subj.Service.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1511,7 +1534,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
// This is effectively a delete, so we DO NOT apply the
// sentinel scope to the service we are overwriting, just
// the regular ACL policy.
if ok && !rule.ServiceWrite(other.Service, nil) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if ok && rule.ServiceWrite(other.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -1540,7 +1564,8 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
// Node-level check.
if check.ServiceID == "" {
if !rule.NodeWrite(subj.Node, scope) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.NodeWrite(subj.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
continue
@ -1568,7 +1593,7 @@ func vetRegisterWithACL(rule acl.Authorizer, subj *structs.RegisterRequest,
// We are only adding a check here, so we don't add the scope,
// since the sentinel policy doesn't apply to adding checks at
// this time.
if !rule.ServiceWrite(other.Service, nil) {
if rule.ServiceWrite(other.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -1595,7 +1620,8 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest,
// Allow service deregistration if the token has write permission for the node.
// This accounts for cases where the agent no longer has a token with write permission
// on the service to deregister it.
if rule.NodeWrite(subj.Node, nil) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.NodeWrite(subj.Node, nil) == acl.Allow {
return nil
}
@ -1607,7 +1633,8 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest,
if ns == nil {
return fmt.Errorf("Unknown service '%s'", subj.ServiceID)
}
if !rule.ServiceWrite(ns.Service, nil) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.ServiceWrite(ns.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else if subj.CheckID != "" {
@ -1615,11 +1642,13 @@ func vetDeregisterWithACL(rule acl.Authorizer, subj *structs.DeregisterRequest,
return fmt.Errorf("Unknown check '%s'", subj.CheckID)
}
if nc.ServiceID != "" {
if !rule.ServiceWrite(nc.ServiceName, nil) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.ServiceWrite(nc.ServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(subj.Node, nil) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.NodeWrite(subj.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -1641,24 +1670,27 @@ func vetNodeTxnOp(op *structs.TxnNodeOp, rule acl.Authorizer) error {
node := op.Node
n := &api.Node{
Node: node.Node,
ID: string(node.ID),
Address: node.Address,
Datacenter: node.Datacenter,
TaggedAddresses: node.TaggedAddresses,
Meta: node.Meta,
}
// TODO (namespaces) uncomment once we bring back sentinel scope creation in the authz ctx
// n := &api.Node{
// Node: node.Node,
// ID: string(node.ID),
// Address: node.Address,
// Datacenter: node.Datacenter,
// TaggedAddresses: node.TaggedAddresses,
// Meta: node.Meta,
// }
// TODO (namespaces) update to create a authz context with a scope once the catalog supports it
// Sentinel doesn't apply to deletes, only creates/updates, so we don't need the scopeFn.
var scope func() map[string]interface{}
if op.Verb != api.NodeDelete && op.Verb != api.NodeDeleteCAS {
scope = func() map[string]interface{} {
return sentinel.ScopeCatalogUpsert(n, nil)
}
}
// var scope func() map[string]interface{}
// if op.Verb != api.NodeDelete && op.Verb != api.NodeDeleteCAS {
// scope = func() map[string]interface{} {
// return sentinel.ScopeCatalogUpsert(n, nil)
// }
// }
if rule != nil && !rule.NodeWrite(node.Node, scope) {
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule != nil && rule.NodeWrite(node.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1674,23 +1706,25 @@ func vetServiceTxnOp(op *structs.TxnServiceOp, rule acl.Authorizer) error {
service := op.Service
n := &api.Node{Node: op.Node}
svc := &api.AgentService{
ID: service.ID,
Service: service.Service,
Tags: service.Tags,
Meta: service.Meta,
Address: service.Address,
Port: service.Port,
EnableTagOverride: service.EnableTagOverride,
}
var scope func() map[string]interface{}
if op.Verb != api.ServiceDelete && op.Verb != api.ServiceDeleteCAS {
scope = func() map[string]interface{} {
return sentinel.ScopeCatalogUpsert(n, svc)
}
}
if !rule.ServiceWrite(service.Service, scope) {
// TODO (namespaces) update to create authz context with the sentinel scope
// n := &api.Node{Node: op.Node}
// svc := &api.AgentService{
// ID: service.ID,
// Service: service.Service,
// Tags: service.Tags,
// Meta: service.Meta,
// Address: service.Address,
// Port: service.Port,
// EnableTagOverride: service.EnableTagOverride,
// }
// var scope func() map[string]interface{}
// if op.Verb != api.ServiceDelete && op.Verb != api.ServiceDeleteCAS {
// scope = func() map[string]interface{} {
// return sentinel.ScopeCatalogUpsert(n, svc)
// }
// }
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.ServiceWrite(service.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1704,31 +1738,36 @@ func vetCheckTxnOp(op *structs.TxnCheckOp, rule acl.Authorizer) error {
return nil
}
n := &api.Node{Node: op.Check.Node}
svc := &api.AgentService{
ID: op.Check.ServiceID,
Service: op.Check.ServiceID,
Tags: op.Check.ServiceTags,
}
var scope func() map[string]interface{}
// TODO (namespaces) uncomment once these are used for sentinel scope creation
// n := &api.Node{Node: op.Check.Node}
// svc := &api.AgentService{
// ID: op.Check.ServiceID,
// Service: op.Check.ServiceID,
// Tags: op.Check.ServiceTags,
// }
// var scope func() map[string]interface{}
if op.Check.ServiceID == "" {
// Node-level check.
if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS {
scope = func() map[string]interface{} {
return sentinel.ScopeCatalogUpsert(n, svc)
}
}
if !rule.NodeWrite(op.Check.Node, scope) {
// TODO (namespaces) update to create authz with sentinel scope
// if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS {
// scope = func() map[string]interface{} {
// return sentinel.ScopeCatalogUpsert(n, svc)
// }
// }
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.NodeWrite(op.Check.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
} else {
// Service-level check.
if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS {
scope = func() map[string]interface{} {
return sentinel.ScopeCatalogUpsert(n, svc)
}
}
if !rule.ServiceWrite(op.Check.ServiceName, scope) {
// TODO (namespaces) update to create authz with sentinel scope
// if op.Verb == api.CheckDelete || op.Verb == api.CheckDeleteCAS {
// scope = func() map[string]interface{} {
// return sentinel.ScopeCatalogUpsert(n, svc)
// }
// }
// TODO (namespaces) update to call with an actual ent authz context once the catalog supports it
if rule.ServiceWrite(op.Check.ServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}

View File

@ -206,9 +206,10 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke
// Only ACLRead privileges are required to list tokens
// However if you do not have ACLWrite as well the token
// secrets will be redacted
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err = a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -223,7 +224,8 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke
index, token, err = state.ACLTokenGetByAccessor(ws, args.TokenID)
if token != nil {
a.srv.filterACLWithAuthorizer(rule, &token)
if !rule.ACLWrite() {
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule.ACLWrite(nil) != acl.Allow {
reply.Redacted = true
}
}
@ -261,9 +263,10 @@ func (a *ACL) TokenClone(args *structs.ACLTokenSetRequest, reply *structs.ACLTok
defer metrics.MeasureSince([]string{"acl", "token", "clone"}, time.Now())
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -324,9 +327,10 @@ func (a *ACL) TokenSet(args *structs.ACLTokenSetRequest, reply *structs.ACLToken
defer metrics.MeasureSince([]string{"acl", "token", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -683,9 +687,10 @@ func (a *ACL) TokenDelete(args *structs.ACLTokenDeleteRequest, reply *string) er
defer metrics.MeasureSince([]string{"acl", "token", "delete"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -760,9 +765,10 @@ func (a *ACL) TokenList(args *structs.ACLTokenListRequest, reply *structs.ACLTok
}
rule, err := a.srv.ResolveToken(args.Token)
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -801,9 +807,10 @@ func (a *ACL) TokenBatchRead(args *structs.ACLTokenBatchGetRequest, reply *struc
}
rule, err := a.srv.ResolveToken(args.Token)
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -819,7 +826,8 @@ func (a *ACL) TokenBatchRead(args *structs.ACLTokenBatchGetRequest, reply *struc
a.srv.filterACLWithAuthorizer(rule, &tokens)
reply.Index, reply.Tokens = index, tokens
reply.Redacted = !rule.ACLWrite()
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
reply.Redacted = rule.ACLWrite(nil) != acl.Allow
return nil
})
}
@ -835,7 +843,7 @@ func (a *ACL) PolicyRead(args *structs.ACLPolicyGetRequest, reply *structs.ACLPo
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -863,7 +871,7 @@ func (a *ACL) PolicyBatchRead(args *structs.ACLPolicyBatchGetRequest, reply *str
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -895,9 +903,10 @@ func (a *ACL) PolicySet(args *structs.ACLPolicySetRequest, reply *structs.ACLPol
defer metrics.MeasureSince([]string{"acl", "policy", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -971,7 +980,7 @@ func (a *ACL) PolicySet(args *structs.ACLPolicySetRequest, reply *structs.ACLPol
}
// validate the rules
_, err = acl.NewPolicyFromSource("", 0, policy.Rules, policy.Syntax, a.srv.sentinel)
_, err = acl.NewPolicyFromSource("", 0, policy.Rules, policy.Syntax, a.srv.enterpriseACLConfig)
if err != nil {
return err
}
@ -1018,9 +1027,10 @@ func (a *ACL) PolicyDelete(args *structs.ACLPolicyDeleteRequest, reply *string)
defer metrics.MeasureSince([]string{"acl", "policy", "delete"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1068,9 +1078,10 @@ func (a *ACL) PolicyList(args *structs.ACLPolicyListRequest, reply *structs.ACLP
return err
}
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1213,9 +1224,10 @@ func (a *ACL) RoleRead(args *structs.ACLRoleGetRequest, reply *structs.ACLRoleRe
return err
}
// TODO (namespaces) update to create and use actual enterprise authorizer context
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1250,9 +1262,10 @@ func (a *ACL) RoleBatchRead(args *structs.ACLRoleBatchGetRequest, reply *structs
return err
}
// TODO (namespaces) update to create and use actual enterprise authorizer context
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1284,9 +1297,10 @@ func (a *ACL) RoleSet(args *structs.ACLRoleSetRequest, reply *structs.ACLRole) e
defer metrics.MeasureSince([]string{"acl", "role", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1422,9 +1436,10 @@ func (a *ACL) RoleDelete(args *structs.ACLRoleDeleteRequest, reply *string) erro
defer metrics.MeasureSince([]string{"acl", "role", "delete"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1468,9 +1483,10 @@ func (a *ACL) RoleList(args *structs.ACLRoleListRequest, reply *structs.ACLRoleL
return err
}
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1544,9 +1560,10 @@ func (a *ACL) BindingRuleRead(args *structs.ACLBindingRuleGetRequest, reply *str
return err
}
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1579,9 +1596,10 @@ func (a *ACL) BindingRuleSet(args *structs.ACLBindingRuleSetRequest, reply *stru
defer metrics.MeasureSince([]string{"acl", "bindingrule", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1696,9 +1714,10 @@ func (a *ACL) BindingRuleDelete(args *structs.ACLBindingRuleDeleteRequest, reply
defer metrics.MeasureSince([]string{"acl", "bindingrule", "delete"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1742,9 +1761,10 @@ func (a *ACL) BindingRuleList(args *structs.ACLBindingRuleListRequest, reply *st
return err
}
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1773,9 +1793,10 @@ func (a *ACL) AuthMethodRead(args *structs.ACLAuthMethodGetRequest, reply *struc
return err
}
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1808,9 +1829,10 @@ func (a *ACL) AuthMethodSet(args *structs.ACLAuthMethodSetRequest, reply *struct
defer metrics.MeasureSince([]string{"acl", "authmethod", "upsert"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1885,9 +1907,10 @@ func (a *ACL) AuthMethodDelete(args *structs.ACLAuthMethodDeleteRequest, reply *
defer metrics.MeasureSince([]string{"acl", "authmethod", "delete"}, time.Now())
// Verify token is permitted to modify ACLs
// TODO (namespaces) update to call ACLWrite with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -1931,9 +1954,10 @@ func (a *ACL) AuthMethodList(args *structs.ACLAuthMethodListRequest, reply *stru
return err
}
// TODO (namespaces) update to call ACLRead with an authz context once ACLs support it
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLRead() {
} else if rule == nil || rule.ACLRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}

View File

@ -114,7 +114,7 @@ func aclApplyInternal(srv *Server, args *structs.ACLRequest, reply *string) erro
}
// Validate the rules compile
_, err := acl.NewPolicyFromSource("", 0, args.ACL.Rules, acl.SyntaxLegacy, srv.sentinel)
_, err := acl.NewPolicyFromSource("", 0, args.ACL.Rules, acl.SyntaxLegacy, srv.enterpriseACLConfig)
if err != nil {
return fmt.Errorf("ACL rule compilation failed: %v", err)
}
@ -160,9 +160,10 @@ func (a *ACL) Apply(args *structs.ACLRequest, reply *string) error {
}
// Verify token is permitted to modify ACLs
// NOTE: We will not support enterprise authorizer contexts with legacy ACLs
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -198,6 +199,10 @@ func (a *ACL) Get(args *structs.ACLSpecificRequest,
return err
}
// NOTE: This has no ACL check because legacy ACLs were managed with
// the secrets and therefore the argument to the Get request is
// authorization in and of itself.
// Verify we are allowed to serve this request
if !a.srv.ACLsEnabled() {
return acl.ErrDisabled
@ -246,9 +251,11 @@ func (a *ACL) List(args *structs.DCSpecificRequest,
}
// Verify token is permitted to list ACLs
// NOTES: Previously with legacy ACL there was no read-only ACL permissions
// and this check for ACLWrite is basically what it did before.
if rule, err := a.srv.ResolveToken(args.Token); err != nil {
return err
} else if rule == nil || !rule.ACLWrite() {
} else if rule == nil || rule.ACLWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}

View File

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

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

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
// delete this and do all the ACL checks down there.
if service.Service != structs.ConsulServiceName {
if rule != nil && !rule.ServiceWrite(service.Service, nil) {
// TODO (namespaces) update to send an actual enterprise authorizer context
if rule != nil && rule.ServiceWrite(service.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
// Proxies must have write permission on their destination
if service.Kind == structs.ServiceKindConnectProxy {
if rule != nil && !rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) {
// TODO (namespaces) update to send an actual enterprise authorizer context
if rule != nil && rule.ServiceWrite(service.Proxy.DestinationServiceName, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -334,7 +336,8 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
return err
}
if rule != nil && !rule.ServiceRead(args.ServiceName) {
// TODO (namespaces) update to send an actual enterprise authorizer context
if rule != nil && rule.ServiceRead(args.ServiceName, nil) != acl.Allow {
// Just return nil, which will return an empty response (tested)
return nil
}

View File

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

View File

@ -231,7 +231,8 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
if err != nil {
return err
}
if rule != nil && !rule.ServiceRead(args.Name) {
// TODO (namespaces) use actual ent authz context
if rule != nil && rule.ServiceRead(args.Name, nil) != acl.Allow {
return acl.ErrPermissionDenied
}

View File

@ -119,7 +119,7 @@ func (s *ConnectCA) ConfigurationGet(
if err != nil {
return err
}
if rule != nil && !rule.OperatorRead() {
if rule != nil && rule.OperatorRead(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -151,7 +151,7 @@ func (s *ConnectCA) ConfigurationSet(
if err != nil {
return err
}
if rule != nil && !rule.OperatorWrite() {
if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -431,7 +431,8 @@ func (s *ConnectCA) Sign(
return err
}
if isService {
if rule != nil && !rule.ServiceWrite(serviceID.Service, nil) {
// TODO (namespaces) use actual ent authz context
if rule != nil && rule.ServiceWrite(serviceID.Service, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -442,7 +443,8 @@ func (s *ConnectCA) Sign(
"we are %s", serviceID.Datacenter, s.srv.config.Datacenter)
}
} else if isAgent {
if rule != nil && !rule.NodeWrite(agentID.Agent, nil) {
// TODO (namespaces) use actual ent authz context
if rule != nil && rule.NodeWrite(agentID.Agent, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -569,7 +571,7 @@ func (s *ConnectCA) SignIntermediate(
if err != nil {
return err
}
if rule != nil && !rule.OperatorWrite() {
if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
return acl.ErrPermissionDenied
}

View File

@ -139,7 +139,8 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
return err
}
if rule != nil && c.srv.config.ACLEnforceVersion8 {
if !rule.NodeWrite(args.Node, nil) {
// TODO (namespaces) use actual ent authz context
if rule.NodeWrite(args.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}
@ -210,7 +211,8 @@ func (c *Coordinate) Node(args *structs.NodeSpecificRequest, reply *structs.Inde
return err
}
if rule != nil && c.srv.config.ACLEnforceVersion8 {
if !rule.NodeRead(args.Node) {
// TODO (namespaces) use actual ent authz context
if rule.NodeRead(args.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}

View File

@ -34,7 +34,8 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs
if err != nil {
return err
}
if rule != nil && !rule.ServiceRead(args.Name) {
// TODO (namespaces) use actual ent authz context
if rule != nil && rule.ServiceRead(args.Name, nil) != acl.Allow {
return acl.ErrPermissionDenied
}

View File

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

View File

@ -11,7 +11,7 @@ import (
func TestFilter_DirEnt(t *testing.T) {
t.Parallel()
policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil)
aclR, _ := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil)
aclR, _ := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
type tcase struct {
in []string
@ -53,7 +53,7 @@ func TestFilter_DirEnt(t *testing.T) {
func TestFilter_Keys(t *testing.T) {
t.Parallel()
policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil)
aclR, _ := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil)
aclR, _ := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
type tcase struct {
in []string
@ -85,7 +85,7 @@ func TestFilter_Keys(t *testing.T) {
func TestFilter_TxnResults(t *testing.T) {
t.Parallel()
policy, _ := acl.NewPolicyFromSource("", 0, testFilterRules, acl.SyntaxLegacy, nil)
aclR, _ := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil)
aclR, _ := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
type tcase struct {
in []string

View File

@ -5,6 +5,7 @@ import (
"sort"
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs"
bexpr "github.com/hashicorp/go-bexpr"
@ -171,7 +172,7 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
return err
}
if rule != nil && !rule.ServiceRead(args.ServiceName) {
if rule != nil && rule.ServiceRead(args.ServiceName, nil) != acl.Allow {
// Just return nil, which will return an empty response (tested)
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
// proposing.
if prefix, ok := args.Query.GetACLPrefix(); ok {
if rule != nil && !rule.PreparedQueryWrite(prefix) {
if rule != nil && rule.PreparedQueryWrite(prefix, nil) != acl.Allow {
p.srv.logger.Printf("[WARN] consul.prepared_query: Operation on prepared query '%s' denied due to ACLs", args.Query.ID)
return acl.ErrPermissionDenied
}
@ -89,7 +89,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string)
}
if prefix, ok := query.GetACLPrefix(); ok {
if rule != nil && !rule.PreparedQueryWrite(prefix) {
if rule != nil && rule.PreparedQueryWrite(prefix, nil) != acl.Allow {
p.srv.logger.Printf("[WARN] consul.prepared_query: Operation on prepared query '%s' denied due to ACLs", args.Query.ID)
return acl.ErrPermissionDenied
}

View File

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

View File

@ -49,12 +49,14 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
if existing == nil {
return fmt.Errorf("Unknown session %q", args.Session.ID)
}
if !rule.SessionWrite(existing.Node) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.SessionWrite(existing.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
case structs.SessionCreate:
if !rule.SessionWrite(args.Session.Node) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.SessionWrite(args.Session.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -241,7 +243,8 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest,
return err
}
if rule != nil && s.srv.config.ACLEnforceVersion8 {
if !rule.SessionWrite(session.Node) {
// TODO (namespaces) - pass through a real ent authz ctx
if rule.SessionWrite(session.Node, nil) != acl.Allow {
return acl.ErrPermissionDenied
}
}

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.
if rule, err := s.ResolveToken(args.Token); err != nil {
return nil, err
} else if rule != nil && !rule.Snapshot() {
} else if rule != nil && rule.Snapshot(nil) != acl.Allow {
return nil, acl.ErrPermissionDenied
}

View File

@ -1373,11 +1373,13 @@ func (s *Store) aclPolicySetTxn(tx *memdb.Txn, idx uint64, policy *structs.ACLPo
}
if existing != nil {
policyMatch := existing.(*structs.ACLPolicy)
if policy.ID == structs.ACLPolicyGlobalManagementID {
// Only the name and description are modifiable
if policy.Rules != policyMatch.Rules {
// Here we specifically check that the rules on the global management policy
// are identical to the correct policy rules within the binary. This is opposed
// to checking against the current rules to allow us to update the rules during
// upgrades.
if policy.Rules != structs.ACLPolicyGlobalManagement {
return fmt.Errorf("Changing the Rules for the builtin global-management policy is not permitted")
}

View File

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

View File

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

View File

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

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

View File

@ -92,11 +92,11 @@ func (e *ServiceConfigEntry) Validate() error {
}
func (e *ServiceConfigEntry) CanRead(rule acl.Authorizer) bool {
return rule.ServiceRead(e.Name)
return rule.ServiceRead(e.Name, nil) == acl.Allow
}
func (e *ServiceConfigEntry) CanWrite(rule acl.Authorizer) bool {
return rule.ServiceWrite(e.Name, nil)
return rule.ServiceWrite(e.Name, nil) == acl.Allow
}
func (e *ServiceConfigEntry) GetRaftIndex() *RaftIndex {
@ -162,7 +162,7 @@ func (e *ProxyConfigEntry) CanRead(rule acl.Authorizer) bool {
}
func (e *ProxyConfigEntry) CanWrite(rule acl.Authorizer) bool {
return rule.OperatorWrite()
return rule.OperatorWrite(nil) == acl.Allow
}
func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {

View File

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

View File

@ -27,7 +27,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
policy, err := acl.NewPolicyFromSource("", 0, buf.String(), acl.SyntaxCurrent, nil)
require.NoError(t, err)
authorizer, err := acl.NewPolicyAuthorizer(acl.DenyAll(), []*acl.Policy{policy}, nil)
authorizer, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(t, err)
return authorizer
}

View File

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

View File

@ -450,7 +450,7 @@ func TestServer_StreamAggregatedResources_ACLEnforcement(t *testing.T) {
// Parse the ACL and enforce it
policy, err := acl.NewPolicyFromSource("", 0, tt.acl, acl.SyntaxLegacy, nil)
require.NoError(t, err)
return acl.NewPolicyAuthorizer(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil)
return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil)
}
envoy := NewTestEnvoy(t, "web-sidecar-proxy", tt.token)
defer envoy.Close()
@ -521,7 +521,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedDuring
return nil, acl.ErrNotFound
}
return acl.NewPolicyAuthorizer(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil)
return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil)
}
envoy := NewTestEnvoy(t, "web-sidecar-proxy", token)
defer envoy.Close()
@ -612,7 +612,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedInBack
return nil, acl.ErrNotFound
}
return acl.NewPolicyAuthorizer(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil)
return acl.NewPolicyAuthorizerWithDefaults(acl.RootAuthorizer("deny"), []*acl.Policy{policy}, nil)
}
envoy := NewTestEnvoy(t, "web-sidecar-proxy", token)
defer envoy.Close()