Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 68ec532

Browse files
authored
feat: add jwt pkg (#14928)
- Adds a `jwtutils` package to be shared amongst the various packages in the codebase that make use of JWTs. It's intended to help us standardize on one library instead of some implementations using `go-jose` and others using `golang-jwt`. The main reason we're converging on `go-jose` is due to its support for JWEs, `golang-jwt` also has a repo to handle it but it doesn't look maintained: https://github.com/golang-jwt/jwe
1 parent 50d9206 commit 68ec532

13 files changed

+1001
-122
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,8 @@ gen/mark-fresh:
537537
tailnet/tailnettest/coordinatormock.go \
538538
tailnet/tailnettest/coordinateemock.go \
539539
tailnet/tailnettest/multiagentmock.go \
540-
"
540+
"
541+
541542
for file in $$files; do
542543
echo "$$file"
543544
if [ ! -f "$$file" ]; then

coderd/cryptokeys/dbkeycache.go

Lines changed: 110 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@ package cryptokeys
22

33
import (
44
"context"
5+
"strconv"
56
"sync"
67
"time"
78

89
"golang.org/x/xerrors"
910

1011
"cdr.dev/slog"
1112
"github.com/coder/coder/v2/coderd/database"
12-
"github.com/coder/coder/v2/coderd/database/db2sdk"
13-
"github.com/coder/coder/v2/codersdk"
1413
"github.com/coder/quartz"
1514
)
1615

1716
// never represents the maximum value for a time.Duration.
1817
const never = 1<<63 - 1
1918

20-
// DBCache implements Keycache for callers with access to the database.
21-
type DBCache struct {
19+
// dbCache implements Keycache for callers with access to the database.
20+
type dbCache struct {
2221
db database.Store
2322
feature database.CryptoKeyFeature
2423
logger slog.Logger
@@ -34,18 +33,34 @@ type DBCache struct {
3433
closed bool
3534
}
3635

37-
type DBCacheOption func(*DBCache)
36+
type DBCacheOption func(*dbCache)
3837

3938
func WithDBCacheClock(clock quartz.Clock) DBCacheOption {
40-
return func(d *DBCache) {
39+
return func(d *dbCache) {
4140
d.clock = clock
4241
}
4342
}
4443

45-
// NewDBCache creates a new DBCache. Close should be called to
44+
// NewSigningCache creates a new DBCache. Close should be called to
4645
// release resources associated with its internal timer.
47-
func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*DBCache)) *DBCache {
48-
d := &DBCache{
46+
func NewSigningCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*dbCache)) (SigningKeycache, error) {
47+
if !isSigningKeyFeature(feature) {
48+
return nil, ErrInvalidFeature
49+
}
50+
51+
return newDBCache(logger, db, feature, opts...), nil
52+
}
53+
54+
func NewEncryptionCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*dbCache)) (EncryptionKeycache, error) {
55+
if !isEncryptionKeyFeature(feature) {
56+
return nil, ErrInvalidFeature
57+
}
58+
59+
return newDBCache(logger, db, feature, opts...), nil
60+
}
61+
62+
func newDBCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*dbCache)) *dbCache {
63+
d := &dbCache{
4964
db: db,
5065
feature: feature,
5166
clock: quartz.NewReal(),
@@ -56,23 +71,61 @@ func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKe
5671
opt(d)
5772
}
5873

74+
// Initialize the timer. This will get properly initialized the first time we fetch.
5975
d.timer = d.clock.AfterFunc(never, d.clear)
6076

6177
return d
6278
}
6379

