chore: add custom samesite options to auth cookies

Advanced feature, not recommended to use
This commit is contained in:
Steven Masley
2025-03-11 15:40:14 -05:00
parent 30179aeaac
commit 7cdfbfe264
10 changed files with 78 additions and 29 deletions

View File

@ -630,7 +630,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
GoogleTokenValidator: googleTokenValidator,
ExternalAuthConfigs: externalAuthConfigs,
RealIPConfig: realIPConfig,
SecureAuthCookie: vals.SecureAuthCookie.Value(),
Cookies: vals.HTTPCookies,
SSHKeygenAlgorithm: sshKeygenAlgorithm,
TracerProvider: tracerProvider,
Telemetry: telemetry.NewNoop(),

View File

@ -382,12 +382,10 @@ func (api *API) createAPIKey(ctx context.Context, params apikey.CreateParams) (*
APIKeys: []telemetry.APIKey{telemetry.ConvertAPIKey(newkey)},
})
return &http.Cookie{
return api.Cookies.Apply(&http.Cookie{
Name: codersdk.SessionTokenCookie,
Value: sessionToken,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: api.SecureAuthCookie,
}, &newkey, nil
}), &newkey, nil
}

View File

@ -154,7 +154,7 @@ type Options struct {
GithubOAuth2Config *GithubOAuth2Config
OIDCConfig *OIDCConfig
PrometheusRegistry *prometheus.Registry
SecureAuthCookie bool
Cookies codersdk.HTTPCookieConfig
StrictTransportSecurityCfg httpmw.HSTSConfig
SSHKeygenAlgorithm gitsshkey.Algorithm
Telemetry telemetry.Reporter
@ -728,7 +728,7 @@ func New(options *Options) *API {
StatsCollector: workspaceapps.NewStatsCollector(options.WorkspaceAppsStatsCollectorOptions),
DisablePathApps: options.DeploymentValues.DisablePathApps.Value(),
SecureAuthCookie: options.DeploymentValues.SecureAuthCookie.Value(),
Cookies: options.DeploymentValues.HTTPCookies,
APIKeyEncryptionKeycache: options.AppEncryptionKeyCache,
}
@ -816,7 +816,7 @@ func New(options *Options) *API {
next.ServeHTTP(w, r)
})
},
httpmw.CSRF(options.SecureAuthCookie),
httpmw.CSRF(options.Cookies),
)
// This incurs a performance hit from the middleware, but is required to make sure

View File

@ -35,3 +35,18 @@ func AsAuthzSystem(mws ...func(http.Handler) http.Handler) func(http.Handler) ht
})
}
}
<<<<<<< Updated upstream
=======
// RecordAuthzChecks enables recording all the authorization checks that
// occurred in the processing of a request. This is mostly helpful for debugging
// and understanding what permissions are required for a given action without
// needing to go hunting for checks in the code, where you're quite likely to
// miss something subtle or a check happening somewhere you didn't expect.
func RecordAuthzChecks(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
r = r.WithContext(rbac.WithAuthzCheckRecorder(r.Context()))
next.ServeHTTP(rw, r)
})
}
>>>>>>> Stashed changes

View File

