@@ -123,9 +123,9 @@ type Options struct {
123
123
SwaggerEndpoint bool
124
124
SetUserGroups func (ctx context.Context ,tx database.Store ,userID uuid.UUID ,groupNames []string )error
125
125
TemplateScheduleStore * atomic.Pointer [schedule.TemplateScheduleStore ]
126
- //AppSigningKey denotes thesymmetric key touse for signing temporary app
127
- //tokens. The key must be 64 bytes long .
128
- AppSigningKey [] byte
126
+ //AppSecurityKey is thecrypto keyused tosign and encrypt tokens related to
127
+ //workspace applications. It consists of both a signing and encryption key .
128
+ AppSecurityKey workspaceapps. SecurityKey
129
129
HealthcheckFunc func (ctx context.Context ) (* healthcheck.Report ,error )
130
130
HealthcheckTimeout time.Duration
131
131
HealthcheckRefresh time.Duration
@@ -241,9 +241,6 @@ func New(options *Options) *API {
241
241
v := schedule .NewAGPLTemplateScheduleStore ()
242
242
options .TemplateScheduleStore .Store (& v )
243
243
}
244
- if len (options .AppSigningKey )!= 64 {
245
- panic ("coderd: AppSigningKey must be 64 bytes long" )
246
- }
247
244
if options .HealthcheckFunc == nil {
248
245
options .HealthcheckFunc = func (ctx context.Context ) (* healthcheck.Report ,error ) {
249
246
return healthcheck .Run (ctx ,& healthcheck.ReportOptions {
@@ -309,7 +306,7 @@ func New(options *Options) *API {
309
306
options .DeploymentValues ,
310
307
oauthConfigs ,
311
308
options .AgentInactiveDisconnectTimeout ,
312
- options .AppSigningKey ,
309
+ options .AppSecurityKey ,
313
310
),
314
311
metricsCache :metricsCache ,
315
312
Auditor : atomic.Pointer [audit.Auditor ]{},
@@ -334,6 +331,21 @@ func New(options *Options) *API {
334
331
api .workspaceAgentCache = wsconncache .New (api .dialWorkspaceAgentTailnet ,0 )
335
332
api .TailnetCoordinator .Store (& options .TailnetCoordinator )
336
333
334
+ api .workspaceAppServer = & workspaceapps.Server {
335
+ Logger :options .Logger .Named ("workspaceapps" ),
336
+
337
+ DashboardURL :api .AccessURL ,
338
+ AccessURL :api .AccessURL ,
339
+ Hostname :api .AppHostname ,
340
+ HostnameRegex :api .AppHostnameRegex ,
341
+ DeploymentValues :options .DeploymentValues ,
342
+ RealIPConfig :options .RealIPConfig ,
343
+
344
+ SignedTokenProvider :api .WorkspaceAppsProvider ,
345
+ WorkspaceConnCache :api .workspaceAgentCache ,
346
+ AppSecurityKey :options .AppSecurityKey ,
347
+ }
348
+
337
349
apiKeyMiddleware := httpmw .ExtractAPIKeyMW (httpmw.ExtractAPIKeyConfig {
338
350
DB :options .Database ,
339
351
OAuth2Configs :oauthConfigs ,
@@ -366,11 +378,12 @@ func New(options *Options) *API {
366
378
httpmw .ExtractRealIP (api .RealIPConfig ),
367
379
httpmw .Logger (api .Logger ),
368
380
httpmw .Prometheus (options .PrometheusRegistry ),
369
- //handleSubdomainApplications checks if the first subdomain is a valid
370
- //app URL. If it is, it will serve that application.
381
+ //SubdomainAppMW checks if the first subdomain is a valid app URL. If
382
+ // it is, it will serve that application.
371
383
//
372
- // Workspace apps do their own auth.
373
- api .handleSubdomainApplications (apiRateLimiter ),
384
+ // Workspace apps do their own auth and must be BEFORE the auth
385
+ // middleware.
386
+ api .workspaceAppServer .SubdomainAppMW (apiRateLimiter ),
374
387
// Build-Version is helpful for debugging.
375
388
func (next http.Handler ) http.Handler {
376
389
return http .HandlerFunc (func (w http.ResponseWriter ,r * http.Request ) {
@@ -393,16 +406,12 @@ func New(options *Options) *API {
393
406
394
407
r .Get ("/healthz" ,func (w http.ResponseWriter ,r * http.Request ) {_ ,_ = w .Write ([]byte ("OK" )) })
395
408
396
- apps := func ( r chi. Router ) {
397
- // Workspace apps do their own auth.
409
+ // Attach workspace apps routes.
410
+ r . Group ( func ( r chi. Router ) {
398
411
r .Use (apiRateLimiter )
399
- r .HandleFunc ("/*" ,api .workspaceAppsProxyPath )
400
- }
401
- // %40 is the encoded character of the @ symbol. VS Code Web does
402
- // not handle character encoding properly, so it's safe to assume
403
- // other applications might not as well.
404
- r .Route ("/%40{user}/{workspace_and_agent}/apps/{workspaceapp}" ,apps )
405
- r .Route ("/@{user}/{workspace_and_agent}/apps/{workspaceapp}" ,apps )
412
+ api .workspaceAppServer .Attach (r )
413
+ })
414
+
406
415
r .Route ("/derp" ,func (r chi.Router ) {
407
416
r .Get ("/" ,derpHandler .ServeHTTP )
408
417
// This is used when UDP is blocked, and latency must be checked via HTTP(s).
@@ -644,9 +653,6 @@ func New(options *Options) *API {
644
653
r .Post ("/report-lifecycle" ,api .workspaceAgentReportLifecycle )
645
654
r .Post ("/metadata/{key}" ,api .workspaceAgentPostMetadata )
646
655
})
647
- // No middleware on the PTY endpoint since it uses workspace
648
- // application auth and signed app tokens.
649
- r .Get ("/{workspaceagent}/pty" ,api .workspaceAgentPTY )
650
656
r .Route ("/{workspaceagent}" ,func (r chi.Router ) {
651
657
r .Use (
652
658
apiKeyMiddleware ,
@@ -655,11 +661,12 @@ func New(options *Options) *API {
655
661
)
656
662
r .Get ("/" ,api .workspaceAgent )
657
663
r .Get ("/watch-metadata" ,api .watchWorkspaceAgentMetadata )
658
- r .Get ("/pty" ,api .workspaceAgentPTY )
659
664
r .Get ("/startup-logs" ,api .workspaceAgentStartupLogs )
660
665
r .Get ("/listening-ports" ,api .workspaceAgentListeningPorts )
661
666
r .Get ("/connection" ,api .workspaceAgentConnection )
662
667
r .Get ("/coordinate" ,api .workspaceAgentClientCoordinate )
668
+
669
+ // PTY is part of workspaceAppServer.
663
670
})
664
671
})
665
672
r .Route ("/workspaces" ,func (r chi.Router ) {
@@ -792,6 +799,7 @@ type API struct {
792
799
workspaceAgentCache * wsconncache.Cache
793
800
updateChecker * updatecheck.Checker
794
801
WorkspaceAppsProvider workspaceapps.SignedTokenProvider
802
+ workspaceAppServer * workspaceapps.Server
795
803
796
804
// Experiments contains the list of experiments currently enabled.
797
805
// This is used to gate features that are not yet ready for production.