Skip to content

Commit 5261442

Browse files
committed
add test for signedtoken
1 parent 886d87c commit 5261442

File tree

1 file changed

+158
-2
lines changed

1 file changed

+158
-2
lines changed

coderd/workspaceapps/apptest/apptest.go

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,67 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
409409
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
410410
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
411411
})
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+
})
412473
})
413474

414475
t.Run("WorkspaceApplicationAuth", func(t *testing.T) {
@@ -582,7 +643,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
582643
require.Equal(t, http.StatusOK, resp.StatusCode)
583644
})
584645

585-
t.Run("BadJWT", func(t *testing.T) {
646+
t.Run("BadJWE", func(t *testing.T) {
586647
t.Parallel()
587648

588649
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
@@ -626,7 +687,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
626687
gotLocation, err = resp.Location()
627688
require.NoError(t, err)
628689

629-
badToken := generateBadJWT(t, workspaceapps.EncryptedAPIKeyPayload{
690+
badToken := generateBadJWE(t, workspaceapps.EncryptedAPIKeyPayload{
630691
APIKey: currentKeyStr,
631692
})
632693

@@ -1142,6 +1203,68 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
11421203
assertWorkspaceLastUsedAtNotUpdated(t, appDetails)
11431204
})
11441205
})
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+
})
11451268
})
11461269

11471270
t.Run("PortSharing", func(t *testing.T) {
@@ -1855,6 +1978,30 @@ func assertWorkspaceLastUsedAtNotUpdated(t testing.TB, details *Details) {
18551978
require.Equal(t, before.LastUsedAt, after.LastUsedAt, "workspace LastUsedAt updated when it should not have been")
18561979
}
18571980

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+
18582005
// generateBadJWT generates a JWT with a random key. It's intended to emulate the old-style JWT's we generated.
18592006
func generateBadJWT(t *testing.T, claims interface{}) string {
18602007
t.Helper()
@@ -1875,3 +2022,12 @@ func generateBadJWT(t *testing.T, claims interface{}) string {
18752022
require.NoError(t, err)
18762023
return compact
18772024
}
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

Comments
 (0)