mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Middleware: Add CSP support (#29740)
* Middleware: Add support for CSP Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored by @iOrcohen
This commit is contained in:
@@ -25,6 +25,8 @@ type IndexViewData struct {
|
||||
AppleTouchIcon template.URL
|
||||
AppTitle string
|
||||
Sentry *setting.Sentry
|
||||
// Nonce is a cryptographic identifier for use with Content Security Policy.
|
||||
Nonce string
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -346,7 +346,8 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
||||
m.Use(middleware.ValidateHostHeader(hs.Cfg))
|
||||
}
|
||||
|
||||
m.Use(middleware.HandleNoCacheHeader())
|
||||
m.Use(middleware.HandleNoCacheHeader)
|
||||
m.Use(middleware.AddCSPHeader(hs.Cfg, hs.log))
|
||||
|
||||
for _, mw := range hs.middlewares {
|
||||
m.Use(mw)
|
||||
|
||||
@@ -427,6 +427,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
||||
AppTitle: "Grafana",
|
||||
NavTree: navTree,
|
||||
Sentry: &hs.Cfg.Sentry,
|
||||
Nonce: c.RequestNonce,
|
||||
}
|
||||
|
||||
if setting.DisableGravatar {
|
||||
|
||||
@@ -21,14 +21,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ViewIndex = "index"
|
||||
LoginErrorCookieName = "login_error"
|
||||
viewIndex = "index"
|
||||
loginErrorCookieName = "login_error"
|
||||
)
|
||||
|
||||
var setIndexViewData = (*HTTPServer).setIndexViewData
|
||||
|
||||
var getViewIndex = func() string {
|
||||
return ViewIndex
|
||||
return viewIndex
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
|
||||
@@ -96,12 +96,12 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
||||
viewData.Settings["oauth"] = enabledOAuths
|
||||
viewData.Settings["samlEnabled"] = hs.License.HasValidLicense() && hs.Cfg.SAMLEnabled
|
||||
|
||||
if loginError, ok := tryGetEncryptedCookie(c, LoginErrorCookieName); ok {
|
||||
if loginError, ok := tryGetEncryptedCookie(c, loginErrorCookieName); ok {
|
||||
// this cookie is only set whenever an OAuth login fails
|
||||
// therefore the loginError should be passed to the view data
|
||||
// and the view should return immediately before attempting
|
||||
// to login again via OAuth and enter to a redirect loop
|
||||
cookies.DeleteCookie(c.Resp, LoginErrorCookieName, hs.CookieOptionsFromCfg)
|
||||
cookies.DeleteCookie(c.Resp, loginErrorCookieName, hs.CookieOptionsFromCfg)
|
||||
viewData.Settings["loginError"] = loginError
|
||||
c.HTML(200, getViewIndex(), viewData)
|
||||
return
|
||||
@@ -317,7 +317,7 @@ func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName s
|
||||
|
||||
func (hs *HTTPServer) redirectWithError(ctx *models.ReqContext, err error, v ...interface{}) {
|
||||
ctx.Logger.Error(err.Error(), v...)
|
||||
if err := hs.trySetEncryptedCookie(ctx, LoginErrorCookieName, err.Error(), 60); err != nil {
|
||||
if err := hs.trySetEncryptedCookie(ctx, loginErrorCookieName, err.Error(), 60); err != nil {
|
||||
hs.log.Error("Failed to set encrypted cookie", "err", err)
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ func (hs *HTTPServer) redirectWithError(ctx *models.ReqContext, err error, v ...
|
||||
|
||||
func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err error, v ...interface{}) *RedirectResponse {
|
||||
ctx.Logger.Error(err.Error(), v...)
|
||||
if err := hs.trySetEncryptedCookie(ctx, LoginErrorCookieName, err.Error(), 60); err != nil {
|
||||
if err := hs.trySetEncryptedCookie(ctx, loginErrorCookieName, err.Error(), 60); err != nil {
|
||||
hs.log.Error("Failed to set encrypted cookie", "err", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -103,25 +103,34 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) {
|
||||
cfg.LoginCookieName = "grafana_session"
|
||||
setting.SecretKey = "login_testing"
|
||||
|
||||
setting.OAuthService = &setting.OAuther{}
|
||||
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
|
||||
setting.OAuthService.OAuthInfos["github"] = &setting.OAuthInfo{
|
||||
ClientId: "fake",
|
||||
ClientSecret: "fakefake",
|
||||
Enabled: true,
|
||||
AllowSignup: true,
|
||||
Name: "github",
|
||||
origOAuthService := setting.OAuthService
|
||||
origOAuthAutoLogin := setting.OAuthAutoLogin
|
||||
t.Cleanup(func() {
|
||||
setting.OAuthService = origOAuthService
|
||||
setting.OAuthAutoLogin = origOAuthAutoLogin
|
||||
})
|
||||
setting.OAuthService = &setting.OAuther{
|
||||
OAuthInfos: map[string]*setting.OAuthInfo{
|
||||
"github": {
|
||||
ClientId: "fake",
|
||||
ClientSecret: "fakefake",
|
||||
Enabled: true,
|
||||
AllowSignup: true,
|
||||
Name: "github",
|
||||
},
|
||||
},
|
||||
}
|
||||
setting.OAuthAutoLogin = true
|
||||
|
||||
oauthError := errors.New("User not a member of one of the required organizations")
|
||||
encryptedError, _ := util.Encrypt([]byte(oauthError.Error()), setting.SecretKey)
|
||||
encryptedError, err := util.Encrypt([]byte(oauthError.Error()), setting.SecretKey)
|
||||
require.NoError(t, err)
|
||||
expCookiePath := "/"
|
||||
if len(setting.AppSubUrl) > 0 {
|
||||
expCookiePath = setting.AppSubUrl
|
||||
}
|
||||
cookie := http.Cookie{
|
||||
Name: LoginErrorCookieName,
|
||||
Name: loginErrorCookieName,
|
||||
MaxAge: 60,
|
||||
Value: hex.EncodeToString(encryptedError),
|
||||
HttpOnly: true,
|
||||
@@ -131,10 +140,10 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
sc.m.Get(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertionsWithCookie("GET", sc.url, cookie).exec()
|
||||
assert.Equal(t, sc.resp.Code, 200)
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
|
||||
responseString, err := getBody(sc.resp)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, strings.Contains(responseString, oauthError.Error()))
|
||||
}
|
||||
|
||||
@@ -276,7 +285,7 @@ func TestLoginViewRedirect(t *testing.T) {
|
||||
}
|
||||
sc.m.Get(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertionsWithCookie("GET", sc.url, cookie).exec()
|
||||
assert.Equal(t, c.status, sc.resp.Code)
|
||||
require.Equal(t, c.status, sc.resp.Code)
|
||||
if c.status == 302 {
|
||||
location, ok := sc.resp.Header()["Location"]
|
||||
assert.True(t, ok)
|
||||
@@ -304,7 +313,7 @@ func TestLoginViewRedirect(t *testing.T) {
|
||||
}
|
||||
|
||||
responseString, err := getBody(sc.resp)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
if c.err != nil {
|
||||
assert.True(t, strings.Contains(responseString, c.err.Error()))
|
||||
}
|
||||
@@ -443,10 +452,10 @@ func TestLoginPostRedirect(t *testing.T) {
|
||||
}
|
||||
sc.m.Post(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertionsWithCookie("POST", sc.url, cookie).exec()
|
||||
assert.Equal(t, sc.resp.Code, 200)
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
redirectURL := respJSON.Get("redirectUrl").MustString()
|
||||
if c.err != nil {
|
||||
assert.Equal(t, "", redirectURL)
|
||||
@@ -496,10 +505,10 @@ func TestLoginOAuthRedirect(t *testing.T) {
|
||||
sc.m.Get(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertions("GET", sc.url).exec()
|
||||
|
||||
assert.Equal(t, sc.resp.Code, 307)
|
||||
require.Equal(t, 307, sc.resp.Code)
|
||||
location, ok := sc.resp.Header()["Location"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, location[0], "/login/github")
|
||||
assert.Equal(t, "/login/github", location[0])
|
||||
}
|
||||
|
||||
func TestLoginInternal(t *testing.T) {
|
||||
@@ -532,16 +541,16 @@ func TestLoginInternal(t *testing.T) {
|
||||
sc.fakeReqNoAssertions("GET", sc.url).exec()
|
||||
|
||||
// Shouldn't redirect to the OAuth login URL
|
||||
assert.Equal(t, sc.resp.Code, 200)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}
|
||||
|
||||
func TestAuthProxyLoginEnableLoginTokenDisabled(t *testing.T) {
|
||||
sc := setupAuthProxyLoginTest(t, false)
|
||||
|
||||
assert.Equal(t, sc.resp.Code, 302)
|
||||
require.Equal(t, 302, sc.resp.Code)
|
||||
location, ok := sc.resp.Header()["Location"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, location[0], "/")
|
||||
assert.Equal(t, "/", location[0])
|
||||
|
||||
_, ok = sc.resp.Header()["Set-Cookie"]
|
||||
assert.False(t, ok, "Set-Cookie does not exist")
|
||||
@@ -549,11 +558,11 @@ func TestAuthProxyLoginEnableLoginTokenDisabled(t *testing.T) {
|
||||
|
||||
func TestAuthProxyLoginWithEnableLoginToken(t *testing.T) {
|
||||
sc := setupAuthProxyLoginTest(t, true)
|
||||
require.Equal(t, sc.resp.Code, 302)
|
||||
require.Equal(t, 302, sc.resp.Code)
|
||||
|
||||
location, ok := sc.resp.Header()["Location"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, location[0], "/")
|
||||
assert.Equal(t, "/", location[0])
|
||||
setCookie := sc.resp.Header()["Set-Cookie"]
|
||||
require.NotNil(t, setCookie, "Set-Cookie should exist")
|
||||
assert.Equal(t, "grafana_session=; Path=/; Max-Age=0; HttpOnly", setCookie[0])
|
||||
|
||||
Reference in New Issue
Block a user