workspaceapps
packageThis package is not in the latest version of its module.
Details
Validgo.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used, modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
- Learn more about best practices
Repository
Links
Documentation¶
Index¶
- Constants
- func AppConnectSessionTokenCookieName(accessMethod AccessMethod) string
- func AppConnectSessionTokenFromRequest(r *http.Request, accessMethod AccessMethod) string
- func WebsocketNetConn(ctx context.Context, conn *websocket.Conn, msgType websocket.MessageType) (context.Context, net.Conn)
- func WriteWorkspaceApp404(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, ...)
- func WriteWorkspaceApp500(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, ...)
- func WriteWorkspaceAppOffline(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, ...)
- func WriteWorkspaceOffline(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, ...)
- type AccessMethod
- type AgentProvider
- type DBTokenProvider
- type EncryptedAPIKeyPayload
- type IssueTokenRequest
- type Request
- type ResolveRequestOptions
- type Server
- type SignedToken
- type SignedTokenProvider
- type StatsCollector
- type StatsCollectorOptions
- type StatsReport
- type StatsReporter
Constants¶
const (// TODO(@deansheather): configurable expiryDefaultTokenExpiry =time.Minute// RedirectURIQueryParam is the query param for the app URL to be passed// back to the API auth endpoint on the main access URL.RedirectURIQueryParam = "redirect_uri")
const (DefaultStatsCollectorReportInterval = 30 *time.SecondDefaultStatsCollectorRollupWindow = 1 *time.MinuteDefaultStatsDBReporterBatchSize = 1024)
const (// This needs to be a super unique query parameter because we don't want to// conflict with query parameters that users may use.//nolint:gosecSubdomainProxyAPIKeyParam = "coder_application_connect_api_key_35e783")
Variables¶
This section is empty.
Functions¶
funcAppConnectSessionTokenCookieName¶added inv2.1.5
func AppConnectSessionTokenCookieName(accessMethodAccessMethod)string
AppConnectSessionTokenCookieName returns the cookie name for the sessiontoken for the given access method.
funcAppConnectSessionTokenFromRequest¶added inv2.1.5
func AppConnectSessionTokenFromRequest(r *http.Request, accessMethodAccessMethod)string
AppConnectSessionTokenFromRequest returns the session token from the requestif it exists. The access method is used to determine which cookie name touse.
We use different cookie names for path apps and for subdomain apps to avoidboth being set and sent to the server at the same time and the server usingthe wrong value.
We use different cookie names for:- path apps on primary access URL: coder_session_token- path apps on proxies: coder_path_app_session_token- subdomain apps: coder_subdomain_app_session_token
First we try the default function to get a token from request, which supportsquery parameters, the Coder-Session-Token header and the coder_session_tokencookie.
Then we try the specific cookie name for the access method.
funcWebsocketNetConn¶
func WebsocketNetConn(ctxcontext.Context, conn *websocket.Conn, msgTypewebsocket.MessageType) (context.Context,net.Conn)
WebsocketNetConn wraps websocket.NetConn and returns a context thatis tied to the parent context and the lifetime of the conn. Any errorduring read or write will cancel the context, but not close theconn. Close should be called to release context resources.
funcWriteWorkspaceApp404¶
func WriteWorkspaceApp404(logslog.Logger, accessURL *url.URL, rwhttp.ResponseWriter, r *http.Request, appReq *Request, warnings []string, detailsstring)
WriteWorkspaceApp404 writes a HTML 404 error page for a workspace app. IfappReq is not nil, it will be used to log the request details at debug level.
The 'warnings' parameter is sent to the user, 'details' is only shown in the logs.
funcWriteWorkspaceApp500¶
func WriteWorkspaceApp500(logslog.Logger, accessURL *url.URL, rwhttp.ResponseWriter, r *http.Request, appReq *Request, errerror, msgstring)
WriteWorkspaceApp500 writes a HTML 500 error page for a workspace app. IfappReq is not nil, it's fields will be added to the logged error message.
funcWriteWorkspaceAppOffline¶
func WriteWorkspaceAppOffline(logslog.Logger, accessURL *url.URL, rwhttp.ResponseWriter, r *http.Request, appReq *Request, msgstring)
WriteWorkspaceAppOffline writes a HTML 502 error page for a workspace app. IfappReq is not nil, it will be used to log the request details at debug level.
funcWriteWorkspaceOffline¶added inv2.7.0
func WriteWorkspaceOffline(logslog.Logger, accessURL *url.URL, rwhttp.ResponseWriter, r *http.Request, appReq *Request)
WriteWorkspaceOffline writes a HTML 400 error page for a workspace app. IfappReq is not nil, it will be used to log the request details at debug level.
Types¶
typeAccessMethod¶
type AccessMethodstring
const (AccessMethodPathAccessMethod = "path"AccessMethodSubdomainAccessMethod = "subdomain"// AccessMethodTerminal is special since it's not a real app and only// applies to the PTY endpoint on the API.AccessMethodTerminalAccessMethod = "terminal")
typeAgentProvider¶
type AgentProvider interface {// ReverseProxy returns an httputil.ReverseProxy for proxying HTTP requests// to the specified agent.ReverseProxy(targetURL, dashboardURL *url.URL, agentIDuuid.UUID, appappurl.ApplicationURL, wildcardHoststring) *httputil.ReverseProxy// AgentConn returns a new connection to the specified agent.AgentConn(ctxcontext.Context, agentIDuuid.UUID) (_ *workspacesdk.AgentConn, release func(), _error)ServeHTTPDebug(whttp.ResponseWriter, r *http.Request)Close()error}
typeDBTokenProvider¶
type DBTokenProvider struct {Loggerslog.Logger// DashboardURL is the main dashboard access URL for error pages.DashboardURL *url.URLAuthorizerrbac.AuthorizerAuditor *atomic.Pointer[audit.Auditor]Databasedatabase.StoreDeploymentValues *codersdk.DeploymentValuesOAuth2Configs *httpmw.OAuth2ConfigsWorkspaceAgentInactiveTimeouttime.DurationWorkspaceAppAuditSessionTimeouttime.DurationKeycachecryptokeys.SigningKeycache}
DBTokenProvider provides authentication and authorization for workspace appsby querying the database if the request is missing a valid token.
func (*DBTokenProvider)FromRequest¶
func (p *DBTokenProvider) FromRequest(r *http.Request) (*SignedToken,bool)
func (*DBTokenProvider)Issue¶
func (p *DBTokenProvider) Issue(ctxcontext.Context, rwhttp.ResponseWriter, r *http.Request, issueReqIssueTokenRequest) (*SignedToken,string,bool)
typeEncryptedAPIKeyPayload¶
type EncryptedAPIKeyPayload struct {jwtutils.RegisteredClaimsAPIKeystring `json:"api_key"`}
func (*EncryptedAPIKeyPayload)Fill¶added inv2.17.0
func (e *EncryptedAPIKeyPayload) Fill(nowtime.Time)
typeIssueTokenRequest¶
type IssueTokenRequest struct {AppRequestRequest `json:"app_request"`// PathAppBaseURL is required.PathAppBaseURLstring `json:"path_app_base_url"`// AppHostname is the optional hostname for subdomain apps on the external// proxy. It must start with an asterisk.AppHostnamestring `json:"app_hostname"`// AppPath is the path of the user underneath the app base path.AppPathstring `json:"app_path"`// AppQuery is the query parameters the user provided in the app request.AppQuerystring `json:"app_query"`// SessionToken is the session token provided by the user.SessionTokenstring `json:"session_token"`}
func (IssueTokenRequest)AppBaseURL¶
func (rIssueTokenRequest) AppBaseURL() (*url.URL,error)
AppBaseURL returns the base URL of this specific app request. An error isreturned if a subdomain app hostname is not provided but the app is asubdomain app.
typeRequest¶
type Request struct {AccessMethodAccessMethod `json:"access_method"`// BasePath of the app. For path apps, this is the path prefix in the router// for this particular app. For subdomain apps, this should be "/". This is// used for setting the cookie path.BasePathstring `json:"base_path"`// Prefix is the prefix of the subdomain app URL. Prefix should have a// trailing "---" if set.Prefixstring `json:"app_prefix"`// For the following fields, if the AccessMethod is AccessMethodTerminal,// then only AgentNameOrID may be set and it must be a UUID. The other// fields must be left blank.UsernameOrIDstring `json:"username_or_id"`// WorkspaceAndAgent xor WorkspaceNameOrID are required.WorkspaceAndAgentstring `json:"-"`// "workspace" or "workspace.agent"WorkspaceNameOrIDstring `json:"workspace_name_or_id"`// AgentNameOrID is not required if the workspace has only one agent.AgentNameOrIDstring `json:"agent_name_or_id"`AppSlugOrPortstring `json:"app_slug_or_port"`}
typeResolveRequestOptions¶
type ResolveRequestOptions struct {Loggerslog.LoggerSignedTokenProviderSignedTokenProviderCookieCfgcodersdk.HTTPCookieConfigDashboardURL *url.URLPathAppBaseURL *url.URLAppHostnamestringAppRequestRequest// TODO: Replace these 2 fields with a "BrowserURL" field which is used for// redirecting the user back to their initial request after authenticating.// AppPath is the path under the app that was hit.AppPathstring// AppQuery is the raw query of the request.AppQuerystring}
typeServer¶
type Server struct {Loggerslog.Logger// DashboardURL should be a url to the coderd dashboard. This can be the// same as the AccessURL if the Server is embedded.DashboardURL *url.URLAccessURL *url.URL// Hostname should be the wildcard hostname to use for workspace// applications INCLUDING the asterisk, (optional) suffix and leading dot.// It will use the same scheme and port number as the access URL.// E.g. "*.apps.coder.com" or "*-apps.coder.com".Hostnamestring// HostnameRegex contains the regex version of Hostname as generated by// appurl.CompileHostnamePattern(). It MUST be set if Hostname is set.HostnameRegex *regexp.RegexpRealIPConfig *httpmw.RealIPConfigSignedTokenProviderSignedTokenProviderAPIKeyEncryptionKeycachecryptokeys.EncryptionKeycache// DisablePathApps disables path-based apps. This is a security feature as path// based apps share the same cookie as the dashboard, and are susceptible to XSS// by a malicious workspace app.//// Subdomain apps are safer with their cookies scoped to the subdomain, and XSS// calls to the dashboard are not possible due to CORs.DisablePathAppsboolCookiescodersdk.HTTPCookieConfigAgentProviderAgentProviderStatsCollector *StatsCollector// contains filtered or unexported fields}
Server serves workspace apps endpoints, including:- Path-based apps- Subdomain app middleware- Workspace reconnecting-pty (aka. web terminal)
func (*Server)Close¶
Close waits for all reconnecting-pty WebSocket connections to drain beforereturning.
func (*Server)HandleSubdomain¶
func (s *Server) HandleSubdomain(middlewares ...func(http.Handler)http.Handler) func(http.Handler)http.Handler
HandleSubdomain handles subdomain-based application proxy requests (aka.DevURLs in Coder V1).
There are a lot of paths here:
- If api.Hostname is not set then we pass on.
- If we can't read the request hostname then we return a 400.
- If the request hostname matches api.AccessURL then we pass on.
- We split the subdomain into the subdomain and the "rest". If there are noperiods in the hostname then we pass on.
- We parse the subdomain into a appurl.ApplicationURL struct. If weencounter an error:a. If the "rest" does not match api.Hostname then we pass on;b. Otherwise, we return a 400.
- Finally, we verify that the "rest" matches api.Hostname, else wereturn a 404.
Rationales for each of the above steps:
- We pass on if api.Hostname is not set to avoid returning any errors if`--app-hostname` is not configured.
- Every request should have a valid Host header anyways.
- We pass on if the request hostname matches api.AccessURL so we cansupport having the access URL be at the same level as the applicationbase hostname.
- We pass on if there are no periods in the hostname as application URLsmust be a subdomain of a hostname, which implies there must be at leastone period.
- a. If the request subdomain is not a valid application URL, and the"rest" does not match api.Hostname, then it is very unlikely thatthe request was intended for this handler. We pass on.b. If the request subdomain is not a valid application URL, but the"rest" matches api.Hostname, then we return a 400 because therequest is probably a typo or something.
- We finally verify that the "rest" matches api.Hostname for securitypurposes regarding re-authentication and application proxy sessiontokens.
typeSignedToken¶
type SignedToken struct {jwtutils.RegisteredClaims// Request details.Request `json:"request"`UserIDuuid.UUID `json:"user_id"`WorkspaceIDuuid.UUID `json:"workspace_id"`AgentIDuuid.UUID `json:"agent_id"`AppURLstring `json:"app_url"`}
SignedToken is the struct data contained inside a workspace app JWE. Itcontains the details of the workspace app that the token is valid for toavoid database queries.
funcFromRequest¶
func FromRequest(r *http.Request, mgrcryptokeys.SigningKeycache) (*SignedToken,bool)
FromRequest returns the signed token from the request, if it exists and isvalid. The caller must check that the token matches the request.
funcResolveRequest¶
func ResolveRequest(rwhttp.ResponseWriter, r *http.Request, optsResolveRequestOptions) (*SignedToken,bool)
func (SignedToken)MatchesRequest¶
func (tSignedToken) MatchesRequest(reqRequest)bool
MatchesRequest returns true if the token matches the request. Any token thatdoes not match the request should be considered invalid.
typeSignedTokenProvider¶
type SignedTokenProvider interface {// FromRequest returns a parsed token from the request. If the request does// not contain a signed app token or is is invalid (expired, invalid// signature, etc.), it returns false.FromRequest(r *http.Request) (*SignedToken,bool)// Issue mints a new token for the given app request. It uses the long-lived// session token in the HTTP request to authenticate and authorize the// client for the given workspace app. The token is returned in struct and// string form. The string form should be written as a cookie.//// If the request is invalid or the user is not authorized to access the// app, false is returned. An error page is written to the response writer// in this case.Issue(ctxcontext.Context, rwhttp.ResponseWriter, r *http.Request, appReqIssueTokenRequest) (*SignedToken,string,bool)}
SignedTokenProvider provides signed workspace app tokens (aka. app tickets).
funcNewDBTokenProvider¶
func NewDBTokenProvider(logslog.Logger,accessURL *url.URL,authzrbac.Authorizer,auditor *atomic.Pointer[audit.Auditor],dbdatabase.Store,cfg *codersdk.DeploymentValues,oauth2Cfgs *httpmw.OAuth2Configs,workspaceAgentInactiveTimeouttime.Duration,workspaceAppAuditSessionTimeouttime.Duration,signercryptokeys.SigningKeycache,)SignedTokenProvider
typeStatsCollector¶
type StatsCollector struct {// contains filtered or unexported fields}
StatsCollector collects workspace app StatsReports and reports themin batches, stats compaction is performed for short-lived sessions.
funcNewStatsCollector¶
func NewStatsCollector(optsStatsCollectorOptions) *StatsCollector
func (*StatsCollector)Close¶
func (sc *StatsCollector) Close()error
func (*StatsCollector)Collect¶
func (sc *StatsCollector) Collect(reportStatsReport)
Collect the given StatsReport for later reporting (non-blocking).
typeStatsCollectorOptions¶
type StatsCollectorOptions struct {Logger *slog.LoggerReporterStatsReporter// ReportInterval is the interval at which stats are reported, both partial// and fully formed stats.ReportIntervaltime.Duration// RollupWindow is the window size for rolling up stats, session shorter// than this will be rolled up and longer than this will be tracked// individually.RollupWindowtime.Duration// Options for tests.Flush <-chan chan<- struct{}Now func()time.Time}
typeStatsReport¶
type StatsReport struct {UserIDuuid.UUID `json:"user_id"`WorkspaceIDuuid.UUID `json:"workspace_id"`AgentIDuuid.UUID `json:"agent_id"`AccessMethodAccessMethod `json:"access_method"`SlugOrPortstring `json:"slug_or_port"`SessionIDuuid.UUID `json:"session_id"`SessionStartedAttime.Time `json:"session_started_at"`SessionEndedAttime.Time `json:"session_ended_at"`// Updated periodically while app is in use active and when the last connection is closed.Requestsint `json:"requests"`// contains filtered or unexported fields}
StatsReport is a report of a workspace app session.
typeStatsReporter¶
type StatsReporter interface {ReportAppStats(context.Context, []StatsReport)error}
StatsReporter reports workspace app StatsReports.