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