mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: use unique cookies for workspace proxies (#19930)
There is currently an issue with subdomain workspace apps on workspace
proxies, where if you have a workspace proxy wildcard nested beneath the
primary wildcard, cookies from the primary may be sent to the server
before cookies from the proxy specifically.
Currently:
1. Use a subdomain app via the primary proxy `*.coder.corp.com`
a. Client sends no cookies
a. Server does token smuggling flow
a. Server sets a cookie `coder_subdomain_app_session_token` on
`*.coder.corp.com`
a. Server redirects client to reload the page
a. Request should succeed as usual
1. Wait until the primary proxy's session token cookie has expired in
the database (or make it invalid yourself)
1. Use a subdomain app via a separate proxy `*.sydney.coder.corp.com`
a. Client sends `coder_subdomain_app_session_token` cookie from
`*.coder.corp.com`
a. Server validates supplied cookie, it fails because it's expired
a. Server does token smuggling flow
a. Server sets a cookie `coder_subdomain_app_session_token` on
`*.sydney.coder.corp.com`
a. Server redirects client to reload page
a. Client sends BOTH cookies.
a. The server will only process the first cookie it receives, so if the
expired cookie for the primary proxy is sent first the request will end
up in a permanent loop on step b.
The fix is to append `_{hash(wildcard_access_url)}` to the subdomain
cookies as we cannot control browser behavior further. This avoids the
conflict as each proxy will only read it's specific cookie.
This commit is contained in:
@@ -81,11 +81,7 @@ type AgentProvider interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Server serves workspace apps endpoints, including:
|
||||
// - Path-based apps
|
||||
// - Subdomain app middleware
|
||||
// - Workspace reconnecting-pty (aka. web terminal)
|
||||
type Server struct {
|
||||
type ServerOptions struct {
|
||||
Logger slog.Logger
|
||||
|
||||
// DashboardURL should be a url to the coderd dashboard. This can be the
|
||||
@@ -112,15 +108,32 @@ type Server struct {
|
||||
// Subdomain apps are safer with their cookies scoped to the subdomain, and XSS
|
||||
// calls to the dashboard are not possible due to CORs.
|
||||
DisablePathApps bool
|
||||
Cookies codersdk.HTTPCookieConfig
|
||||
CookiesConfig codersdk.HTTPCookieConfig
|
||||
|
||||
AgentProvider AgentProvider
|
||||
StatsCollector *StatsCollector
|
||||
}
|
||||
|
||||
// Server serves workspace apps endpoints, including:
|
||||
// - Path-based apps
|
||||
// - Subdomain app middleware
|
||||
// - Workspace reconnecting-pty (aka. web terminal)
|
||||
type Server struct {
|
||||
ServerOptions
|
||||
|
||||
cookies AppCookies
|
||||
|
||||
websocketWaitMutex sync.Mutex
|
||||
websocketWaitGroup sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewServer(options ServerOptions) *Server {
|
||||
return &Server{
|
||||
ServerOptions: options,
|
||||
cookies: NewAppCookies(options.Hostname),
|
||||
}
|
||||
}
|
||||
|
||||
// Close waits for all reconnecting-pty WebSocket connections to drain before
|
||||
// returning.
|
||||
func (s *Server) Close() error {
|
||||
@@ -231,8 +244,8 @@ func (s *Server) handleAPIKeySmuggling(rw http.ResponseWriter, r *http.Request,
|
||||
// We use different cookie names for path apps and for subdomain apps to
|
||||
// avoid both being set and sent to the server at the same time and the
|
||||
// server using the wrong value.
|
||||
http.SetCookie(rw, s.Cookies.Apply(&http.Cookie{
|
||||
Name: AppConnectSessionTokenCookieName(accessMethod),
|
||||
http.SetCookie(rw, s.CookiesConfig.Apply(&http.Cookie{
|
||||
Name: s.cookies.CookieNameForAccessMethod(accessMethod),
|
||||
Value: payload.APIKey,
|
||||
Domain: domain,
|
||||
Path: "/",
|
||||
@@ -299,7 +312,8 @@ func (s *Server) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
|
||||
// permissions to connect to a workspace.
|
||||
token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
|
||||
Logger: s.Logger,
|
||||
CookieCfg: s.Cookies,
|
||||
Cookies: s.cookies,
|
||||
CookieCfg: s.CookiesConfig,
|
||||
SignedTokenProvider: s.SignedTokenProvider,
|
||||
DashboardURL: s.DashboardURL,
|
||||
PathAppBaseURL: s.AccessURL,
|
||||
@@ -433,6 +447,8 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
|
||||
// Generate a signed token for the request.
|
||||
token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
|
||||
Logger: s.Logger,
|
||||
Cookies: s.cookies,
|
||||
CookieCfg: s.CookiesConfig,
|
||||
SignedTokenProvider: s.SignedTokenProvider,
|
||||
DashboardURL: s.DashboardURL,
|
||||
PathAppBaseURL: s.AccessURL,
|
||||
@@ -666,7 +682,8 @@ func (s *Server) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
appToken, ok := ResolveRequest(rw, r, ResolveRequestOptions{
|
||||
Logger: s.Logger,
|
||||
CookieCfg: s.Cookies,
|
||||
Cookies: s.cookies,
|
||||
CookieCfg: s.CookiesConfig,
|
||||
SignedTokenProvider: s.SignedTokenProvider,
|
||||
DashboardURL: s.DashboardURL,
|
||||
PathAppBaseURL: s.AccessURL,
|
||||
|
||||
Reference in New Issue
Block a user