github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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 a, _, err := c.resolveTokenAndACL(secretID) 74 return a, err 75 } 76 77 func (c *Client) ResolveSecretToken(secretID string) (*structs.ACLToken, error) { 78 _, t, err := c.resolveTokenAndACL(secretID) 79 return t, err 80 } 81 82 func (c *Client) resolveTokenAndACL(secretID string) (*acl.ACL, *structs.ACLToken, error) { 83 // Fast-path if ACLs are disabled 84 if !c.config.ACLEnabled { 85 return nil, nil, nil 86 } 87 defer metrics.MeasureSince([]string{"client", "acl", "resolve_token"}, time.Now()) 88 89 // Resolve the token value 90 token, err := c.resolveTokenValue(secretID) 91 if err != nil { 92 return nil, nil, err 93 } 94 if token == nil { 95 return nil, nil, structs.ErrTokenNotFound 96 } 97 98 // Check if this is a management token 99 if token.Type == structs.ACLManagementToken { 100 return acl.ManagementACL, token, nil 101 } 102 103 // Resolve the policies 104 policies, err := c.resolvePolicies(token.SecretID, token.Policies) 105 if err != nil { 106 return nil, nil, err 107 } 108 109 // Resolve the ACL object 110 aclObj, err := structs.CompileACLObject(c.aclCache, policies) 111 if err != nil { 112 return nil, nil, err 113 } 114 return aclObj, token, nil 115 } 116 117 // resolveTokenValue is used to translate a secret ID into an ACL token with caching 118 // We use a local cache up to the TTL limit, and then resolve via a server. If we cannot 119 // reach a server, but have a cached value we extend the TTL to gracefully handle outages. 120 func (c *Client) resolveTokenValue(secretID string) (*structs.ACLToken, error) { 121 // Hot-path the anonymous token 122 if secretID == "" { 123 return structs.AnonymousACLToken, nil 124 } 125 126 // Lookup the token in the cache 127 raw, ok := c.tokenCache.Get(secretID) 128 if ok { 129 cached := raw.(*cachedACLValue) 130 if cached.Age() <= c.config.ACLTokenTTL { 131 return cached.Token, nil 132 } 133 } 134 135 // Lookup the token 136 req := structs.ResolveACLTokenRequest{ 137 SecretID: secretID, 138 QueryOptions: structs.QueryOptions{ 139 Region: c.Region(), 140 AllowStale: true, 141 }, 142 } 143 var resp structs.ResolveACLTokenResponse 144 if err := c.RPC("ACL.ResolveToken", &req, &resp); err != nil { 145 // If we encounter an error but have a cached value, mask the error and extend the cache 146 if ok { 147 c.logger.Warn("failed to resolve token, using expired cached value", "error", err) 148 cached := raw.(*cachedACLValue) 149 return cached.Token, nil 150 } 151 return nil, err 152 } 153 154 // Cache the response (positive or negative) 155 c.tokenCache.Add(secretID, &cachedACLValue{ 156 Token: resp.Token, 157 CacheTime: time.Now(), 158 }) 159 return resp.Token, nil 160 } 161 162 // resolvePolicies is used to translate a set of named ACL policies into the objects. 163 // We cache the policies locally, and fault them from a server as necessary. Policies 164 // are cached for a TTL, and then refreshed. If a server cannot be reached, the cache TTL 165 // will be ignored to gracefully handle outages. 166 func (c *Client) resolvePolicies(secretID string, policies []string) ([]*structs.ACLPolicy, error) { 167 var out []*structs.ACLPolicy 168 var expired []*structs.ACLPolicy 169 var missing []string 170 171 // Scan the cache for each policy 172 for _, policyName := range policies { 173 // Lookup the policy in the cache 174 raw, ok := c.policyCache.Get(policyName) 175 if !ok { 176 missing = append(missing, policyName) 177 continue 178 } 179 180 // Check if the cached value is valid or expired 181 cached := raw.(*cachedACLValue) 182 if cached.Age() <= c.config.ACLPolicyTTL { 183 out = append(out, cached.Policy) 184 } else { 185 expired = append(expired, cached.Policy) 186 } 187 } 188 189 // Hot-path if we have no missing or expired policies 190 if len(missing)+len(expired) == 0 { 191 return out, nil 192 } 193 194 // Lookup the missing and expired policies 195 fetch := missing 196 for _, p := range expired { 197 fetch = append(fetch, p.Name) 198 } 199 req := structs.ACLPolicySetRequest{ 200 Names: fetch, 201 QueryOptions: structs.QueryOptions{ 202 Region: c.Region(), 203 AuthToken: secretID, 204 AllowStale: true, 205 }, 206 } 207 var resp structs.ACLPolicySetResponse 208 if err := c.RPC("ACL.GetPolicies", &req, &resp); err != nil { 209 // If we encounter an error but have cached policies, mask the error and extend the cache 210 if len(missing) == 0 { 211 c.logger.Warn("failed to resolve policies, using expired cached value", "error", err) 212 out = append(out, expired...) 213 return out, nil 214 } 215 return nil, err 216 } 217 218 // Handle each output 219 for _, policy := range resp.Policies { 220 c.policyCache.Add(policy.Name, &cachedACLValue{ 221 Policy: policy, 222 CacheTime: time.Now(), 223 }) 224 out = append(out, policy) 225 } 226 227 // Return the valid policies 228 return out, nil 229 }