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:
Arve Knudsen
2021-01-12 07:42:32 +01:00
committed by GitHub
parent 4ed901e1f9
commit 50b649a869
19 changed files with 449 additions and 222 deletions

50
pkg/middleware/csp.go Normal file
View File

@@ -0,0 +1,50 @@
package middleware
import (
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
macaron "gopkg.in/macaron.v1"
)
// AddCSPHeader adds the Content Security Policy header.
func AddCSPHeader(cfg *setting.Cfg, logger log.Logger) macaron.Handler {
return func(w http.ResponseWriter, req *http.Request, c *macaron.Context) {
if !cfg.CSPEnabled {
logger.Debug("Not adding CSP header to response since it's disabled")
return
}
logger.Debug("Adding CSP header to response", "cfg", fmt.Sprintf("%p", cfg))
ctx, ok := c.Data["ctx"].(*models.ReqContext)
if !ok {
panic("Failed to convert context into models.ReqContext")
}
if cfg.CSPTemplate == "" {
logger.Debug("CSP template not configured, so returning 500")
ctx.JsonApiErr(500, "CSP template has to be configured", nil)
return
}
var buf [16]byte
if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
logger.Error("Failed to generate CSP nonce", "err", err)
ctx.JsonApiErr(500, "Failed to generate CSP nonce", err)
}
nonce := base64.RawStdEncoding.EncodeToString(buf[:])
val := strings.ReplaceAll(cfg.CSPTemplate, "$NONCE", fmt.Sprintf("'nonce-%s'", nonce))
w.Header().Set("Content-Security-Policy", val)
ctx.RequestNonce = nonce
logger.Debug("Successfully generated CSP nonce", "nonce", nonce)
}
}

View File

@@ -1,14 +0,0 @@
package middleware
import (
"github.com/grafana/grafana/pkg/models"
macaron "gopkg.in/macaron.v1"
)
const HeaderNameNoBackendCache = "X-Grafana-NoCache"
func HandleNoCacheHeader() macaron.Handler {
return func(ctx *models.ReqContext) {
ctx.SkipCache = ctx.Req.Header.Get(HeaderNameNoBackendCache) == "true"
}
}

View File

@@ -20,16 +20,20 @@ var (
ReqOrgAdmin = RoleAuth(models.ROLE_ADMIN)
)
func HandleNoCacheHeader(ctx *models.ReqContext) {
ctx.SkipCache = ctx.Req.Header.Get("X-Grafana-NoCache") == "true"
}
func AddDefaultResponseHeaders(cfg *setting.Cfg) macaron.Handler {
return func(ctx *macaron.Context) {
ctx.Resp.Before(func(w macaron.ResponseWriter) {
return func(c *macaron.Context) {
c.Resp.Before(func(w macaron.ResponseWriter) {
// if response has already been written, skip.
if w.Written() {
return
}
if !strings.HasPrefix(ctx.Req.URL.Path, "/api/datasources/proxy/") {
addNoCacheHeaders(ctx.Resp)
if !strings.HasPrefix(c.Req.URL.Path, "/api/datasources/proxy/") {
addNoCacheHeaders(c.Resp)
}
if !cfg.AllowEmbedding {

View File

@@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models"
@@ -539,6 +540,8 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
t.Run(desc, func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
logger := log.New("test")
loginMaxLifetime, err := gtime.ParseDuration("30d")
require.NoError(t, err)
cfg := setting.NewCfg()
@@ -560,6 +563,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
sc.m = macaron.New()
sc.m.Use(AddDefaultResponseHeaders(cfg))
sc.m.Use(AddCSPHeader(cfg, logger))
sc.m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: viewsPath,
Delims: macaron.Delims{Left: "[[", Right: "]]"},