Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit21b92ef

Browse files
authored
feat: add cache abstraction for fetching signing keys (#14777)
- Adds the database implementation for fetching and caching keysused for JWT signing. It's been merged into the `keyrotate` pkg andrenamed to `cryptokeys` since they're coupled concepts.
1 parentf7ddbb7 commit21b92ef

File tree

18 files changed

+1060
-178
lines changed

18 files changed

+1060
-178
lines changed

‎coderd/apidoc/docs.go

Lines changed: 36 additions & 34 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 32 additions & 30 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/cryptokeys/dbkeycache.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package cryptokeys
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
8+
"golang.org/x/xerrors"
9+
10+
"cdr.dev/slog"
11+
"github.com/coder/coder/v2/coderd/database"
12+
"github.com/coder/coder/v2/coderd/database/db2sdk"
13+
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/quartz"
15+
)
16+
17+
// never represents the maximum value for a time.Duration.
18+
constnever=1<<63-1
19+
20+
// DBCache implements Keycache for callers with access to the database.
21+
typeDBCachestruct {
22+
db database.Store
23+
feature database.CryptoKeyFeature
24+
logger slog.Logger
25+
clock quartz.Clock
26+
27+
// The following are initialized by NewDBCache.
28+
keysMu sync.RWMutex
29+
keysmap[int32]database.CryptoKey
30+
latestKey database.CryptoKey
31+
timer*quartz.Timer
32+
// invalidateAt is the time at which the keys cache should be invalidated.
33+
invalidateAt time.Time
34+
closedbool
35+
}
36+
37+
typeDBCacheOptionfunc(*DBCache)
38+
39+
funcWithDBCacheClock(clock quartz.Clock)DBCacheOption {
40+
returnfunc(d*DBCache) {
41+
d.clock=clock
42+
}
43+
}
44+
45+
// NewDBCache creates a new DBCache. Close should be called to
46+
// release resources associated with its internal timer.
47+
funcNewDBCache(logger slog.Logger,db database.Store,feature database.CryptoKeyFeature,opts...func(*DBCache))*DBCache {
48+
d:=&DBCache{
49+
db:db,
50+
feature:feature,
51+
clock:quartz.NewReal(),
52+
logger:logger,
53+
}
54+
55+
for_,opt:=rangeopts {
56+
opt(d)
57+
}
58+
59+
d.timer=d.clock.AfterFunc(never,d.clear)
60+
61+
returnd
62+
}
63+
64+
// Verifying returns the CryptoKey with the given sequence number, provided that
65+
// it is neither deleted nor has breached its deletion date. It should only be
66+
// used for verifying or decrypting payloads. To sign/encrypt call Signing.
67+
func (d*DBCache)Verifying(ctx context.Context,sequenceint32) (codersdk.CryptoKey,error) {
68+
d.keysMu.RLock()
69+
ifd.closed {
70+
d.keysMu.RUnlock()
71+
return codersdk.CryptoKey{},ErrClosed
72+
}
73+
74+
now:=d.clock.Now()
75+
key,ok:=d.keys[sequence]
76+
d.keysMu.RUnlock()
77+
ifok {
78+
returncheckKey(key,now)
79+
}
80+
81+
d.keysMu.Lock()
82+
deferd.keysMu.Unlock()
83+
84+
ifd.closed {
85+
return codersdk.CryptoKey{},ErrClosed
86+
}
87+
88+
key,ok=d.keys[sequence]
89+
ifok {
90+
returncheckKey(key,now)
91+
}
92+
93+
err:=d.fetch(ctx)
94+
iferr!=nil {
95+
return codersdk.CryptoKey{},xerrors.Errorf("fetch: %w",err)
96+
}
97+
98+
key,ok=d.keys[sequence]
99+
if!ok {
100+
return codersdk.CryptoKey{},ErrKeyNotFound
101+
}
102+
103+
returncheckKey(key,now)
104+
}
105+
106+
// Signing returns the latest valid key for signing. A valid key is one that is
107+
// both past its start time and before its deletion time.
108+
func (d*DBCache)Signing(ctx context.Context) (codersdk.CryptoKey,error) {
109+
d.keysMu.RLock()
110+
111+
ifd.closed {
112+
d.keysMu.RUnlock()
113+
return codersdk.CryptoKey{},ErrClosed
114+
}
115+
116+
latest:=d.latestKey
117+
d.keysMu.RUnlock()
118+
119+
now:=d.clock.Now()
120+
iflatest.CanSign(now) {
121+
returndb2sdk.CryptoKey(latest),nil
122+
}
123+
124+
d.keysMu.Lock()
125+
deferd.keysMu.Unlock()
126+
127+
ifd.closed {
128+
return codersdk.CryptoKey{},ErrClosed
129+
}
130+
131+
ifd.latestKey.CanSign(now) {
132+
returndb2sdk.CryptoKey(d.latestKey),nil
133+
}
134+
135+
// Refetch all keys for this feature so we can find the latest valid key.
136+
err:=d.fetch(ctx)
137+
iferr!=nil {
138+
return codersdk.CryptoKey{},xerrors.Errorf("fetch: %w",err)
139+
}
140+
141+
returndb2sdk.CryptoKey(d.latestKey),nil
142+
}
143+
144+
// clear invalidates the cache. This forces the subsequent call to fetch fresh keys.
145+
func (d*DBCache)clear() {
146+
now:=d.clock.Now("DBCache","clear")
147+
d.keysMu.Lock()
148+
deferd.keysMu.Unlock()
149+
// Check if we raced with a fetch. It's possible that the timer fired and we
150+
// lost the race to the mutex. We want to avoid invalidating
151+
// a cache that was just refetched.
152+
ifnow.Before(d.invalidateAt) {
153+
return
154+
}
155+
d.keys=nil
156+
d.latestKey= database.CryptoKey{}
157+
}
158+
159+
// fetch fetches all keys for the given feature and determines the latest key.
160+
// It must be called while holding the keysMu lock.
161+
func (d*DBCache)fetch(ctx context.Context)error {
162+
keys,err:=d.db.GetCryptoKeysByFeature(ctx,d.feature)
163+
iferr!=nil {
164+
returnxerrors.Errorf("get crypto keys by feature: %w",err)
165+
}
166+
167+
now:=d.clock.Now()
168+
_=d.timer.Reset(time.Minute*10)
169+
d.invalidateAt=now.Add(time.Minute*10)
170+
171+
cache:=make(map[int32]database.CryptoKey)
172+
varlatest database.CryptoKey
173+
for_,key:=rangekeys {
174+
cache[key.Sequence]=key
175+
ifkey.CanSign(now)&&key.Sequence>latest.Sequence {
176+
latest=key
177+
}
178+
}
179+
180+
iflen(cache)==0 {
181+
returnErrKeyNotFound
182+
}
183+
184+
if!latest.CanSign(now) {
185+
returnErrKeyInvalid
186+
}
187+
188+
d.keys,d.latestKey=cache,latest
189+
returnnil
190+
}
191+
192+
funccheckKey(key database.CryptoKey,now time.Time) (codersdk.CryptoKey,error) {
193+
if!key.CanVerify(now) {
194+
return codersdk.CryptoKey{},ErrKeyInvalid
195+
}
196+
197+
returndb2sdk.CryptoKey(key),nil
198+
}
199+
200+
func (d*DBCache)Close() {
201+
d.keysMu.Lock()
202+
deferd.keysMu.Unlock()
203+
204+
ifd.closed {
205+
return
206+
}
207+
208+
d.timer.Stop()
209+
d.closed=true
210+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp