@@ -2,23 +2,22 @@ package cryptokeys
2
2
3
3
import (
4
4
"context"
5
+ "strconv"
5
6
"sync"
6
7
"time"
7
8
8
9
"golang.org/x/xerrors"
9
10
10
11
"cdr.dev/slog"
11
12
"github.com/coder/coder/v2/coderd/database"
12
- "github.com/coder/coder/v2/coderd/database/db2sdk"
13
- "github.com/coder/coder/v2/codersdk"
14
13
"github.com/coder/quartz"
15
14
)
16
15
17
16
// never represents the maximum value for a time.Duration.
18
17
const never = 1 << 63 - 1
19
18
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 {
22
21
db database.Store
23
22
feature database.CryptoKeyFeature
24
23
logger slog.Logger
@@ -34,18 +33,34 @@ type DBCache struct {
34
33
closed bool
35
34
}
36
35
37
- type DBCacheOption func (* DBCache )
36
+ type DBCacheOption func (* dbCache )
38
37
39
38
func WithDBCacheClock (clock quartz.Clock ) DBCacheOption {
40
- return func (d * DBCache ) {
39
+ return func (d * dbCache ) {
41
40
d .clock = clock
42
41
}
43
42
}
44
43
45
- // NewDBCache creates a new DBCache. Close should be called to
44
+ // NewSigningCache creates a new DBCache. Close should be called to
46
45
// 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 {
49
64
db : db ,
50
65
feature : feature ,
51
66
clock : quartz .NewReal (),
@@ -56,23 +71,61 @@ func NewDBCache(logger slog.Logger, db database.Store, feature database.CryptoKe
56
71
opt (d )
57
72
}
58
73
74
+ // Initialize the timer. This will get properly initialized the first time we fetch.
59
75
d .timer = d .clock .AfterFunc (never , d .clear )
60
76
61
77
return d
62
78
}
63
79
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
65
113
// it is neither deleted nor has breached its deletion date. It should only be
66
114
// 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
+
68
121
d .keysMu .RLock ()
69
122
if d .closed {
70
123
d .keysMu .RUnlock ()
71
- return codersdk. CryptoKey {} , ErrClosed
124
+ return nil , ErrClosed
72
125
}
73
126
74
127
now := d .clock .Now ()
75
- key , ok := d .keys [sequence ]
128
+ key , ok := d .keys [int32 ( sequence ) ]
76
129
d .keysMu .RUnlock ()
77
130
if ok {
78
131
return checkKey (key , now )
@@ -82,67 +135,67 @@ func (d *DBCache) Verifying(ctx context.Context, sequence int32) (codersdk.Crypt
82
135
defer d .keysMu .Unlock ()
83
136
84
137
if d .closed {
85
- return codersdk. CryptoKey {} , ErrClosed
138
+ return nil , ErrClosed
86
139
}
87
140
88
- key , ok = d .keys [sequence ]
141
+ key , ok = d .keys [int32 ( sequence ) ]
89
142
if ok {
90
143
return checkKey (key , now )
91
144
}
92
145
93
- err : = d .fetch (ctx )
146
+ err = d .fetch (ctx )
94
147
if err != nil {
95
- return codersdk. CryptoKey {} , xerrors .Errorf ("fetch: %w" , err )
148
+ return nil , xerrors .Errorf ("fetch: %w" , err )
96
149
}
97
150
98
- key , ok = d .keys [sequence ]
151
+ key , ok = d .keys [int32 ( sequence ) ]
99
152
if ! ok {
100
- return codersdk. CryptoKey {} , ErrKeyNotFound
153
+ return nil , ErrKeyNotFound
101
154
}
102
155
103
156
return checkKey (key , now )
104
157
}
105
158
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
107
160
// 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 ) {
109
162
d .keysMu .RLock ()
110
163
111
164
if d .closed {
112
165
d .keysMu .RUnlock ()
113
- return codersdk. CryptoKey {} , ErrClosed
166
+ return "" , nil , ErrClosed
114
167
}
115
168
116
169
latest := d .latestKey
117
170
d .keysMu .RUnlock ()
118
171
119
172
now := d .clock .Now ()
120
173
if latest .CanSign (now ) {
121
- return db2sdk . CryptoKey (latest ), nil
174
+ return idSecret (latest )
122
175
}
123
176
124
177
d .keysMu .Lock ()
125
178
defer d .keysMu .Unlock ()
126
179
127
180
if d .closed {
128
- return codersdk. CryptoKey {} , ErrClosed
181
+ return "" , nil , ErrClosed
129
182
}
130
183
131
184
if d .latestKey .CanSign (now ) {
132
- return db2sdk . CryptoKey (d .latestKey ), nil
185
+ return idSecret (d .latestKey )
133
186
}
134
187
135
188
// Refetch all keys for this feature so we can find the latest valid key.
136
189
err := d .fetch (ctx )
137
190
if err != nil {
138
- return codersdk. CryptoKey {} , xerrors .Errorf ("fetch: %w" , err )
191
+ return "" , nil , xerrors .Errorf ("fetch: %w" , err )
139
192
}
140
193
141
- return db2sdk . CryptoKey (d .latestKey ), nil
194
+ return idSecret (d .latestKey )
142
195
}
143
196
144
197
// clear invalidates the cache. This forces the subsequent call to fetch fresh keys.
145
- func (d * DBCache ) clear () {
198
+ func (d * dbCache ) clear () {
146
199
now := d .clock .Now ("DBCache" , "clear" )
147
200
d .keysMu .Lock ()
148
201
defer d .keysMu .Unlock ()
@@ -158,7 +211,7 @@ func (d *DBCache) clear() {
158
211
159
212
// fetch fetches all keys for the given feature and determines the latest key.
160
213
// 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 {
162
215
keys , err := d .db .GetCryptoKeysByFeature (ctx , d .feature )
163
216
if err != nil {
164
217
return xerrors .Errorf ("get crypto keys by feature: %w" , err )
@@ -189,22 +242,45 @@ func (d *DBCache) fetch(ctx context.Context) error {
189
242
return nil
190
243
}
191
244
192
- func checkKey (key database.CryptoKey , now time.Time ) (codersdk. CryptoKey , error ) {
245
+ func checkKey (key database.CryptoKey , now time.Time ) (interface {} , error ) {
193
246
if ! key .CanVerify (now ) {
194
- return codersdk. CryptoKey {} , ErrKeyInvalid
247
+ return nil , ErrKeyInvalid
195
248
}
196
249
197
- return db2sdk . CryptoKey ( key ), nil
250
+ return key . DecodeString ()
198
251
}
199
252
200
- func (d * DBCache ) Close () {
253
+ func (d * dbCache ) Close () error {
201
254
d .keysMu .Lock ()
202
255
defer d .keysMu .Unlock ()
203
256
204
257
if d .closed {
205
- return
258
+ return nil
206
259
}
207
260
208
261
d .timer .Stop ()
209
262
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
210
286
}
0 commit comments