Commit f778c1d9 by Marcus Efraimsson Committed by GitHub

Security: Responses from backend should not be cached (#16848)

Currently all API requests set Cache-control: no-cache to avoid browsers 
caching sensitive data. This fixes so that all responses returned from 
backend not are cached using http headers. The exception is the data proxy 
where we don't add these http headers in case datasource backend needs 
to control whether data can be cached or not.

Fixes #16845
parent d5245a98
......@@ -225,6 +225,8 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
hs.mapStatic(m, hs.Cfg.ImagesDir, "", "/public/img/attachments")
}
m.Use(middleware.AddDefaultResponseHeaders())
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "views"),
IndentJSON: macaron.Env != macaron.PROD,
......@@ -245,7 +247,6 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
}
m.Use(middleware.HandleNoCacheHeader())
m.Use(middleware.AddDefaultResponseHeaders())
}
func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
......
......@@ -4,6 +4,7 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/grafana/grafana/pkg/bus"
......@@ -231,11 +232,17 @@ func WriteSessionCookie(ctx *m.ReqContext, value string, maxLifetimeDays int) {
}
func AddDefaultResponseHeaders() macaron.Handler {
return func(ctx *m.ReqContext) {
if ctx.IsApiRequest() && ctx.Req.Method == "GET" {
ctx.Resp.Header().Add("Cache-Control", "no-cache")
ctx.Resp.Header().Add("Pragma", "no-cache")
ctx.Resp.Header().Add("Expires", "-1")
return func(ctx *macaron.Context) {
ctx.Resp.Before(func(w macaron.ResponseWriter) {
if !strings.HasPrefix(ctx.Req.URL.Path, "/api/datasources/proxy/") {
AddNoCacheHeaders(ctx.Resp)
}
})
}
}
func AddNoCacheHeaders(w macaron.ResponseWriter) {
w.Header().Add("Cache-Control", "no-cache")
w.Header().Add("Pragma", "no-cache")
w.Header().Add("Expires", "-1")
}
......@@ -10,6 +10,7 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/remotecache"
m "github.com/grafana/grafana/pkg/models"
......@@ -34,16 +35,34 @@ func TestMiddlewareContext(t *testing.T) {
So(sc.resp.Code, ShouldEqual, 200)
})
middlewareScenario(t, "middleware should add Cache-Control header for GET requests to API", func(sc *scenarioContext) {
middlewareScenario(t, "middleware should add Cache-Control header for requests to API", func(sc *scenarioContext) {
sc.fakeReq("GET", "/api/search").exec()
So(sc.resp.Header().Get("Cache-Control"), ShouldEqual, "no-cache")
So(sc.resp.Header().Get("Pragma"), ShouldEqual, "no-cache")
So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1")
})
middlewareScenario(t, "middleware should not add Cache-Control header to for non-API GET requests", func(sc *scenarioContext) {
sc.fakeReq("GET", "/").exec()
middlewareScenario(t, "middleware should not add Cache-Control header for requests to datasource proxy API", func(sc *scenarioContext) {
sc.fakeReq("GET", "/api/datasources/proxy/1/test").exec()
So(sc.resp.Header().Get("Cache-Control"), ShouldBeEmpty)
So(sc.resp.Header().Get("Pragma"), ShouldBeEmpty)
So(sc.resp.Header().Get("Expires"), ShouldBeEmpty)
})
middlewareScenario(t, "middleware should add Cache-Control header for GET requests with html response", func(sc *scenarioContext) {
sc.handler(func(c *m.ReqContext) {
data := &dtos.IndexViewData{
User: &dtos.CurrentUser{},
Settings: map[string]interface{}{},
NavTree: []*dtos.NavLink{},
}
c.HTML(200, "index-template", data)
})
sc.fakeReq("GET", "/").exec()
So(sc.resp.Code, ShouldEqual, 200)
So(sc.resp.Header().Get("Cache-Control"), ShouldEqual, "no-cache")
So(sc.resp.Header().Get("Pragma"), ShouldEqual, "no-cache")
So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1")
})
middlewareScenario(t, "Invalid api key", func(sc *scenarioContext) {
......@@ -413,6 +432,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
viewsPath, _ := filepath.Abs("../../public/views")
sc.m = macaron.New()
sc.m.Use(AddDefaultResponseHeaders())
sc.m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: viewsPath,
Delims: macaron.Delims{Left: "[[", Right: "]]"},
......@@ -424,7 +444,6 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
sc.m.Use(GetContextHandler(sc.userAuthTokenService, sc.remoteCacheService))
sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders())
sc.defaultHandler = func(c *m.ReqContext) {
sc.context = c
......
......@@ -59,6 +59,7 @@ func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
sc.m = macaron.New()
sc.m.Use(Recovery())
sc.m.Use(AddDefaultResponseHeaders())
sc.m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: viewsPath,
Delims: macaron.Delims{Left: "[[", Right: "]]"},
......@@ -70,7 +71,6 @@ func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
sc.m.Use(GetContextHandler(sc.userAuthTokenService, sc.remoteCacheService))
// mock out gc goroutine
sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders())
sc.defaultHandler = func(c *m.ReqContext) {
sc.context = c
......
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