@@ -20,6 +20,9 @@ import (
20
20
"github.com/coder/coder/v2/pty/ptytest"
21
21
)
22
22
23
+ // TestServerDBCrypt tests end-to-end encryption, decryption, and deletion
24
+ // of encrypted user data.
25
+ //
23
26
// nolint: paralleltest // use of t.Setenv
24
27
func TestServerDBCrypt (t * testing.T ) {
25
28
if ! dbtestutil .WillUsePostgres () {
@@ -41,15 +44,38 @@ func TestServerDBCrypt(t *testing.T) {
41
44
})
42
45
db := database .New (sqlDB )
43
46
47
+ t .Cleanup (func () {
48
+ if t .Failed () {
49
+ t .Logf ("Dumping data due to failed test. I hope you find what you're looking for!" )
50
+ dumpUsers (t , sqlDB )
51
+ }
52
+ })
53
+
44
54
// Populate the database with some unencrypted data.
45
- users := genData (t , db , 10 )
55
+ t .Logf ("Generating unencrypted data" )
56
+ users := genData (t , db )
46
57
47
- // Setup an initial cipher
58
+ // Setup an initial cipher A
48
59
keyA := mustString (t , 32 )
49
60
cipherA , err := dbcrypt .NewCiphers ([]byte (keyA ))
50
61
require .NoError (t , err )
51
62
63
+ // Create an encrypted database
64
+ cryptdb , err := dbcrypt .New (ctx , db , cipherA ... )
65
+ require .NoError (t , err )
66
+
67
+ // Populate the database with some encrypted data using cipher A.
68
+ t .Logf ("Generating data encrypted with cipher A" )
69
+ newUsers := genData (t , cryptdb )
70
+
71
+ // Validate that newly created users were encrypted with cipher A
72
+ for _ , usr := range newUsers {
73
+ requireEncryptedWithCipher (ctx , t , db , cipherA [0 ], usr .ID )
74
+ }
75
+ users = append (users , newUsers ... )
76
+
52
77
// Encrypt all the data with the initial cipher.
78
+ t .Logf ("Encrypting all data with cipher A" )
53
79
inv , _ := newCLI (t , "server" , "dbcrypt" , "rotate" ,
54
80
"--postgres-url" , connectionURL ,
55
81
"--new-key" , base64 .StdEncoding .EncodeToString ([]byte (keyA )),
@@ -65,18 +91,12 @@ func TestServerDBCrypt(t *testing.T) {
65
91
requireEncryptedWithCipher (ctx , t , db , cipherA [0 ], usr .ID )
66
92
}
67
93
68
- // Create an encrypted database
69
- cryptdb , err := dbcrypt .New (ctx , db , cipherA ... )
70
- require .NoError (t , err )
71
-
72
- // Populate the database with some encrypted data using cipher A.
73
- users = append (users , genData (t , cryptdb , 10 )... )
74
-
75
94
// Re-encrypt all existing data with a new cipher.
76
95
keyB := mustString (t , 32 )
77
96
cipherBA , err := dbcrypt .NewCiphers ([]byte (keyB ), []byte (keyA ))
78
97
require .NoError (t , err )
79
98
99
+ t .Logf ("Enrypting all data with cipher B" )
80
100
inv , _ = newCLI (t , "server" , "dbcrypt" , "rotate" ,
81
101
"--postgres-url" , connectionURL ,
82
102
"--new-key" , base64 .StdEncoding .EncodeToString ([]byte (keyB )),
@@ -94,6 +114,7 @@ func TestServerDBCrypt(t *testing.T) {
94
114
}
95
115
96
116
// Assert that we can revoke the old key.
117
+ t .Logf ("Revoking cipher A" )
97
118
err = db .RevokeDBCryptKey (ctx , cipherA [0 ].HexDigest ())
98
119
require .NoError (t , err , "failed to revoke old key" )
99
120
@@ -109,13 +130,15 @@ func TestServerDBCrypt(t *testing.T) {
109
130
require .Empty (t , oldKey .ActiveKeyDigest .String , "expected the old key to not be active" )
110
131
111
132
// Revoking the new key should fail.
133
+ t .Logf ("Attempting to revoke cipher B should fail as it is still in use" )
112
134
err = db .RevokeDBCryptKey (ctx , cipherBA [0 ].HexDigest ())
113
135
require .Error (t , err , "expected to fail to revoke the new key" )
114
136
var pgErr * pq.Error
115
137
require .True (t , xerrors .As (err , & pgErr ), "expected a pg error" )
116
138
require .EqualValues (t , "23503" , pgErr .Code , "expected a foreign key constraint violation error" )
117
139
118
140
// Decrypt the data using only cipher B. This should result in the key being revoked.
141
+ t .Logf ("Decrypting with cipher B" )
119
142
inv , _ = newCLI (t , "server" , "dbcrypt" , "decrypt" ,
120
143
"--postgres-url" , connectionURL ,
121
144
"--keys" , base64 .StdEncoding .EncodeToString ([]byte (keyB )),
@@ -144,6 +167,7 @@ func TestServerDBCrypt(t *testing.T) {
144
167
cipherC , err := dbcrypt .NewCiphers ([]byte (keyC ))
145
168
require .NoError (t , err )
146
169
170
+ t .Logf ("Re-encrypting with cipher C" )
147
171
inv , _ = newCLI (t , "server" , "dbcrypt" , "rotate" ,
148
172
"--postgres-url" , connectionURL ,
149
173
"--new-key" , base64 .StdEncoding .EncodeToString ([]byte (keyC )),
@@ -161,6 +185,7 @@ func TestServerDBCrypt(t *testing.T) {
161
185
}
162
186
163
187
// Now delete all the encrypted data.
188
+ t .Logf ("Deleting all encrypted data" )
164
189
inv , _ = newCLI (t , "server" , "dbcrypt" , "delete" ,
165
190
"--postgres-url" , connectionURL ,
166
191
"--external-token-encryption-keys" , base64 .StdEncoding .EncodeToString ([]byte (keyC )),
@@ -191,30 +216,84 @@ func TestServerDBCrypt(t *testing.T) {
191
216
}
192
217
}
193
218
194
- func genData (t * testing.T , db database.Store , n int ) []database.User {
219
+ func genData (t * testing.T , db database.Store ) []database.User {
195
220
t .Helper ()
196
221
var users []database.User
197
- for i := 0 ; i < n ; i ++ {
198
- usr := dbgen .User (t , db , database.User {
199
- LoginType : database .LoginTypeOIDC ,
200
- })
201
- _ = dbgen .UserLink (t , db , database.UserLink {
202
- UserID : usr .ID ,
203
- LoginType : usr .LoginType ,
204
- OAuthAccessToken : "access-" + usr .ID .String (),
205
- OAuthRefreshToken : "refresh-" + usr .ID .String (),
206
- })
207
- _ = dbgen .GitAuthLink (t , db , database.GitAuthLink {
208
- UserID : usr .ID ,
209
- ProviderID : "fake" ,
210
- OAuthAccessToken : "access-" + usr .ID .String (),
211
- OAuthRefreshToken : "refresh-" + usr .ID .String (),
212
- })
213
- users = append (users , usr )
222
+ // Make some users
223
+ for _ , status := range database .AllUserStatusValues () {
224
+ for _ , loginType := range database .AllLoginTypeValues () {
225
+ for _ , deleted := range []bool {false , true } {
226
+ usr := dbgen .User (t , db , database.User {
227
+ LoginType : loginType ,
228
+ Status : status ,
229
+ Deleted : deleted ,
230
+ })
231
+ _ = dbgen .GitAuthLink (t , db , database.GitAuthLink {
232
+ UserID : usr .ID ,
233
+ ProviderID : "fake" ,
234
+ OAuthAccessToken : "access-" + usr .ID .String (),
235
+ OAuthRefreshToken : "refresh-" + usr .ID .String (),
236
+ })
237
+ // Fun fact: our schema allows _all_ login types to have
238
+ // a user_link. Even though I'm not sure how it could occur
239
+ // in practice, making sure to test all combinations here.
240
+ _ = dbgen .UserLink (t , db , database.UserLink {
241
+ UserID : usr .ID ,
242
+ LoginType : usr .LoginType ,
243
+ OAuthAccessToken : "access-" + usr .ID .String (),
244
+ OAuthRefreshToken : "refresh-" + usr .ID .String (),
245
+ })
246
+ users = append (users , usr )
247
+ }
248
+ }
214
249
}
215
250
return users
216
251
}
217
252
253
+ func dumpUsers (t * testing.T , db * sql.DB ) {
254
+ t .Helper ()
255
+ rows , err := db .QueryContext (context .Background (), `SELECT
256
+ u.id,
257
+ u.login_type,
258
+ u.status,
259
+ u.deleted,
260
+ ul.oauth_access_token_key_id AS uloatkid,
261
+ ul.oauth_refresh_token_key_id AS ulortkid,
262
+ gal.oauth_access_token_key_id AS galoatkid,
263
+ gal.oauth_refresh_token_key_id AS galortkid
264
+ FROM users u
265
+ LEFT OUTER JOIN user_links ul ON u.id = ul.user_id
266
+ LEFT OUTER JOIN git_auth_links gal ON u.id = gal.user_id
267
+ ORDER BY u.created_at ASC;` )
268
+ require .NoError (t , err )
269
+ defer rows .Close ()
270
+ for rows .Next () {
271
+ var (
272
+ id string
273
+ loginType string
274
+ status string
275
+ deleted bool
276
+ UlOatKid sql.NullString
277
+ UlOrtKid sql.NullString
278
+ GalOatKid sql.NullString
279
+ GalOrtKid sql.NullString
280
+ )
281
+ require .NoError (t , rows .Scan (
282
+ & id ,
283
+ & loginType ,
284
+ & status ,
285
+ & deleted ,
286
+ & UlOatKid ,
287
+ & UlOrtKid ,
288
+ & GalOatKid ,
289
+ & GalOrtKid ,
290
+ ))
291
+ t .Logf ("user: id:%s login_type:%-8s status:%-9s deleted:%-5t ul_kids{at:%-7s rt:%-7s} gal_kids{at:%-7s rt:%-7s}" ,
292
+ id , loginType , status , deleted , UlOatKid .String , UlOrtKid .String , GalOatKid .String , GalOrtKid .String ,
293
+ )
294
+ }
295
+ }
296
+
218
297
func mustString (t * testing.T , n int ) string {
219
298
t .Helper ()
220
299
s , err := cryptorand .String (n )
0 commit comments