Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: use JWT ticket to avoid DB queries on apps
WIP

Issue a JWT ticket on the first request with a short expiry that
contains details about which workspace/agent/app combo the ticket is
valid for.

Refactor the workspace app auth logic into workspaceappsauth.go.
  • Loading branch information
deansheather committed Feb 10, 2023
commit cd239fa2a8d440bf9314d87e562e684f297ce925
38 changes: 5 additions & 33 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,21 +281,9 @@ func New(options *Options) *API {
httpmw.Prometheus(options.PrometheusRegistry),
// handleSubdomainApplications checks if the first subdomain is a valid
// app URL. If it is, it will serve that application.
api.handleSubdomainApplications(
apiRateLimiter,
// Middleware to impose on the served application.
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
DB: options.Database,
OAuth2Configs: oauthConfigs,
// The code handles the the case where the user is not
// authenticated automatically.
RedirectToLogin: false,
DisableSessionExpiryRefresh: options.DeploymentConfig.DisableSessionExpiryRefresh.Value,
Optional: true,
}),
httpmw.ExtractUserParam(api.Database, false),
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
),
//
// Workspace apps do their own auth.
api.handleSubdomainApplications(apiRateLimiter),
// Build-Version is helpful for debugging.
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -309,24 +297,8 @@ func New(options *Options) *API {
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("OK")) })

