1package acl
2
3import (
4	"crypto/md5"
5	"fmt"
6
7	"github.com/hashicorp/consul/sentinel"
8	"github.com/hashicorp/golang-lru"
9)
10
11// FaultFunc is a function used to fault in the parent,
12// rules for an ACL given its ID
13type FaultFunc func(id string) (string, string, error)
14
15// aclEntry allows us to store the ACL with it's policy ID
16type aclEntry struct {
17	ACL    ACL
18	Parent string
19	RuleID string
20}
21
22// Cache is used to implement policy and ACL caching
23type Cache struct {
24	faultfn     FaultFunc
25	aclCache    *lru.TwoQueueCache // Cache id -> acl
26	policyCache *lru.TwoQueueCache // Cache policy -> acl
27	ruleCache   *lru.TwoQueueCache // Cache rules -> policy
28	sentinel    sentinel.Evaluator
29}
30
31// NewCache constructs a new policy and ACL cache of a given size
32func NewCache(size int, faultfn FaultFunc, sentinel sentinel.Evaluator) (*Cache, error) {
33	if size <= 0 {
34		return nil, fmt.Errorf("Must provide positive cache size")
35	}
36
37	rc, err := lru.New2Q(size)
38	if err != nil {
39		return nil, err
40	}
41
42	pc, err := lru.New2Q(size)
43	if err != nil {
44		return nil, err
45	}
46
47	ac, err := lru.New2Q(size)
48	if err != nil {
49		return nil, err
50	}
51
52	c := &Cache{
53		faultfn:     faultfn,
54		aclCache:    ac,
55		policyCache: pc,
56		ruleCache:   rc,
57		sentinel:    sentinel,
58	}
59	return c, nil
60}
61
62// GetPolicy is used to get a potentially cached policy set.
63// If not cached, it will be parsed, and then cached.
64func (c *Cache) GetPolicy(rules string) (*Policy, error) {
65	return c.getPolicy(RuleID(rules), rules)
66}
67
68// getPolicy is an internal method to get a cached policy,
69// but it assumes a pre-computed ID
70func (c *Cache) getPolicy(id, rules string) (*Policy, error) {
71	raw, ok := c.ruleCache.Get(id)
72	if ok {
73		return raw.(*Policy), nil
74	}
75	policy, err := Parse(rules, c.sentinel)
76	if err != nil {
77		return nil, err
78	}
79	policy.ID = id
80	c.ruleCache.Add(id, policy)
81	return policy, nil
82
83}
84
85// RuleID is used to generate an ID for a rule
86func RuleID(rules string) string {
87	return fmt.Sprintf("%x", md5.Sum([]byte(rules)))
88}
89
90// policyID returns the cache ID for a policy
91func (c *Cache) policyID(parent, ruleID string) string {
92	return parent + ":" + ruleID
93}
94
95// GetACLPolicy is used to get the potentially cached ACL
96// policy. If not cached, it will be generated and then cached.
97func (c *Cache) GetACLPolicy(id string) (string, *Policy, error) {
98	// Check for a cached acl
99	if raw, ok := c.aclCache.Get(id); ok {
100		cached := raw.(aclEntry)
101		if raw, ok := c.ruleCache.Get(cached.RuleID); ok {
102			return cached.Parent, raw.(*Policy), nil
103		}
104	}
105
106	// Fault in the rules
107	parent, rules, err := c.faultfn(id)
108	if err != nil {
109		return "", nil, err
110	}
111
112	// Get cached
113	policy, err := c.GetPolicy(rules)
114	return parent, policy, err
115}
116
117// GetACL is used to get a potentially cached ACL policy.
118// If not cached, it will be generated and then cached.
119func (c *Cache) GetACL(id string) (ACL, error) {
120	// Look for the ACL directly
121	raw, ok := c.aclCache.Get(id)
122	if ok {
123		return raw.(aclEntry).ACL, nil
124	}
125
126	// Get the rules
127	parentID, rules, err := c.faultfn(id)
128	if err != nil {
129		return nil, err
130	}
131	ruleID := RuleID(rules)
132
133	// Check for a compiled ACL
134	policyID := c.policyID(parentID, ruleID)
135	var compiled ACL
136	if raw, ok := c.policyCache.Get(policyID); ok {
137		compiled = raw.(ACL)
138	} else {
139		// Get the policy
140		policy, err := c.getPolicy(ruleID, rules)
141		if err != nil {
142			return nil, err
143		}
144
145		// Get the parent ACL
146		parent := RootACL(parentID)
147		if parent == nil {
148			parent, err = c.GetACL(parentID)
149			if err != nil {
150				return nil, err
151			}
152		}
153
154		// Compile the ACL
155		acl, err := New(parent, policy, c.sentinel)
156		if err != nil {
157			return nil, err
158		}
159
160		// Cache the compiled ACL
161		c.policyCache.Add(policyID, acl)
162		compiled = acl
163	}
164
165	// Cache and return the ACL
166	c.aclCache.Add(id, aclEntry{compiled, parentID, ruleID})
167	return compiled, nil
168}
169
170// ClearACL is used to clear the ACL cache if any
171func (c *Cache) ClearACL(id string) {
172	c.aclCache.Remove(id)
173}
174
175// Purge is used to clear all the ACL caches. The
176// rule and policy caches are not purged, since they
177// are content-hashed anyways.
178func (c *Cache) Purge() {
179	c.aclCache.Purge()
180}
181