diff --git a/conf/defaults.ini b/conf/defaults.ini index 6e0a7a6c41a..8699ea11e90 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -255,7 +255,8 @@ content_security_policy = false # Set Content Security Policy template used when adding the Content-Security-Policy header to your requests. # $NONCE in the template includes a random nonce. -content_security_policy_template = """script-src 'self' 'unsafe-eval' 'unsafe-inline';object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline';img-src * data:;base-uri 'self';connect-src 'self' grafana.com;manifest-src 'self';media-src 'none';form-action 'self';""" +# $ROOT_PATH is server.root_url without the protocol. +content_security_policy_template = """script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src * data:;base-uri 'self';connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;manifest-src 'self';media-src 'none';form-action 'self';""" #################################### Snapshots ########################### [snapshots] diff --git a/conf/sample.ini b/conf/sample.ini index 78e4db378de..7b66c7f141e 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -261,7 +261,8 @@ # Set Content Security Policy template used when adding the Content-Security-Policy header to your requests. # $NONCE in the template includes a random nonce. -;content_security_policy_template = """script-src 'self' 'unsafe-eval' 'unsafe-inline';object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline';img-src * data:;base-uri 'self';connect-src 'self' grafana.com;manifest-src 'self';media-src 'none';form-action 'self';""" +# $ROOT_PATH is server.root_url without the protocol. +;content_security_policy_template = """script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src * data:;base-uri 'self';connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;manifest-src 'self';media-src 'none';form-action 'self';""" #################################### Snapshots ########################### [snapshots] diff --git a/pkg/middleware/csp.go b/pkg/middleware/csp.go index 46acb5a8ce5..3fff0cbe04f 100644 --- a/pkg/middleware/csp.go +++ b/pkg/middleware/csp.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "regexp" "strings" "github.com/grafana/grafana/pkg/infra/log" @@ -42,6 +43,10 @@ func AddCSPHeader(cfg *setting.Cfg, logger log.Logger) macaron.Handler { nonce := base64.RawStdEncoding.EncodeToString(buf[:]) val := strings.ReplaceAll(cfg.CSPTemplate, "$NONCE", fmt.Sprintf("'nonce-%s'", nonce)) + + re := regexp.MustCompile(`^\w+:(//)?`) + rootPath := re.ReplaceAllString(cfg.AppURL, "") + val = strings.ReplaceAll(val, "$ROOT_PATH", rootPath) w.Header().Set("Content-Security-Policy", val) ctx.RequestNonce = nonce logger.Debug("Successfully generated CSP nonce", "nonce", nonce) diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index aa52cb60a14..9a3d5ae6d35 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -28,7 +28,7 @@ func TestLoadingSettings(t *testing.T) { Convey("Given the default ini files", func() { cfg := NewCfg() - err := cfg.Load(&CommandLineArgs{HomePath: "../../"}) + err := cfg.Load(&CommandLineArgs{HomePath: "../../", Config: "../../conf/defaults.ini"}) So(err, ShouldBeNil) So(cfg.AdminUser, ShouldEqual, "admin") diff --git a/pkg/tests/web/index_view_test.go b/pkg/tests/web/index_view_test.go index 0f6086016aa..8c4af6aa641 100644 --- a/pkg/tests/web/index_view_test.go +++ b/pkg/tests/web/index_view_test.go @@ -23,8 +23,7 @@ func TestIndexView(t *testing.T) { // nolint:bodyclose resp, html := makeRequest(t, addr) - - assert.Regexp(t, `script-src 'self' 'unsafe-eval' 'unsafe-inline';object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline';img-src \* data:;base-uri 'self';connect-src 'self' grafana.com;manifest-src 'self';media-src 'none';form-action 'self';`, resp.Header.Get("Content-Security-Policy")) + assert.Regexp(t, `script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' 'nonce-[^']+';object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src \* data:;base-uri 'self';connect-src 'self' grafana.com ws://localhost:3000/ wss://localhost:3000/;manifest-src 'self';media-src 'none';form-action 'self';`, resp.Header.Get("Content-Security-Policy")) assert.Regexp(t, `