mirror of
https://github.com/coder/coder.git
synced 2025-03-14 10:09:57 +00:00
chore: add custom samesite options to auth cookies
Advanced feature, not recommended to use
This commit is contained in:
@ -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(),
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 &&
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.",
|
||||
|
Reference in New Issue
Block a user