Commit a1579283 by Marcus Efraimsson Committed by GitHub

Add disabled option for cookie samesite attribute (#21472)

Breaking change: If disabled the cookie samesite cookie attribute
will not be set, but if none the attribute will be set and is a
breaking change compared to before where none did not render the
attribute. This was due to a known issue in Safari.

Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>
Co-Authored-By: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

Fixes #19847
parent 49291284
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
## Breaking changes ## Breaking changes
* **PagerDuty**: Change `payload.custom_details` field in PagerDuty notification to be a JSON object instead of a string. * **PagerDuty**: Change `payload.custom_details` field in PagerDuty notification to be a JSON object instead of a string.
* **Security**: The `[security]` setting `cookie_samesite` configured to `none` now renders cookies with `SameSite=None` attribute compared to before where no `SameSite` attribute was added to cookies. To get the old behavior, use value `disabled` instead of `none`. Refer to [Upgrade Grafana](https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v6-6) for more information.
# 6.5.2 (2019-12-11) # 6.5.2 (2019-12-11)
......
...@@ -179,7 +179,7 @@ disable_brute_force_login_protection = false ...@@ -179,7 +179,7 @@ disable_brute_force_login_protection = false
# set to true if you host Grafana behind HTTPS. default is false. # set to true if you host Grafana behind HTTPS. default is false.
cookie_secure = false cookie_secure = false
# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none" # set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled"
cookie_samesite = lax cookie_samesite = lax
# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false. # set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
......
...@@ -180,7 +180,7 @@ ...@@ -180,7 +180,7 @@
# set to true if you host Grafana behind HTTPS. default is false. # set to true if you host Grafana behind HTTPS. default is false.
;cookie_secure = false ;cookie_secure = false
# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none" # set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled"
;cookie_samesite = lax ;cookie_samesite = lax
# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false. # set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
......
...@@ -24,14 +24,14 @@ Do not change `defaults.ini`! Grafana defaults are stored in this file. Dependin ...@@ -24,14 +24,14 @@ Do not change `defaults.ini`! Grafana defaults are stored in this file. Dependin
- Custom configuration from `$WORKING_DIR/conf/custom.ini` - Custom configuration from `$WORKING_DIR/conf/custom.ini`
- The custom configuration file path can be overridden using the `--config` parameter - The custom configuration file path can be overridden using the `--config` parameter
**Linux** **Linux**
If you installed Grafana using the `deb` or `rpm` packages, then your configuration file is located at `/etc/grafana/grafana.ini` and a separate `custom.ini` is not used. This path is specified in the Grafana init.d script using `--config` file parameter. If you installed Grafana using the `deb` or `rpm` packages, then your configuration file is located at `/etc/grafana/grafana.ini` and a separate `custom.ini` is not used. This path is specified in the Grafana init.d script using `--config` file parameter.
**Windows** **Windows**
`sample.ini` is in the same directory as `defaults.ini` and contains all the settings commented out. Copy `sample.ini` and name it `custom.ini`. `sample.ini` is in the same directory as `defaults.ini` and contains all the settings commented out. Copy `sample.ini` and name it `custom.ini`.
**macOS** **macOS**
By default, the configuration file is located at `/usr/local/etc/grafana/grafana.ini`. By default, the configuration file is located at `/usr/local/etc/grafana/grafana.ini`.
## Comments in .ini Files ## Comments in .ini Files
...@@ -361,7 +361,7 @@ Set to `true` if you host Grafana behind HTTPS. Default is `false`. ...@@ -361,7 +361,7 @@ Set to `true` if you host Grafana behind HTTPS. Default is `false`.
### cookie_samesite ### cookie_samesite
Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. The main goal is mitigate the risk of cross-origin information leakage. It also provides some protection against cross-site request forgery attacks (CSRF), [read more here](https://www.owasp.org/index.php/SameSite). Valid values are `lax`, `strict` and `none`. Default is `lax`. Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. The main goal is to mitigate the risk of cross-origin information leakage. This setting also provides some protection against cross-site request forgery attacks (CSRF), [read more about SameSite here](https://www.owasp.org/index.php/SameSite). Valid values are `lax`, `strict`, `none`, and `disabled`. Default is `lax`. Using value `disabled` does not add any `SameSite` attribute to cookies.
### allow_embedding ### allow_embedding
...@@ -611,7 +611,7 @@ You can choose between (s3, webdav, gcs, azure_blob, local). If left empty Grafa ...@@ -611,7 +611,7 @@ You can choose between (s3, webdav, gcs, azure_blob, local). If left empty Grafa
## [external_image_storage.s3] ## [external_image_storage.s3]
### endpoint ### endpoint
Optional endpoint URL (hostname or fully qualified URI) to override the default generated S3 endpoint. If you want to Optional endpoint URL (hostname or fully qualified URI) to override the default generated S3 endpoint. If you want to
keep the default, just leave this empty. You must still provide a `region` value if you specify an endpoint. keep the default, just leave this empty. You must still provide a `region` value if you specify an endpoint.
## path_style_access ## path_style_access
......
...@@ -226,3 +226,14 @@ The handling of multi-valued template variables in dimension values has been cha ...@@ -226,3 +226,14 @@ The handling of multi-valued template variables in dimension values has been cha
## Upgrading to v6.6 ## Upgrading to v6.6
The Generic OAuth setting `send_client_credentials_via_post`, used for supporting non-compliant providers, has been removed. From now on, Grafana will automatically detect if credentials should be sent as part of the URL or request body for a specific provider. The result will be remembered and used for additional OAuth requests for that provider. The Generic OAuth setting `send_client_credentials_via_post`, used for supporting non-compliant providers, has been removed. From now on, Grafana will automatically detect if credentials should be sent as part of the URL or request body for a specific provider. The result will be remembered and used for additional OAuth requests for that provider.
### Important changes regarding SameSite cookie attribute
Chrome 80 treats cookies as `SameSite=Lax` by default if no `SameSite` attribute is specified, see https://www.chromestatus.com/feature/5088147346030592.
Due to this change in Chrome, the `[security]` setting `cookie_samesite` configured to `none` now renders cookies with `SameSite=None` attribute compared to before where no `SameSite` attribute was added to cookies. To get the old behavior, use value `disabled` instead of `none`, see [cookie_samesite in Configuration]({{< relref "configuration/#cookie-samesite" >}}) for more information.
**Note:** There is currently a bug affecting Mac OSX and iOS that causes `SameSite=None` cookies to be treated as `SameSite=Strict` and therefore not sent with cross-site requests. (See https://bugs.webkit.org/show_bug.cgi?id=198181.) Until this is fixed, `SameSite=None` might not work properly on Safari.
This version of Chrome also rejects insecure `SameSite=None` cookies. See https://www.chromestatus.com/feature/5633521622188032 for more information. Make sure that you
change the `[security]` setting `cookie_secure` to `true` and use HTTPS when `cookie_samesite` is configured to `none`, otherwise authentication in Grafana won't work properly.
...@@ -32,7 +32,7 @@ https://play.grafana.org/d/000000012/grafana-play-home?orgId=1&from=156871968017 ...@@ -32,7 +32,7 @@ https://play.grafana.org/d/000000012/grafana-play-home?orgId=1&from=156871968017
You can embed a panel using an iframe on another web site. This tab will show you the html that you need to use. You can embed a panel using an iframe on another web site. This tab will show you the html that you need to use.
> *Notice* This sharing require either anonymous access or setting [cookie_samesite]({{< relref "../installation/configuration.md#cookie-samesite" >}}) to none > **Note:** This sharing requires [allow_embedding]({{< relref "../installation/configuration.md#allow-embedding" >}}) enabled and anonymous access, or proper configuration of the [cookie_samesite]({{< relref "../installation/configuration.md#cookie-samesite" >}}) setting.
Example: Example:
......
...@@ -43,9 +43,10 @@ func (hs *HTTPServer) validateRedirectTo(redirectTo string) error { ...@@ -43,9 +43,10 @@ func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions { func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions {
return middleware.CookieOptions{ return middleware.CookieOptions{
Path: hs.Cfg.AppSubUrl + "/", Path: hs.Cfg.AppSubUrl + "/",
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSite, SameSiteDisabled: hs.Cfg.CookieSameSiteDisabled,
SameSiteMode: hs.Cfg.CookieSameSiteMode,
} }
} }
......
...@@ -113,7 +113,7 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) { ...@@ -113,7 +113,7 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) {
HttpOnly: true, HttpOnly: true,
Path: setting.AppSubUrl + "/", Path: setting.AppSubUrl + "/",
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSite, SameSite: hs.Cfg.CookieSameSiteMode,
} }
sc.m.Get(sc.url, sc.defaultHandler) sc.m.Get(sc.url, sc.defaultHandler)
sc.fakeReqNoAssertionsWithCookie("GET", sc.url, cookie).exec() sc.fakeReqNoAssertionsWithCookie("GET", sc.url, cookie).exec()
...@@ -204,7 +204,7 @@ func TestLoginViewRedirect(t *testing.T) { ...@@ -204,7 +204,7 @@ func TestLoginViewRedirect(t *testing.T) {
HttpOnly: true, HttpOnly: true,
Path: hs.Cfg.AppSubUrl + "/", Path: hs.Cfg.AppSubUrl + "/",
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSite, SameSite: hs.Cfg.CookieSameSiteMode,
} }
sc.m.Get(sc.url, sc.defaultHandler) sc.m.Get(sc.url, sc.defaultHandler)
sc.fakeReqNoAssertionsWithCookie("GET", sc.url, cookie).exec() sc.fakeReqNoAssertionsWithCookie("GET", sc.url, cookie).exec()
...@@ -312,7 +312,7 @@ func TestLoginPostRedirect(t *testing.T) { ...@@ -312,7 +312,7 @@ func TestLoginPostRedirect(t *testing.T) {
HttpOnly: true, HttpOnly: true,
Path: hs.Cfg.AppSubUrl + "/", Path: hs.Cfg.AppSubUrl + "/",
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSite, SameSite: hs.Cfg.CookieSameSiteMode,
} }
sc.m.Post(sc.url, sc.defaultHandler) sc.m.Post(sc.url, sc.defaultHandler)
sc.fakeReqNoAssertionsWithCookie("POST", sc.url, cookie).exec() sc.fakeReqNoAssertionsWithCookie("POST", sc.url, cookie).exec()
......
...@@ -7,16 +7,18 @@ import ( ...@@ -7,16 +7,18 @@ import (
) )
type CookieOptions struct { type CookieOptions struct {
Path string Path string
Secure bool Secure bool
SameSite http.SameSite SameSiteDisabled bool
SameSiteMode http.SameSite
} }
func newCookieOptions() CookieOptions { func newCookieOptions() CookieOptions {
return CookieOptions{ return CookieOptions{
Path: setting.AppSubUrl + "/", Path: setting.AppSubUrl + "/",
Secure: setting.CookieSecure, Secure: setting.CookieSecure,
SameSite: setting.CookieSameSite, SameSiteDisabled: setting.CookieSameSiteDisabled,
SameSiteMode: setting.CookieSameSiteMode,
} }
} }
...@@ -36,8 +38,8 @@ func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, g ...@@ -36,8 +38,8 @@ func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, g
Path: options.Path, Path: options.Path,
Secure: options.Secure, Secure: options.Secure,
} }
if options.SameSite != http.SameSiteDefaultMode { if !options.SameSiteDisabled {
cookie.SameSite = options.SameSite cookie.SameSite = options.SameSiteMode
} }
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)
} }
...@@ -256,12 +256,12 @@ func TestMiddlewareContext(t *testing.T) { ...@@ -256,12 +256,12 @@ func TestMiddlewareContext(t *testing.T) {
maxAge := (maxAgeHours + time.Hour).Seconds() maxAge := (maxAgeHours + time.Hour).Seconds()
sameSitePolicies := []http.SameSite{ sameSitePolicies := []http.SameSite{
http.SameSiteDefaultMode, http.SameSiteNoneMode,
http.SameSiteLaxMode, http.SameSiteLaxMode,
http.SameSiteStrictMode, http.SameSiteStrictMode,
} }
for _, sameSitePolicy := range sameSitePolicies { for _, sameSitePolicy := range sameSitePolicies {
setting.CookieSameSite = sameSitePolicy setting.CookieSameSiteMode = sameSitePolicy
expectedCookie := &http.Cookie{ expectedCookie := &http.Cookie{
Name: setting.LoginCookieName, Name: setting.LoginCookieName,
Value: "rotated", Value: "rotated",
...@@ -269,9 +269,7 @@ func TestMiddlewareContext(t *testing.T) { ...@@ -269,9 +269,7 @@ func TestMiddlewareContext(t *testing.T) {
HttpOnly: true, HttpOnly: true,
MaxAge: int(maxAge), MaxAge: int(maxAge),
Secure: setting.CookieSecure, Secure: setting.CookieSecure,
} SameSite: sameSitePolicy,
if sameSitePolicy != http.SameSiteDefaultMode {
expectedCookie.SameSite = sameSitePolicy
} }
sc.fakeReq("GET", "/").exec() sc.fakeReq("GET", "/").exec()
...@@ -287,6 +285,22 @@ func TestMiddlewareContext(t *testing.T) { ...@@ -287,6 +285,22 @@ func TestMiddlewareContext(t *testing.T) {
So(sc.resp.Header().Get("Set-Cookie"), ShouldEqual, expectedCookie.String()) So(sc.resp.Header().Get("Set-Cookie"), ShouldEqual, expectedCookie.String())
}) })
} }
Convey("Should not set cookie with SameSite attribute when setting.CookieSameSiteDisabled is true", func() {
setting.CookieSameSiteDisabled = true
setting.CookieSameSiteMode = http.SameSiteLaxMode
expectedCookie := &http.Cookie{
Name: setting.LoginCookieName,
Value: "rotated",
Path: setting.AppSubUrl + "/",
HttpOnly: true,
MaxAge: int(maxAge),
Secure: setting.CookieSecure,
}
sc.fakeReq("GET", "/").exec()
So(sc.resp.Header().Get("Set-Cookie"), ShouldEqual, expectedCookie.String())
})
}) })
middlewareScenario(t, "Invalid/expired auth token in cookie", func(sc *scenarioContext) { middlewareScenario(t, "Invalid/expired auth token in cookie", func(sc *scenarioContext) {
......
...@@ -101,7 +101,8 @@ var ( ...@@ -101,7 +101,8 @@ var (
DataProxyWhiteList map[string]bool DataProxyWhiteList map[string]bool
DisableBruteForceLoginProtection bool DisableBruteForceLoginProtection bool
CookieSecure bool CookieSecure bool
CookieSameSite http.SameSite CookieSameSiteDisabled bool
CookieSameSiteMode http.SameSite
AllowEmbedding bool AllowEmbedding bool
XSSProtectionHeader bool XSSProtectionHeader bool
ContentTypeProtectionHeader bool ContentTypeProtectionHeader bool
...@@ -248,7 +249,8 @@ type Cfg struct { ...@@ -248,7 +249,8 @@ type Cfg struct {
DisableInitAdminCreation bool DisableInitAdminCreation bool
DisableBruteForceLoginProtection bool DisableBruteForceLoginProtection bool
CookieSecure bool CookieSecure bool
CookieSameSite http.SameSite CookieSameSiteDisabled bool
CookieSameSiteMode http.SameSite
TempDataLifetime time.Duration TempDataLifetime time.Duration
MetricsEndpointEnabled bool MetricsEndpointEnabled bool
...@@ -719,18 +721,24 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { ...@@ -719,18 +721,24 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
if err != nil { if err != nil {
return err return err
} }
validSameSiteValues := map[string]http.SameSite{
"lax": http.SameSiteLaxMode,
"strict": http.SameSiteStrictMode,
"none": http.SameSiteDefaultMode,
}
if samesite, ok := validSameSiteValues[samesiteString]; ok { if samesiteString == "disabled" {
CookieSameSite = samesite CookieSameSiteDisabled = true
cfg.CookieSameSite = CookieSameSite cfg.CookieSameSiteDisabled = CookieSameSiteDisabled
} else { } else {
CookieSameSite = http.SameSiteLaxMode validSameSiteValues := map[string]http.SameSite{
cfg.CookieSameSite = CookieSameSite "lax": http.SameSiteLaxMode,
"strict": http.SameSiteStrictMode,
"none": http.SameSiteNoneMode,
}
if samesite, ok := validSameSiteValues[samesiteString]; ok {
CookieSameSiteMode = samesite
cfg.CookieSameSiteMode = CookieSameSiteMode
} else {
CookieSameSiteMode = http.SameSiteLaxMode
cfg.CookieSameSiteMode = CookieSameSiteMode
}
} }
AllowEmbedding = security.Key("allow_embedding").MustBool(false) AllowEmbedding = security.Key("allow_embedding").MustBool(false)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment