mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
chore: move app proxying code to workspaceapps pkg (#6998)
* chore: move app proxying code to workspaceapps pkg Moves path-app, subdomain-app and reconnecting PTY proxying to the new workspaceapps.WorkspaceAppServer struct. This is in preparation for external workspace proxies. Updates app logout flow to avoid redirecting to coder-logout.${app_host} on logout. Instead, all subdomain app tokens owned by the logging-out user will be deleted every time you logout for simplicity sake. Tests will remain in their original package, pending being moved to an apptest package (or similar). Co-authored-by: Steven Masley <stevenmasley@coder.com>
This commit is contained in:
@ -123,9 +123,9 @@ type Options struct {
|
||||
SwaggerEndpoint bool
|
||||
SetUserGroups func(ctx context.Context, tx database.Store, userID uuid.UUID, groupNames []string) error
|
||||
TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
|
||||
// AppSigningKey denotes the symmetric key to use for signing temporary app
|
||||
// tokens. The key must be 64 bytes long.
|
||||
AppSigningKey []byte
|
||||
// AppSecurityKey is the crypto key used to sign and encrypt tokens related to
|
||||
// workspace applications. It consists of both a signing and encryption key.
|
||||
AppSecurityKey workspaceapps.SecurityKey
|
||||
HealthcheckFunc func(ctx context.Context) (*healthcheck.Report, error)
|
||||
HealthcheckTimeout time.Duration
|
||||
HealthcheckRefresh time.Duration
|
||||
@ -241,9 +241,6 @@ func New(options *Options) *API {
|
||||
v := schedule.NewAGPLTemplateScheduleStore()
|
||||
options.TemplateScheduleStore.Store(&v)
|
||||
}
|
||||
if len(options.AppSigningKey) != 64 {
|
||||
panic("coderd: AppSigningKey must be 64 bytes long")
|
||||
}
|
||||
if options.HealthcheckFunc == nil {
|
||||
options.HealthcheckFunc = func(ctx context.Context) (*healthcheck.Report, error) {
|
||||
return healthcheck.Run(ctx, &healthcheck.ReportOptions{
|
||||
@ -309,7 +306,7 @@ func New(options *Options) *API {
|
||||
options.DeploymentValues,
|
||||
oauthConfigs,
|
||||
options.AgentInactiveDisconnectTimeout,
|
||||
options.AppSigningKey,
|
||||
options.AppSecurityKey,
|
||||
),
|
||||
metricsCache: metricsCache,
|
||||
Auditor: atomic.Pointer[audit.Auditor]{},
|
||||
@ -334,6 +331,21 @@ func New(options *Options) *API {
|
||||
api.workspaceAgentCache = wsconncache.New(api.dialWorkspaceAgentTailnet, 0)
|
||||
api.TailnetCoordinator.Store(&options.TailnetCoordinator)
|
||||
|
||||
api.workspaceAppServer = &workspaceapps.Server{
|
||||
Logger: options.Logger.Named("workspaceapps"),
|
||||
|
||||
DashboardURL: api.AccessURL,
|
||||
AccessURL: api.AccessURL,
|
||||
Hostname: api.AppHostname,
|
||||
HostnameRegex: api.AppHostnameRegex,
|
||||
DeploymentValues: options.DeploymentValues,
|
||||
RealIPConfig: options.RealIPConfig,
|
||||
|
||||
SignedTokenProvider: api.WorkspaceAppsProvider,
|
||||
WorkspaceConnCache: api.workspaceAgentCache,
|
||||
AppSecurityKey: options.AppSecurityKey,
|
||||
}
|
||||
|
||||
apiKeyMiddleware := httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: options.Database,
|
||||
OAuth2Configs: oauthConfigs,
|
||||
@ -366,11 +378,12 @@ func New(options *Options) *API {
|
||||
httpmw.ExtractRealIP(api.RealIPConfig),
|
||||
httpmw.Logger(api.Logger),
|
||||
httpmw.Prometheus(options.PrometheusRegistry),
|
||||
// handleSubdomainApplications checks if the first subdomain is a valid
|
||||
// app URL. If it is, it will serve that application.
|
||||
// SubdomainAppMW checks if the first subdomain is a valid app URL. If
|
||||
// it is, it will serve that application.
|
||||
//
|
||||
// Workspace apps do their own auth.
|
||||
api.handleSubdomainApplications(apiRateLimiter),
|
||||
// Workspace apps do their own auth and must be BEFORE the auth
|
||||
// middleware.
|
||||
api.workspaceAppServer.SubdomainAppMW(apiRateLimiter),
|
||||
// Build-Version is helpful for debugging.
|
||||
func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -393,16 +406,12 @@ func New(options *Options) *API {
|
||||
|
||||
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("OK")) })
|
||||
|
||||
apps := func(r chi.Router) {
|
||||
// Workspace apps do their own auth.
|
||||
// Attach workspace apps routes.
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(apiRateLimiter)
|
||||
r.HandleFunc("/*", api.workspaceAppsProxyPath)
|
||||
}
|
||||
// %40 is the encoded character of the @ symbol. VS Code Web does
|
||||
// not handle character encoding properly, so it's safe to assume
|
||||
// other applications might not as well.
|
||||
r.Route("/%40{user}/{workspace_and_agent}/apps/{workspaceapp}", apps)
|
||||
r.Route("/@{user}/{workspace_and_agent}/apps/{workspaceapp}", apps)
|
||||
api.workspaceAppServer.Attach(r)
|
||||
})
|
||||
|
||||
r.Route("/derp", func(r chi.Router) {
|
||||
r.Get("/", derpHandler.ServeHTTP)
|
||||
// This is used when UDP is blocked, and latency must be checked via HTTP(s).
|
||||
@ -644,9 +653,6 @@ func New(options *Options) *API {
|
||||
r.Post("/report-lifecycle", api.workspaceAgentReportLifecycle)
|
||||
r.Post("/metadata/{key}", api.workspaceAgentPostMetadata)
|
||||
})
|
||||
// No middleware on the PTY endpoint since it uses workspace
|
||||
// application auth and signed app tokens.
|
||||
r.Get("/{workspaceagent}/pty", api.workspaceAgentPTY)
|
||||
r.Route("/{workspaceagent}", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
@ -655,11 +661,12 @@ func New(options *Options) *API {
|
||||
)
|
||||
r.Get("/", api.workspaceAgent)
|
||||
r.Get("/watch-metadata", api.watchWorkspaceAgentMetadata)
|
||||
r.Get("/pty", api.workspaceAgentPTY)
|
||||
r.Get("/startup-logs", api.workspaceAgentStartupLogs)
|
||||
r.Get("/listening-ports", api.workspaceAgentListeningPorts)
|
||||
r.Get("/connection", api.workspaceAgentConnection)
|
||||
r.Get("/coordinate", api.workspaceAgentClientCoordinate)
|
||||
|
||||
// PTY is part of workspaceAppServer.
|
||||
})
|
||||
})
|
||||
r.Route("/workspaces", func(r chi.Router) {
|
||||
@ -792,6 +799,7 @@ type API struct {
|
||||
workspaceAgentCache *wsconncache.Cache
|
||||
updateChecker *updatecheck.Checker
|
||||
WorkspaceAppsProvider workspaceapps.SignedTokenProvider
|
||||
workspaceAppServer *workspaceapps.Server
|
||||
|
||||
// Experiments contains the list of experiments currently enabled.
|
||||
// This is used to gate features that are not yet ready for production.
|
||||
|
Reference in New Issue
Block a user