2022-01-13 22:55:28 +00:00
package coderd
import (
2022-11-16 22:34:06 +00:00
"context"
2022-10-17 13:43:30 +00:00
"crypto/tls"
2022-04-19 13:48:13 +00:00
"crypto/x509"
2022-11-16 22:34:06 +00:00
"encoding/json"
2023-05-11 19:30:51 +00:00
"flag"
2022-10-25 00:46:24 +00:00
"fmt"
2022-06-13 18:14:22 +00:00
"io"
2022-01-13 22:55:28 +00:00
"net/http"
2022-02-28 18:26:01 +00:00
"net/url"
2022-06-21 16:53:36 +00:00
"path/filepath"
2022-10-14 18:25:11 +00:00
"regexp"
2023-01-18 19:12:53 +00:00
"strings"
2022-02-20 22:29:16 +00:00
"sync"
2022-09-20 04:11:01 +00:00
"sync/atomic"
2022-03-22 19:17:50 +00:00
"time"
2022-01-13 22:55:28 +00:00
2022-06-13 18:14:22 +00:00
"github.com/andybalholm/brotli"
2022-01-31 03:13:14 +00:00
"github.com/go-chi/chi/v5"
2022-05-03 12:48:02 +00:00
"github.com/go-chi/chi/v5/middleware"
2022-11-06 21:27:09 +00:00
"github.com/google/uuid"
2022-06-13 18:14:22 +00:00
"github.com/klauspost/compress/zstd"
2022-11-16 22:34:06 +00:00
"github.com/moby/moby/pkg/namesgenerator"
2022-08-08 15:09:46 +00:00
"github.com/prometheus/client_golang/prometheus"
2023-03-30 16:46:58 +00:00
httpSwagger "github.com/swaggo/http-swagger/v2"
2022-09-16 16:43:22 +00:00
"go.opentelemetry.io/otel/trace"
2022-05-03 21:10:19 +00:00
"golang.org/x/xerrors"
2022-02-21 20:36:29 +00:00
"google.golang.org/api/idtoken"
2022-11-16 22:34:06 +00:00
"storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver"
2022-09-01 01:09:44 +00:00
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
2023-04-03 06:28:42 +00:00
"tailscale.com/util/singleflight"
2022-01-20 13:46:51 +00:00
2022-01-13 22:55:28 +00:00
"cdr.dev/slog"
2022-12-19 17:43:46 +00:00
2023-04-17 19:57:21 +00:00
"github.com/coder/coder/buildinfo"
2023-04-27 10:34:00 +00:00
"github.com/coder/coder/codersdk/agentsdk"
2023-04-17 19:57:21 +00:00
// Used for swagger docs.
2022-12-19 17:43:46 +00:00
_ "github.com/coder/coder/coderd/apidoc"
2022-09-20 04:11:01 +00:00
"github.com/coder/coder/coderd/audit"
2022-03-28 19:31:03 +00:00
"github.com/coder/coder/coderd/awsidentity"
2022-03-25 21:07:45 +00:00
"github.com/coder/coder/coderd/database"
2023-02-14 14:27:06 +00:00
"github.com/coder/coder/coderd/database/dbauthz"
2022-11-16 22:34:06 +00:00
"github.com/coder/coder/coderd/database/dbtype"
2022-10-25 00:46:24 +00:00
"github.com/coder/coder/coderd/gitauth"
2022-04-06 00:18:26 +00:00
"github.com/coder/coder/coderd/gitsshkey"
2023-04-03 06:28:42 +00:00
"github.com/coder/coder/coderd/healthcheck"
2022-03-25 21:07:45 +00:00
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
2022-09-01 19:58:23 +00:00
"github.com/coder/coder/coderd/metricscache"
2022-11-16 22:34:06 +00:00
"github.com/coder/coder/coderd/provisionerdserver"
2022-05-03 21:10:19 +00:00
"github.com/coder/coder/coderd/rbac"
2023-03-07 14:14:58 +00:00
"github.com/coder/coder/coderd/schedule"
2022-06-17 05:26:40 +00:00
"github.com/coder/coder/coderd/telemetry"
2022-05-19 22:43:07 +00:00
"github.com/coder/coder/coderd/tracing"
2022-12-01 17:43:28 +00:00
"github.com/coder/coder/coderd/updatecheck"
2023-01-18 19:12:53 +00:00
"github.com/coder/coder/coderd/util/slice"
2023-03-07 19:38:11 +00:00
"github.com/coder/coder/coderd/workspaceapps"
2022-06-04 20:13:37 +00:00
"github.com/coder/coder/coderd/wsconncache"
2022-04-07 17:18:58 +00:00
"github.com/coder/coder/codersdk"
2022-11-14 17:57:33 +00:00
"github.com/coder/coder/provisionerd/proto"
2022-11-16 22:34:06 +00:00
"github.com/coder/coder/provisionersdk"
2022-01-18 21:13:19 +00:00
"github.com/coder/coder/site"
2022-09-01 01:09:44 +00:00
"github.com/coder/coder/tailnet"
2022-01-13 22:55:28 +00:00
)
2023-01-11 10:42:49 +00:00
// We must only ever instantiate one httpSwagger.Handler because of a data race
// inside the handler. This issue is triggered by tests that create multiple
// coderd instances.
//
// See https://github.com/swaggo/http-swagger/issues/78
var globalHTTPSwaggerHandler http . HandlerFunc
func init ( ) {
globalHTTPSwaggerHandler = httpSwagger . Handler ( httpSwagger . URL ( "/swagger/doc.json" ) )
}
2022-01-13 22:55:28 +00:00
// Options are requires parameters for Coder to start.
type Options struct {
2022-04-18 22:40:25 +00:00
AccessURL * url . URL
2022-09-22 22:30:32 +00:00
// AppHostname should be the wildcard hostname to use for workspace
2022-10-14 18:25:11 +00:00
// applications INCLUDING the asterisk, (optional) suffix and leading dot.
2022-12-15 18:43:00 +00:00
// It will use the same scheme and port number as the access URL.
2022-10-14 18:25:11 +00:00
// E.g. "*.apps.coder.com" or "*-apps.coder.com".
2022-09-22 22:30:32 +00:00
AppHostname string
2022-10-14 18:25:11 +00:00
// AppHostnameRegex contains the regex version of options.AppHostname as
// generated by httpapi.CompileHostnamePattern(). It MUST be set if
// options.AppHostname is set.
AppHostnameRegex * regexp . Regexp
Logger slog . Logger
Database database . Store
Pubsub database . Pubsub
2022-03-31 17:31:06 +00:00
2022-06-21 16:53:36 +00:00
// CacheDir is used for caching files served by the API.
CacheDir string
2022-09-20 04:11:01 +00:00
Auditor audit . Auditor
2022-04-18 22:40:25 +00:00
AgentConnectionUpdateFrequency time . Duration
2022-06-27 15:06:51 +00:00
AgentInactiveDisconnectTimeout time . Duration
2023-01-05 18:05:20 +00:00
AWSCertificates awsidentity . Certificates
Authorizer rbac . Authorizer
AzureCertificates x509 . VerifyOptions
GoogleTokenValidator * idtoken . Validator
GithubOAuth2Config * GithubOAuth2Config
OIDCConfig * OIDCConfig
PrometheusRegistry * prometheus . Registry
SecureAuthCookie bool
2023-02-10 16:52:49 +00:00
StrictTransportSecurityCfg httpmw . HSTSConfig
2023-01-05 18:05:20 +00:00
SSHKeygenAlgorithm gitsshkey . Algorithm
Telemetry telemetry . Reporter
TracerProvider trace . TracerProvider
GitAuthConfigs [ ] * gitauth . Config
RealIPConfig * httpmw . RealIPConfig
TrialGenerator func ( ctx context . Context , email string ) error
2022-10-17 13:43:30 +00:00
// TLSCertificates is used to mesh DERP servers securely.
2023-03-07 14:14:58 +00:00
TLSCertificates [ ] tls . Certificate
TailnetCoordinator tailnet . Coordinator
DERPServer * derp . Server
DERPMap * tailcfg . DERPMap
SwaggerEndpoint bool
SetUserGroups func ( ctx context . Context , tx database . Store , userID uuid . UUID , groupNames [ ] string ) error
2023-04-04 12:48:35 +00:00
TemplateScheduleStore * atomic . Pointer [ schedule . TemplateScheduleStore ]
2023-04-05 18:41:55 +00:00
// 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
2023-04-03 06:28:42 +00:00
HealthcheckFunc func ( ctx context . Context ) ( * healthcheck . Report , error )
HealthcheckTimeout time . Duration
HealthcheckRefresh time . Duration
2022-09-01 19:58:23 +00:00
2023-01-05 18:05:20 +00:00
// APIRateLimit is the minutely throughput rate limit per user or ip.
// Setting a rate limit <0 will disable the rate limiter across the entire
// app. Some specific routes have their own configurable rate limits.
APIRateLimit int
LoginRateLimit int
FilesRateLimit int
2022-09-01 19:58:23 +00:00
MetricsCacheRefreshInterval time . Duration
AgentStatsRefreshInterval time . Duration
2023-03-07 21:10:01 +00:00
DeploymentValues * codersdk . DeploymentValues
2022-12-01 17:43:28 +00:00
UpdateCheckOptions * updatecheck . Options // Set non-nil to enable update checking.
2022-12-19 17:43:46 +00:00
2023-03-16 18:03:37 +00:00
// SSHConfig is the response clients use to configure config-ssh locally.
SSHConfig codersdk . SSHConfigResponse
2022-12-19 17:43:46 +00:00
HTTPClient * http . Client
2023-04-27 10:34:00 +00:00
UpdateAgentMetrics func ( ctx context . Context , username , workspaceName , agentName string , metrics [ ] agentsdk . AgentMetric )
2022-01-13 22:55:28 +00:00
}
2022-12-19 17:43:46 +00:00
// @title Coder API
// @version 2.0
// @description Coderd is the service created by running coder server. It is a thin API that connects workspaces, provisioners and users. coderd stores its state in Postgres and is the only service that communicates with Postgres.
// @termsOfService https://coder.com/legal/terms-of-service
// @contact.name API Support
// @contact.url https://coder.com
// @contact.email support@coder.com
// @license.name AGPL-3.0
// @license.url https://github.com/coder/coder/blob/main/LICENSE
// @BasePath /api/v2
// @securitydefinitions.apiKey CoderSessionToken
// @in header
// @name Coder-Session-Token
2022-05-26 03:14:08 +00:00
// New constructs a Coder API handler.
func New ( options * Options ) * API {
2022-09-20 04:11:01 +00:00
if options == nil {
options = & Options { }
}
2023-03-21 14:10:22 +00:00
2023-04-11 13:57:23 +00:00
if options . DeploymentValues . DisableOwnerWorkspaceExec {
rbac . ReloadBuiltinRoles ( & rbac . RoleOptions {
NoOwnerWorkspaceExec : true ,
} )
}
2023-03-21 14:10:22 +00:00
if options . Authorizer == nil {
options . Authorizer = rbac . NewCachingAuthorizer ( options . PrometheusRegistry )
}
options . Database = dbauthz . New (
options . Database ,
options . Authorizer ,
options . Logger . Named ( "authz_querier" ) ,
)
2023-03-07 21:10:01 +00:00
experiments := initExperiments (
options . Logger , options . DeploymentValues . Experiments . Value ( ) ,
)
2022-10-14 18:25:11 +00:00
if options . AppHostname != "" && options . AppHostnameRegex == nil || options . AppHostname == "" && options . AppHostnameRegex != nil {
panic ( "coderd: both AppHostname and AppHostnameRegex must be set or unset" )
}
2022-03-22 19:17:50 +00:00
if options . AgentConnectionUpdateFrequency == 0 {
2023-04-06 01:58:54 +00:00
options . AgentConnectionUpdateFrequency = 15 * time . Second
2022-03-22 19:17:50 +00:00
}
2022-06-27 15:06:51 +00:00
if options . AgentInactiveDisconnectTimeout == 0 {
// Multiply the update by two to allow for some lag-time.
options . AgentInactiveDisconnectTimeout = options . AgentConnectionUpdateFrequency * 2
2023-03-14 13:14:47 +00:00
// Set a minimum timeout to avoid disconnecting too soon.
if options . AgentInactiveDisconnectTimeout < 2 * time . Second {
options . AgentInactiveDisconnectTimeout = 2 * time . Second
}
2022-06-27 15:06:51 +00:00
}
2022-09-20 00:46:29 +00:00
if options . AgentStatsRefreshInterval == 0 {
2022-12-01 17:43:28 +00:00
options . AgentStatsRefreshInterval = 5 * time . Minute
2022-09-20 00:46:29 +00:00
}
if options . MetricsCacheRefreshInterval == 0 {
options . MetricsCacheRefreshInterval = time . Hour
}
2022-04-22 20:27:55 +00:00
if options . APIRateLimit == 0 {
options . APIRateLimit = 512
}
2023-01-05 18:05:20 +00:00
if options . LoginRateLimit == 0 {
options . LoginRateLimit = 60
}
if options . FilesRateLimit == 0 {
options . FilesRateLimit = 12
}
2022-11-05 00:03:01 +00:00
if options . PrometheusRegistry == nil {
options . PrometheusRegistry = prometheus . NewRegistry ( )
2022-08-08 15:09:46 +00:00
}
2022-09-01 01:09:44 +00:00
if options . TailnetCoordinator == nil {
2023-04-27 09:59:01 +00:00
options . TailnetCoordinator = tailnet . NewCoordinator ( options . Logger )
2022-09-01 01:09:44 +00:00
}
2022-10-17 13:43:30 +00:00
if options . DERPServer == nil {
options . DERPServer = derp . NewServer ( key . NewNode ( ) , tailnet . Logger ( options . Logger . Named ( "derp" ) ) )
}
2022-09-20 04:11:01 +00:00
if options . Auditor == nil {
options . Auditor = audit . NewNop ( )
2022-08-29 23:45:40 +00:00
}
2023-03-16 18:03:37 +00:00
if options . SSHConfig . HostnamePrefix == "" {
options . SSHConfig . HostnamePrefix = "coder."
}
2023-05-03 23:02:35 +00:00
if options . TracerProvider == nil {
options . TracerProvider = trace . NewNoopTracerProvider ( )
}
2023-02-02 19:53:48 +00:00
if options . SetUserGroups == nil {
2023-03-21 19:25:45 +00:00
options . SetUserGroups = func ( ctx context . Context , _ database . Store , id uuid . UUID , groups [ ] string ) error {
options . Logger . Warn ( ctx , "attempted to assign OIDC groups without enterprise license" ,
slog . F ( "id" , id ) , slog . F ( "groups" , groups ) ,
)
return nil
}
2023-02-02 19:53:48 +00:00
}
2023-03-07 14:14:58 +00:00
if options . TemplateScheduleStore == nil {
2023-04-04 12:48:35 +00:00
options . TemplateScheduleStore = & atomic . Pointer [ schedule . TemplateScheduleStore ] { }
}
if options . TemplateScheduleStore . Load ( ) == nil {
v := schedule . NewAGPLTemplateScheduleStore ( )
options . TemplateScheduleStore . Store ( & v )
2023-03-07 14:14:58 +00:00
}
2023-04-03 06:28:42 +00:00
if options . HealthcheckFunc == nil {
options . HealthcheckFunc = func ( ctx context . Context ) ( * healthcheck . Report , error ) {
return healthcheck . Run ( ctx , & healthcheck . ReportOptions {
2023-04-25 15:11:45 +00:00
AccessURL : options . AccessURL ,
DERPMap : options . DERPMap . Clone ( ) ,
2023-04-03 06:28:42 +00:00
} )
}
}
if options . HealthcheckTimeout == 0 {
options . HealthcheckTimeout = 30 * time . Second
}
if options . HealthcheckRefresh == 0 {
options . HealthcheckRefresh = 10 * time . Minute
}
2022-05-26 03:14:08 +00:00
2022-06-21 16:53:36 +00:00
siteCacheDir := options . CacheDir
if siteCacheDir != "" {
siteCacheDir = filepath . Join ( siteCacheDir , "site" )
}
2023-01-17 18:38:08 +00:00
binFS , binHashes , err := site . ExtractOrReadBinFS ( siteCacheDir , site . FS ( ) )
2022-06-21 16:53:36 +00:00
if err != nil {
panic ( xerrors . Errorf ( "read site bin failed: %w" , err ) )
}
2022-09-01 19:58:23 +00:00
metricsCache := metricscache . New (
options . Database ,
options . Logger . Named ( "metrics_cache" ) ,
2023-03-09 03:05:45 +00:00
metricscache . Intervals {
TemplateDAUs : options . MetricsCacheRefreshInterval ,
DeploymentStats : options . AgentStatsRefreshInterval ,
} ,
2022-09-01 19:58:23 +00:00
)
2023-02-10 16:52:49 +00:00
staticHandler := site . Handler ( site . FS ( ) , binFS , binHashes )
// Static file handler must be wrapped with HSTS handler if the
// StrictTransportSecurityAge is set. We only need to set this header on
// static files since it only affects browsers.
staticHandler = httpmw . HSTS ( staticHandler , options . StrictTransportSecurityCfg )
2023-03-07 19:38:11 +00:00
oauthConfigs := & httpmw . OAuth2Configs {
Github : options . GithubOAuth2Config ,
OIDC : options . OIDCConfig ,
}
2023-02-10 18:23:02 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
2023-04-17 19:57:21 +00:00
r := chi . NewRouter ( )
2022-05-26 03:14:08 +00:00
api := & API {
2023-02-10 18:23:02 +00:00
ctx : ctx ,
cancel : cancel ,
2022-11-06 21:27:09 +00:00
ID : uuid . New ( ) ,
2022-06-04 20:13:37 +00:00
Options : options ,
2022-09-20 04:11:01 +00:00
RootHandler : r ,
2023-02-10 16:52:49 +00:00
siteHandler : staticHandler ,
2022-09-20 04:11:01 +00:00
HTTPAuth : & HTTPAuthorizer {
2022-08-22 22:02:50 +00:00
Authorizer : options . Authorizer ,
Logger : options . Logger ,
} ,
2023-04-04 00:59:41 +00:00
WorkspaceAppsProvider : workspaceapps . NewDBTokenProvider (
2023-03-07 19:38:11 +00:00
options . Logger . Named ( "workspaceapps" ) ,
options . AccessURL ,
options . Authorizer ,
options . Database ,
2023-03-07 21:10:01 +00:00
options . DeploymentValues ,
2023-03-07 19:38:11 +00:00
oauthConfigs ,
2023-03-30 13:24:51 +00:00
options . AgentInactiveDisconnectTimeout ,
2023-04-05 18:41:55 +00:00
options . AppSecurityKey ,
2023-03-07 19:38:11 +00:00
) ,
2023-03-07 14:14:58 +00:00
metricsCache : metricsCache ,
Auditor : atomic . Pointer [ audit . Auditor ] { } ,
2023-04-04 12:48:35 +00:00
TemplateScheduleStore : options . TemplateScheduleStore ,
2023-03-07 14:14:58 +00:00
Experiments : experiments ,
2023-04-03 06:28:42 +00:00
healthCheckGroup : & singleflight . Group [ string , * healthcheck . Report ] { } ,
2022-05-26 03:14:08 +00:00
}
2022-12-01 17:43:28 +00:00
if options . UpdateCheckOptions != nil {
api . updateChecker = updatecheck . New (
options . Database ,
options . Logger . Named ( "update_checker" ) ,
* options . UpdateCheckOptions ,
)
}
2023-03-30 08:36:57 +00:00
var oidcAuthURLParams map [ string ] string
if options . OIDCConfig != nil {
oidcAuthURLParams = options . OIDCConfig . AuthURLParams
}
2022-09-20 04:11:01 +00:00
api . Auditor . Store ( & options . Auditor )
2022-09-20 00:46:29 +00:00
api . workspaceAgentCache = wsconncache . New ( api . dialWorkspaceAgentTailnet , 0 )
2022-10-17 13:43:30 +00:00
api . TailnetCoordinator . Store ( & options . TailnetCoordinator )
2022-09-22 22:30:32 +00:00
2023-04-05 18:41:55 +00:00
api . workspaceAppServer = & workspaceapps . Server {
Logger : options . Logger . Named ( "workspaceapps" ) ,
2023-04-17 19:57:21 +00:00
DashboardURL : api . AccessURL ,
AccessURL : api . AccessURL ,
Hostname : api . AppHostname ,
HostnameRegex : api . AppHostnameRegex ,
RealIPConfig : options . RealIPConfig ,
2023-04-05 18:41:55 +00:00
SignedTokenProvider : api . WorkspaceAppsProvider ,
WorkspaceConnCache : api . workspaceAgentCache ,
AppSecurityKey : options . AppSecurityKey ,
2023-04-17 19:57:21 +00:00
DisablePathApps : options . DeploymentValues . DisablePathApps . Value ( ) ,
SecureAuthCookie : options . DeploymentValues . SecureAuthCookie . Value ( ) ,
2023-04-05 18:41:55 +00:00
}
2023-03-07 19:38:11 +00:00
apiKeyMiddleware := httpmw . ExtractAPIKeyMW ( httpmw . ExtractAPIKeyConfig {
2023-02-03 17:38:36 +00:00
DB : options . Database ,
OAuth2Configs : oauthConfigs ,
RedirectToLogin : false ,
2023-03-07 21:10:01 +00:00
DisableSessionExpiryRefresh : options . DeploymentValues . DisableSessionExpiryRefresh . Value ( ) ,
2023-02-03 17:38:36 +00:00
Optional : false ,
2022-09-22 22:30:32 +00:00
} )
// Same as above but it redirects to the login page.
2023-03-07 19:38:11 +00:00
apiKeyMiddlewareRedirect := httpmw . ExtractAPIKeyMW ( httpmw . ExtractAPIKeyConfig {
2023-02-03 17:38:36 +00:00
DB : options . Database ,
OAuth2Configs : oauthConfigs ,
RedirectToLogin : true ,
2023-03-07 21:10:01 +00:00
DisableSessionExpiryRefresh : options . DeploymentValues . DisableSessionExpiryRefresh . Value ( ) ,
2023-02-03 17:38:36 +00:00
Optional : false ,
2022-09-22 22:30:32 +00:00
} )
2023-04-17 19:57:21 +00:00
// Same as the first but it's optional.
apiKeyMiddlewareOptional := httpmw . ExtractAPIKeyMW ( httpmw . ExtractAPIKeyConfig {
DB : options . Database ,
OAuth2Configs : oauthConfigs ,
RedirectToLogin : false ,
DisableSessionExpiryRefresh : options . DeploymentValues . DisableSessionExpiryRefresh . Value ( ) ,
Optional : true ,
} )
2022-05-03 21:10:19 +00:00
2023-01-05 18:05:20 +00:00
// API rate limit middleware. The counter is local and not shared between
// replicas or instances of this middleware.
apiRateLimiter := httpmw . RateLimit ( options . APIRateLimit , time . Minute )
2023-03-01 22:18:14 +00:00
derpHandler := derphttp . Handler ( api . DERPServer )
derpHandler , api . derpCloseFunc = tailnet . WithWebsocketSupport ( api . DERPServer , derpHandler )
2023-05-22 18:02:39 +00:00
cors := httpmw . Cors ( options . DeploymentValues . Dangerous . AllowAllCors . Value ( ) )
2023-03-01 22:18:14 +00:00
2022-05-03 12:48:02 +00:00
r . Use (
2023-05-22 18:02:39 +00:00
cors ,
2022-08-29 22:00:52 +00:00
httpmw . Recover ( api . Logger ) ,
2022-11-28 21:22:10 +00:00
tracing . StatusWriterMiddleware ,
tracing . Middleware ( api . TracerProvider ) ,
httpmw . AttachRequestID ,
2022-10-23 18:21:49 +00:00
httpmw . ExtractRealIP ( api . RealIPConfig ) ,
2022-08-29 22:00:52 +00:00
httpmw . Logger ( api . Logger ) ,
2022-11-05 00:03:01 +00:00
httpmw . Prometheus ( options . PrometheusRegistry ) ,
2023-04-05 18:41:55 +00:00
// SubdomainAppMW checks if the first subdomain is a valid app URL. If
// it is, it will serve that application.
2023-03-07 19:38:11 +00:00
//
2023-04-05 18:41:55 +00:00
// Workspace apps do their own auth and must be BEFORE the auth
// middleware.
2023-04-17 19:57:21 +00:00
api . workspaceAppServer . HandleSubdomain ( apiRateLimiter ) ,
2022-09-01 19:58:23 +00:00
// Build-Version is helpful for debugging.
func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2022-09-21 22:07:00 +00:00
w . Header ( ) . Add ( "X-Coder-Build-Version" , buildinfo . Version ( ) )
2022-09-01 19:58:23 +00:00
next . ServeHTTP ( w , r )
} )
} ,
2023-02-25 17:18:45 +00:00
// This header stops a browser from trying to MIME-sniff the content type and
// forces it to stick with the declared content-type. This is the only valid
// value for this header.
// See: https://github.com/coder/security/issues/12
func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Add ( "X-Content-Type-Options" , "nosniff" )
next . ServeHTTP ( w , r )
} )
} ,
2022-09-13 19:26:46 +00:00
httpmw . CSRF ( options . SecureAuthCookie ) ,
2022-05-03 12:48:02 +00:00
)
2022-11-07 19:35:52 +00:00
r . Get ( "/healthz" , func ( w http . ResponseWriter , r * http . Request ) { _ , _ = w . Write ( [ ] byte ( "OK" ) ) } )
2023-04-05 18:41:55 +00:00
// Attach workspace apps routes.
r . Group ( func ( r chi . Router ) {
2023-03-07 19:38:11 +00:00
r . Use ( apiRateLimiter )
2023-04-05 18:41:55 +00:00
api . workspaceAppServer . Attach ( r )
} )
2022-09-01 16:41:47 +00:00
r . Route ( "/derp" , func ( r chi . Router ) {
2023-03-01 22:18:14 +00:00
r . Get ( "/" , derpHandler . ServeHTTP )
2022-09-01 16:41:47 +00:00
// This is used when UDP is blocked, and latency must be checked via HTTP(s).
r . Get ( "/latency-check" , func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
} )
} )
2022-06-04 20:13:37 +00:00
2022-10-25 00:46:24 +00:00
r . Route ( "/gitauth" , func ( r chi . Router ) {
for _ , gitAuthConfig := range options . GitAuthConfigs {
r . Route ( fmt . Sprintf ( "/%s" , gitAuthConfig . ID ) , func ( r chi . Router ) {
r . Use (
2023-03-30 08:36:57 +00:00
httpmw . ExtractOAuth2 ( gitAuthConfig , options . HTTPClient , nil ) ,
2023-05-04 17:19:44 +00:00
apiKeyMiddlewareRedirect ,
2022-10-25 00:46:24 +00:00
)
r . Get ( "/callback" , api . gitAuthCallback ( gitAuthConfig ) )
} )
}
} )
2022-01-13 22:55:28 +00:00
r . Route ( "/api/v2" , func ( r chi . Router ) {
2022-09-20 04:11:01 +00:00
api . APIHandler = r
2022-09-20 20:16:26 +00:00
r . NotFound ( func ( rw http . ResponseWriter , r * http . Request ) { httpapi . RouteNotFound ( rw ) } )
2022-04-04 22:32:05 +00:00
r . Use (
2023-01-05 18:05:20 +00:00
// Specific routes can specify different limits, but every rate
// limit must be configurable by the admin.
apiRateLimiter ,
2022-04-04 22:32:05 +00:00
)
2022-12-22 14:53:14 +00:00
r . Get ( "/" , apiRoot )
2022-05-27 14:59:13 +00:00
// All CSP errors will be logged
r . Post ( "/csp/reports" , api . logReportCSPViolations )
2023-04-17 19:57:21 +00:00
r . Get ( "/buildinfo" , buildInfo ( api . AccessURL ) )
2023-04-25 14:37:52 +00:00
// /regions is overridden in the enterprise version
r . Group ( func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/regions" , api . regions )
} )
2023-03-09 03:05:45 +00:00
r . Route ( "/deployment" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/config" , api . deploymentValues )
r . Get ( "/stats" , api . deploymentStats )
2023-03-16 18:03:37 +00:00
r . Get ( "/ssh" , api . sshConfig )
2023-03-09 03:05:45 +00:00
} )
2023-01-18 19:12:53 +00:00
r . Route ( "/experiments" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/" , api . handleExperimentsGet )
} )
2022-12-22 14:53:14 +00:00
r . Get ( "/updatecheck" , api . updateCheck )
2022-09-07 16:38:19 +00:00
r . Route ( "/audit" , func ( r chi . Router ) {
r . Use (
apiKeyMiddleware ,
)
r . Get ( "/" , api . auditLogs )
r . Post ( "/testgenerate" , api . generateFakeAuditLog )
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/files" , func ( r chi . Router ) {
2022-04-04 22:32:05 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2023-01-05 18:05:20 +00:00
httpmw . RateLimit ( options . FilesRateLimit , time . Minute ) ,
2022-04-04 22:32:05 +00:00
)
2022-10-13 23:02:52 +00:00
r . Get ( "/{fileID}" , api . fileByID )
2022-05-26 03:14:08 +00:00
r . Post ( "/" , api . postFile )
2022-03-07 17:40:54 +00:00
} )
2022-05-27 16:19:13 +00:00
r . Route ( "/organizations" , func ( r chi . Router ) {
2022-01-25 19:52:58 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-01-25 19:52:58 +00:00
)
2022-05-27 16:19:13 +00:00
r . Post ( "/" , api . postOrganizations )
r . Route ( "/{organization}" , func ( r chi . Router ) {
r . Use (
httpmw . ExtractOrganizationParam ( options . Database ) ,
)
r . Get ( "/" , api . organization )
2023-02-02 21:47:53 +00:00
r . Post ( "/templateversions" , api . postTemplateVersionsByOrganization )
2022-05-27 16:19:13 +00:00
r . Route ( "/templates" , func ( r chi . Router ) {
r . Post ( "/" , api . postTemplateByOrganization )
r . Get ( "/" , api . templatesByOrganization )
2022-12-09 19:29:50 +00:00
r . Get ( "/examples" , api . templateExamples )
2023-02-02 21:47:53 +00:00
r . Route ( "/{templatename}" , func ( r chi . Router ) {
r . Get ( "/" , api . templateByOrganizationAndName )
r . Route ( "/versions/{templateversionname}" , func ( r chi . Router ) {
r . Get ( "/" , api . templateVersionByOrganizationTemplateAndName )
r . Get ( "/previous" , api . previousTemplateVersionByOrganizationTemplateAndName )
} )
} )
2022-04-25 21:11:03 +00:00
} )
2022-05-27 16:19:13 +00:00
r . Route ( "/members" , func ( r chi . Router ) {
r . Get ( "/roles" , api . assignableOrgRoles )
r . Route ( "/{user}" , func ( r chi . Router ) {
r . Use (
2022-10-14 16:46:38 +00:00
httpmw . ExtractUserParam ( options . Database , false ) ,
2022-05-27 16:19:13 +00:00
httpmw . ExtractOrganizationMemberParam ( options . Database ) ,
)
r . Put ( "/roles" , api . putMemberRoles )
2022-09-24 01:17:10 +00:00
r . Post ( "/workspaces" , api . postWorkspacesByOrganization )
2022-05-27 16:19:13 +00:00
} )
2022-04-29 14:04:19 +00:00
} )
} )
2022-03-07 17:40:54 +00:00
} )
r . Route ( "/parameters/{scope}/{id}" , func ( r chi . Router ) {
2022-05-31 13:06:42 +00:00
r . Use ( apiKeyMiddleware )
2022-05-26 03:14:08 +00:00
r . Post ( "/" , api . postParameter )
r . Get ( "/" , api . parameters )
2022-03-07 17:40:54 +00:00
r . Route ( "/{name}" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Delete ( "/" , api . deleteParameter )
2022-01-23 05:58:10 +00:00
} )
2022-01-20 13:46:51 +00:00
} )
2022-04-06 17:42:40 +00:00
r . Route ( "/templates/{template}" , func ( r chi . Router ) {
2022-01-24 17:07:42 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-04-06 17:42:40 +00:00
httpmw . ExtractTemplateParam ( options . Database ) ,
2022-01-24 17:07:42 +00:00
)
2022-09-01 19:58:23 +00:00
r . Get ( "/daus" , api . templateDAUs )
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . template )
r . Delete ( "/" , api . deleteTemplate )
2022-06-08 14:14:57 +00:00
r . Patch ( "/" , api . patchTemplateMeta )
2022-03-07 17:40:54 +00:00
r . Route ( "/versions" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . templateVersionsByTemplate )
r . Patch ( "/" , api . patchActiveTemplateVersion )
r . Get ( "/{templateversionname}" , api . templateVersionByName )
2022-01-25 19:52:58 +00:00
} )
} )
2022-04-06 17:42:40 +00:00
r . Route ( "/templateversions/{templateversion}" , func ( r chi . Router ) {
2022-03-07 17:40:54 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-04-06 17:42:40 +00:00
httpmw . ExtractTemplateVersionParam ( options . Database ) ,
2022-03-07 17:40:54 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . templateVersion )
2023-03-23 16:26:50 +00:00
r . Patch ( "/" , api . patchTemplateVersion )
2022-05-26 03:14:08 +00:00
r . Patch ( "/cancel" , api . patchCancelTemplateVersion )
r . Get ( "/schema" , api . templateVersionSchema )
r . Get ( "/parameters" , api . templateVersionParameters )
2023-01-17 10:22:11 +00:00
r . Get ( "/rich-parameters" , api . templateVersionRichParameters )
2023-02-27 16:18:19 +00:00
r . Get ( "/gitauth" , api . templateVersionGitAuth )
2023-02-15 17:24:15 +00:00
r . Get ( "/variables" , api . templateVersionVariables )
2022-05-26 03:14:08 +00:00
r . Get ( "/resources" , api . templateVersionResources )
r . Get ( "/logs" , api . templateVersionLogs )
2022-06-01 14:44:53 +00:00
r . Route ( "/dry-run" , func ( r chi . Router ) {
r . Post ( "/" , api . postTemplateVersionDryRun )
r . Get ( "/{jobID}" , api . templateVersionDryRun )
r . Get ( "/{jobID}/resources" , api . templateVersionDryRunResources )
r . Get ( "/{jobID}/logs" , api . templateVersionDryRunLogs )
r . Patch ( "/{jobID}/cancel" , api . patchTemplateVersionDryRunCancel )
} )
2022-03-07 17:40:54 +00:00
} )
r . Route ( "/users" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/first" , api . firstUser )
r . Post ( "/first" , api . postFirstUser )
2023-01-05 18:05:20 +00:00
r . Get ( "/authmethods" , api . userAuthMethods )
2022-10-20 17:01:23 +00:00
r . Group ( func ( r chi . Router ) {
2023-01-05 18:05:20 +00:00
// We use a tight limit for password login to protect against
// audit-log write DoS, pbkdf2 DoS, and simple brute-force
// attacks.
2022-10-20 17:01:23 +00:00
//
2023-01-05 18:05:20 +00:00
// This value is intentionally increased during tests.
r . Use ( httpmw . RateLimit ( options . LoginRateLimit , time . Minute ) )
2022-10-20 17:01:23 +00:00
r . Post ( "/login" , api . postLogin )
2023-01-05 18:05:20 +00:00
r . Route ( "/oauth2" , func ( r chi . Router ) {
r . Route ( "/github" , func ( r chi . Router ) {
2023-03-30 08:36:57 +00:00
r . Use ( httpmw . ExtractOAuth2 ( options . GithubOAuth2Config , options . HTTPClient , nil ) )
2023-01-05 18:05:20 +00:00
r . Get ( "/callback" , api . userOAuth2Github )
} )
} )
r . Route ( "/oidc/callback" , func ( r chi . Router ) {
2023-03-30 08:36:57 +00:00
r . Use ( httpmw . ExtractOAuth2 ( options . OIDCConfig , options . HTTPClient , oidcAuthURLParams ) )
2023-01-05 18:05:20 +00:00
r . Get ( "/" , api . userOIDC )
2022-04-23 22:58:57 +00:00
} )
2022-08-01 04:05:35 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Group ( func ( r chi . Router ) {
2022-05-03 21:10:19 +00:00
r . Use (
apiKeyMiddleware ,
)
2022-05-26 03:14:08 +00:00
r . Post ( "/" , api . postUser )
r . Get ( "/" , api . users )
2022-05-27 20:47:03 +00:00
r . Post ( "/logout" , api . postLogout )
2022-05-03 21:10:19 +00:00
// These routes query information about site wide roles.
r . Route ( "/roles" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . assignableSiteRoles )
2022-05-03 21:10:19 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/{user}" , func ( r chi . Router ) {
2022-10-14 16:46:38 +00:00
r . Use ( httpmw . ExtractUserParam ( options . Database , false ) )
2022-09-12 23:24:20 +00:00
r . Delete ( "/" , api . deleteUser )
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . userByName )
r . Put ( "/profile" , api . putUserProfile )
2022-05-16 20:29:27 +00:00
r . Route ( "/status" , func ( r chi . Router ) {
2023-01-11 13:08:04 +00:00
r . Put ( "/suspend" , api . putSuspendUserAccount ( ) )
r . Put ( "/activate" , api . putActivateUserAccount ( ) )
2022-05-16 20:29:27 +00:00
} )
2022-05-06 14:20:08 +00:00
r . Route ( "/password" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Put ( "/" , api . putUserPassword )
2022-05-06 14:20:08 +00:00
} )
2022-05-03 21:10:19 +00:00
// These roles apply to the site wide permissions.
2022-05-26 03:14:08 +00:00
r . Put ( "/roles" , api . putUserRoles )
r . Get ( "/roles" , api . userRoles )
2022-05-03 21:10:19 +00:00
2022-06-27 18:50:52 +00:00
r . Route ( "/keys" , func ( r chi . Router ) {
2022-10-06 21:56:43 +00:00
r . Post ( "/" , api . postAPIKey )
2022-10-06 19:02:27 +00:00
r . Route ( "/tokens" , func ( r chi . Router ) {
r . Post ( "/" , api . postToken )
r . Get ( "/" , api . tokens )
2023-03-16 15:25:08 +00:00
r . Get ( "/tokenconfig" , api . tokenConfig )
2023-03-02 17:39:38 +00:00
r . Route ( "/{keyname}" , func ( r chi . Router ) {
r . Get ( "/" , api . apiKeyByName )
} )
2022-10-06 19:02:27 +00:00
} )
r . Route ( "/{keyid}" , func ( r chi . Router ) {
2023-03-02 17:39:38 +00:00
r . Get ( "/" , api . apiKeyByID )
2022-10-06 19:02:27 +00:00
r . Delete ( "/" , api . deleteAPIKey )
} )
2022-06-27 18:50:52 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/organizations" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . organizationsByUser )
r . Get ( "/{organizationname}" , api . organizationByUserAndName )
2022-03-07 17:40:54 +00:00
} )
2022-06-10 16:08:50 +00:00
r . Route ( "/workspace/{workspacename}" , func ( r chi . Router ) {
r . Get ( "/" , api . workspaceByOwnerAndName )
r . Get ( "/builds/{buildnumber}" , api . workspaceBuildByBuildNumber )
} )
2022-05-26 03:14:08 +00:00
r . Get ( "/gitsshkey" , api . gitSSHKey )
r . Put ( "/gitsshkey" , api . regenerateGitSSHKey )
2022-01-24 17:07:42 +00:00
} )
} )
} )
2022-04-11 21:06:15 +00:00
r . Route ( "/workspaceagents" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Post ( "/azure-instance-identity" , api . postWorkspaceAuthAzureInstanceIdentity )
r . Post ( "/aws-instance-identity" , api . postWorkspaceAuthAWSInstanceIdentity )
r . Post ( "/google-instance-identity" , api . postWorkspaceAuthGoogleInstanceIdentity )
2022-04-11 21:06:15 +00:00
r . Route ( "/me" , func ( r chi . Router ) {
2022-03-07 17:40:54 +00:00
r . Use ( httpmw . ExtractWorkspaceAgent ( options . Database ) )
2023-03-31 20:26:19 +00:00
r . Get ( "/manifest" , api . workspaceAgentManifest )
// This route is deprecated and will be removed in a future release.
// New agents will use /me/manifest instead.
r . Get ( "/metadata" , api . workspaceAgentManifest )
2023-02-07 21:35:09 +00:00
r . Post ( "/startup" , api . postWorkspaceAgentStartup )
2023-03-23 19:09:13 +00:00
r . Patch ( "/startup-logs" , api . patchWorkspaceAgentStartupLogs )
2022-09-23 19:51:04 +00:00
r . Post ( "/app-health" , api . postWorkspaceAppHealth )
2022-10-25 00:46:24 +00:00
r . Get ( "/gitauth" , api . workspaceAgentsGitAuth )
2022-05-26 03:14:08 +00:00
r . Get ( "/gitsshkey" , api . agentGitSSHKey )
2022-09-01 01:09:44 +00:00
r . Get ( "/coordinate" , api . workspaceAgentCoordinate )
2022-11-18 22:46:53 +00:00
r . Post ( "/report-stats" , api . workspaceAgentReportStats )
2023-01-24 12:24:27 +00:00
r . Post ( "/report-lifecycle" , api . workspaceAgentReportLifecycle )
2023-03-31 20:26:19 +00:00
r . Post ( "/metadata/{key}" , api . workspaceAgentPostMetadata )
2022-03-07 17:40:54 +00:00
} )
2022-04-11 21:06:15 +00:00
r . Route ( "/{workspaceagent}" , func ( r chi . Router ) {
2022-03-07 17:40:54 +00:00
r . Use (
2023-04-17 19:57:21 +00:00
// Allow either API key or external workspace proxy auth and require it.
apiKeyMiddlewareOptional ,
httpmw . ExtractWorkspaceProxy ( httpmw . ExtractWorkspaceProxyConfig {
DB : options . Database ,
Optional : true ,
} ) ,
httpmw . RequireAPIKeyOrWorkspaceProxyAuth ( ) ,
2022-04-11 21:06:15 +00:00
httpmw . ExtractWorkspaceAgentParam ( options . Database ) ,
2022-06-10 14:46:48 +00:00
httpmw . ExtractWorkspaceParam ( options . Database ) ,
2022-03-07 17:40:54 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaceAgent )
2023-03-31 20:26:19 +00:00
r . Get ( "/watch-metadata" , api . watchWorkspaceAgentMetadata )
2023-03-23 19:09:13 +00:00
r . Get ( "/startup-logs" , api . workspaceAgentStartupLogs )
2022-10-06 12:38:22 +00:00
r . Get ( "/listening-ports" , api . workspaceAgentListeningPorts )
2022-09-01 01:09:44 +00:00
r . Get ( "/connection" , api . workspaceAgentConnection )
r . Get ( "/coordinate" , api . workspaceAgentClientCoordinate )
2023-04-05 18:41:55 +00:00
// PTY is part of workspaceAppServer.
2022-02-21 20:36:29 +00:00
} )
} )
2022-05-18 15:09:07 +00:00
r . Route ( "/workspaces" , func ( r chi . Router ) {
2022-02-12 19:34:04 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-02-12 19:34:04 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaces )
2022-05-18 15:09:07 +00:00
r . Route ( "/{workspace}" , func ( r chi . Router ) {
r . Use (
httpmw . ExtractWorkspaceParam ( options . Database ) ,
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspace )
2022-08-26 09:28:38 +00:00
r . Patch ( "/" , api . patchWorkspace )
2022-05-18 15:09:07 +00:00
r . Route ( "/builds" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaceBuilds )
r . Post ( "/" , api . postWorkspaceBuilds )
2022-05-18 15:09:07 +00:00
} )
r . Route ( "/autostart" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Put ( "/" , api . putWorkspaceAutostart )
2022-05-18 15:09:07 +00:00
} )
2022-05-19 19:09:27 +00:00
r . Route ( "/ttl" , func ( r chi . Router ) {
2022-05-26 03:14:08 +00:00
r . Put ( "/" , api . putWorkspaceTTL )
2022-05-18 15:09:07 +00:00
} )
2022-05-26 03:14:08 +00:00
r . Get ( "/watch" , api . watchWorkspace )
2022-05-26 17:08:11 +00:00
r . Put ( "/extend" , api . putExtendWorkspace )
2022-04-07 09:03:35 +00:00
} )
2022-02-12 19:34:04 +00:00
} )
2022-03-07 17:40:54 +00:00
r . Route ( "/workspacebuilds/{workspacebuild}" , func ( r chi . Router ) {
2022-02-12 19:34:04 +00:00
r . Use (
2022-04-23 22:58:57 +00:00
apiKeyMiddleware ,
2022-03-07 17:40:54 +00:00
httpmw . ExtractWorkspaceBuildParam ( options . Database ) ,
httpmw . ExtractWorkspaceParam ( options . Database ) ,
2022-02-12 19:34:04 +00:00
)
2022-05-26 03:14:08 +00:00
r . Get ( "/" , api . workspaceBuild )
r . Patch ( "/cancel" , api . patchCancelWorkspaceBuild )
r . Get ( "/logs" , api . workspaceBuildLogs )
2023-01-17 15:24:45 +00:00
r . Get ( "/parameters" , api . workspaceBuildParameters )
2022-05-26 03:14:08 +00:00
r . Get ( "/resources" , api . workspaceBuildResources )
r . Get ( "/state" , api . workspaceBuildState )
2022-02-12 19:34:04 +00:00
} )
2022-09-22 22:30:32 +00:00
r . Route ( "/authcheck" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Post ( "/" , api . checkAuthorization )
} )
r . Route ( "/applications" , func ( r chi . Router ) {
r . Route ( "/host" , func ( r chi . Router ) {
// Don't leak the hostname to unauthenticated users.
r . Use ( apiKeyMiddleware )
r . Get ( "/" , api . appHost )
} )
r . Route ( "/auth-redirect" , func ( r chi . Router ) {
// We want to redirect to login if they are not authenticated.
r . Use ( apiKeyMiddlewareRedirect )
// This is a GET request as it's redirected to by the subdomain app
// handler and the login page.
r . Get ( "/" , api . workspaceApplicationAuth )
} )
} )
2023-01-26 01:03:47 +00:00
r . Route ( "/insights" , func ( r chi . Router ) {
r . Use ( apiKeyMiddleware )
r . Get ( "/daus" , api . deploymentDAUs )
} )
2023-01-25 21:27:36 +00:00
r . Route ( "/debug" , func ( r chi . Router ) {
r . Use (
apiKeyMiddleware ,
// Ensure only owners can access debug endpoints.
func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( rw http . ResponseWriter , r * http . Request ) {
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceDebugInfo ) {
httpapi . ResourceNotFound ( rw )
return
}
next . ServeHTTP ( rw , r )
} )
} ,
)
r . Get ( "/coordinator" , api . debugCoordinator )
2023-04-03 06:28:42 +00:00
r . Get ( "/health" , api . debugDeploymentHealth )
2023-01-25 21:27:36 +00:00
} )
2022-01-13 22:55:28 +00:00
} )
2022-06-13 18:14:22 +00:00
2022-12-19 17:43:46 +00:00
if options . SwaggerEndpoint {
// Swagger UI requires the URL trailing slash. Otherwise, the browser tries to load /assets
// from http://localhost:8080/assets instead of http://localhost:8080/swagger/assets.
r . Get ( "/swagger" , http . RedirectHandler ( "/swagger/" , http . StatusTemporaryRedirect ) . ServeHTTP )
2023-01-11 10:42:49 +00:00
// See globalHTTPSwaggerHandler comment as to why we use a package
// global variable here.
r . Get ( "/swagger/*" , globalHTTPSwaggerHandler )
2022-12-19 17:43:46 +00:00
}
2023-05-02 13:30:44 +00:00
// Add CSP headers to all static assets and pages. CSP headers only affect
// browsers, so these don't make sense on api routes.
cspMW := httpmw . CSPHeaders ( func ( ) [ ] string {
2023-05-22 18:02:39 +00:00
if api . DeploymentValues . Dangerous . AllowAllCors {
// In this mode, allow all external requests
return [ ] string { "*" }
}
2023-05-02 13:30:44 +00:00
if f := api . WorkspaceProxyHostsFn . Load ( ) ; f != nil {
return ( * f ) ( )
}
// By default we do not add extra websocket connections to the CSP
return [ ] string { }
} )
r . NotFound ( cspMW ( compressHandler ( http . HandlerFunc ( api . siteHandler . ServeHTTP ) ) ) . ServeHTTP )
2023-05-11 20:42:30 +00:00
// This must be before all middleware to improve the response time.
// So make a new router, and mount the old one as the root.
rootRouter := chi . NewRouter ( )
// This is the only route we add before all the middleware.
// We want to time the latency of the request, so any middleware will
// interfere with that timing.
2023-05-22 18:02:39 +00:00
rootRouter . Get ( "/latency-check" , cors ( LatencyCheck ( options . DeploymentValues . Dangerous . AllowAllCors . Value ( ) , api . AccessURL ) ) . ServeHTTP )
2023-05-11 20:42:30 +00:00
rootRouter . Mount ( "/" , r )
api . RootHandler = rootRouter
2022-05-26 03:14:08 +00:00
return api
2022-01-13 22:55:28 +00:00
}
2022-02-01 22:15:26 +00:00
2022-05-26 03:14:08 +00:00
type API struct {
2023-02-10 18:23:02 +00:00
// ctx is canceled immediately on shutdown, it can be used to abort
// interruptible tasks.
ctx context . Context
cancel context . CancelFunc
2022-02-20 22:29:16 +00:00
* Options
2022-11-06 21:27:09 +00:00
// ID is a uniquely generated ID on initialization.
// This is used to associate objects with a specific
// Coder API instance, like workspace agents to a
// specific replica.
ID uuid . UUID
2022-09-22 15:14:22 +00:00
Auditor atomic . Pointer [ audit . Auditor ]
WorkspaceClientCoordinateOverride atomic . Pointer [ func ( rw http . ResponseWriter ) bool ]
2022-10-17 13:43:30 +00:00
TailnetCoordinator atomic . Pointer [ tailnet . Coordinator ]
2022-11-14 17:57:33 +00:00
QuotaCommitter atomic . Pointer [ proto . QuotaCommitter ]
2023-05-02 13:30:44 +00:00
// WorkspaceProxyHostsFn returns the hosts of healthy workspace proxies
// for header reasons.
WorkspaceProxyHostsFn atomic . Pointer [ func ( ) [ ] string ]
// TemplateScheduleStore is a pointer to an atomic pointer because this is
// passed to another struct, and we want them all to be the same reference.
TemplateScheduleStore * atomic . Pointer [ schedule . TemplateScheduleStore ]
2023-02-03 17:38:36 +00:00
HTTPAuth * HTTPAuthorizer
2022-02-20 22:29:16 +00:00
2022-09-20 04:11:01 +00:00
// APIHandler serves "/api/v2"
APIHandler chi . Router
// RootHandler serves "/"
RootHandler chi . Router
2022-09-01 01:09:44 +00:00
2022-12-01 17:43:28 +00:00
siteHandler http . Handler
2022-11-16 22:34:06 +00:00
WebsocketWaitMutex sync . Mutex
WebsocketWaitGroup sync . WaitGroup
2023-03-01 22:18:14 +00:00
derpCloseFunc func ( )
2022-11-16 22:34:06 +00:00
2023-03-07 19:38:11 +00:00
metricsCache * metricscache . Cache
workspaceAgentCache * wsconncache . Cache
updateChecker * updatecheck . Checker
2023-04-04 00:59:41 +00:00
WorkspaceAppsProvider workspaceapps . SignedTokenProvider
2023-04-05 18:41:55 +00:00
workspaceAppServer * workspaceapps . Server
2023-01-18 19:12:53 +00:00
// Experiments contains the list of experiments currently enabled.
// This is used to gate features that are not yet ready for production.
Experiments codersdk . Experiments
2023-04-03 06:28:42 +00:00
healthCheckGroup * singleflight . Group [ string , * healthcheck . Report ]
2022-02-01 22:15:26 +00:00
}
2022-04-08 14:35:29 +00:00
2022-05-26 03:14:08 +00:00
// Close waits for all WebSocket connections to drain before returning.
2022-06-04 20:13:37 +00:00
func ( api * API ) Close ( ) error {
2023-02-10 18:23:02 +00:00
api . cancel ( )
2023-03-01 22:18:14 +00:00
api . derpCloseFunc ( )
2023-02-10 18:23:02 +00:00
2022-11-16 22:34:06 +00:00
api . WebsocketWaitMutex . Lock ( )
api . WebsocketWaitGroup . Wait ( )
api . WebsocketWaitMutex . Unlock ( )
2022-06-04 20:13:37 +00:00
2022-09-01 19:58:23 +00:00
api . metricsCache . Close ( )
2022-12-01 17:43:28 +00:00
if api . updateChecker != nil {
api . updateChecker . Close ( )
}
2022-10-17 13:43:30 +00:00
coordinator := api . TailnetCoordinator . Load ( )
if coordinator != nil {
_ = ( * coordinator ) . Close ( )
}
2022-06-04 20:13:37 +00:00
return api . workspaceAgentCache . Close ( )
2022-05-26 03:14:08 +00:00
}
2022-06-13 18:14:22 +00:00
func compressHandler ( h http . Handler ) http . Handler {
2023-05-11 19:30:51 +00:00
level := 5
if flag . Lookup ( "test.v" ) != nil {
level = 1
}
cmp := middleware . NewCompressor ( level ,
2022-06-13 18:14:22 +00:00
"text/*" ,
"application/*" ,
"image/*" ,
)
cmp . SetEncoder ( "br" , func ( w io . Writer , level int ) io . Writer {
return brotli . NewWriterLevel ( w , level )
} )
cmp . SetEncoder ( "zstd" , func ( w io . Writer , level int ) io . Writer {
zw , err := zstd . NewWriter ( w , zstd . WithEncoderLevel ( zstd . EncoderLevelFromZstd ( level ) ) )
if err != nil {
panic ( "invalid zstd compressor: " + err . Error ( ) )
}
return zw
} )
return cmp . Handler ( h )
}
2022-11-16 22:34:06 +00:00
2022-12-01 22:54:53 +00:00
// CreateInMemoryProvisionerDaemon is an in-memory connection to a provisionerd.
// Useful when starting coderd and provisionerd in the same process.
2022-11-16 22:34:06 +00:00
func ( api * API ) CreateInMemoryProvisionerDaemon ( ctx context . Context , debounce time . Duration ) ( client proto . DRPCProvisionerDaemonClient , err error ) {
2023-05-03 23:02:35 +00:00
tracer := api . TracerProvider . Tracer ( tracing . TracerName )
2022-11-22 18:19:32 +00:00
clientSession , serverSession := provisionersdk . MemTransportPipe ( )
2022-11-16 22:34:06 +00:00
defer func ( ) {
if err != nil {
_ = clientSession . Close ( )
_ = serverSession . Close ( )
}
} ( )
name := namesgenerator . GetRandomName ( 1 )
2023-03-10 18:09:28 +00:00
// nolint:gocritic // Inserting a provisioner daemon is a system function.
daemon , err := api . Database . InsertProvisionerDaemon ( dbauthz . AsSystemRestricted ( ctx ) , database . InsertProvisionerDaemonParams {
2022-11-16 22:34:06 +00:00
ID : uuid . New ( ) ,
CreatedAt : database . Now ( ) ,
Name : name ,
Provisioners : [ ] database . ProvisionerType { database . ProvisionerTypeEcho , database . ProvisionerTypeTerraform } ,
Tags : dbtype . StringMap {
provisionerdserver . TagScope : provisionerdserver . ScopeOrganization ,
} ,
} )
if err != nil {
return nil , xerrors . Errorf ( "insert provisioner daemon %q: %w" , name , err )
}
tags , err := json . Marshal ( daemon . Tags )
if err != nil {
return nil , xerrors . Errorf ( "marshal tags: %w" , err )
}
mux := drpcmux . New ( )
2023-02-27 16:18:19 +00:00
2022-11-16 22:34:06 +00:00
err = proto . DRPCRegisterProvisionerDaemon ( mux , & provisionerdserver . Server {
2023-03-07 14:14:58 +00:00
AccessURL : api . AccessURL ,
ID : daemon . ID ,
2023-03-17 20:25:08 +00:00
OIDCConfig : api . OIDCConfig ,
2023-03-07 14:14:58 +00:00
Database : api . Database ,
Pubsub : api . Pubsub ,
Provisioners : daemon . Provisioners ,
2023-03-22 19:37:08 +00:00
GitAuthConfigs : api . GitAuthConfigs ,
2023-03-07 14:14:58 +00:00
Telemetry : api . Telemetry ,
2023-05-03 23:02:35 +00:00
Tracer : tracer ,
2023-03-07 14:14:58 +00:00
Tags : tags ,
QuotaCommitter : & api . QuotaCommitter ,
Auditor : & api . Auditor ,
2023-04-04 12:48:35 +00:00
TemplateScheduleStore : api . TemplateScheduleStore ,
2023-03-07 14:14:58 +00:00
AcquireJobDebounce : debounce ,
Logger : api . Logger . Named ( fmt . Sprintf ( "provisionerd-%s" , daemon . Name ) ) ,
2023-05-18 04:29:22 +00:00
DeploymentValues : api . DeploymentValues ,
2022-11-16 22:34:06 +00:00
} )
if err != nil {
return nil , err
}
2023-05-03 23:02:35 +00:00
server := drpcserver . NewWithOptions ( & tracing . DRPCHandler { Handler : mux } ,
drpcserver . Options {
Log : func ( err error ) {
if xerrors . Is ( err , io . EOF ) {
return
}
api . Logger . Debug ( ctx , "drpc server error" , slog . Error ( err ) )
} ,
2022-11-16 22:34:06 +00:00
} ,
2023-05-03 23:02:35 +00:00
)
2022-11-16 22:34:06 +00:00
go func ( ) {
err := server . Serve ( ctx , serverSession )
if err != nil && ! xerrors . Is ( err , io . EOF ) {
api . Logger . Debug ( ctx , "provisioner daemon disconnected" , slog . Error ( err ) )
}
// close the sessions so we don't leak goroutines serving them.
_ = clientSession . Close ( )
_ = serverSession . Close ( )
} ( )
2022-11-22 18:19:32 +00:00
return proto . NewDRPCProvisionerDaemonClient ( clientSession ) , nil
2022-11-16 22:34:06 +00:00
}
2023-01-18 19:12:53 +00:00
// nolint:revive
2023-03-07 21:10:01 +00:00
func initExperiments ( log slog . Logger , raw [ ] string ) codersdk . Experiments {
2023-01-18 19:12:53 +00:00
exps := make ( [ ] codersdk . Experiment , 0 , len ( raw ) )
for _ , v := range raw {
switch v {
case "*" :
exps = append ( exps , codersdk . ExperimentsAll ... )
default :
ex := codersdk . Experiment ( strings . ToLower ( v ) )
if ! slice . Contains ( codersdk . ExperimentsAll , ex ) {
log . Warn ( context . Background ( ) , "🐉 HERE BE DRAGONS: opting into hidden experiment" , slog . F ( "experiment" , ex ) )
}
exps = append ( exps , ex )
}
}
return exps
}