From 52c757531a2602765bd0d5c19383322e29095a1d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 31 Aug 2022 17:09:45 -0400 Subject: [PATCH 01/26] feat: Implement CSRF in cli client and FE api --- coderd/coderd.go | 1 + coderd/httpmw/apikey.go | 19 ++++++- coderd/httpmw/apikey_test.go | 60 +++++---------------- coderd/httpmw/authorize_test.go | 5 +- coderd/httpmw/csrf.go | 63 ++++++++++++++++++++++ coderd/httpmw/organizationparam_test.go | 5 +- coderd/httpmw/templateparam_test.go | 5 +- coderd/httpmw/templateversionparam_test.go | 5 +- coderd/httpmw/userparam_test.go | 5 +- coderd/httpmw/workspaceagent_test.go | 5 +- coderd/httpmw/workspaceagentparam_test.go | 5 +- coderd/httpmw/workspacebuildparam_test.go | 5 +- coderd/httpmw/workspaceparam_test.go | 10 +--- codersdk/client.go | 13 +++-- site/src/api/api.ts | 9 ++++ 15 files changed, 119 insertions(+), 96 deletions(-) create mode 100644 coderd/httpmw/csrf.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 58a4386c90005..052584917a926 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -130,6 +130,7 @@ func New(options *Options) *API { httpmw.Recover(api.Logger), httpmw.Logger(api.Logger), httpmw.Prometheus(options.PrometheusRegistry), + httpmw.CSRF(options.SecureAuthCookie), ) apps := func(r chi.Router) { diff --git a/coderd/httpmw/apikey.go b/coderd/httpmw/apikey.go index ed2a1e617f567..70d4ceeec1971 100644 --- a/coderd/httpmw/apikey.go +++ b/coderd/httpmw/apikey.go @@ -82,12 +82,29 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool } var cookieValue string + // Find the session token from: + // 1: The cookie + // 2: The old cookie + // 3. The coder_session_token query parameter + // 4. The custom auth header cookie, err := r.Cookie(codersdk.SessionTokenKey) if err != nil { - cookieValue = r.URL.Query().Get(codersdk.SessionTokenKey) + // TODO: @emyrk in October 2022, remove this oldCookie check. + // This is just to support the old cli for 1 release. Then everyone + // must update. + oldCookie, err := r.Cookie("session_token") + if err != nil { + cookieValue = r.URL.Query().Get(codersdk.SessionTokenKey) + if cookieValue == "" { + cookieValue = r.Header.Get(codersdk.SessionCustomHeader) + } + } else { + cookieValue = oldCookie.Value + } } else { cookieValue = cookie.Value } + if cookieValue == "" { write(http.StatusUnauthorized, codersdk.Response{ Message: signedOutErrorMessage, diff --git a/coderd/httpmw/apikey_test.go b/coderd/httpmw/apikey_test.go index adc13b2f176fa..fc7bf92af781b 100644 --- a/coderd/httpmw/apikey_test.go +++ b/coderd/httpmw/apikey_test.go @@ -74,10 +74,7 @@ func TestAPIKey(t *testing.T) { r = httptest.NewRequest("GET", "/", nil) rw = httptest.NewRecorder() ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: "test-wow-hello", - }) + r.Header.Set(codersdk.SessionCustomHeader, "test-wow-hello") httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r) res := rw.Result() @@ -92,10 +89,7 @@ func TestAPIKey(t *testing.T) { r = httptest.NewRequest("GET", "/", nil) rw = httptest.NewRecorder() ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: "test-wow", - }) + r.Header.Set(codersdk.SessionCustomHeader, "test-wow") httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r) res := rw.Result() @@ -110,10 +104,7 @@ func TestAPIKey(t *testing.T) { r = httptest.NewRequest("GET", "/", nil) rw = httptest.NewRecorder() ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: "testtestid-wow", - }) + r.Header.Set(codersdk.SessionCustomHeader, "testtestid-wow") httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r) res := rw.Result() @@ -129,10 +120,7 @@ func TestAPIKey(t *testing.T) { r = httptest.NewRequest("GET", "/", nil) rw = httptest.NewRecorder() ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r) res := rw.Result() @@ -149,10 +137,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) // Use a different secret so they don't match! hashed := sha256.Sum256([]byte("differentsecret")) @@ -178,10 +163,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) _, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, @@ -206,10 +188,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) sentAPIKey, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, @@ -280,10 +259,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) sentAPIKey, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, @@ -316,10 +292,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) sentAPIKey, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, @@ -352,10 +325,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) sentAPIKey, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, @@ -395,10 +365,7 @@ func TestAPIKey(t *testing.T) { rw = httptest.NewRecorder() user = createUser(r.Context(), t, db) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) sentAPIKey, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, @@ -449,10 +416,7 @@ func TestAPIKey(t *testing.T) { user = createUser(r.Context(), t, db) ) r.RemoteAddr = "1.1.1.1:3555" - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) _, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, diff --git a/coderd/httpmw/authorize_test.go b/coderd/httpmw/authorize_test.go index dcae5bff96526..32e2742ccd47f 100644 --- a/coderd/httpmw/authorize_test.go +++ b/coderd/httpmw/authorize_test.go @@ -93,10 +93,7 @@ func TestExtractUserRoles(t *testing.T) { }) req := httptest.NewRequest("GET", "/", nil) - req.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: token, - }) + req.Header.Set(codersdk.SessionCustomHeader, token) rtr.ServeHTTP(rw, req) resp := rw.Result() diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go new file mode 100644 index 0000000000000..e5f27bdae729e --- /dev/null +++ b/coderd/httpmw/csrf.go @@ -0,0 +1,63 @@ +package httpmw + +import ( + "net/http" + "regexp" + + "github.com/justinas/nosurf" + "golang.org/x/xerrors" + + "github.com/coder/coder/codersdk" +) + +// CSRF is a middleware that verifies that a CSRF token is present in the request +// for non-GET requests. +func CSRF(secureCookie bool) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + mw := nosurf.New(next) + mw.SetBaseCookie(http.Cookie{Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: secureCookie}) + mw.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Something is wrong with your CSRF token. Please refresh the page. If this error persists, try clearing your cookies.", http.StatusBadRequest) + })) + + // Exempt all requests that do not require CSRF protection. + // All GET requests are exempt by default. + mw.ExemptPath("/api/v2/csp/reports") + + // Top level agent routes. + mw.ExemptRegexp(regexp.MustCompile("api/v2/workspaceagents/[^/]*$")) + // Agent authenticated routes + mw.ExemptRegexp(regexp.MustCompile("api/v2/workspaceagents/me/*")) + + mw.ExemptFunc(func(r *http.Request) bool { + // CSRF only affects requests that automatically attach credentials via a cookie. + // If no cookie is present, then there is no risk of CSRF. + sessCookie, err := r.Cookie(codersdk.SessionTokenKey) + if xerrors.Is(err, http.ErrNoCookie) { + return true + } + + if token := r.Header.Get(codersdk.SessionCustomHeader); token == sessCookie.Value { + // If the cookie and header match, we can assume this is the same as just using the + // custom header auth. Custom header auth can bypass CSRF, as CSRF attacks + // cannot add custom headers. + return true + } + + if token := r.URL.Query().Get(codersdk.SessionTokenKey); token == sessCookie.Value { + // If the auth is set in a url param and matches the cookie, it + // is the same as just using the url param. + return true + } + + // If the X-CSRF-TOKEN header is set, we can exempt the func if it's valid. + // This is the CSRF check. + sent := r.Header.Get("X-CSRF-TOKEN") + if sent != "" { + return nosurf.VerifyToken(nosurf.Token(r), sent) + } + return false + }) + return mw + } +} diff --git a/coderd/httpmw/organizationparam_test.go b/coderd/httpmw/organizationparam_test.go index bdf442391091c..0da000802879f 100644 --- a/coderd/httpmw/organizationparam_test.go +++ b/coderd/httpmw/organizationparam_test.go @@ -29,10 +29,7 @@ func TestOrganizationParam(t *testing.T) { r = httptest.NewRequest("GET", "/", nil) hashed = sha256.Sum256([]byte(secret)) ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) diff --git a/coderd/httpmw/templateparam_test.go b/coderd/httpmw/templateparam_test.go index 7da35889aa536..1e936b403ee5a 100644 --- a/coderd/httpmw/templateparam_test.go +++ b/coderd/httpmw/templateparam_test.go @@ -29,10 +29,7 @@ func TestTemplateParam(t *testing.T) { hashed = sha256.Sum256([]byte(secret)) ) r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) diff --git a/coderd/httpmw/templateversionparam_test.go b/coderd/httpmw/templateversionparam_test.go index fe4ebba9dfcc3..6c28cb743f6ff 100644 --- a/coderd/httpmw/templateversionparam_test.go +++ b/coderd/httpmw/templateversionparam_test.go @@ -29,10 +29,7 @@ func TestTemplateVersionParam(t *testing.T) { hashed = sha256.Sum256([]byte(secret)) ) r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) diff --git a/coderd/httpmw/userparam_test.go b/coderd/httpmw/userparam_test.go index d21f2a583d58f..0b5e6013c38d2 100644 --- a/coderd/httpmw/userparam_test.go +++ b/coderd/httpmw/userparam_test.go @@ -29,10 +29,7 @@ func TestUserParam(t *testing.T) { r = httptest.NewRequest("GET", "/", nil) rw = httptest.NewRecorder() ) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) user, err := db.InsertUser(r.Context(), database.InsertUserParams{ ID: uuid.New(), diff --git a/coderd/httpmw/workspaceagent_test.go b/coderd/httpmw/workspaceagent_test.go index eef7fc80847f1..a33bc51bdce9e 100644 --- a/coderd/httpmw/workspaceagent_test.go +++ b/coderd/httpmw/workspaceagent_test.go @@ -22,10 +22,7 @@ func TestWorkspaceAgent(t *testing.T) { setup := func(db database.Store) (*http.Request, uuid.UUID) { token := uuid.New() r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: token.String(), - }) + r.Header.Set(codersdk.SessionCustomHeader, token.String()) return r, token } diff --git a/coderd/httpmw/workspaceagentparam_test.go b/coderd/httpmw/workspaceagentparam_test.go index c2d047fdda983..435308bd3d4c9 100644 --- a/coderd/httpmw/workspaceagentparam_test.go +++ b/coderd/httpmw/workspaceagentparam_test.go @@ -29,10 +29,7 @@ func TestWorkspaceAgentParam(t *testing.T) { hashed = sha256.Sum256([]byte(secret)) ) r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) diff --git a/coderd/httpmw/workspacebuildparam_test.go b/coderd/httpmw/workspacebuildparam_test.go index c993a0cb66fb6..271229067ecb7 100644 --- a/coderd/httpmw/workspacebuildparam_test.go +++ b/coderd/httpmw/workspacebuildparam_test.go @@ -29,10 +29,7 @@ func TestWorkspaceBuildParam(t *testing.T) { hashed = sha256.Sum256([]byte(secret)) ) r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) diff --git a/coderd/httpmw/workspaceparam_test.go b/coderd/httpmw/workspaceparam_test.go index 5b3f9a5830bc7..7dfaa55724996 100644 --- a/coderd/httpmw/workspaceparam_test.go +++ b/coderd/httpmw/workspaceparam_test.go @@ -32,10 +32,7 @@ func TestWorkspaceParam(t *testing.T) { hashed = sha256.Sum256([]byte(secret)) ) r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) @@ -340,10 +337,7 @@ func setupWorkspaceWithAgents(t testing.TB, cfg setupConfig) (database.Store, *h hashed = sha256.Sum256([]byte(secret)) ) r := httptest.NewRequest("GET", "/", nil) - r.AddCookie(&http.Cookie{ - Name: codersdk.SessionTokenKey, - Value: fmt.Sprintf("%s-%s", id, secret), - }) + r.Header.Set(codersdk.SessionCustomHeader, fmt.Sprintf("%s-%s", id, secret)) userID := uuid.New() username, err := cryptorand.String(8) diff --git a/codersdk/client.go b/codersdk/client.go index bbeaa990da81b..b464c6c0f3f7e 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -20,9 +20,11 @@ import ( // Be sure to strip additional cookies in httpapi.StripCoder Cookies! const ( // SessionTokenKey represents the name of the cookie or query parameter the API key is stored in. - SessionTokenKey = "session_token" - OAuth2StateKey = "oauth_state" - OAuth2RedirectKey = "oauth_redirect" + SessionTokenKey = "coder_session_token" + // SessionCustomHeader is the custom header to use for authentication. + SessionCustomHeader = "Coder-Session-Token" + OAuth2StateKey = "oauth_state" + OAuth2RedirectKey = "oauth_redirect" ) // New creates a Coder client for the provided URL. @@ -70,10 +72,7 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac if err != nil { return nil, xerrors.Errorf("create request: %w", err) } - req.AddCookie(&http.Cookie{ - Name: SessionTokenKey, - Value: c.SessionToken, - }) + req.Header.Set(SessionCustomHeader, c.SessionToken) if body != nil { req.Header.Set("Content-Type", "application/json") } diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 20a5156e9d3f9..415a805fa3ea9 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -4,6 +4,15 @@ import * as Types from "./types" import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" + +// Always attach CSRF token to all requests +let token = document.head.querySelector('meta[property="csrf-token"]'); +if (token) { + axios.defaults.headers.common['X-CSRF-TOKEN'] = token.getAttribute("content")!; +} else { + console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); +} + const CONTENT_TYPE_JSON: AxiosRequestHeaders = { "Content-Type": "application/json", } From 642a29f0b154e66dab4c926bb8881cb5b31cba26 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 31 Aug 2022 17:17:38 -0400 Subject: [PATCH 02/26] Make fmt --- site/src/api/api.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 415a805fa3ea9..4313f19fb23ab 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -4,13 +4,12 @@ import * as Types from "./types" import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" - // Always attach CSRF token to all requests -let token = document.head.querySelector('meta[property="csrf-token"]'); +let token = document.head.querySelector('meta[property="csrf-token"]') if (token) { - axios.defaults.headers.common['X-CSRF-TOKEN'] = token.getAttribute("content")!; + axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content")! } else { - console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); + console.error("CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token") } const CONTENT_TYPE_JSON: AxiosRequestHeaders = { From 37c0ba8ec77ec8b63ad282b724b19184271b9557 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 31 Aug 2022 17:22:57 -0400 Subject: [PATCH 03/26] const vs let --- site/src/api/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 4313f19fb23ab..9ab44d7f0faee 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -5,7 +5,7 @@ import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" // Always attach CSRF token to all requests -let token = document.head.querySelector('meta[property="csrf-token"]') +const token = document.head.querySelector('meta[property="csrf-token"]') if (token) { axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content")! } else { From c774fcc60fcd1bc10a7d0903189f6bfb9444720b Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 31 Aug 2022 22:08:33 +0000 Subject: [PATCH 04/26] Fix lint error --- site/src/api/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 9ab44d7f0faee..b786627bfd9ad 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -7,7 +7,7 @@ import * as TypesGen from "./typesGenerated" // Always attach CSRF token to all requests const token = document.head.querySelector('meta[property="csrf-token"]') if (token) { - axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content")! + axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content") ?? "" } else { console.error("CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token") } From 3c7596726316a82ce9681783c917f06cdd4ced58 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 31 Aug 2022 20:32:09 -0400 Subject: [PATCH 05/26] remove bad console log --- site/src/api/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index b786627bfd9ad..a756f1b4bc178 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -9,7 +9,7 @@ const token = document.head.querySelector('meta[property="csrf-token"]') if (token) { axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content") ?? "" } else { - console.error("CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token") + console.error("CSRF token not found") } const CONTENT_TYPE_JSON: AxiosRequestHeaders = { From ab48b4a2147b154676f97dd774db03e7082e8aae Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 09:08:17 -0400 Subject: [PATCH 06/26] Add CSRF token in header --- site/webpack.dev.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/webpack.dev.ts b/site/webpack.dev.ts index cb43015edd7df..c58b4a3e43bcd 100644 --- a/site/webpack.dev.ts +++ b/site/webpack.dev.ts @@ -51,6 +51,7 @@ const config: Configuration = { }, devMiddleware: { publicPath: "/", + headers: {"Set-Cookie":"csrf_token=JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=; Path=/; HttpOnly; SameSite=Lax"} }, headers: { "Access-Control-Allow-Origin": "*", From 51d856e1dda550b80ab1f8057312c896477738ff Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 1 Sep 2022 13:43:33 +0000 Subject: [PATCH 07/26] Log error if token content is null --- site/src/api/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index b786627bfd9ad..bfa5b0735cc4e 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -6,7 +6,7 @@ import * as TypesGen from "./typesGenerated" // Always attach CSRF token to all requests const token = document.head.querySelector('meta[property="csrf-token"]') -if (token) { +if (token !== null && token.getAttribute("content") !== null) { axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content") ?? "" } else { console.error("CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token") From b03610b691ea099b542034baaca5927b375360f6 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 10:15:07 -0400 Subject: [PATCH 08/26] Fix dev server csrf with hardcoded value --- site/src/api/api.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 6ac04fae68269..57c571b374af3 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -4,10 +4,27 @@ import * as Types from "./types" import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" +export const hardCodedCSRFCookie = (): string => { + // This is a hard coded CSRF token/cookie pair for local development. + // In prod, the GoLang webserver generates a random cookie with a new token for + // each document request. For local development, we don't use the Go webserver for static files, + // so this is the 'hack' to make local development work with remote apis. + // The CSRF cookie for this token is "JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=" + const csrfToken = "KNKvagCBEHZK7ihe2t7fj6VeJ0UyTDco1yVUJE8N06oNqxLu5Zx1vRxZbgfC0mJJgeGkVjgs08mgPbcWPBkZ1A==" + axios.defaults.headers.common["X-CSRF-TOKEN"] = csrfToken + return csrfToken +} + // Always attach CSRF token to all requests const token = document.head.querySelector('meta[property="csrf-token"]') if (token !== null && token.getAttribute("content") !== null) { - axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content") ?? "" + if(process.env.NODE_ENV === "development") { + // Development mode uses a hard-coded CSRF token + axios.defaults.headers.common["X-CSRF-TOKEN"] = hardCodedCSRFCookie() + token.setAttribute("content", hardCodedCSRFCookie()) + } else { + axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content") ?? "" + } } else { console.error("CSRF token not found") } From e798e114a02f0ee8746ade8e7349cc19f5244d52 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 10:21:29 -0400 Subject: [PATCH 09/26] Do not error log in JS unit test --- site/src/api/api.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 57c571b374af3..61743fe074e04 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -26,7 +26,10 @@ if (token !== null && token.getAttribute("content") !== null) { axios.defaults.headers.common["X-CSRF-TOKEN"] = token.getAttribute("content") ?? "" } } else { - console.error("CSRF token not found") + // Do not write error logs if we are in a FE unit test. + if(process.env.JEST_WORKER_ID === undefined) { + console.error("CSRF token not found") + } } const CONTENT_TYPE_JSON: AxiosRequestHeaders = { From 1c4810ad65935b02bccf23a358d0a4c2596c8c03 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 10:22:37 -0400 Subject: [PATCH 10/26] Make fmt on js files --- site/src/api/api.ts | 7 ++++--- site/webpack.dev.ts | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 61743fe074e04..de2369d742be9 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -10,7 +10,8 @@ export const hardCodedCSRFCookie = (): string => { // each document request. For local development, we don't use the Go webserver for static files, // so this is the 'hack' to make local development work with remote apis. // The CSRF cookie for this token is "JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=" - const csrfToken = "KNKvagCBEHZK7ihe2t7fj6VeJ0UyTDco1yVUJE8N06oNqxLu5Zx1vRxZbgfC0mJJgeGkVjgs08mgPbcWPBkZ1A==" + const csrfToken = + "KNKvagCBEHZK7ihe2t7fj6VeJ0UyTDco1yVUJE8N06oNqxLu5Zx1vRxZbgfC0mJJgeGkVjgs08mgPbcWPBkZ1A==" axios.defaults.headers.common["X-CSRF-TOKEN"] = csrfToken return csrfToken } @@ -18,7 +19,7 @@ export const hardCodedCSRFCookie = (): string => { // Always attach CSRF token to all requests const token = document.head.querySelector('meta[property="csrf-token"]') if (token !== null && token.getAttribute("content") !== null) { - if(process.env.NODE_ENV === "development") { + if (process.env.NODE_ENV === "development") { // Development mode uses a hard-coded CSRF token axios.defaults.headers.common["X-CSRF-TOKEN"] = hardCodedCSRFCookie() token.setAttribute("content", hardCodedCSRFCookie()) @@ -27,7 +28,7 @@ if (token !== null && token.getAttribute("content") !== null) { } } else { // Do not write error logs if we are in a FE unit test. - if(process.env.JEST_WORKER_ID === undefined) { + if (process.env.JEST_WORKER_ID === undefined) { console.error("CSRF token not found") } } diff --git a/site/webpack.dev.ts b/site/webpack.dev.ts index c58b4a3e43bcd..09f03fef28dab 100644 --- a/site/webpack.dev.ts +++ b/site/webpack.dev.ts @@ -51,7 +51,10 @@ const config: Configuration = { }, devMiddleware: { publicPath: "/", - headers: {"Set-Cookie":"csrf_token=JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=; Path=/; HttpOnly; SameSite=Lax"} + headers: { + "Set-Cookie": + "csrf_token=JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=; Path=/; HttpOnly; SameSite=Lax", + }, }, headers: { "Access-Control-Allow-Origin": "*", From a6fdac86fbe3818c3305ccc4bf61efd87df46830 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 10:41:58 -0400 Subject: [PATCH 11/26] Fix agent token checking --- coderd/httpapi/cookie_test.go | 6 ++-- coderd/httpmw/apikey.go | 58 +++++++++++++++++++-------------- coderd/httpmw/workspaceagent.go | 6 ++-- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/coderd/httpapi/cookie_test.go b/coderd/httpapi/cookie_test.go index 546f1d2a7b30c..48c66abc439f0 100644 --- a/coderd/httpapi/cookie_test.go +++ b/coderd/httpapi/cookie_test.go @@ -17,13 +17,13 @@ func TestStripCoderCookies(t *testing.T) { "testing=hello; wow=test", "testing=hello; wow=test", }, { - "session_token=moo; wow=test", + "coder_session_token=moo; wow=test", "wow=test", }, { - "another_token=wow; session_token=ok", + "another_token=wow; coder_session_token=ok", "another_token=wow", }, { - "session_token=ok; oauth_state=wow; oauth_redirect=/", + "coder_session_token=ok; oauth_state=wow; oauth_redirect=/", "", }} { tc := tc diff --git a/coderd/httpmw/apikey.go b/coderd/httpmw/apikey.go index 70d4ceeec1971..6927c56a8068c 100644 --- a/coderd/httpmw/apikey.go +++ b/coderd/httpmw/apikey.go @@ -81,30 +81,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool httpapi.Write(rw, code, response) } - var cookieValue string - // Find the session token from: - // 1: The cookie - // 2: The old cookie - // 3. The coder_session_token query parameter - // 4. The custom auth header - cookie, err := r.Cookie(codersdk.SessionTokenKey) - if err != nil { - // TODO: @emyrk in October 2022, remove this oldCookie check. - // This is just to support the old cli for 1 release. Then everyone - // must update. - oldCookie, err := r.Cookie("session_token") - if err != nil { - cookieValue = r.URL.Query().Get(codersdk.SessionTokenKey) - if cookieValue == "" { - cookieValue = r.Header.Get(codersdk.SessionCustomHeader) - } - } else { - cookieValue = oldCookie.Value - } - } else { - cookieValue = cookie.Value - } - + cookieValue := apiTokenFromRequest(r) if cookieValue == "" { write(http.StatusUnauthorized, codersdk.Response{ Message: signedOutErrorMessage, @@ -310,3 +287,36 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool }) } } + +// apiTokenFromRequest returns the api token from the request. +// Find the session token from: +// 1: The cookie +// 2: The old cookie +// 3. The coder_session_token query parameter +// 4. The custom auth header +func apiTokenFromRequest(r *http.Request) string { + cookie, err := r.Cookie(codersdk.SessionTokenKey) + if err == nil && cookie.Value != "" { + return cookie.Value + } + + // TODO: @emyrk in October 2022, remove this oldCookie check. + // This is just to support the old cli for 1 release. Then everyone + // must update. + oldCookie, err := r.Cookie("session_token") + if err == nil && oldCookie.Value != "" { + return oldCookie.Value + } + + urlValue := r.URL.Query().Get(codersdk.SessionTokenKey) + if urlValue != "" { + return urlValue + } + + headerValue := r.Header.Get(codersdk.SessionCustomHeader) + if headerValue != "" { + return headerValue + } + + return "" +} diff --git a/coderd/httpmw/workspaceagent.go b/coderd/httpmw/workspaceagent.go index 70331d80a540f..26a7eae41875c 100644 --- a/coderd/httpmw/workspaceagent.go +++ b/coderd/httpmw/workspaceagent.go @@ -29,14 +29,14 @@ func WorkspaceAgent(r *http.Request) database.WorkspaceAgent { func ExtractWorkspaceAgent(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(codersdk.SessionTokenKey) - if err != nil { + cookieValue := apiTokenFromRequest(r) + if cookieValue == "" { httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.SessionTokenKey), }) return } - token, err := uuid.Parse(cookie.Value) + token, err := uuid.Parse(cookieValue) if err != nil { httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ Message: "Agent token is invalid.", From a343da91b762d38dc388223685d3778d44a1335a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 10:51:15 -0400 Subject: [PATCH 12/26] Fix unit test --- coderd/userauth_test.go | 14 ++++++++++++-- coderd/users_test.go | 10 +++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 59f8c1d5e0c79..342d581a504d7 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "strings" "testing" "time" @@ -244,7 +245,7 @@ func TestUserOAuth2Github(t *testing.T) { resp := oauth2Callback(t, client) require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode) - client.SessionToken = resp.Cookies()[0].Value + client.SessionToken = authCookieValue(resp.Cookies()) user, err := client.User(context.Background(), "me") require.NoError(t, err) require.Equal(t, "kyle@coder.com", user.Email) @@ -374,7 +375,7 @@ func TestUserOIDC(t *testing.T) { defer cancel() if tc.Username != "" { - client.SessionToken = resp.Cookies()[0].Value + client.SessionToken = authCookieValue(resp.Cookies()) user, err := client.User(ctx, "me") require.NoError(t, err) require.Equal(t, tc.Username, user.Username) @@ -503,3 +504,12 @@ func oidcCallback(t *testing.T, client *codersdk.Client) *http.Response { func i64ptr(i int64) *int64 { return &i } + +func authCookieValue(cookies []*http.Cookie) string { + for _, cookie := range cookies { + if strings.Contains(cookie.Name, "token") { + return cookie.Value + } + } + return "" +} diff --git a/coderd/users_test.go b/coderd/users_test.go index 6836c257290d1..3622886b84191 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -281,10 +281,14 @@ func TestPostLogout(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode) cookies := res.Cookies() - require.Len(t, cookies, 1, "Exactly one cookie should be returned") + require.Len(t, cookies, 2, "Exactly one cookie should be returned") - require.Equal(t, codersdk.SessionTokenKey, cookies[0].Name, "Cookie should be the auth cookie") - require.Equal(t, -1, cookies[0].MaxAge, "Cookie should be set to delete") + for _, cookie := range cookies { + if cookie.Name == codersdk.SessionTokenKey { + require.Equal(t, codersdk.SessionTokenKey, cookies[0].Name, "Cookie should be the auth cookie") + require.Equal(t, -1, cookies[0].MaxAge, "Cookie should be set to delete") + } + } _, err = client.GetAPIKey(ctx, admin.UserID.String(), keyID) sdkErr := &codersdk.Error{} From dd80cc9dbff7fdbafcc53314ef2d96b9141d1ad1 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 10:54:31 -0400 Subject: [PATCH 13/26] Check auth cookie exists --- coderd/users_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coderd/users_test.go b/coderd/users_test.go index 3622886b84191..b0b4d0156e028 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -283,12 +283,15 @@ func TestPostLogout(t *testing.T) { cookies := res.Cookies() require.Len(t, cookies, 2, "Exactly one cookie should be returned") + var found bool for _, cookie := range cookies { if cookie.Name == codersdk.SessionTokenKey { require.Equal(t, codersdk.SessionTokenKey, cookies[0].Name, "Cookie should be the auth cookie") require.Equal(t, -1, cookies[0].MaxAge, "Cookie should be set to delete") + found = true } } + require.True(t, found, "auth cookie should be returned") _, err = client.GetAPIKey(ctx, admin.UserID.String(), keyID) sdkErr := &codersdk.Error{} From 08e76d4c0e49e9bbe57e06332f548355143551a2 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 11:44:26 -0400 Subject: [PATCH 14/26] Fix test auth --- coderd/userauth_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 342d581a504d7..e0c14e29edc9e 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/url" - "strings" "testing" "time" @@ -507,7 +506,7 @@ func i64ptr(i int64) *int64 { func authCookieValue(cookies []*http.Cookie) string { for _, cookie := range cookies { - if strings.Contains(cookie.Name, "token") { + if cookie.Name == codersdk.SessionTokenKey { return cookie.Value } } From 0aae08acce5b36342ee9e9ebceb32430b2b38a01 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 1 Sep 2022 14:55:15 -0400 Subject: [PATCH 15/26] Fix logout test --- coderd/users_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index b0b4d0156e028..9ce495ee85ebd 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -286,8 +286,8 @@ func TestPostLogout(t *testing.T) { var found bool for _, cookie := range cookies { if cookie.Name == codersdk.SessionTokenKey { - require.Equal(t, codersdk.SessionTokenKey, cookies[0].Name, "Cookie should be the auth cookie") - require.Equal(t, -1, cookies[0].MaxAge, "Cookie should be set to delete") + require.Equal(t, codersdk.SessionTokenKey, cookie.Name, "Cookie should be the auth cookie") + require.Equal(t, -1, cookie.MaxAge, "Cookie should be set to delete") found = true } } From 10b42960c517393921cc925572b8edea8d830b39 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 09:27:05 -0400 Subject: [PATCH 16/26] Fix merge issues --- coderd/coderd.go | 1 - coderd/httpmw/csrf.go | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index b8d99641b0a3f..87633709c05c7 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -406,7 +406,6 @@ func New(options *Options) *API { httpmw.ExtractWorkspaceParam(options.Database), ) r.Get("/", api.workspaceAgent) - r.Post("/peer", api.postWorkspaceAgentWireguardPeer) r.Get("/dial", api.workspaceAgentDial) r.Get("/turn", api.userWorkspaceAgentTurn) r.Get("/pty", api.workspaceAgentPTY) diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go index e5f27bdae729e..e94c3a7cb17c9 100644 --- a/coderd/httpmw/csrf.go +++ b/coderd/httpmw/csrf.go @@ -16,7 +16,7 @@ func CSRF(secureCookie bool) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { mw := nosurf.New(next) mw.SetBaseCookie(http.Cookie{Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: secureCookie}) - mw.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mw.SetFailureHandler(http.HandlerFunc(func(wg http.ResponseWriter, r *http.Request) { http.Error(w, "Something is wrong with your CSRF token. Please refresh the page. If this error persists, try clearing your cookies.", http.StatusBadRequest) })) @@ -28,6 +28,8 @@ func CSRF(secureCookie bool) func(next http.Handler) http.Handler { mw.ExemptRegexp(regexp.MustCompile("api/v2/workspaceagents/[^/]*$")) // Agent authenticated routes mw.ExemptRegexp(regexp.MustCompile("api/v2/workspaceagents/me/*")) + // Derp routes + mw.ExemptRegexp(regexp.MustCompile("derp/*")) mw.ExemptFunc(func(r *http.Request) bool { // CSRF only affects requests that automatically attach credentials via a cookie. From 71779090a6b6e1ce9798a6ef04d9d2120384e6d1 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 09:27:43 -0400 Subject: [PATCH 17/26] fixup! Fix merge issues --- coderd/httpmw/csrf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go index e94c3a7cb17c9..b06090cb405fd 100644 --- a/coderd/httpmw/csrf.go +++ b/coderd/httpmw/csrf.go @@ -16,7 +16,7 @@ func CSRF(secureCookie bool) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { mw := nosurf.New(next) mw.SetBaseCookie(http.Cookie{Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: secureCookie}) - mw.SetFailureHandler(http.HandlerFunc(func(wg http.ResponseWriter, r *http.Request) { + mw.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "Something is wrong with your CSRF token. Please refresh the page. If this error persists, try clearing your cookies.", http.StatusBadRequest) })) From 633118e5a8ed9c5806f717435fda505f84820d7c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 09:39:25 -0400 Subject: [PATCH 18/26] Make unit test use correct session value --- coderd/userauth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 13f8f209722ba..5c411155904bb 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -405,7 +405,7 @@ func TestUserOIDC(t *testing.T) { } if tc.AvatarURL != "" { - client.SessionToken = resp.Cookies()[0].Value + client.SessionToken = authCookieValue(resp.Cookies()) user, err := client.User(ctx, "me") require.NoError(t, err) require.Equal(t, tc.AvatarURL, user.AvatarURL) From 5662a55fb67a50974fc2387c4a1cbaa6ec0fccaa Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 09:53:20 -0400 Subject: [PATCH 19/26] puppeteer does not have document defined Might need to await some page load or detect in puppeteer --- site/src/api/api.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 3713e0d6a2c68..ee34bda72ede0 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -16,8 +16,13 @@ export const hardCodedCSRFCookie = (): string => { return csrfToken } -// Always attach CSRF token to all requests -const token = document.head.querySelector('meta[property="csrf-token"]') +// Always attach CSRF token to all requests. +// In puppeteer the document is undefined. In those cases, just +// do nothing. +const token = typeof document !== 'undefined' ? + document.head.querySelector('meta[property="csrf-token"]') : + null + if (token !== null && token.getAttribute("content") !== null) { if (process.env.NODE_ENV === "development") { // Development mode uses a hard-coded CSRF token From 86b9ecf2821b748c29fbadb164324a7fe104bdd4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 10:10:34 -0400 Subject: [PATCH 20/26] Make fmt --- site/src/api/api.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index ee34bda72ede0..3e2ea86c16d8b 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -19,9 +19,10 @@ export const hardCodedCSRFCookie = (): string => { // Always attach CSRF token to all requests. // In puppeteer the document is undefined. In those cases, just // do nothing. -const token = typeof document !== 'undefined' ? - document.head.querySelector('meta[property="csrf-token"]') : - null +const token = + typeof document !== "undefined" + ? document.head.querySelector('meta[property="csrf-token"]') + : null if (token !== null && token.getAttribute("content") !== null) { if (process.env.NODE_ENV === "development") { From ecaf61f4169f2adc046c210072b0264bcb725015 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 15:47:17 +0000 Subject: [PATCH 21/26] Update wireguard dep --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 13832bcfd4cf2..23576413066d0 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20220912224234-e80c replace github.com/gliderlabs/ssh => github.com/coder/ssh v0.0.0-20220811105153-fcea99919338 // Fixes a deadlock on close in devtunnel. -replace golang.zx2c4.com/wireguard => github.com/coder/wireguard-go v0.0.0-20220913030355-902de6e9b175 +replace golang.zx2c4.com/wireguard => github.com/coder/wireguard-go v0.0.0-20220913030931-b1b3bb45caf9 require ( cdr.dev/slog v1.4.2-0.20220525200111-18dce5c2cd5f diff --git a/go.sum b/go.sum index e3d95afa7bd3a..41957b92a542a 100644 --- a/go.sum +++ b/go.sum @@ -357,8 +357,8 @@ github.com/coder/ssh v0.0.0-20220811105153-fcea99919338 h1:tN5GKFT68YLVzJoA8AHui github.com/coder/ssh v0.0.0-20220811105153-fcea99919338/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= github.com/coder/tailscale v1.1.1-0.20220912224234-e80caec6c05f h1:NN9O1Pgno2QQy+JBnZk1VQ3vyAmWaB+yEotUDEuFKm8= github.com/coder/tailscale v1.1.1-0.20220912224234-e80caec6c05f/go.mod h1:5amxy08qijEa8bcTW2SeIy4MIqcmd7LMsuOxqOlj2Ak= -github.com/coder/wireguard-go v0.0.0-20220913030355-902de6e9b175 h1:obgyZIctZKcztM+TiIBUIJkOf04L9Fg+oLb47XEGy44= -github.com/coder/wireguard-go v0.0.0-20220913030355-902de6e9b175/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg= +github.com/coder/wireguard-go v0.0.0-20220913030931-b1b3bb45caf9 h1:AeU4w8hSB+XEj3e8HjvEUTy/MWQd6tddnr9dELrRjKk= +github.com/coder/wireguard-go v0.0.0-20220913030931-b1b3bb45caf9/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= From 484fe2b80b183ee6697f859024ce46c06de28bac Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 12:06:16 -0400 Subject: [PATCH 22/26] Add comment about BE cookie --- site/webpack.dev.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/webpack.dev.ts b/site/webpack.dev.ts index 09f03fef28dab..9e59efa6aa3a9 100644 --- a/site/webpack.dev.ts +++ b/site/webpack.dev.ts @@ -52,6 +52,8 @@ const config: Configuration = { devMiddleware: { publicPath: "/", headers: { + // This header corresponds to "src/api/api.ts"'s hardcoded FE token. + // This is the secret side of the CSRF double cookie submit method. "Set-Cookie": "csrf_token=JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=; Path=/; HttpOnly; SameSite=Lax", }, From b18ea2e90e56c01eddf3c3856a5eeacb4247746c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 12:57:15 -0400 Subject: [PATCH 23/26] chore: Ensure multiple version compatibility --- coderd/coderd.go | 4 +++- codersdk/client.go | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index 87633709c05c7..2f39d751dfdd8 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -167,7 +167,9 @@ func New(options *Options) *API { next.ServeHTTP(w, r) }) }, - httpmw.CSRF(options.SecureAuthCookie), + // Enable CSRF in November 2022 by uncommenting out this line. + // This is commented out for backwards compatibility. + // httpmw.CSRF(options.SecureAuthCookie), ) apps := func(r chi.Router) { diff --git a/codersdk/client.go b/codersdk/client.go index 08339e270f59b..6fa34e91b3e06 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -73,6 +73,14 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac return nil, xerrors.Errorf("create request: %w", err) } req.Header.Set(SessionCustomHeader, c.SessionToken) + + // Delete this custom cookie set in November 2022. This is just to remain + // backwards compatible with older versions of Coder. + req.AddCookie(&http.Cookie{ + Name: "session_token", + Value: c.SessionToken, + }) + if body != nil { req.Header.Set("Content-Type", "application/json") } From 3f1eedf5a47f0487680bf29bfdb1f7ce8d54f608 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 14:52:46 -0400 Subject: [PATCH 24/26] Do not enforce CSRF --- coderd/coderd.go | 4 +--- coderd/httpmw/csrf.go | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/coderd/coderd.go b/coderd/coderd.go index 6343e6a9ba654..4b5f143bdee9b 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -187,9 +187,7 @@ func New(options *Options) *API { next.ServeHTTP(w, r) }) }, - // Enable CSRF in November 2022 by uncommenting out this line. - // This is commented out for backwards compatibility. - // httpmw.CSRF(options.SecureAuthCookie), + httpmw.CSRF(options.SecureAuthCookie), ) apps := func(r chi.Router) { diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go index b06090cb405fd..6bde413abe0f8 100644 --- a/coderd/httpmw/csrf.go +++ b/coderd/httpmw/csrf.go @@ -32,6 +32,11 @@ func CSRF(secureCookie bool) func(next http.Handler) http.Handler { mw.ExemptRegexp(regexp.MustCompile("derp/*")) mw.ExemptFunc(func(r *http.Request) bool { + // Enable CSRF in November 2022 by deleting this "return true" line. + // CSRF is not enforced to ensure backwards compatibility with older + // cli versions. + return true + // CSRF only affects requests that automatically attach credentials via a cookie. // If no cookie is present, then there is no risk of CSRF. sessCookie, err := r.Cookie(codersdk.SessionTokenKey) From 8f367d2c5d7ababb1cb46c36fb2bf10fec5a2501 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 15:00:41 -0400 Subject: [PATCH 25/26] Add nolint --- coderd/httpmw/csrf.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/httpmw/csrf.go b/coderd/httpmw/csrf.go index 6bde413abe0f8..b4f0880569187 100644 --- a/coderd/httpmw/csrf.go +++ b/coderd/httpmw/csrf.go @@ -35,10 +35,12 @@ func CSRF(secureCookie bool) func(next http.Handler) http.Handler { // Enable CSRF in November 2022 by deleting this "return true" line. // CSRF is not enforced to ensure backwards compatibility with older // cli versions. + //nolint:revive return true // CSRF only affects requests that automatically attach credentials via a cookie. // If no cookie is present, then there is no risk of CSRF. + //nolint:govet sessCookie, err := r.Cookie(codersdk.SessionTokenKey) if xerrors.Is(err, http.ErrNoCookie) { return true From 85dcbfde90d48696c00d7f8bc1837197229536eb Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 13 Sep 2022 15:06:58 -0400 Subject: [PATCH 26/26] Account for devurl cookie --- coderd/users_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index c501b41daafec..6f1c2d7ebba37 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -330,7 +330,6 @@ func TestPostLogout(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode) cookies := res.Cookies() - require.Len(t, cookies, 2, "Exactly two cookies should be returned") var found bool for _, cookie := range cookies { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy