Documentation ¶
Index ¶
- Constants
- 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, ...)
- type AccessMethod
- type DBTokenProvider
- type EncryptedAPIKeyPayload
- type Request
- type SecurityKey
- type Server
- type SignedToken
- type SignedTokenProvider
Constants ¶
const ( // TODO(@deansheather): configurable expiry DefaultTokenExpiry = 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 ( // This needs to be a super unique query parameter because we don't want to // conflict with query parameters that users may use. //nolint:gosec SubdomainProxyAPIKeyParam = "coder_application_connect_api_key_35e783" )
Variables ¶
This section is empty.
Functions ¶
func WebsocketNetConn ¶ added in v0.22.0
func WebsocketNetConn(ctx context.Context, conn *websocket.Conn, msgType websocket.MessageType) (context.Context, net.Conn)
WebsocketNetConn wraps websocket.NetConn and returns a context that is tied to the parent context and the lifetime of the conn. Any error during read or write will cancel the context, but not close the conn. Close should be called to release context resources.
func WriteWorkspaceApp404 ¶ added in v0.22.0
func WriteWorkspaceApp404(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, appReq *Request, msg string)
WriteWorkspaceApp404 writes a HTML 404 error page for a workspace app. If appReq is not nil, it will be used to log the request details at debug level.
func WriteWorkspaceApp500 ¶ added in v0.22.0
func WriteWorkspaceApp500(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, appReq *Request, err error, msg string)
WriteWorkspaceApp500 writes a HTML 500 error page for a workspace app. If appReq is not nil, it's fields will be added to the logged error message.
func WriteWorkspaceAppOffline ¶ added in v0.22.0
func WriteWorkspaceAppOffline(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, appReq *Request, msg string)
WriteWorkspaceAppOffline writes a HTML 502 error page for a workspace app. If appReq is not nil, it will be used to log the request details at debug level.
Types ¶
type AccessMethod ¶
type AccessMethod string
const ( AccessMethodPath AccessMethod = "path" AccessMethodSubdomain AccessMethod = "subdomain" // AccessMethodTerminal is special since it's not a real app and only // applies to the PTY endpoint on the API. AccessMethodTerminal AccessMethod = "terminal" )
type DBTokenProvider ¶ added in v0.22.0
type DBTokenProvider struct { Logger slog.Logger // AccessURL is the main dashboard access URL for error pages. AccessURL *url.URL Authorizer rbac.Authorizer Database database.Store DeploymentValues *codersdk.DeploymentValues OAuth2Configs *httpmw.OAuth2Configs WorkspaceAgentInactiveTimeout time.Duration SigningKey SecurityKey }
DBTokenProvider provides authentication and authorization for workspace apps by querying the database if the request is missing a valid token.
func (*DBTokenProvider) CreateToken ¶ added in v0.22.0
func (p *DBTokenProvider) CreateToken(ctx context.Context, rw http.ResponseWriter, r *http.Request, appReq Request) (*SignedToken, string, bool)
ResolveRequest takes an app request, checks if it's valid and authenticated, and returns a token with details about the app.
func (*DBTokenProvider) TokenFromRequest ¶ added in v0.22.0
func (p *DBTokenProvider) TokenFromRequest(r *http.Request) (*SignedToken, bool)
type EncryptedAPIKeyPayload ¶ added in v0.22.0
type Request ¶
type Request struct { AccessMethod AccessMethod `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. BasePath string `json:"base_path"` // 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. UsernameOrID string `json:"username_or_id"` // WorkspaceAndAgent xor WorkspaceNameOrID are required. WorkspaceAndAgent string `json:"-"` // "workspace" or "workspace.agent" WorkspaceNameOrID string `json:"workspace_name_or_id"` // AgentNameOrID is not required if the workspace has only one agent. AgentNameOrID string `json:"agent_name_or_id"` AppSlugOrPort string `json:"app_slug_or_port"` }
type SecurityKey ¶ added in v0.22.0
type SecurityKey [96]byte
SecurityKey is used for signing and encrypting app tokens and API keys.
The first 64 bytes of the key are used for signing tokens with HMAC-SHA256, and the last 32 bytes are used for encrypting API keys with AES-256-GCM. We use a single key for both operations to avoid having to store and manage two keys.
func KeyFromString ¶ added in v0.22.0
func KeyFromString(str string) (SecurityKey, error)
func (SecurityKey) DecryptAPIKey ¶ added in v0.22.0
func (k SecurityKey) DecryptAPIKey(encryptedAPIKey string) (string, error)
DecryptAPIKey undoes EncryptAPIKey and is used in the subdomain app handler.
func (SecurityKey) EncryptAPIKey ¶ added in v0.22.0
func (k SecurityKey) EncryptAPIKey(payload EncryptedAPIKeyPayload) (string, error)
EncryptAPIKey encrypts an API key for subdomain token smuggling.
func (SecurityKey) SignToken ¶ added in v0.22.0
func (k SecurityKey) SignToken(payload SignedToken) (string, error)
SignToken generates a signed workspace app token with the given payload. If the payload doesn't have an expiry, it will be set to the current time plus the default expiry.
func (SecurityKey) VerifySignedToken ¶ added in v0.22.0
func (k SecurityKey) VerifySignedToken(str string) (SignedToken, error)
VerifySignedToken parses a signed workspace app token with the given key and returns the payload. If the token is invalid or expired, an error is returned.
type Server ¶ added in v0.22.0
type Server struct { Logger slog.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.URL AccessURL *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". Hostname string // HostnameRegex contains the regex version of Hostname as generated by // httpapi.CompileHostnamePattern(). It MUST be set if Hostname is set. HostnameRegex *regexp.Regexp DeploymentValues *codersdk.DeploymentValues RealIPConfig *httpmw.RealIPConfig SignedTokenProvider SignedTokenProvider WorkspaceConnCache *wsconncache.Cache AppSecurityKey SecurityKey // 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 ¶ added in v0.22.0
Close waits for all reconnecting-pty WebSocket connections to drain before returning.
func (*Server) SubdomainAppMW ¶ added in v0.22.0
func (s *Server) SubdomainAppMW(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler
SubdomainAppMW 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 no periods in the hostname then we pass on.
- We parse the subdomain into a httpapi.ApplicationURL struct. If we encounter 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 we return 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 can support having the access URL be at the same level as the application base hostname.
- We pass on if there are no periods in the hostname as application URLs must be a subdomain of a hostname, which implies there must be at least one 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 that the 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 the request is probably a typo or something.
- We finally verify that the "rest" matches api.Hostname for security purposes regarding re-authentication and application proxy session tokens.
type SignedToken ¶ added in v0.22.0
type SignedToken struct { // Request details. Request `json:"request"` // Trusted resolved details. Expiry time.Time `json:"expiry"` // set by GenerateToken if unset UserID uuid.UUID `json:"user_id"` WorkspaceID uuid.UUID `json:"workspace_id"` AgentID uuid.UUID `json:"agent_id"` AppURL string `json:"app_url"` }
SignedToken is the struct data contained inside a workspace app JWE. It contains the details of the workspace app that the token is valid for to avoid database queries.
func ResolveRequest ¶ added in v0.22.0
func ResolveRequest(log slog.Logger, dashboardURL *url.URL, p SignedTokenProvider, rw http.ResponseWriter, r *http.Request, appReq Request) (*SignedToken, bool)
ResolveRequest calls SignedTokenProvider to use an existing signed app token in the request or issue a new one. If it returns a newly minted token, it sets the cookie for you.
func (SignedToken) MatchesRequest ¶ added in v0.22.0
func (t SignedToken) MatchesRequest(req Request) bool
MatchesRequest returns true if the token matches the request. Any token that does not match the request should be considered invalid.
type SignedTokenProvider ¶ added in v0.22.0
type SignedTokenProvider interface { // TokenFromRequest 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. TokenFromRequest(r *http.Request) (*SignedToken, bool) // CreateToken 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. CreateToken(ctx context.Context, rw http.ResponseWriter, r *http.Request, appReq Request) (*SignedToken, string, bool) }
SignedTokenProvider provides signed workspace app tokens (aka. app tickets).
func NewDBTokenProvider ¶ added in v0.22.0
func NewDBTokenProvider(log slog.Logger, accessURL *url.URL, authz rbac.Authorizer, db database.Store, cfg *codersdk.DeploymentValues, oauth2Cfgs *httpmw.OAuth2Configs, workspaceAgentInactiveTimeout time.Duration, signingKey SecurityKey) SignedTokenProvider