mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: allow bypassing current CORS magic based on template config (#18706)
Solves https://github.com/coder/coder/issues/15096 This is a slight rework/refactor of the earlier PRs from @dannykopping and @Emyrk: - https://github.com/coder/coder/pull/15669 - https://github.com/coder/coder/pull/15684 - https://github.com/coder/coder/pull/17596 Rather than having a per-app CORS behaviour setting and additionally a template level setting for ports, this PR adds a single template level CORS behaviour setting that is then used by all apps/ports for workspaces created from that template. The main changes are in `proxy.go` and `request.go` to: a) get the CORS behaviour setting from the template b) have `HandleSubdomain` bypass the CORS middleware handler if the selected behaviour is `passthru` c) in `proxyWorkspaceApp`, do not modify the response if the selected behaviour is `passthru` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for configuring CORS behavior ("simple" or "passthru") at the template level for all shared ports. * Introduced a new "CORS Behavior" setting in the template creation and settings forms. * API endpoints and responses now include the optional `cors_behavior` property for templates. * Workspace apps and proxy now honor the specified CORS behavior, enabling conditional CORS middleware application. * Enhanced workspace app tests with comprehensive scenarios covering CORS behaviors and authentication states. * **Bug Fixes** * None. * **Documentation** * Updated API and admin documentation to describe the new `cors_behavior` property and its usage. * Added examples and schema references for CORS behavior in relevant API docs. * **Tests** * Extended automated tests to cover different CORS behavior scenarios for templates and workspace apps. * **Chores** * Updated audit logging to track changes to the `cors_behavior` field on templates. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Callum Styan <callumstyan@gmail.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
|
||||
"github.com/coder/coder/v2/coderd/workspaceapps/cors"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||
"github.com/coder/coder/v2/site"
|
||||
@@ -323,6 +324,37 @@ func (s *Server) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
|
||||
s.proxyWorkspaceApp(rw, r, *token, chiPath, appurl.ApplicationURL{})
|
||||
}
|
||||
|
||||
// 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 codersdk.CORSBehavior
|
||||
if token != nil {
|
||||
behavior = token.CORSBehavior
|
||||
}
|
||||
|
||||
// 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 codersdk.CORSBehaviorPassthru:
|
||||
// Bypass the CORS middleware.
|
||||
next.ServeHTTP(rw, r)
|
||||
return
|
||||
default:
|
||||
// Apply the CORS middleware.
|
||||
corsHandler.ServeHTTP(rw, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleSubdomain handles subdomain-based application proxy requests (aka.
|
||||
// DevURLs in Coder V1).
|
||||
//
|
||||
@@ -394,36 +426,36 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
|
||||
return
|
||||
}
|
||||
|
||||
// Use the passed in app middlewares before checking authentication and
|
||||
// passing to the proxy app.
|
||||
mws := chi.Middlewares(append(middlewares, httpmw.WorkspaceAppCors(s.HostnameRegex, app)))
|
||||
mws.Handler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if !s.handleAPIKeySmuggling(rw, r, AccessMethodSubdomain) {
|
||||
return
|
||||
}
|
||||
if !s.handleAPIKeySmuggling(rw, r, AccessMethodSubdomain) {
|
||||
return
|
||||
}
|
||||
|
||||
token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
|
||||
Logger: s.Logger,
|
||||
CookieCfg: s.Cookies,
|
||||
SignedTokenProvider: s.SignedTokenProvider,
|
||||
DashboardURL: s.DashboardURL,
|
||||
PathAppBaseURL: s.AccessURL,
|
||||
AppHostname: s.Hostname,
|
||||
AppRequest: Request{
|
||||
AccessMethod: AccessMethodSubdomain,
|
||||
BasePath: "/",
|
||||
Prefix: app.Prefix,
|
||||
UsernameOrID: app.Username,
|
||||
WorkspaceNameOrID: app.WorkspaceName,
|
||||
AgentNameOrID: app.AgentName,
|
||||
AppSlugOrPort: app.AppSlugOrPort,
|
||||
},
|
||||
AppPath: r.URL.Path,
|
||||
AppQuery: r.URL.RawQuery,
|
||||
})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// Generate a signed token for the request.
|
||||
token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
|
||||
Logger: s.Logger,
|
||||
SignedTokenProvider: s.SignedTokenProvider,
|
||||
DashboardURL: s.DashboardURL,
|
||||
PathAppBaseURL: s.AccessURL,
|
||||
AppHostname: s.Hostname,
|
||||
AppRequest: Request{
|
||||
AccessMethod: AccessMethodSubdomain,
|
||||
BasePath: "/",
|
||||
Prefix: app.Prefix,
|
||||
UsernameOrID: app.Username,
|
||||
WorkspaceNameOrID: app.WorkspaceName,
|
||||
AgentNameOrID: app.AgentName,
|
||||
AppSlugOrPort: app.AppSlugOrPort,
|
||||
},
|
||||
AppPath: r.URL.Path,
|
||||
AppQuery: r.URL.RawQuery,
|
||||
})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 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))
|
||||
})
|
||||
@@ -560,6 +592,10 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT
|
||||
proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname)
|
||||
|
||||
proxy.ModifyResponse = func(r *http.Response) error {
|
||||
// If passthru behavior is set, disable our CORS header stripping.
|
||||
if cors.HasBehavior(r.Request.Context(), codersdk.CORSBehaviorPassthru) {
|
||||
return nil
|
||||
}
|
||||
r.Header.Del(httpmw.AccessControlAllowOriginHeader)
|
||||
r.Header.Del(httpmw.AccessControlAllowCredentialsHeader)
|
||||
r.Header.Del(httpmw.AccessControlAllowMethodsHeader)
|
||||
|
||||
Reference in New Issue
Block a user