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:
50
pkg/middleware/csp.go
Normal file
50
pkg/middleware/csp.go
Normal 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)
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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: "]]"},
|
||||
|
Reference in New Issue
Block a user