baa89c7c65
* Renamed structs.IntentionWildcard to structs.WildcardSpecifier * Refactor ACL Config Get rid of remnants of enterprise only renaming. Add a WildcardName field for specifying what string should be used to indicate a wildcard. * Add wildcard support in the ACL package For read operations they can call anyAllowed to determine if any read access to the given resource would be granted. For write operations they can call allAllowed to ensure that write access is granted to everything. * Make v1/agent/connect/authorize namespace aware * Update intention ACL enforcement This also changes how intention:read is granted. Before the Intention.List RPC would allow viewing an intention if the token had intention:read on the destination. However Intention.Match allowed viewing if access was allowed for either the source or dest side. Now Intention.List and Intention.Get fall in line with Intention.Matches previous behavior. Due to this being done a few different places ACL enforcement for a singular intention is now done with the CanRead and CanWrite methods on the intention itself. * Refactor Intention.Apply to make things easier to follow.
256 lines
7.9 KiB
Go
256 lines
7.9 KiB
Go
package acl
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
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"
|
|
}
|
|
}
|
|
|
|
type Resource string
|
|
|
|
const (
|
|
ResourceACL Resource = "acl"
|
|
ResourceAgent Resource = "agent"
|
|
ResourceEvent Resource = "event"
|
|
ResourceIntention Resource = "intention"
|
|
ResourceKey Resource = "key"
|
|
ResourceKeyring Resource = "keyring"
|
|
ResourceNode Resource = "node"
|
|
ResourceOperator Resource = "operator"
|
|
ResourceQuery Resource = "query"
|
|
ResourceService Resource = "service"
|
|
ResourceSession Resource = "session"
|
|
)
|
|
|
|
// Authorizer is the interface for policy enforcement.
|
|
type Authorizer interface {
|
|
// ACLRead checks for permission to list all the ACLs
|
|
ACLRead(*AuthorizerContext) EnforcementDecision
|
|
|
|
// ACLWrite checks for permission to manipulate ACLs
|
|
ACLWrite(*AuthorizerContext) EnforcementDecision
|
|
|
|
// AgentRead checks for permission to read from agent endpoints for a
|
|
// given node.
|
|
AgentRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// AgentWrite checks for permission to make changes via agent endpoints
|
|
// for a given node.
|
|
AgentWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// EventRead determines if a specific event can be queried.
|
|
EventRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// EventWrite determines if a specific event may be fired.
|
|
EventWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// IntentionDefaultAllow determines the default authorized behavior
|
|
// when no intentions match a Connect request.
|
|
IntentionDefaultAllow(*AuthorizerContext) EnforcementDecision
|
|
|
|
// IntentionRead determines if a specific intention can be read.
|
|
IntentionRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// IntentionWrite determines if a specific intention can be
|
|
// created, modified, or deleted.
|
|
IntentionWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// KeyList checks for permission to list keys under a prefix
|
|
KeyList(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// KeyRead checks for permission to read a given key
|
|
KeyRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// KeyWrite checks for permission to write a given key
|
|
KeyWrite(string, *AuthorizerContext) 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, *AuthorizerContext) EnforcementDecision
|
|
|
|
// KeyringRead determines if the encryption keyring used in
|
|
// the gossip layer can be read.
|
|
KeyringRead(*AuthorizerContext) EnforcementDecision
|
|
|
|
// KeyringWrite determines if the keyring can be manipulated
|
|
KeyringWrite(*AuthorizerContext) EnforcementDecision
|
|
|
|
// NodeRead checks for permission to read (discover) a given node.
|
|
NodeRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// NodeWrite checks for permission to create or update (register) a
|
|
// given node.
|
|
NodeWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// OperatorRead determines if the read-only Consul operator functions
|
|
// can be used.
|
|
OperatorRead(*AuthorizerContext) EnforcementDecision
|
|
|
|
// OperatorWrite determines if the state-changing Consul operator
|
|
// functions can be used.
|
|
OperatorWrite(*AuthorizerContext) EnforcementDecision
|
|
|
|
// PreparedQueryRead determines if a specific prepared query can be read
|
|
// to show its contents (this is not used for execution).
|
|
PreparedQueryRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// PreparedQueryWrite determines if a specific prepared query can be
|
|
// created, modified, or deleted.
|
|
PreparedQueryWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// ServiceRead checks for permission to read a given service
|
|
ServiceRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// ServiceWrite checks for permission to create or update a given
|
|
// service
|
|
ServiceWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// SessionRead checks for permission to read sessions for a given node.
|
|
SessionRead(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// SessionWrite checks for permission to create sessions for a given
|
|
// node.
|
|
SessionWrite(string, *AuthorizerContext) EnforcementDecision
|
|
|
|
// Snapshot checks for permission to take and restore snapshots.
|
|
Snapshot(*AuthorizerContext) EnforcementDecision
|
|
|
|
// Embedded Interface for Consul Enterprise specific ACL enforcement
|
|
enterpriseAuthorizer
|
|
}
|
|
|
|
func Enforce(authz Authorizer, rsc Resource, segment string, access string, ctx *AuthorizerContext) (EnforcementDecision, error) {
|
|
lowerAccess := strings.ToLower(access)
|
|
|
|
switch rsc {
|
|
case ResourceACL:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.ACLRead(ctx), nil
|
|
case "write":
|
|
return authz.ACLWrite(ctx), nil
|
|
}
|
|
case ResourceAgent:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.AgentRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.AgentWrite(segment, ctx), nil
|
|
}
|
|
case ResourceEvent:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.EventRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.EventWrite(segment, ctx), nil
|
|
}
|
|
case ResourceIntention:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.IntentionRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.IntentionWrite(segment, ctx), nil
|
|
}
|
|
case ResourceKey:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.KeyRead(segment, ctx), nil
|
|
case "list":
|
|
return authz.KeyList(segment, ctx), nil
|
|
case "write":
|
|
return authz.KeyWrite(segment, ctx), nil
|
|
case "write-prefix":
|
|
return authz.KeyWritePrefix(segment, ctx), nil
|
|
}
|
|
case ResourceKeyring:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.KeyringRead(ctx), nil
|
|
case "write":
|
|
return authz.KeyringWrite(ctx), nil
|
|
}
|
|
case ResourceNode:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.NodeRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.NodeWrite(segment, ctx), nil
|
|
}
|
|
case ResourceOperator:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.OperatorRead(ctx), nil
|
|
case "write":
|
|
return authz.OperatorWrite(ctx), nil
|
|
}
|
|
case ResourceQuery:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.PreparedQueryRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.PreparedQueryWrite(segment, ctx), nil
|
|
}
|
|
case ResourceService:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.ServiceRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.ServiceWrite(segment, ctx), nil
|
|
}
|
|
case ResourceSession:
|
|
switch lowerAccess {
|
|
case "read":
|
|
return authz.SessionRead(segment, ctx), nil
|
|
case "write":
|
|
return authz.SessionWrite(segment, ctx), nil
|
|
}
|
|
default:
|
|
if processed, decision, err := enforceEnterprise(authz, rsc, segment, lowerAccess, ctx); processed {
|
|
return decision, err
|
|
}
|
|
return Deny, fmt.Errorf("Invalid ACL resource requested: %q", rsc)
|
|
}
|
|
|
|
return Deny, fmt.Errorf("Invalid access level for %s resource: %s", rsc, access)
|
|
}
|
|
|
|
// NewAuthorizerFromRules is a convenience function to invoke NewPolicyFromSource followed by NewPolicyAuthorizer with
|
|
// the parse policy.
|
|
func NewAuthorizerFromRules(id string, revision uint64, rules string, syntax SyntaxVersion, conf *Config, meta *EnterprisePolicyMeta) (Authorizer, error) {
|
|
policy, err := NewPolicyFromSource(id, revision, rules, syntax, conf, meta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewPolicyAuthorizer([]*Policy{policy}, conf)
|
|
}
|