- Notifications
You must be signed in to change notification settings - Fork927
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.
Merged
feat: add jwt pkg#14928
Changes fromall commits
Commits
Show all changes
25 commits Select commitHold shift + click to select a range
b5d939e
feat: add jwt pkg
sreya6025c7b
update make gen
sreya8b235be
Refactor JWT package to modularize key functions
sreya843de38
Remove unused JWT test file from repository
sreya099544f
Refactor JWT key functions and add tests
sreyaacc4db3
Rename VerifyFn to ParseFn in JWT tests
sreyab4973a8
Remove unused JWE test file
sreyaf7d7c95
Refactor JWT test structs to use public field names
sreya3ba8ad3
Refactor JWT to use new crypto key management system
sreya73c902c
Refactor JWT package for improved modularity and clarity
sreyae348a7a
mv dir
sreyac7489b4
update references
sreyad890ea2
refactor interfaces
sreya67ccd5c
refactor dbkeycache
sreya1a81c7a
Refactor JWT utility options for flexibility
sreyae529c4a
Enhance key generation and JWT error messages
sreya437e587
Update cryptographic key length requirements
sreya54214e2
Refactor key provider interfaces in JWT utilities
sreya93603a2
Refactor dbCache to remove feature validation
sreyae654a65
Refactor cryptokeys and jwtutils interfaces and logic
sreya0efabfd
Remove unused test code and mock cleanup
sreyae065356
Remove cryptokeys keycachemock from Makefile
sreya938bdda
Add feature validation to dbCache key methods
sreya48b1b3b
fmt
sreya1dd2205
Add initialization comment for db key cache timer
sreyaFile filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
3 changes: 2 additions & 1 deletionMakefile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
144 changes: 110 additions & 34 deletionscoderd/cryptokeys/dbkeycache.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
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(), | ||
@@ -56,23 +71,61 @@ func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKe | ||
opt(d) | ||
} | ||
// Initialize the timer. This will get properly initialized the first time we fetch. | ||
d.timer = d.clock.AfterFunc(never, d.clear) | ||
return d | ||
} | ||
func (d *dbCache) EncryptingKey(ctx context.Context) (id string, key interface{}, err error) { | ||
if !isEncryptionKeyFeature(d.feature) { | ||
return "", nil, ErrInvalidFeature | ||
} | ||
return d.latest(ctx) | ||
} | ||
func (d *dbCache) DecryptingKey(ctx context.Context, id string) (key interface{}, err error) { | ||
if !isEncryptionKeyFeature(d.feature) { | ||
return nil, ErrInvalidFeature | ||
} | ||
return d.sequence(ctx, id) | ||
} | ||
func (d *dbCache) SigningKey(ctx context.Context) (id string, key interface{}, err error) { | ||
if !isSigningKeyFeature(d.feature) { | ||
return "", nil, ErrInvalidFeature | ||
} | ||
return d.latest(ctx) | ||
} | ||
func (d *dbCache) VerifyingKey(ctx context.Context, id string) (key interface{}, err error) { | ||
if !isSigningKeyFeature(d.feature) { | ||
return nil, ErrInvalidFeature | ||
} | ||
return d.sequence(ctx, id) | ||
} | ||
// 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 +135,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 +211,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 +242,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 | ||
} |
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.