apps := func(r chi.Router) {
r.Use(
apiRateLimiter,
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
DB: options.Database,
OAuth2Configs: oauthConfigs,
// Optional is true to allow for public apps. If an
// authorization check fails and the user is not authenticated,
// they will be redirected to the login page by the app handler.
RedirectToLogin: false,
DisableSessionExpiryRefresh: options.DeploymentConfig.DisableSessionExpiryRefresh.Value,
Optional: true,
}),
// Redirect to the login page if the user tries to open an app with
// "me" as the username and they are not logged in.
httpmw.ExtractUserParam(api.Database, true),
// Extracts the <workspace.agent> from the url
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
)
// Workspace apps do their own auth.
r.Use(apiRateLimiter)
r.HandleFunc("/*", api.workspaceAppsProxyPath)
}
// %40 is the encoded character of the @ symbol. VS Code Web does
Expand Down
4 changes: 3 additions & 1 deletion coderd/httpapi/cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ func StripCoderCookies(header string) string {
name, _, _ := strings.Cut(part, "=")
if name == codersdk.SessionTokenCookie ||
name == codersdk.OAuth2StateCookie ||
name == codersdk.OAuth2RedirectCookie {
name == codersdk.OAuth2RedirectCookie ||
name == codersdk.DevURLSessionTokenCookie ||
name == codersdk.DevURLSessionTicketCookie {
continue
}
cookies = append(cookies, part)
Expand Down
29 changes: 3 additions & 26 deletions coderd/httpapi/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"net"
"regexp"
"strconv"
"strings"

"golang.org/x/xerrors"
Expand All @@ -23,9 +22,7 @@ var (

// ApplicationURL is a parsed application URL hostname.
type ApplicationURL struct {
// Only one of AppSlug or Port will be set.
AppSlug string
Port uint16
AppSlugOrPort string
AgentName string
WorkspaceName string
Username string
Expand All @@ -34,12 +31,7 @@ type ApplicationURL struct {
// String returns the application URL hostname without scheme. You will likely
// want to append a period and the base hostname.
func (a ApplicationURL) String() string {
appSlugOrPort := a.AppSlug
if a.Port != 0 {
appSlugOrPort = strconv.Itoa(int(a.Port))
}

return fmt.Sprintf("%s--%s--%s--%s", appSlugOrPort, a.AgentName, a.WorkspaceName, a.Username)
return fmt.Sprintf("%s--%s--%s--%s", a.AppSlugOrPort, a.AgentName, a.WorkspaceName, a.Username)
}

// ParseSubdomainAppURL parses an ApplicationURL from the given subdomain. If
Expand All @@ -60,29 +52,14 @@ func ParseSubdomainAppURL(subdomain string) (ApplicationURL, error) {
}
matchGroup := matches[0]

appSlug, port := AppSlugOrPort(matchGroup[appURL.SubexpIndex("AppSlug")])
return ApplicationURL{
AppSlug: appSlug,
Port: port,
AppSlugOrPort: matchGroup[appURL.SubexpIndex("AppSlug")],
AgentName: matchGroup[appURL.SubexpIndex("AgentName")],
WorkspaceName: matchGroup[appURL.SubexpIndex("WorkspaceName")],
Username: matchGroup[appURL.SubexpIndex("Username")],
}, nil
}

// AppSlugOrPort takes a string and returns either the input string or a port
// number.
func AppSlugOrPort(val string) (string, uint16) {
port, err := strconv.ParseUint(val, 10, 16)
if err != nil || port == 0 {
port = 0
} else {
val = ""
}

return val, uint16(port)
}

// HostnamesMatch returns true if the hostnames are equal, disregarding
// capitalization, extra leading or trailing periods, and ports.
func HostnamesMatch(a, b string) bool {
Expand Down
27 changes: 5 additions & 22 deletions coderd/httpapi/url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ func TestApplicationURLString(t *testing.T) {
{
Name: "AppName",
URL: httpapi.ApplicationURL{
AppSlug: "app",
Port: 0,
AppSlugOrPort: "app",
AgentName: "agent",
WorkspaceName: "workspace",
Username: "user",
Expand All @@ -36,26 +35,13 @@ func TestApplicationURLString(t *testing.T) {
{
Name: "Port",
URL: httpapi.ApplicationURL{
AppSlug: "",
Port: 8080,
AppSlugOrPort: "8080",
AgentName: "agent",
WorkspaceName: "workspace",
Username: "user",
},
Expected: "8080--agent--workspace--user",
},
{
Name: "Both",
URL: httpapi.ApplicationURL{
AppSlug: "app",
Port: 8080,
AgentName: "agent",
WorkspaceName: "workspace",
Username: "user",
},
// Prioritizes port over app name.
Expected: "8080--agent--workspace--user",
},
}

for _, c := range testCases {
Expand Down Expand Up @@ -111,8 +97,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
Name: "AppName--Agent--Workspace--User",
Subdomain: "app--agent--workspace--user",
Expected: httpapi.ApplicationURL{
AppSlug: "app",
Port: 0,
AppSlugOrPort: "app",
AgentName: "agent",
WorkspaceName: "workspace",
Username: "user",
Expand All @@ -122,8 +107,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
Name: "Port--Agent--Workspace--User",
Subdomain: "8080--agent--workspace--user",
Expected: httpapi.ApplicationURL{
AppSlug: "",
Port: 8080,
AppSlugOrPort: "8080",
AgentName: "agent",
WorkspaceName: "workspace",
Username: "user",
Expand All @@ -133,8 +117,7 @@ func TestParseSubdomainAppURL(t *testing.T) {
Name: "HyphenatedNames",
Subdomain: "app-slug--agent-name--workspace-name--user-name",
Expected: httpapi.ApplicationURL{
AppSlug: "app-slug",
Port: 0,
AppSlugOrPort: "app-slug",
AgentName: "agent-name",
WorkspaceName: "workspace-name",
Username: "user-name",
Expand Down
9 changes: 1 addition & 8 deletions coderd/httpmw/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ import (
"github.com/coder/coder/codersdk"
)

// The special cookie name used for subdomain-based application proxying.
// TODO: this will make dogfooding harder so come up with a more unique
// solution
//
//nolint:gosec
const DevURLSessionTokenCookie = "coder_devurl_session_token"

type apiKeyContextKey struct{}

// APIKeyOptional may return an API key from the ExtractAPIKey handler.
Expand Down Expand Up @@ -382,7 +375,7 @@ func apiTokenFromRequest(r *http.Request) string {
return headerValue
}

cookie, err = r.Cookie(DevURLSessionTokenCookie)
cookie, err = r.Cookie(codersdk.DevURLSessionTokenCookie)
if err == nil && cookie.Value != "" {
return cookie.Value
}
Expand Down
4 changes: 2 additions & 2 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
// Deployments should not host app tokens on the same domain as the
// primary deployment. But in the case they are, we should also delete this
// token.
if appCookie, _ := r.Cookie(httpmw.DevURLSessionTokenCookie); appCookie != nil {
if appCookie, _ := r.Cookie(codersdk.DevURLSessionTokenCookie); appCookie != nil {
appCookieRemove := &http.Cookie{
// MaxAge < 0 means to delete the cookie now.
MaxAge: -1,
Name: httpmw.DevURLSessionTokenCookie,
Name: codersdk.DevURLSessionTokenCookie,
Path: "/",
Domain: "." + api.AccessURL.Hostname(),
}
Expand Down
Loading