github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/acl.go (about) 1 package client 2 3 import ( 4 "time" 5 6 metrics "github.com/armon/go-metrics" 7 lru "github.com/hashicorp/golang-lru" 8 "github.com/hashicorp/nomad/acl" 9 "github.com/hashicorp/nomad/nomad/structs" 10 ) 11 12 const ( 13 // policyCacheSize is the number of ACL policies to keep cached. Policies have a fetching cost 14 // so we keep the hot policies cached to reduce the ACL token resolution time. 15 policyCacheSize = 64 16 17 // aclCacheSize is the number of ACL objects to keep cached. ACLs have a parsing and 18 // construction cost, so we keep the hot objects cached to reduce the ACL token resolution time. 19 aclCacheSize = 64 20 21 // tokenCacheSize is the number of ACL tokens to keep cached. Tokens have a fetching cost, 22 // so we keep the hot tokens cached to reduce the lookups. 23 tokenCacheSize = 64 24 ) 25 26 // clientACLResolver holds the state required for client resolution 27 // of ACLs 28 type clientACLResolver struct { 29 // aclCache is used to maintain the parsed ACL objects 30 aclCache *lru.TwoQueueCache 31 32 // policyCache is used to maintain the fetched policy objects 33 policyCache *lru.TwoQueueCache 34 35 // tokenCache is used to maintain the fetched token objects 36 tokenCache *lru.TwoQueueCache 37 } 38 39 // init is used to setup the client resolver state 40 func (c *clientACLResolver) init() error { 41 // Create the ACL object cache 42 var err error 43 c.aclCache, err = lru.New2Q(aclCacheSize) 44 if err != nil { 45 return err 46 } 47 c.policyCache, err = lru.New2Q(policyCacheSize) 48 if err != nil { 49 return err 50 } 51 c.tokenCache, err = lru.New2Q(tokenCacheSize) 52 if err != nil { 53 return err 54 } 55 return nil 56 } 57 58 // cachedACLValue is used to manage ACL Token or Policy TTLs 59 type cachedACLValue struct { 60 Token *structs.ACLToken 61 Policy *structs.ACLPolicy 62 CacheTime time.Time 63 } 64 65 // Age is the time since the token was cached 66 func (c *cachedACLValue) Age() time.Duration { 67 return time.Since(c.CacheTime) 68 } 69 70 // ResolveToken is used to translate an ACL Token Secret ID into 71 // an ACL object, nil if ACLs are disabled, or an error. 72 func (c *Client) ResolveToken(secretID string) (*acl.ACL, error) { 73 // Fast-path if ACLs are disabled 74 if !c.config.ACLEnabled { 75 return nil, nil 76 } 77 defer metrics.MeasureSince([]string{"client", "acl", "resolve_token"}, time.Now()) 78 79 // Resolve the token value 80 token, err := c.resolveTokenValue(secretID) 81 if err != nil { 82 return nil, err 83 } 84 if token == nil { 85 return nil, structs.ErrTokenNotFound 86 } 87 88 // Check if this is a management token 89 if token.Type == structs.ACLManagementToken { 90 return acl.ManagementACL, nil 91 } 92 93 // Resolve the policies 94 policies, err := c.resolvePolicies(token.SecretID, token.Policies) 95 if err != nil { 96 return nil, err 97 } 98 99 // Resolve the ACL object 100 aclObj, err := structs.CompileACLObject(c.aclCache, policies) 101 if err != nil { 102 return nil, err 103 } 104 return aclObj, nil 105 } 106 107 // resolveTokenValue is used to translate a secret ID into an ACL token with caching 108 // We use a local cache up to the TTL limit, and then resolve via a server. If we cannot 109 // reach a server, but have a cached value we extend the TTL to gracefully handle outages. 110 func (c *Client) resolveTokenValue(secretID string) (*structs.ACLToken, error) { 111 // Hot-path the anonymous token 112 if secretID == "" { 113 return structs.AnonymousACLToken, nil 114 } 115 116 // Lookup the token in the cache 117 raw, ok := c.tokenCache.Get(secretID) 118 if ok { 119 cached := raw.(*cachedACLValue) 120 if cached.Age() <= c.config.ACLTokenTTL { 121 return cached.Token, nil 122 } 123 } 124 125 // Lookup the token 126 req := structs.ResolveACLTokenRequest{ 127 SecretID: secretID, 128 QueryOptions: structs.QueryOptions{ 129 Region: c.Region(), 130 AllowStale: true, 131 }, 132 } 133 var resp structs.ResolveACLTokenResponse 134 if err := c.RPC("ACL.ResolveToken", &req, &resp); err != nil { 135 // If we encounter an error but have a cached value, mask the error and extend the cache 136 if ok { 137 c.logger.Printf("[WARN] client: failed to resolve token, using expired cached value: %v", err) 138 cached := raw.(*cachedACLValue) 139 return cached.Token, nil 140 } 141 return nil, err 142 } 143 144 // Cache the response (positive or negative) 145 c.tokenCache.Add(secretID, &cachedACLValue{ 146 Token: resp.Token, 147 CacheTime: time.Now(), 148 }) 149 return resp.Token, nil 150 } 151 152 // resolvePolicies is used to translate a set of named ACL policies into the objects. 153 // We cache the policies locally, and fault them from a server as necessary. Policies 154 // are cached for a TTL, and then refreshed. If a server cannot be reached, the cache TTL 155 // will be ignored to gracefully handle outages. 156 func (c *Client) resolvePolicies(secretID string, policies []string) ([]*structs.ACLPolicy, error) { 157 var out []*structs.ACLPolicy 158 var expired []*structs.ACLPolicy 159 var missing []string 160 161 // Scan the cache for each policy 162 for _, policyName := range policies { 163 // Lookup the policy in the cache 164 raw, ok := c.policyCache.Get(policyName) 165 if !ok { 166 missing = append(missing, policyName) 167 continue 168 } 169 170 // Check if the cached value is valid or expired 171 cached := raw.(*cachedACLValue) 172 if cached.Age() <= c.config.ACLPolicyTTL { 173 out = append(out, cached.Policy) 174 } else { 175 expired = append(expired, cached.Policy) 176 } 177 } 178 179 // Hot-path if we have no missing or expired policies 180 if len(missing)+len(expired) == 0 { 181 return out, nil 182 } 183 184 // Lookup the missing and expired policies 185 fetch := missing 186 for _, p := range expired { 187 fetch = append(fetch, p.Name) 188 } 189 req := structs.ACLPolicySetRequest{ 190 Names: fetch, 191 QueryOptions: structs.QueryOptions{ 192 Region: c.Region(), 193 AuthToken: secretID, 194 AllowStale: true, 195 }, 196 } 197 var resp structs.ACLPolicySetResponse 198 if err := c.RPC("ACL.GetPolicies", &req, &resp); err != nil { 199 // If we encounter an error but have cached policies, mask the error and extend the cache 200 if len(missing) == 0 { 201 c.logger.Printf("[WARN] client: failed to resolve policies, using expired cached value: %v", err) 202 out = append(out, expired...) 203 return out, nil 204 } 205 return nil, err 206 } 207 208 // Handle each output 209 for _, policy := range resp.Policies { 210 c.policyCache.Add(policy.Name, &cachedACLValue{ 211 Policy: policy, 212 CacheTime: time.Now(), 213 }) 214 out = append(out, policy) 215 } 216 217 // Return the valid policies 218 return out, nil 219 }