| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package auth | 5 package auth |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "crypto/sha1" | 9 "crypto/sha1" |
| 10 "encoding/base64" | 10 "encoding/base64" |
| 11 "encoding/gob" | 11 "encoding/gob" |
| 12 "fmt" | 12 "fmt" |
| 13 "time" | 13 "time" |
| 14 | 14 |
| 15 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 16 | 16 |
| 17 "github.com/luci/luci-go/common/clock" | 17 "github.com/luci/luci-go/common/clock" |
| 18 "github.com/luci/luci-go/common/data/caching/lru" |
| 18 "github.com/luci/luci-go/common/data/rand/mathrand" | 19 "github.com/luci/luci-go/common/data/rand/mathrand" |
| 19 ) | 20 ) |
| 20 | 21 |
| 21 // delegationTokenCache is used to store delegation tokens in the global cache. | 22 // delegationTokenCache is used to store delegation tokens in the cache. |
| 22 var delegationTokenCache = tokenCache{ | 23 var delegationTokenCache = tokenCache{ |
| 23 Kind: "delegation", | 24 Kind: "delegation", |
| 24 Version: 2, | 25 Version: 2, |
| 25 ExpRandPercent: 10, | 26 ExpRandPercent: 10, |
| 26 MinAcceptedLifetime: 5 * time.Minute, | 27 MinAcceptedLifetime: 5 * time.Minute, |
| 27 } | 28 } |
| 28 | 29 |
| 29 // tokenCache knows how to keep tokens in the global cache. | 30 // tokenCache knows how to keep tokens in the cache. |
| 30 // | 31 // |
| 31 // Uses Config.GlobalCache as a storage backend. | 32 // Uses Config.Cache as a storage backend. |
| 32 // | 33 // |
| 33 // It implements probabilistic early expiration to workaround cache stampede | 34 // It implements probabilistic early expiration to workaround cache stampede |
| 34 // problem for hot items, see Fetch. | 35 // problem for hot items, see Fetch. |
| 35 type tokenCache struct { | 36 type tokenCache struct { |
| 36 // Kind defines the token kind. Will be used as part of the cache key. | 37 // Kind defines the token kind. Will be used as part of the cache key. |
| 37 Kind string | 38 Kind string |
| 38 | 39 |
| 39 // Version defines format of the data. Will be used as part of the cache
key. | 40 // Version defines format of the data. Will be used as part of the cache
key. |
| 40 // | 41 // |
| 41 // If you change a type behind interface{} in Token field, you MUST bump
the | 42 // If you change a type behind interface{} in Token field, you MUST bump
the |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 } | 74 } |
| 74 ttl := tok.Expiry.Sub(tok.Created) | 75 ttl := tok.Expiry.Sub(tok.Created) |
| 75 if ttl < tc.MinAcceptedLifetime { | 76 if ttl < tc.MinAcceptedLifetime { |
| 76 return fmt.Errorf("refusing to store a token that expires in %s"
, ttl) | 77 return fmt.Errorf("refusing to store a token that expires in %s"
, ttl) |
| 77 } | 78 } |
| 78 blob, err := tc.marshal(&tok) | 79 blob, err := tc.marshal(&tok) |
| 79 if err != nil { | 80 if err != nil { |
| 80 return err | 81 return err |
| 81 } | 82 } |
| 82 cfg := GetConfig(c) | 83 cfg := GetConfig(c) |
| 83 » if cfg == nil || cfg.GlobalCache == nil { | 84 » if cfg == nil || cfg.Cache == nil { |
| 84 return ErrNotConfigured | 85 return ErrNotConfigured |
| 85 } | 86 } |
| 86 » return cfg.GlobalCache.Set(c, tc.itemKey(tok.Key), blob, ttl) | 87 » return cfg.Cache.Set(c, tc.itemKey(tok.Key), blob, ttl) |
| 87 } | 88 } |
| 88 | 89 |
| 89 // Fetch grabs cached token if it hasn't expired yet. | 90 // Fetch grabs cached token if it hasn't expired yet. |
| 90 // | 91 // |
| 91 // Returns (nil, nil) if no such token or it has expired already. If the cached | 92 // Returns (nil, nil) if no such token or it has expired already. If the cached |
| 92 // token is close to expiration, this function will randomly return a cache | 93 // token is close to expiration, this function will randomly return a cache |
| 93 // miss. That way if multiple concurrent processes all constantly use the same | 94 // miss. That way if multiple concurrent processes all constantly use the same |
| 94 // token, only the most unlucky one will refresh it. | 95 // token, only the most unlucky one will refresh it. |
| 95 func (tc *tokenCache) Fetch(c context.Context, key string) (*cachedToken, error)
{ | 96 func (tc *tokenCache) Fetch(c context.Context, key string) (*cachedToken, error)
{ |
| 96 cfg := GetConfig(c) | 97 cfg := GetConfig(c) |
| 97 » if cfg == nil || cfg.GlobalCache == nil { | 98 » if cfg == nil || cfg.Cache == nil { |
| 98 return nil, ErrNotConfigured | 99 return nil, ErrNotConfigured |
| 99 } | 100 } |
| 100 | 101 |
| 101 » blob, err := cfg.GlobalCache.Get(c, tc.itemKey(key)) | 102 » blob, err := cfg.Cache.Get(c, tc.itemKey(key)) |
| 102 switch { | 103 switch { |
| 103 case err != nil: | 104 case err != nil: |
| 104 return nil, err | 105 return nil, err |
| 105 case blob == nil: | 106 case blob == nil: |
| 106 return nil, nil | 107 return nil, nil |
| 107 } | 108 } |
| 108 | 109 |
| 109 tok, err := tc.unmarshal(blob) | 110 tok, err := tc.unmarshal(blob) |
| 110 if err != nil { | 111 if err != nil { |
| 111 return nil, err | 112 return nil, err |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 153 | 154 |
| 154 // unmarshal is reverse of marshal. | 155 // unmarshal is reverse of marshal. |
| 155 func (tc *tokenCache) unmarshal(blob []byte) (*cachedToken, error) { | 156 func (tc *tokenCache) unmarshal(blob []byte) (*cachedToken, error) { |
| 156 dec := gob.NewDecoder(bytes.NewReader(blob)) | 157 dec := gob.NewDecoder(bytes.NewReader(blob)) |
| 157 out := &cachedToken{} | 158 out := &cachedToken{} |
| 158 if err := dec.Decode(out); err != nil { | 159 if err := dec.Decode(out); err != nil { |
| 159 return nil, err | 160 return nil, err |
| 160 } | 161 } |
| 161 return out, nil | 162 return out, nil |
| 162 } | 163 } |
| 164 |
| 165 type memoryCache struct { |
| 166 cache *lru.Cache |
| 167 } |
| 168 |
| 169 // MemoryCache creates a new in-memory Cache instance that is built on |
| 170 // top of an LRU cache of the specified size. |
| 171 func MemoryCache(size int) Cache { |
| 172 return memoryCache{ |
| 173 cache: lru.New(size), |
| 174 } |
| 175 } |
| 176 |
| 177 func (mc memoryCache) Get(c context.Context, key string) ([]byte, error) { |
| 178 var item *memoryCacheItem |
| 179 now := clock.Now(c) |
| 180 _ = mc.cache.Mutate(key, func(cur interface{}) interface{} { |
| 181 if cur == nil { |
| 182 return nil |
| 183 } |
| 184 |
| 185 item = cur.(*memoryCacheItem) |
| 186 if now.After(item.exp) { |
| 187 // Cache item is too old, so expire it. |
| 188 item = nil |
| 189 } |
| 190 return item |
| 191 }) |
| 192 if item == nil { |
| 193 // Cache miss (or expired). |
| 194 return nil, nil |
| 195 } |
| 196 return item.value, nil |
| 197 } |
| 198 |
| 199 func (mc memoryCache) Set(c context.Context, key string, value []byte, exp time.
Duration) error { |
| 200 mc.cache.Put(key, &memoryCacheItem{ |
| 201 value: value, |
| 202 exp: clock.Now(c).Add(exp), |
| 203 }) |
| 204 return nil |
| 205 } |
| 206 |
| 207 type memoryCacheItem struct { |
| 208 value []byte |
| 209 exp time.Time |
| 210 } |
| OLD | NEW |