64-
// Verifying returns the CryptoKey with the given sequence number, provided that
80+
func (d *dbCache) EncryptingKey(ctx context.Context) (id string, key interface{}, err error) {
81+
if !isEncryptionKeyFeature(d.feature) {
82+
return "", nil, ErrInvalidFeature
83+
}
84+
85+
return d.latest(ctx)
86+
}
87+
88+
func (d *dbCache) DecryptingKey(ctx context.Context, id string) (key interface{}, err error) {
89+
if !isEncryptionKeyFeature(d.feature) {
90+
return nil, ErrInvalidFeature
91+
}
92+
93+
return d.sequence(ctx, id)
94+
}
95+
96+
func (d *dbCache) SigningKey(ctx context.Context) (id string, key interface{}, err error) {
97+
if !isSigningKeyFeature(d.feature) {
98+
return "", nil, ErrInvalidFeature
99+
}
100+
101+
return d.latest(ctx)
102+
}
103+
104+
func (d *dbCache) VerifyingKey(ctx context.Context, id string) (key interface{}, err error) {
105+
if !isSigningKeyFeature(d.feature) {
106+
return nil, ErrInvalidFeature
107+
}
108+
109+
return d.sequence(ctx, id)
110+
}
111+
112+
// sequence returns the CryptoKey with the given sequence number, provided that
65113
// it is neither deleted nor has breached its deletion date. It should only be
66114
// used for verifying or decrypting payloads. To sign/encrypt call Signing.
67-
func (d *DBCache) Verifying(ctx context.Context, sequence int32) (codersdk.CryptoKey, error) {
115+
func (d *dbCache) sequence(ctx context.Context, id string) (interface{}, error) {
116+
sequence, err := strconv.ParseInt(id, 10, 32)
117+
if err != nil {
118+
return nil, xerrors.Errorf("expecting sequence number got %q: %w", id, err)
119+
}
120+
68121
d.keysMu.RLock()
69122
if d.closed {
70123
d.keysMu.RUnlock()
71-
return codersdk.CryptoKey{}, ErrClosed
124+
return nil, ErrClosed
72125
}
73126

74127
now := d.clock.Now()
75-
key, ok := d.keys[sequence]
128+
key, ok := d.keys[int32(sequence)]
76129
d.keysMu.RUnlock()
77130
if ok {
78131
return checkKey(key, now)
@@ -82,67 +135,67 @@ func (d *DBCache) Verifying(ctx context.Context, sequence int32) (codersdk.Crypt
82135
defer d.keysMu.Unlock()
83136

84137
if d.closed {
85-
return codersdk.CryptoKey{}, ErrClosed
138+
return nil, ErrClosed
86139
}
87140

88-
key, ok = d.keys[sequence]
141+
key, ok = d.keys[int32(sequence)]
89142
if ok {
90143
return checkKey(key, now)
91144
}
92145

93-
err := d.fetch(ctx)
146+
err = d.fetch(ctx)
94147
if err != nil {
95-
return codersdk.CryptoKey{}, xerrors.Errorf("fetch: %w", err)
148+
return nil, xerrors.Errorf("fetch: %w", err)
96149
}
97150

98-
key, ok = d.keys[sequence]
151+
key, ok = d.keys[int32(sequence)]
99152
if !ok {
100-
return codersdk.CryptoKey{}, ErrKeyNotFound
153+
return nil, ErrKeyNotFound
101154
}
102155

103156
return checkKey(key, now)
104157
}
105158

106-
// Signing returns the latest valid key for signing. A valid key is one that is
159+
// latest returns the latest valid key for signing. A valid key is one that is
107160
// both past its start time and before its deletion time.
108-
func (d *DBCache) Signing(ctx context.Context) (codersdk.CryptoKey, error) {
161+
func (d *dbCache) latest(ctx context.Context) (string, interface{}, error) {
109162
d.keysMu.RLock()
110163

111164
if d.closed {
112165
d.keysMu.RUnlock()
113-
return codersdk.CryptoKey{}, ErrClosed
166+
return "", nil, ErrClosed
114167
}
115168

116169
latest := d.latestKey
117170
d.keysMu.RUnlock()
118171

119172
now := d.clock.Now()
120173
if latest.CanSign(now) {
121-
return db2sdk.CryptoKey(latest), nil
174+
return idSecret(latest)
122175
}
123176

124177
d.keysMu.Lock()
125178
defer d.keysMu.Unlock()
126179

127180
if d.closed {
128-
return codersdk.CryptoKey{}, ErrClosed
181+
return "", nil, ErrClosed
129182
}
130183

131184
if d.latestKey.CanSign(now) {
132-
return db2sdk.CryptoKey(d.latestKey), nil
185+
return idSecret(d.latestKey)
133186
}
134187

135188
// Refetch all keys for this feature so we can find the latest valid key.
136189
err := d.fetch(ctx)
137190
if err != nil {
138-
return codersdk.CryptoKey{}, xerrors.Errorf("fetch: %w", err)
191+
return "", nil, xerrors.Errorf("fetch: %w", err)
139192
}
140193

141-
return db2sdk.CryptoKey(d.latestKey), nil
194+
return idSecret(d.latestKey)
142195
}
143196

144197
// clear invalidates the cache. This forces the subsequent call to fetch fresh keys.
145-
func (d *DBCache) clear() {
198+
func (d *dbCache) clear() {
146199
now := d.clock.Now("DBCache", "clear")
147200
d.keysMu.Lock()
148201
defer d.keysMu.Unlock()
@@ -158,7 +211,7 @@ func (d *DBCache) clear() {
158211

159212
// fetch fetches all keys for the given feature and determines the latest key.
160213
// It must be called while holding the keysMu lock.
161-
func (d *DBCache) fetch(ctx context.Context) error {
214+
func (d *dbCache) fetch(ctx context.Context) error {
162215
keys, err := d.db.GetCryptoKeysByFeature(ctx, d.feature)
163216
if err != nil {
164217
return xerrors.Errorf("get crypto keys by feature: %w", err)
@@ -189,22 +242,45 @@ func (d *DBCache) fetch(ctx context.Context) error {
189242
return nil
190243
}
191244

192-
func checkKey(key database.CryptoKey, now time.Time) (codersdk.CryptoKey, error) {
245+
func checkKey(key database.CryptoKey, now time.Time) (interface{}, error) {
193246
if !key.CanVerify(now) {
194-
return codersdk.CryptoKey{}, ErrKeyInvalid
247+
return nil, ErrKeyInvalid
195248
}
196249

197-
return db2sdk.CryptoKey(key), nil
250+
return key.DecodeString()
198251
}
199252

200-
func (d *DBCache) Close() {
253+
func (d *dbCache) Close() error {
201254
d.keysMu.Lock()
202255
defer d.keysMu.Unlock()
203256

204257
if d.closed {
205-
return
258+
return nil
206259
}
207260

208261
d.timer.Stop()
209262
d.closed = true
263+
return nil
264+
}
265+
266+
func isEncryptionKeyFeature(feature database.CryptoKeyFeature) bool {
267+
return feature == database.CryptoKeyFeatureWorkspaceApps
268+
}
269+
270+
func isSigningKeyFeature(feature database.CryptoKeyFeature) bool {
271+
switch feature {
272+
case database.CryptoKeyFeatureTailnetResume, database.CryptoKeyFeatureOidcConvert:
273+
return true
274+
default:
275+
return false
276+
}
277+
}
278+
279+
func idSecret(k database.CryptoKey) (string, interface{}, error) {
280+
key, err := k.DecodeString()
281+
if err != nil {
282+
return "", nil, xerrors.Errorf("decode key: %w", err)
283+
}
284+
285+
return strconv.FormatInt(int64(k.Sequence), 10), key, nil
210286
}

0 commit comments

Comments
 (0)