- Notifications
You must be signed in to change notification settings - Fork1k
feat: Implement (but not enforce) CSRF for FE requests#3786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
52c7575
642a29f
37c0ba8
c774fcc
3c75967
ab48b4a
51d856e
c8c8be0
b03610b
e798e11
1c4810a
a6fdac8
a343da9
dd80cc9
08e76d4
0aae08a
3116964
10b4296
7177909
633118e
5662a55
86b9ecf
ecaf61f
484fe2b
b18ea2e
b97225f
3f1eedf
8f367d2
85dcbfd
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Is this not an issue? Can't we still send data with a GET? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. CSRF really only needs to protect endpoints that have side effects. Our router checks the method on all API calls. Seehttps://security.stackexchange.com/questions/115794/should-i-use-csrf-protection-for-get-requests. If we design our api correctly, there is no need to enforce CSRF on GET requests. | ||
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/*")) | ||
Comment on lines +23 to +30 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Pretty sure this is all we need to exempt. If provisionerd needs to make api calls, we should omit that as well. | ||
// Derp routes | ||
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. | ||
//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 | ||
} | ||
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 | ||
} | ||
} |
Uh oh!
There was an error while loading.Please reload this page.