Skip to content

Workspace applications CORS handling prevents external requests to apps #15096

@Emyrk

Description

@Emyrk

Problem

  • External requests to a Coder app will always fail.
  • Workspace apps cannot make requests to each other if they are owned by different users.

Explanation of why

As coder stands today (v2.15.3), we allow cross domain requests between workspace apps of the same user. Meaning you could host a backend webserver on workspace A, and host the frontend on workspace B. As long as the workspaces are owned by the same user, all web requests will function correctly (assuming credentials: "include").

This is all handled magically here

func WorkspaceAppCors(regex *regexp.Regexp, app appurl.ApplicationURL) func(next http.Handler) http.Handler {
return cors.Handler(cors.Options{
AllowOriginFunc: func(r *http.Request, rawOrigin string) bool {
origin, err := url.Parse(rawOrigin)
if rawOrigin == "" || origin.Host == "" || err != nil {
return false
}
subdomain, ok := appurl.ExecuteHostnamePattern(regex, origin.Host)
if !ok {
return false
}
originApp, err := appurl.ParseSubdomainAppURL(subdomain)
if err != nil {
return false
}
return ok && originApp.Username == app.Username
},
AllowedMethods: []string{
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
AllowedHeaders: []string{"*"},
AllowCredentials: true,
})
}

This allows basic intuitive functionality. By handling this for the user's, we can address some of the nuanced CORS behaviors within Coder. For example, the most common answer on solving CORS is Access-Control-Allow-Origin: *. This will not work for an application in Coder. All Coder apps require Coder authentication via a cookie, and credentials are not supported if CORS is set to *.

From Alice Bob
Workspace 1 Workspace 2 Workspace 3
To App A App B App C App D
Alice Workspace 1 App A * *
App B ✅* *
Workspace 2 App C * *
Bob Workspace 3 App D

🚫 What is the status quo?

The status quo is that Coder manages CORS requests between apps. This approach has generally lead to less customer support tickets on how to handle CORS.

We currently do not allow requests between different users. This does not appear to be because of huge security risk. Subdomain apps are given very limited session tokens that only allow workspace app connections. There is a risk that someone could steal this token and go to other apps by the user, but they cannot interact with other Coder resources.

Solution #1 Solve it in Coder (INCOMPLETE SOLUTION)

We can adjust the table above to include cross user requests. This would expand the security surface by default, which although attacks are limited in escalation, providing this by default seems insecure.

It also does not handle requests external to Coder. External requests must be supported by public shared apps to handle the use cases requested.

🚫 Solution #2 Suggest workarounds (Not always possible)

Instead of allowing cross site requests, the user could deploy a local webserver to proxy requests to the second user's app.

Many frontend servers have this as a feature:

If the web app in question has a backend server, then the backend server can proxy traffic to avoid CORS issues.

❓ Solution #3 Disable CORS handling by Coder

The best option is to disable our custom CORS handling and defer to the workspace app. There is a few ways we could maybe do this:

Disable at the deployment level.

  • Adds another server flag 👎
  • Breaks the QOL behavior for likely 1 use case 👎
  • Easy to switch behavior without any state (db) lookups 👍

Disable at the app level

  • Could configure in terraform per app 👍
  • Have to do a db lookup on requests or maintain a cache 👎

Disable based on response headers.
Given CORS requests are preflight requests, can we do this? Can we pass the preflight to the workspace app, get the headers. If CORS headers are present, return as is. If they are not, add them to the response? Then we just defer to the app if they configure it themselves. No Coder config necessary.

Final thoughts

When workspace sharing becomes a feature, this use case might show up more.

Implementation notes

The implementation cannot be configured at the workspace, template, or user level. To be configured there, an in memory cache would be required.

The requirements to a solution cannot require a database lookup on each request. That would cause too many database calls for web apps with hundreds of static resources.

The only input information on each request you get from making a request from app A to app B.

  • The origin, https://8002--<agent>--<workspace>--<user_A>--apps.coder.com/
  • The request to B, https://8002--<agent>--<workspace>--<user_B>--apps.coder.com/ with cookies for the app B

Related Links:

Metadata

Metadata

Assignees

Labels

must-doIssues that must be completed by the end of the Sprint. Or else. Only humans may set this.needs decisionNeeds a higher-level decision to be unblocked.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions