Skip to content

Commit 8bd34e1

Browse files
authored
Intentions ACL enforcement updates (hashicorp#7028)
* 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.
1 parent 3bf2e64 commit 8bd34e1

27 files changed

+1564
-255
lines changed

acl/acl.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package acl
2+
3+
const (
4+
WildcardName = "*"
5+
)
6+
7+
// Config encapsualtes all of the generic configuration parameters used for
8+
// policy parsing and enforcement
9+
type Config struct {
10+
// WildcardName is the string that represents a request to authorize a wildcard permission
11+
WildcardName string
12+
13+
// embedded enterprise configuration
14+
EnterpriseConfig
15+
}
16+
17+
// GetWildcardName will retrieve the configured wildcard name or provide a default
18+
// in the case that the config is Nil or the wildcard name is unset.
19+
func (c *Config) GetWildcardName() string {
20+
if c == nil || c.WildcardName == "" {
21+
return WildcardName
22+
}
23+
return c.WildcardName
24+
}
25+
26+
// Close will relinquish any resources this Config might be holding on to or
27+
// managing.
28+
func (c *Config) Close() {
29+
if c != nil {
30+
c.EnterpriseConfig.Close()
31+
}
32+
}

acl/acl_oss.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
package acl
44

5-
// Config stub
6-
type Config struct{}
5+
type EnterpriseConfig struct {
6+
// no fields in OSS
7+
}
78

8-
func (_ *Config) Close() {}
9+
func (_ *EnterpriseConfig) Close() {
10+
// do nothing
11+
}

acl/authorizer.go

+11
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,14 @@ func Enforce(authz Authorizer, rsc Resource, segment string, access string, ctx
242242

243243
return Deny, fmt.Errorf("Invalid access level for %s resource: %s", rsc, access)
244244
}
245+
246+
// NewAuthorizerFromRules is a convenience function to invoke NewPolicyFromSource followed by NewPolicyAuthorizer with
247+
// the parse policy.
248+
func NewAuthorizerFromRules(id string, revision uint64, rules string, syntax SyntaxVersion, conf *Config, meta *EnterprisePolicyMeta) (Authorizer, error) {
249+
policy, err := NewPolicyFromSource(id, revision, rules, syntax, conf, meta)
250+
if err != nil {
251+
return nil, err
252+
}
253+
254+
return NewPolicyAuthorizer([]*Policy{policy}, conf)
255+
}

acl/policy_authorizer.go

+109
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,107 @@ func newPolicyAuthorizerFromRules(rules *PolicyRules, ent *Config) (Authorizer,
340340
return p, nil
341341
}
342342

343+
// enforceCallbacks are to be passed to anyAllowed or allAllowed. The interface{}
344+
// parameter will be a value stored in the radix.Tree passed to those functions.
345+
// prefixOnly indicates that only we only want to consider the prefix matching rule
346+
// if any. The return value indicates whether this one leaf node in the tree would
347+
// allow, deny or make no decision regarding some authorization.
348+
type enforceCallback func(raw interface{}, prefixOnly bool) EnforcementDecision
349+
350+
func anyAllowed(tree *radix.Tree, enforceFn enforceCallback) EnforcementDecision {
351+
decision := Default
352+
353+
// special case for handling a catch-all prefix rule. If the rule woul Deny access then our default decision
354+
// should be to Deny, but this decision should still be overridable with other more specific rules.
355+
if raw, found := tree.Get(""); found {
356+
decision = enforceFn(raw, true)
357+
if decision == Allow {
358+
return Allow
359+
}
360+
}
361+
362+
tree.Walk(func(path string, raw interface{}) bool {
363+
if enforceFn(raw, false) == Allow {
364+
decision = Allow
365+
return true
366+
}
367+
368+
return false
369+
})
370+
371+
return decision
372+
}
373+
374+
func allAllowed(tree *radix.Tree, enforceFn enforceCallback) EnforcementDecision {
375+
decision := Default
376+
377+
// look for a "" prefix rule
378+
if raw, found := tree.Get(""); found {
379+
// ensure that the empty prefix rule would allow the access
380+
// if it does allow it we still must check all the other rules to ensure
381+
// nothing overrides the top level grant with a different access level
382+
// if not we can return early
383+
decision = enforceFn(raw, true)
384+
385+
// the top level prefix rule denied access so we can return early.
386+
if decision == Deny {
387+
return Deny
388+
}
389+
}
390+
391+
tree.Walk(func(path string, raw interface{}) bool {
392+
if enforceFn(raw, false) == Deny {
393+
decision = Deny
394+
return true
395+
}
396+
return false
397+
})
398+
399+
return decision
400+
}
401+
402+
func (authz *policyAuthorizer) anyAllowed(tree *radix.Tree, requiredPermission AccessLevel) EnforcementDecision {
403+
return anyAllowed(tree, func(raw interface{}, prefixOnly bool) EnforcementDecision {
404+
leaf := raw.(*policyAuthorizerRadixLeaf)
405+
decision := Default
406+
407+
if leaf.prefix != nil {
408+
decision = enforce(leaf.prefix.access, requiredPermission)
409+
}
410+
411+
if prefixOnly || decision == Allow || leaf.exact == nil {
412+
return decision
413+
}
414+
415+
return enforce(leaf.exact.access, requiredPermission)
416+
})
417+
}
418+
419+
func (authz *policyAuthorizer) allAllowed(tree *radix.Tree, requiredPermission AccessLevel) EnforcementDecision {
420+
return allAllowed(tree, func(raw interface{}, prefixOnly bool) EnforcementDecision {
421+
leaf := raw.(*policyAuthorizerRadixLeaf)
422+
prefixDecision := Default
423+
424+
if leaf.prefix != nil {
425+
prefixDecision = enforce(leaf.prefix.access, requiredPermission)
426+
}
427+
428+
if prefixOnly || prefixDecision == Deny || leaf.exact == nil {
429+
return prefixDecision
430+
}
431+
432+
decision := enforce(leaf.exact.access, requiredPermission)
433+
434+
if decision == Default {
435+
// basically this means defer to the prefix decision as the
436+
// authorizer rule made no decision with an exact match rule
437+
return prefixDecision
438+
}
439+
440+
return decision
441+
})
442+
}
443+
343444
// ACLRead checks if listing of ACLs is allowed
344445
func (p *policyAuthorizer) ACLRead(*AuthorizerContext) EnforcementDecision {
345446
if p.aclRule != nil {
@@ -410,6 +511,10 @@ func (p *policyAuthorizer) IntentionDefaultAllow(_ *AuthorizerContext) Enforceme
410511
// IntentionRead checks if writing (creating, updating, or deleting) of an
411512
// intention is allowed.
412513
func (p *policyAuthorizer) IntentionRead(prefix string, _ *AuthorizerContext) EnforcementDecision {
514+
if prefix == "*" {
515+
return p.anyAllowed(p.intentionRules, AccessRead)
516+
}
517+
413518
if rule, ok := getPolicy(prefix, p.intentionRules); ok {
414519
return enforce(rule.access, AccessRead)
415520
}
@@ -419,6 +524,10 @@ func (p *policyAuthorizer) IntentionRead(prefix string, _ *AuthorizerContext) En
419524
// IntentionWrite checks if writing (creating, updating, or deleting) of an
420525
// intention is allowed.
421526
func (p *policyAuthorizer) IntentionWrite(prefix string, _ *AuthorizerContext) EnforcementDecision {
527+
if prefix == "*" {
528+
return p.allAllowed(p.intentionRules, AccessWrite)
529+
}
530+
422531
if rule, ok := getPolicy(prefix, p.intentionRules); ok {
423532
return enforce(rule.access, AccessWrite)
424533
}

0 commit comments

Comments
 (0)