-
Notifications
You must be signed in to change notification settings - Fork 894
fix: relax csrf to exclude path based apps #11430
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
658d7df
fe4e2ba
f2e615d
a4bbc80
63133cf
3828354
c6c4141
42a3021
043c79d
388e56e
fee870b
6966f87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package coderd_test | |
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/netip" | ||
|
@@ -21,6 +22,9 @@ import ( | |
|
||
"cdr.dev/slog" | ||
"cdr.dev/slog/sloggers/slogtest" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/coder/v2/coderd/database/dbfake" | ||
"github.com/coder/coder/v2/provisionersdk/proto" | ||
|
||
"github.com/coder/coder/v2/agent/agenttest" | ||
"github.com/coder/coder/v2/buildinfo" | ||
|
@@ -315,3 +319,63 @@ func TestSwagger(t *testing.T) { | |
require.Equal(t, "<pre>\n</pre>\n", string(body)) | ||
}) | ||
} | ||
|
||
func TestCSRFExempt(t *testing.T) { | ||
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 it this one? 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. Yup. All of our existing unit tests bypass CSRF since the codersdk uses the HEADER method to auth, not the cookie. |
||
t.Parallel() | ||
|
||
// This test build a workspace with an agent and an app. The app is not | ||
// a real http server, so it will fail to serve requests. We just want | ||
// to make sure the failure is not a CSRF failure, as path based | ||
// apps should be exempt. | ||
t.Run("PathBasedApp", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
client, _, api := coderdtest.NewWithAPI(t, nil) | ||
first := coderdtest.CreateFirstUser(t, client) | ||
owner, err := client.User(context.Background(), "me") | ||
require.NoError(t, err) | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) | ||
defer cancel() | ||
|
||
// Create a workspace. | ||
const agentSlug = "james" | ||
const appSlug = "web" | ||
wrk := dbfake.WorkspaceBuild(t, api.Database, database.Workspace{ | ||
OwnerID: owner.ID, | ||
OrganizationID: first.OrganizationID, | ||
}). | ||
WithAgent(func(agents []*proto.Agent) []*proto.Agent { | ||
agents[0].Name = agentSlug | ||
agents[0].Apps = []*proto.App{{ | ||
Slug: appSlug, | ||
DisplayName: appSlug, | ||
Subdomain: false, | ||
Url: "/", | ||
}} | ||
|
||
return agents | ||
}). | ||
Do() | ||
|
||
u := client.URL.JoinPath(fmt.Sprintf("/@%s/%s.%s/apps/%s", owner.Username, wrk.Workspace.Name, agentSlug, appSlug)).String() | ||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil) | ||
req.AddCookie(&http.Cookie{ | ||
Name: codersdk.SessionTokenCookie, | ||
Value: client.SessionToken(), | ||
Path: "/", | ||
Domain: client.URL.String(), | ||
}) | ||
require.NoError(t, err) | ||
|
||
resp, err := client.HTTPClient.Do(req) | ||
require.NoError(t, err) | ||
data, _ := io.ReadAll(resp.Body) | ||
_ = resp.Body.Close() | ||
|
||
// A StatusBadGateway means Coderd tried to proxy to the agent and failed because the agent | ||
// was not there. This means CSRF did not block the app request, which is what we want. | ||
require.Equal(t, http.StatusBadGateway, resp.StatusCode, "status code 500 is CSRF failure") | ||
require.NotContains(t, string(data), "CSRF") | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a bunch of
ExemptRegexp()
calls inside this middleware that may need to be reviewed after this change. It might be nicer to have those passed explicitly as arguments tohttpmw.CSRF
instead of being hidden inside the middleware.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is, this will pretty quickly fail on someone when they add a new route if it breaks. The error is a CSRF error, which brings you to here. I prefer to have this all in 1 place, and this should never be configurable by a customer except maybe to disable it entirely.
I think the current exempts are overkill. I intentionally went a bit overkill because I was afraid of breaking anything. Some
The reason there are exempts like this is when I turned this back on, I was trying to error on the side of caution. I think we should have a PR to remove an exempt 1 by 1 when we determine they can never cause an issue.
An example is the
/provisionerdaemons
ones. They are currently only GET requests, and I do not think the actual daemons are using cookies to authenticate. However I error-ed on the side of caution since these routes should never use CSRF anyway.I can make follow up PRs to drop these with rational for each one