- Notifications
You must be signed in to change notification settings - Fork928
feat: add jwt pkg#14928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
feat: add jwt pkg#14928
Changes from19 commits
b5d939e
6025c7b
8b235be
843de38
099544f
acc4db3
b4973a8
f7d7c95
3ba8ad3
73c902c
e348a7a
c7489b4
d890ea2
67ccd5c
1a81c7a
e529c4a
437e587
54214e2
93603a2
e654a65
0efabfd
e065356
938bdda
48b1b3b
1dd2205
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -2,23 +2,22 @@ package cryptokeys | ||
import ( | ||
"context" | ||
"strconv" | ||
"sync" | ||
"time" | ||
"golang.org/x/xerrors" | ||
"cdr.dev/slog" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/quartz" | ||
) | ||
// never represents the maximum value for a time.Duration. | ||
const never = 1<<63 - 1 | ||
//dbCache implements Keycache for callers with access to the database. | ||
typedbCache struct { | ||
db database.Store | ||
feature database.CryptoKeyFeature | ||
logger slog.Logger | ||
@@ -34,18 +33,34 @@ type DBCache struct { | ||
closed bool | ||
} | ||
type DBCacheOption func(*dbCache) | ||
func WithDBCacheClock(clock quartz.Clock) DBCacheOption { | ||
return func(d *dbCache) { | ||
d.clock = clock | ||
} | ||
} | ||
//NewSigningCache creates a new DBCache. Close should be called to | ||
// release resources associated with its internal timer. | ||
func NewSigningCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*dbCache)) (SigningKeycache, error) { | ||
if !isSigningKeyFeature(feature) { | ||
return nil, ErrInvalidFeature | ||
} | ||
return newDBCache(logger, db, feature, opts...), nil | ||
} | ||
func NewEncryptionCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*dbCache)) (EncryptionKeycache, error) { | ||
if !isEncryptionKeyFeature(feature) { | ||
return nil, ErrInvalidFeature | ||
} | ||
return newDBCache(logger, db, feature, opts...), nil | ||
} | ||
sreya marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
func newDBCache(logger slog.Logger, db database.Store, feature database.CryptoKeyFeature, opts ...func(*dbCache)) *dbCache { | ||
d := &dbCache{ | ||
sreya marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
db: db, | ||
feature: feature, | ||
clock: quartz.NewReal(), | ||
@@ -61,18 +76,39 @@ func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKe | ||
return d | ||
} | ||
func (d *dbCache) EncryptingKey(ctx context.Context) (id string, key interface{}, err error) { | ||
return d.latest(ctx) | ||
} | ||
func (d *dbCache) DecryptingKey(ctx context.Context, id string) (key interface{}, err error) { | ||
return d.sequence(ctx, id) | ||
} | ||
sreya marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
func (d *dbCache) SigningKey(ctx context.Context) (id string, key interface{}, err error) { | ||
return d.latest(ctx) | ||
} | ||
func (d *dbCache) VerifyingKey(ctx context.Context, id string) (key interface{}, err error) { | ||
return d.sequence(ctx, id) | ||
} | ||
sreya marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
// sequence returns the CryptoKey with the given sequence number, provided that | ||
// it is neither deleted nor has breached its deletion date. It should only be | ||
// used for verifying or decrypting payloads. To sign/encrypt call Signing. | ||
func (d *dbCache) sequence(ctx context.Context, id string) (interface{}, error) { | ||
sequence, err := strconv.ParseInt(id, 10, 32) | ||
if err != nil { | ||
return nil, xerrors.Errorf("expecting sequence number got %q: %w", id, err) | ||
} | ||
d.keysMu.RLock() | ||
if d.closed { | ||
d.keysMu.RUnlock() | ||
returnnil, ErrClosed | ||
} | ||
now := d.clock.Now() | ||
key, ok := d.keys[int32(sequence)] | ||
d.keysMu.RUnlock() | ||
if ok { | ||
return checkKey(key, now) | ||
@@ -82,67 +118,67 @@ func (d *DBCache) Verifying(ctx context.Context, sequence int32) (codersdk.Crypt | ||
defer d.keysMu.Unlock() | ||
if d.closed { | ||
returnnil, ErrClosed | ||
} | ||
key, ok = d.keys[int32(sequence)] | ||
if ok { | ||
return checkKey(key, now) | ||
} | ||
err = d.fetch(ctx) | ||
if err != nil { | ||
returnnil, xerrors.Errorf("fetch: %w", err) | ||
} | ||
key, ok = d.keys[int32(sequence)] | ||
if !ok { | ||
returnnil, ErrKeyNotFound | ||
} | ||
return checkKey(key, now) | ||
} | ||
//latest returns the latest valid key for signing. A valid key is one that is | ||
// both past its start time and before its deletion time. | ||
func (d *dbCache) latest(ctx context.Context) (string, interface{}, error) { | ||
d.keysMu.RLock() | ||
if d.closed { | ||
d.keysMu.RUnlock() | ||
return"", nil, ErrClosed | ||
} | ||
latest := d.latestKey | ||
d.keysMu.RUnlock() | ||
now := d.clock.Now() | ||
if latest.CanSign(now) { | ||
returnidSecret(latest) | ||
} | ||
d.keysMu.Lock() | ||
defer d.keysMu.Unlock() | ||
if d.closed { | ||
return"", nil, ErrClosed | ||
} | ||
if d.latestKey.CanSign(now) { | ||
returnidSecret(d.latestKey) | ||
} | ||
// Refetch all keys for this feature so we can find the latest valid key. | ||
err := d.fetch(ctx) | ||
if err != nil { | ||
return"", nil, xerrors.Errorf("fetch: %w", err) | ||
} | ||
returnidSecret(d.latestKey) | ||
} | ||
// clear invalidates the cache. This forces the subsequent call to fetch fresh keys. | ||
func (d *dbCache) clear() { | ||
now := d.clock.Now("DBCache", "clear") | ||
d.keysMu.Lock() | ||
defer d.keysMu.Unlock() | ||
@@ -158,7 +194,7 @@ func (d *DBCache) clear() { | ||
// fetch fetches all keys for the given feature and determines the latest key. | ||
// It must be called while holding the keysMu lock. | ||
func (d *dbCache) fetch(ctx context.Context) error { | ||
keys, err := d.db.GetCryptoKeysByFeature(ctx, d.feature) | ||
if err != nil { | ||
return xerrors.Errorf("get crypto keys by feature: %w", err) | ||
@@ -189,22 +225,45 @@ func (d *DBCache) fetch(ctx context.Context) error { | ||
return nil | ||
} | ||
func checkKey(key database.CryptoKey, now time.Time) (interface{}, error) { | ||
if !key.CanVerify(now) { | ||
returnnil, ErrKeyInvalid | ||
} | ||
returnkey.DecodeString() | ||
} | ||
func (d *dbCache) Close() error { | ||
d.keysMu.Lock() | ||
defer d.keysMu.Unlock() | ||
if d.closed { | ||
return nil | ||
} | ||
d.timer.Stop() | ||
d.closed = true | ||
return nil | ||
} | ||
func isEncryptionKeyFeature(feature database.CryptoKeyFeature) bool { | ||
return feature == database.CryptoKeyFeatureWorkspaceApps | ||
} | ||
func isSigningKeyFeature(feature database.CryptoKeyFeature) bool { | ||
switch feature { | ||
case database.CryptoKeyFeatureTailnetResume, database.CryptoKeyFeatureOidcConvert: | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
func idSecret(k database.CryptoKey) (string, interface{}, error) { | ||
key, err := k.DecodeString() | ||
if err != nil { | ||
return "", nil, xerrors.Errorf("decode key: %w", err) | ||
} | ||
return strconv.FormatInt(int64(k.Sequence), 10), key, nil | ||
} |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.