@ -16,10 +16,10 @@ import (
// for non-GET requests.
// If enforce is false, then CSRF enforcement is disabled. We still want
// to include the CSRF middleware because it will set the CSRF cookie.
func CSRF(secureCookie bool) func(next http.Handler) http.Handler {
func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
mw := nosurf.New(next)
mw.SetBaseCookie(http.Cookie{Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: secureCookie})
mw.SetBaseCookie(*cookieCfg.Apply(&http.Cookie{Path: "/", HttpOnly: true}))
mw.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sessCookie, err := r.Cookie(codersdk.SessionTokenCookie)
if err == nil &&

View File

@ -40,7 +40,7 @@ func OAuth2(r *http.Request) OAuth2State {
// a "code" URL parameter will be redirected.
// AuthURLOpts are passed to the AuthCodeURL function. If this is nil,
// the default option oauth2.AccessTypeOffline will be used.
func ExtractOAuth2(config promoauth.OAuth2Config, client *http.Client, authURLOpts map[string]string) func(http.Handler) http.Handler {
func ExtractOAuth2(config promoauth.OAuth2Config, client *http.Client, cookieCfg codersdk.HTTPCookieConfig, authURLOpts map[string]string) func(http.Handler) http.Handler {
opts := make([]oauth2.AuthCodeOption, 0, len(authURLOpts)+1)
opts = append(opts, oauth2.AccessTypeOffline)
for k, v := range authURLOpts {
@ -118,22 +118,20 @@ func ExtractOAuth2(config promoauth.OAuth2Config, client *http.Client, authURLOp
}
}
http.SetCookie(rw, &http.Cookie{
http.SetCookie(rw, cookieCfg.Apply(&http.Cookie{
Name: codersdk.OAuth2StateCookie,
Value: state,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
}))
// Redirect must always be specified, otherwise
// an old redirect could apply!
http.SetCookie(rw, &http.Cookie{
http.SetCookie(rw, cookieCfg.Apply(&http.Cookie{
Name: codersdk.OAuth2RedirectCookie,
Value: redirect,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
}))
http.Redirect(rw, r, config.AuthCodeURL(state, opts...), http.StatusTemporaryRedirect)
return

View File

@ -203,7 +203,7 @@ func (api *API) postConvertLoginType(rw http.ResponseWriter, r *http.Request) {
Path: "/",
Value: token,
Expires: claims.Expiry.Time(),
Secure: api.SecureAuthCookie,
Secure: api.Cookies.Secure.Value(),
HttpOnly: true,
// Must be SameSite to work on the redirected auth flow from the
// oauth provider.
@ -1911,7 +1911,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
Name: codersdk.SessionTokenCookie,
Path: "/",
MaxAge: -1,
Secure: api.SecureAuthCookie,
Secure: api.Cookies.Secure.Value(),
HttpOnly: true,
})
// This is intentional setting the key to the deleted old key,

View File

@ -22,6 +22,7 @@ const (
type ResolveRequestOptions struct {
Logger slog.Logger
SignedTokenProvider SignedTokenProvider
CookieCfg codersdk.HTTPCookieConfig
DashboardURL *url.URL
PathAppBaseURL *url.URL
@ -75,12 +76,12 @@ func ResolveRequest(rw http.ResponseWriter, r *http.Request, opts ResolveRequest
//
// For subdomain apps, this applies to the entire subdomain, e.g.
// app--agent--workspace--user.apps.example.com
http.SetCookie(rw, &http.Cookie{
http.SetCookie(rw, opts.CookieCfg.Apply(&http.Cookie{
Name: codersdk.SignedAppTokenCookie,
Value: tokenStr,
Path: appReq.BasePath,
Expires: token.Expiry.Time(),
})
}))
return token, true
}

View File

@ -110,8 +110,8 @@ type Server struct {
//
// Subdomain apps are safer with their cookies scoped to the subdomain, and XSS
// calls to the dashboard are not possible due to CORs.
DisablePathApps bool
SecureAuthCookie bool
DisablePathApps bool
Cookies codersdk.HTTPCookieConfig
AgentProvider AgentProvider
StatsCollector *StatsCollector
@ -230,16 +230,14 @@ func (s *Server) handleAPIKeySmuggling(rw http.ResponseWriter, r *http.Request,
// We use different cookie names for path apps and for subdomain apps to
// avoid both being set and sent to the server at the same time and the
// server using the wrong value.
http.SetCookie(rw, &http.Cookie{
http.SetCookie(rw, s.Cookies.Apply(&http.Cookie{
Name: AppConnectSessionTokenCookieName(accessMethod),
Value: payload.APIKey,
Domain: domain,
Path: "/",
MaxAge: 0,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: s.SecureAuthCookie,
})
}))
// Strip the query parameter.
path := r.URL.Path
@ -300,6 +298,7 @@ func (s *Server) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
// permissions to connect to a workspace.
token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
Logger: s.Logger,
CookieCfg: s.Cookies,
SignedTokenProvider: s.SignedTokenProvider,
DashboardURL: s.DashboardURL,
PathAppBaseURL: s.AccessURL,
@ -405,6 +404,7 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
Logger: s.Logger,
CookieCfg: s.Cookies,
SignedTokenProvider: s.SignedTokenProvider,
DashboardURL: s.DashboardURL,
PathAppBaseURL: s.AccessURL,
@ -630,6 +630,7 @@ func (s *Server) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
appToken, ok := ResolveRequest(rw, r, ResolveRequestOptions{
Logger: s.Logger,
CookieCfg: s.Cookies,
SignedTokenProvider: s.SignedTokenProvider,
DashboardURL: s.DashboardURL,
PathAppBaseURL: s.AccessURL,

View File

@ -358,7 +358,7 @@ type DeploymentValues struct {
Telemetry TelemetryConfig `json:"telemetry,omitempty" typescript:",notnull"`
TLS TLSConfig `json:"tls,omitempty" typescript:",notnull"`
Trace TraceConfig `json:"trace,omitempty" typescript:",notnull"`
SecureAuthCookie serpent.Bool `json:"secure_auth_cookie,omitempty" typescript:",notnull"`
HTTPCookies HTTPCookieConfig `json:"http_cookies,omitempty" typescript:",notnull"`
StrictTransportSecurity serpent.Int64 `json:"strict_transport_security,omitempty" typescript:",notnull"`
StrictTransportSecurityOptions serpent.StringArray `json:"strict_transport_security_options,omitempty" typescript:",notnull"`
SSHKeygenAlgorithm serpent.String `json:"ssh_keygen_algorithm,omitempty" typescript:",notnull"`
@ -585,6 +585,30 @@ type TraceConfig struct {
DataDog serpent.Bool `json:"data_dog" typescript:",notnull"`
}
type HTTPCookieConfig struct {
Secure serpent.Bool `json:"secure_auth_cookie,omitempty" typescript:",notnull"`
SameSite string `json:"same_site,omitempty" typescript:",notnull"`
}
func (cfg HTTPCookieConfig) Apply(c *http.Cookie) *http.Cookie {
c.Secure = cfg.Secure.Value()
c.SameSite = cfg.HTTPSameSite()
return c
}
func (cfg HTTPCookieConfig) HTTPSameSite() http.SameSite {
switch strings.ToLower(cfg.SameSite) {
case "lax":
return http.SameSiteLaxMode
case "strict":
return http.SameSiteStrictMode
case "none":
return http.SameSiteNoneMode
default:
return http.SameSiteDefaultMode
}
}
type ExternalAuthConfig struct {
// Type is the type of external auth config.
Type string `json:"type" yaml:"type"`
@ -2363,11 +2387,23 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
Description: "Controls if the 'Secure' property is set on browser session cookies.",
Flag: "secure-auth-cookie",
Env: "CODER_SECURE_AUTH_COOKIE",
Value: &c.SecureAuthCookie,
Value: &c.HTTPCookies.Secure,
Group: &deploymentGroupNetworking,
YAML: "secureAuthCookie",
Annotations: serpent.Annotations{}.Mark(annotationExternalProxies, "true"),
},
{
Name: "SameSite Auth Cookie",
Description: "Controls the 'SameSite' property is set on browser session cookies.",
Flag: "samesite-auth-cookie",
Env: "CODER_SAMESITE_AUTH_COOKIE",
// Do not allow "strict" same-site cookies. That would potentially break workspace apps.
Value: serpent.EnumOf(&c.HTTPCookies.SameSite, "lax", "none"),
Default: "lax",
Group: &deploymentGroupNetworking,
YAML: "sameSiteAuthCookie",
Annotations: serpent.Annotations{}.Mark(annotationExternalProxies, "true"),
},
{
Name: "Terms of Service URL",
Description: "A URL to an external Terms of Service that must be accepted by users when logging in.",