Skip to content

Commit

Permalink
Intentions ACL enforcement updates (#7028)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
mkeeler authored Jan 13, 2020
1 parent 3bf2e64 commit 8bd34e1
Show file tree
Hide file tree
Showing 27 changed files with 1,564 additions and 255 deletions.
32 changes: 32 additions & 0 deletions acl/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package acl

const (
WildcardName = "*"
)

// Config encapsualtes all of the generic configuration parameters used for
// policy parsing and enforcement
type Config struct {
// WildcardName is the string that represents a request to authorize a wildcard permission
WildcardName string

// embedded enterprise configuration
EnterpriseConfig
}

// GetWildcardName will retrieve the configured wildcard name or provide a default
// in the case that the config is Nil or the wildcard name is unset.
func (c *Config) GetWildcardName() string {
if c == nil || c.WildcardName == "" {
return WildcardName
}
return c.WildcardName
}

// Close will relinquish any resources this Config might be holding on to or
// managing.
func (c *Config) Close() {
if c != nil {
c.EnterpriseConfig.Close()
}
}
9 changes: 6 additions & 3 deletions acl/acl_oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

package acl

// Config stub
type Config struct{}
type EnterpriseConfig struct {
// no fields in OSS
}

func (_ *Config) Close() {}
func (_ *EnterpriseConfig) Close() {
// do nothing
}
11 changes: 11 additions & 0 deletions acl/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,14 @@ func Enforce(authz Authorizer, rsc Resource, segment string, access string, ctx

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)
}
109 changes: 109 additions & 0 deletions acl/policy_authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,107 @@ func newPolicyAuthorizerFromRules(rules *PolicyRules, ent *Config) (Authorizer,
return p, nil
}

// enforceCallbacks are to be passed to anyAllowed or allAllowed. The interface{}
// parameter will be a value stored in the radix.Tree passed to those functions.
// prefixOnly indicates that only we only want to consider the prefix matching rule
// if any. The return value indicates whether this one leaf node in the tree would
// allow, deny or make no decision regarding some authorization.
type enforceCallback func(raw interface{}, prefixOnly bool) EnforcementDecision

func anyAllowed(tree *radix.Tree, enforceFn enforceCallback) EnforcementDecision {
decision := Default

// special case for handling a catch-all prefix rule. If the rule woul Deny access then our default decision
// should be to Deny, but this decision should still be overridable with other more specific rules.
if raw, found := tree.Get(""); found {
decision = enforceFn(raw, true)
if decision == Allow {
return Allow
}
}

tree.Walk(func(path string, raw interface{}) bool {
if enforceFn(raw, false) == Allow {
decision = Allow
return true
}

return false
})

return decision
}

func allAllowed(tree *radix.Tree, enforceFn enforceCallback) EnforcementDecision {
decision := Default

// look for a "" prefix rule
if raw, found := tree.Get(""); found {
// ensure that the empty prefix rule would allow the access
// if it does allow it we still must check all the other rules to ensure
// nothing overrides the top level grant with a different access level
// if not we can return early
decision = enforceFn(raw, true)

// the top level prefix rule denied access so we can return early.
if decision == Deny {
return Deny
}
}

tree.Walk(func(path string, raw interface{}) bool {
if enforceFn(raw, false) == Deny {
decision = Deny
return true
}
return false
})

return decision
}

func (authz *policyAuthorizer) anyAllowed(tree *radix.Tree, requiredPermission AccessLevel) EnforcementDecision {
return anyAllowed(tree, func(raw interface{}, prefixOnly bool) EnforcementDecision {
leaf := raw.(*policyAuthorizerRadixLeaf)
decision := Default

if leaf.prefix != nil {
decision = enforce(leaf.prefix.access, requiredPermission)
}

if prefixOnly || decision == Allow || leaf.exact == nil {
return decision
}

return enforce(leaf.exact.access, requiredPermission)
})
}

func (authz *policyAuthorizer) allAllowed(tree *radix.Tree, requiredPermission AccessLevel) EnforcementDecision {
return allAllowed(tree, func(raw interface{}, prefixOnly bool) EnforcementDecision {
leaf := raw.(*policyAuthorizerRadixLeaf)
prefixDecision := Default

if leaf.prefix != nil {
prefixDecision = enforce(leaf.prefix.access, requiredPermission)
}

if prefixOnly || prefixDecision == Deny || leaf.exact == nil {
return prefixDecision
}

decision := enforce(leaf.exact.access, requiredPermission)

if decision == Default {
// basically this means defer to the prefix decision as the
// authorizer rule made no decision with an exact match rule
return prefixDecision
}

return decision
})
}

// ACLRead checks if listing of ACLs is allowed
func (p *policyAuthorizer) ACLRead(*AuthorizerContext) EnforcementDecision {
if p.aclRule != nil {
Expand Down Expand Up @@ -410,6 +511,10 @@ func (p *policyAuthorizer) IntentionDefaultAllow(_ *AuthorizerContext) Enforceme
// IntentionRead checks if writing (creating, updating, or deleting) of an
// intention is allowed.
func (p *policyAuthorizer) IntentionRead(prefix string, _ *AuthorizerContext) EnforcementDecision {
if prefix == "*" {
return p.anyAllowed(p.intentionRules, AccessRead)
}

if rule, ok := getPolicy(prefix, p.intentionRules); ok {
return enforce(rule.access, AccessRead)
}
Expand All @@ -419,6 +524,10 @@ func (p *policyAuthorizer) IntentionRead(prefix string, _ *AuthorizerContext) En
// IntentionWrite checks if writing (creating, updating, or deleting) of an
// intention is allowed.
func (p *policyAuthorizer) IntentionWrite(prefix string, _ *AuthorizerContext) EnforcementDecision {
if prefix == "*" {
return p.allAllowed(p.intentionRules, AccessWrite)
}

if rule, ok := getPolicy(prefix, p.intentionRules); ok {
return enforce(rule.access, AccessWrite)
}
Expand Down
Loading

0 comments on commit 8bd34e1

Please sign in to comment.