@@ -409,6 +409,67 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
409
409
require .Equal (t , http .StatusInternalServerError , resp .StatusCode )
410
410
assertWorkspaceLastUsedAtNotUpdated (t , appDetails )
411
411
})
412
+
413
+ t .Run ("BadJWT" , func (t * testing.T ) {
414
+ t .Parallel ()
415
+
416
+ appDetails := setupProxyTest (t , nil )
417
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitLong )
418
+ defer cancel ()
419
+
420
+ u := appDetails .PathAppURL (appDetails .Apps .Owner )
421
+ resp , err := requestWithRetries (ctx , t , appDetails .AppClient (t ), http .MethodGet , u .String (), nil )
422
+ require .NoError (t , err )
423
+ defer resp .Body .Close ()
424
+ body , err := io .ReadAll (resp .Body )
425
+ require .NoError (t , err )
426
+ require .Equal (t , proxyTestAppBody , string (body ))
427
+ require .Equal (t , http .StatusOK , resp .StatusCode )
428
+
429
+ appTokenCookie := findCookie (resp .Cookies (), codersdk .SignedAppTokenCookie )
430
+ require .NotNil (t , appTokenCookie , "no signed app token cookie in response" )
431
+ require .Equal (t , appTokenCookie .Path , u .Path , "incorrect path on app token cookie" )
432
+
433
+ object , err := jose .ParseSigned (appTokenCookie .Value )
434
+ require .NoError (t , err )
435
+ require .Len (t , object .Signatures , 1 )
436
+
437
+ // Parse the payload.
438
+ var tok workspaceapps.SignedToken
439
+ //nolint:gosec
440
+ err = json .Unmarshal (object .UnsafePayloadWithoutVerification (), & tok )
441
+ require .NoError (t , err )
442
+
443
+ appTokenClient := appDetails .AppClient (t )
444
+ apiKey := appTokenClient .SessionToken ()
445
+ appTokenClient .SetSessionToken ("" )
446
+ appTokenClient .HTTPClient .Jar , err = cookiejar .New (nil )
447
+ require .NoError (t , err )
448
+ // Sign the token with an old-style key.
449
+ appTokenCookie .Value = generateBadJWT (t , tok )
450
+ appTokenClient .HTTPClient .Jar .SetCookies (u ,
451
+ []* http.Cookie {
452
+ appTokenCookie ,
453
+ {
454
+ Name : codersdk .PathAppSessionTokenCookie ,
455
+ Value : apiKey ,
456
+ },
457
+ },
458
+ )
459
+
460
+ resp , err = requestWithRetries (ctx , t , appTokenClient , http .MethodGet , u .String (), nil )
461
+ require .NoError (t , err )
462
+ defer resp .Body .Close ()
463
+ body , err = io .ReadAll (resp .Body )
464
+ require .NoError (t , err )
465
+ require .Equal (t , proxyTestAppBody , string (body ))
466
+ require .Equal (t , http .StatusOK , resp .StatusCode )
467
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
468
+
469
+ // Since the old token is invalid, the signed app token cookie should have a new value.
470
+ newTokenCookie := findCookie (resp .Cookies (), codersdk .SignedAppTokenCookie )
471
+ require .NotEqual (t , appTokenCookie .Value , newTokenCookie .Value )
472
+ })
412
473
})
413
474
414
475
t .Run ("WorkspaceApplicationAuth" , func (t * testing.T ) {
@@ -582,7 +643,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
582
643
require .Equal (t , http .StatusOK , resp .StatusCode )
583
644
})
584
645
585
- t .Run ("BadJWT " , func (t * testing.T ) {
646
+ t .Run ("BadJWE " , func (t * testing.T ) {
586
647
t .Parallel ()
587
648
588
649
ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitLong )
@@ -626,7 +687,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
626
687
gotLocation , err = resp .Location ()
627
688
require .NoError (t , err )
628
689
629
- badToken := generateBadJWT (t , workspaceapps.EncryptedAPIKeyPayload {
690
+ badToken := generateBadJWE (t , workspaceapps.EncryptedAPIKeyPayload {
630
691
APIKey : currentKeyStr ,
631
692
})
632
693
@@ -1142,6 +1203,68 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
1142
1203
assertWorkspaceLastUsedAtNotUpdated (t , appDetails )
1143
1204
})
1144
1205
})
1206
+
1207
+ t .Run ("BadJWT" , func (t * testing.T ) {
1208
+ t .Parallel ()
1209
+
1210
+ appDetails := setupProxyTest (t , nil )
1211
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitLong )
1212
+ defer cancel ()
1213
+
1214
+ u := appDetails .SubdomainAppURL (appDetails .Apps .Owner )
1215
+ resp , err := requestWithRetries (ctx , t , appDetails .AppClient (t ), http .MethodGet , u .String (), nil )
1216
+ require .NoError (t , err )
1217
+ defer resp .Body .Close ()
1218
+ body , err := io .ReadAll (resp .Body )
1219
+ require .NoError (t , err )
1220
+ require .Equal (t , proxyTestAppBody , string (body ))
1221
+ require .Equal (t , http .StatusOK , resp .StatusCode )
1222
+
1223
+ appTokenCookie := findCookie (resp .Cookies (), codersdk .SignedAppTokenCookie )
1224
+ require .NotNil (t , appTokenCookie , "no signed token cookie in response" )
1225
+ require .Equal (t , appTokenCookie .Path , "/" , "incorrect path on signed token cookie" )
1226
+
1227
+ object , err := jose .ParseSigned (appTokenCookie .Value )
1228
+ require .NoError (t , err )
1229
+ require .Len (t , object .Signatures , 1 )
1230
+
1231
+ // Parse the payload.
1232
+ var tok workspaceapps.SignedToken
1233
+ //nolint:gosec
1234
+ err = json .Unmarshal (object .UnsafePayloadWithoutVerification (), & tok )
1235
+ require .NoError (t , err )
1236
+
1237
+ appTokenClient := appDetails .AppClient (t )
1238
+ apiKey := appTokenClient .SessionToken ()
1239
+ appTokenClient .SetSessionToken ("" )
1240
+ appTokenClient .HTTPClient .Jar , err = cookiejar .New (nil )
1241
+ require .NoError (t , err )
1242
+ // Sign the token with an old-style key.
1243
+ appTokenCookie .Value = generateBadJWT (t , tok )
1244
+ appTokenClient .HTTPClient .Jar .SetCookies (u ,
1245
+ []* http.Cookie {
1246
+ appTokenCookie ,
1247
+ {
1248
+ Name : codersdk .SubdomainAppSessionTokenCookie ,
1249
+ Value : apiKey ,
1250
+ },
1251
+ },
1252
+ )
1253
+
1254
+ // We should still be able to successfully proxy.
1255
+ resp , err = requestWithRetries (ctx , t , appTokenClient , http .MethodGet , u .String (), nil )
1256
+ require .NoError (t , err )
1257
+ defer resp .Body .Close ()
1258
+ body , err = io .ReadAll (resp .Body )
1259
+ require .NoError (t , err )
1260
+ require .Equal (t , proxyTestAppBody , string (body ))
1261
+ require .Equal (t , http .StatusOK , resp .StatusCode )
1262
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
1263
+
1264
+ // Since the old token is invalid, the signed app token cookie should have a new value.
1265
+ newTokenCookie := findCookie (resp .Cookies (), codersdk .SignedAppTokenCookie )
1266
+ require .NotEqual (t , appTokenCookie .Value , newTokenCookie .Value )
1267
+ })
1145
1268
})
1146
1269
1147
1270
t .Run ("PortSharing" , func (t * testing.T ) {
@@ -1855,6 +1978,30 @@ func assertWorkspaceLastUsedAtNotUpdated(t testing.TB, details *Details) {
1855
1978
require .Equal (t , before .LastUsedAt , after .LastUsedAt , "workspace LastUsedAt updated when it should not have been" )
1856
1979
}
1857
1980
1981
+ func generateBadJWE (t * testing.T , claims interface {}) string {
1982
+ t .Helper ()
1983
+ var buf [32 ]byte
1984
+ _ , err := rand .Read (buf [:])
1985
+ require .NoError (t , err )
1986
+ encrypt , err := jose .NewEncrypter (
1987
+ jose .A256GCM ,
1988
+ jose.Recipient {
1989
+ Algorithm : jose .A256GCMKW ,
1990
+ Key : buf [:],
1991
+ }, & jose.EncrypterOptions {
1992
+ Compression : jose .DEFLATE ,
1993
+ },
1994
+ )
1995
+ require .NoError (t , err )
1996
+ payload , err := json .Marshal (claims )
1997
+ require .NoError (t , err )
1998
+ signed , err := encrypt .Encrypt (payload )
1999
+ require .NoError (t , err )
2000
+ compact , err := signed .CompactSerialize ()
2001
+ require .NoError (t , err )
2002
+ return compact
2003
+ }
2004
+
1858
2005
// generateBadJWT generates a JWT with a random key. It's intended to emulate the old-style JWT's we generated.
1859
2006
func generateBadJWT (t * testing.T , claims interface {}) string {
1860
2007
t .Helper ()
@@ -1875,3 +2022,12 @@ func generateBadJWT(t *testing.T, claims interface{}) string {
1875
2022
require .NoError (t , err )
1876
2023
return compact
1877
2024
}
2025
+
2026
+ func findCookie (cookies []* http.Cookie , name string ) * http.Cookie {
2027
+ for _ , cookie := range cookies {
2028
+ if cookie .Name == name {
2029
+ return cookie
2030
+ }
2031
+ }
2032
+ return nil
2033
+ }
0 commit comments