Skip to content

feat: bypass built-in CORS handling for workspace apps #15669

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

Closed
wants to merge 12 commits into from
Prev Previous commit
Next Next commit
Hard & simplify CORORS handling logic
Signed-off-by: Danny Kopping <danny@coder.com>
  • Loading branch information
dannykopping committed Nov 28, 2024
commit a4d3c8d29fe3ebff6dc04edfeb04e8e39ed0a519
6 changes: 0 additions & 6 deletions coderd/httpmw/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/go-chi/cors"

"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
ws_cors "github.com/coder/coder/v2/coderd/workspaceapps/cors"
)

const (
Expand Down Expand Up @@ -48,11 +47,6 @@ func Cors(allowAll bool, origins ...string) func(next http.Handler) http.Handler
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 {
// If passthru behavior is set, disable our simplified CORS handling.
if ws_cors.HasBehavior(r.Context(), ws_cors.AppCORSBehaviorPassthru) {
return true
}

origin, err := url.Parse(rawOrigin)
if rawOrigin == "" || origin.Host == "" || err != nil {
return false
Expand Down
26 changes: 22 additions & 4 deletions coderd/workspaceapps/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,24 +424,42 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
return
}

// Use the passed in app middlewares and CORS middleware with the token
mws := chi.Middlewares(append(middlewares, s.injectCORSBehavior(token), httpmw.WorkspaceAppCors(s.HostnameRegex, app)))
// Proxy the request (possibly with the CORS middleware).
mws := chi.Middlewares(append(middlewares, s.determineCORSBehavior(token, app)))
mws.Handler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
s.proxyWorkspaceApp(rw, r, *token, r.URL.Path, app)
})).ServeHTTP(rw, r.WithContext(ctx))
})
}
}

func (s *Server) injectCORSBehavior(token *SignedToken) func(http.Handler) http.Handler {
// determineCORSBehavior examines the given token and conditionally applies
// CORS middleware if the token specifies that behavior.
func (s *Server) determineCORSBehavior(token *SignedToken, app appurl.ApplicationURL) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
// Create the CORS middleware handler upfront.
corsHandler := httpmw.WorkspaceAppCors(s.HostnameRegex, app)(next)

return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
var behavior cors.AppCORSBehavior
if token != nil {
behavior = token.CORSBehavior
}

next.ServeHTTP(rw, r.WithContext(cors.WithBehavior(r.Context(), behavior)))
// Add behavior to context regardless of which handler we use,
// since we will use this later on to determine if we should strip
// CORS headers in the response.
r = r.WithContext(cors.WithBehavior(r.Context(), behavior))

switch behavior {
case cors.AppCORSBehaviorPassthru:
// Bypass the CORS middleware.
next.ServeHTTP(rw, r)
return
default:
// Apply the CORS middleware.
corsHandler.ServeHTTP(rw, r)
}
})
}
}
Expand Down