Commit d94796a0 by Erik Sundell Committed by GitHub

Auth: Remove trailing / in cookies' path (#22265) (#22265)

According to the stackoverflow answer below, it is recommended to not
include a trailing / in cookies' path. By removing the trailing / for
our cookies path value, people's browsers visiting grafana will pass the
cookie not only to /grafana/ sub paths but also to /grafana sub paths.

This commit avoids the situation where a user would visit
http://localhost/grafana, get redirected to
http://localhost/grafana/login, and following login get redirected back
to http://localhost/grafana, but since the grafana_session cookie isn't
passed along get redirected back once more to
http://localhost/grafana/login.

ref: https://stackoverflow.com/questions/36131023/setting-a-slash-on-cookie-path/53784228#53784228
ref: https://tools.ietf.org/html/rfc6265#section-5.1.4
parent 2299e6bf
...@@ -46,8 +46,12 @@ func (hs *HTTPServer) validateRedirectTo(redirectTo string) error { ...@@ -46,8 +46,12 @@ func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
} }
func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions { func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions {
path := "/"
if len(hs.Cfg.AppSubUrl) > 0 {
path = hs.Cfg.AppSubUrl
}
return middleware.CookieOptions{ return middleware.CookieOptions{
Path: hs.Cfg.AppSubUrl + "/", Path: path,
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSiteDisabled: hs.Cfg.CookieSameSiteDisabled, SameSiteDisabled: hs.Cfg.CookieSameSiteDisabled,
SameSiteMode: hs.Cfg.CookieSameSiteMode, SameSiteMode: hs.Cfg.CookieSameSiteMode,
......
...@@ -10,8 +10,6 @@ import ( ...@@ -10,8 +10,6 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
...@@ -19,6 +17,7 @@ import ( ...@@ -19,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
...@@ -109,12 +108,16 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) { ...@@ -109,12 +108,16 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) {
oauthError := errors.New("User not a member of one of the required organizations") oauthError := errors.New("User not a member of one of the required organizations")
encryptedError, _ := util.Encrypt([]byte(oauthError.Error()), setting.SecretKey) encryptedError, _ := util.Encrypt([]byte(oauthError.Error()), setting.SecretKey)
expCookiePath := "/"
if len(setting.AppSubUrl) > 0 {
expCookiePath = setting.AppSubUrl
}
cookie := http.Cookie{ cookie := http.Cookie{
Name: LoginErrorCookieName, Name: LoginErrorCookieName,
MaxAge: 60, MaxAge: 60,
Value: hex.EncodeToString(encryptedError), Value: hex.EncodeToString(encryptedError),
HttpOnly: true, HttpOnly: true,
Path: setting.AppSubUrl + "/", Path: expCookiePath,
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSiteMode, SameSite: hs.Cfg.CookieSameSiteMode,
} }
...@@ -210,12 +213,16 @@ func TestLoginViewRedirect(t *testing.T) { ...@@ -210,12 +213,16 @@ func TestLoginViewRedirect(t *testing.T) {
hs.Cfg.AppUrl = c.appURL hs.Cfg.AppUrl = c.appURL
hs.Cfg.AppSubUrl = c.appSubURL hs.Cfg.AppSubUrl = c.appSubURL
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
expCookiePath := "/"
if len(hs.Cfg.AppSubUrl) > 0 {
expCookiePath = hs.Cfg.AppSubUrl
}
cookie := http.Cookie{ cookie := http.Cookie{
Name: "redirect_to", Name: "redirect_to",
MaxAge: 60, MaxAge: 60,
Value: c.url, Value: c.url,
HttpOnly: true, HttpOnly: true,
Path: hs.Cfg.AppSubUrl + "/", Path: expCookiePath,
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSiteMode, SameSite: hs.Cfg.CookieSameSiteMode,
} }
...@@ -238,7 +245,7 @@ func TestLoginViewRedirect(t *testing.T) { ...@@ -238,7 +245,7 @@ func TestLoginViewRedirect(t *testing.T) {
expCookieValue = "" expCookieValue = ""
expCookieMaxAge = 0 expCookieMaxAge = 0
} }
expCookie := fmt.Sprintf("redirect_to=%v; Path=%v; Max-Age=%v; HttpOnly; Secure", expCookieValue, hs.Cfg.AppSubUrl+"/", expCookieMaxAge) expCookie := fmt.Sprintf("redirect_to=%v; Path=%v; Max-Age=%v; HttpOnly; Secure", expCookieValue, expCookiePath, expCookieMaxAge)
for _, cookieValue := range setCookie { for _, cookieValue := range setCookie {
if cookieValue == expCookie { if cookieValue == expCookie {
redirectToCookieFound = true redirectToCookieFound = true
...@@ -332,12 +339,16 @@ func TestLoginPostRedirect(t *testing.T) { ...@@ -332,12 +339,16 @@ func TestLoginPostRedirect(t *testing.T) {
hs.Cfg.AppUrl = c.appURL hs.Cfg.AppUrl = c.appURL
hs.Cfg.AppSubUrl = c.appSubURL hs.Cfg.AppSubUrl = c.appSubURL
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
expCookiePath := "/"
if len(hs.Cfg.AppSubUrl) > 0 {
expCookiePath = hs.Cfg.AppSubUrl
}
cookie := http.Cookie{ cookie := http.Cookie{
Name: "redirect_to", Name: "redirect_to",
MaxAge: 60, MaxAge: 60,
Value: c.url, Value: c.url,
HttpOnly: true, HttpOnly: true,
Path: hs.Cfg.AppSubUrl + "/", Path: expCookiePath,
Secure: hs.Cfg.CookieSecure, Secure: hs.Cfg.CookieSecure,
SameSite: hs.Cfg.CookieSameSiteMode, SameSite: hs.Cfg.CookieSameSiteMode,
} }
...@@ -358,7 +369,7 @@ func TestLoginPostRedirect(t *testing.T) { ...@@ -358,7 +369,7 @@ func TestLoginPostRedirect(t *testing.T) {
assert.True(t, ok, "Set-Cookie exists") assert.True(t, ok, "Set-Cookie exists")
assert.Greater(t, len(setCookie), 0) assert.Greater(t, len(setCookie), 0)
var redirectToCookieFound bool var redirectToCookieFound bool
expCookieValue := fmt.Sprintf("redirect_to=; Path=%v; Max-Age=0; HttpOnly; Secure", hs.Cfg.AppSubUrl+"/") expCookieValue := fmt.Sprintf("redirect_to=; Path=%v; Max-Age=0; HttpOnly; Secure", expCookiePath)
for _, cookieValue := range setCookie { for _, cookieValue := range setCookie {
if cookieValue == expCookieValue { if cookieValue == expCookieValue {
redirectToCookieFound = true redirectToCookieFound = true
......
...@@ -14,8 +14,12 @@ type CookieOptions struct { ...@@ -14,8 +14,12 @@ type CookieOptions struct {
} }
func newCookieOptions() CookieOptions { func newCookieOptions() CookieOptions {
path := "/"
if len(setting.AppSubUrl) > 0 {
path = setting.AppSubUrl
}
return CookieOptions{ return CookieOptions{
Path: setting.AppSubUrl + "/", Path: path,
Secure: setting.CookieSecure, Secure: setting.CookieSecure,
SameSiteDisabled: setting.CookieSameSiteDisabled, SameSiteDisabled: setting.CookieSameSiteDisabled,
SameSiteMode: setting.CookieSameSiteMode, SameSiteMode: setting.CookieSameSiteMode,
......
...@@ -264,10 +264,14 @@ func TestMiddlewareContext(t *testing.T) { ...@@ -264,10 +264,14 @@ func TestMiddlewareContext(t *testing.T) {
} }
for _, sameSitePolicy := range sameSitePolicies { for _, sameSitePolicy := range sameSitePolicies {
setting.CookieSameSiteMode = sameSitePolicy setting.CookieSameSiteMode = sameSitePolicy
expectedCookiePath := "/"
if len(setting.AppSubUrl) > 0 {
expectedCookiePath = setting.AppSubUrl
}
expectedCookie := &http.Cookie{ expectedCookie := &http.Cookie{
Name: setting.LoginCookieName, Name: setting.LoginCookieName,
Value: "rotated", Value: "rotated",
Path: setting.AppSubUrl + "/", Path: expectedCookiePath,
HttpOnly: true, HttpOnly: true,
MaxAge: int(maxAge), MaxAge: int(maxAge),
Secure: setting.CookieSecure, Secure: setting.CookieSecure,
...@@ -291,10 +295,14 @@ func TestMiddlewareContext(t *testing.T) { ...@@ -291,10 +295,14 @@ func TestMiddlewareContext(t *testing.T) {
Convey("Should not set cookie with SameSite attribute when setting.CookieSameSiteDisabled is true", func() { Convey("Should not set cookie with SameSite attribute when setting.CookieSameSiteDisabled is true", func() {
setting.CookieSameSiteDisabled = true setting.CookieSameSiteDisabled = true
setting.CookieSameSiteMode = http.SameSiteLaxMode setting.CookieSameSiteMode = http.SameSiteLaxMode
expectedCookiePath := "/"
if len(setting.AppSubUrl) > 0 {
expectedCookiePath = setting.AppSubUrl
}
expectedCookie := &http.Cookie{ expectedCookie := &http.Cookie{
Name: setting.LoginCookieName, Name: setting.LoginCookieName,
Value: "rotated", Value: "rotated",
Path: setting.AppSubUrl + "/", Path: expectedCookiePath,
HttpOnly: true, HttpOnly: true,
MaxAge: int(maxAge), MaxAge: int(maxAge),
Secure: setting.CookieSecure, Secure: setting.CookieSecure,
......
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