Security: Update default CSP template and fix firefox CSP issues (#34836)

* Security: Update default content_security_policy_template
- Add 'strict-dynamic' back to script-src
- Add ws(s)://$ROOT_PATH to connect-src
- Change onEvent to on-event in angular templates to fix CSP issues in firefox.
- Add blob: to style-src
This commit is contained in:
kay delaney 2021-05-28 16:01:10 +01:00 committed by GitHub
parent 244d3f61c3
commit 8143991b94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 26 additions and 22 deletions

View File

@ -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]

View File

@ -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]

View File

@ -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)

View File

@ -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")

View File

@ -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, `<script nonce="[^"]+"`, html)
})

View File

@ -12,7 +12,7 @@ export function spectrumPicker() {
require: 'ngModel',
scope: true,
replace: true,
template: '<color-picker color="ngModel.$viewValue" onChange="onColorChange"></color-picker>',
template: '<color-picker color="ngModel.$viewValue" on-change="onColorChange"></color-picker>',
link: (scope: any, element: any, attrs: any, ngModel: any) => {
scope.ngModel = ngModel;
scope.onColorChange = (color: string) => {

View File

@ -17,7 +17,7 @@
Unit
<info-popover mode="right-normal">The default unit used when not defined by the datasource or in the Fields or Overrides configuration.</info-popover>
</label>
<unit-picker onChange="ctrl.setUnitFormat(yaxis)" value="yaxis.format" class="flex-grow-1" />
<unit-picker on-change="ctrl.setUnitFormat(yaxis)" value="yaxis.format" class="flex-grow-1" />
</div>
</div>

View File

@ -9,7 +9,7 @@ const template = `
<div ng-if="ctrl.contextMenuCtrl.isVisible">
<graph-context-menu
itemsGroup="ctrl.contextMenuCtrl.menuItemsSupplier()"
onClose="ctrl.onContextMenuClose"
on-close="ctrl.onContextMenuClose"
getContextMenuSource="ctrl.contextMenuCtrl.getSource"
timeZone="ctrl.getTimeZone()"
x="ctrl.contextMenuCtrl.position.x"

View File

@ -54,7 +54,7 @@
<div class="gf-form" ng-if="threshold.fill && threshold.colorMode === 'custom'">
<label class="gf-form-label">Fill color</label>
<span class="gf-form-label">
<color-picker color="threshold.fillColor" onChange="ctrl.onFillColorChange($index)"></color-picker>
<color-picker color="threshold.fillColor" on-change="ctrl.onFillColorChange($index)"></color-picker>
</span>
</div>
@ -69,7 +69,7 @@
<div class="gf-form" ng-if="threshold.line && threshold.colorMode === 'custom'">
<label class="gf-form-label">Line color</label>
<span class="gf-form-label">
<color-picker color="threshold.lineColor" onChange="ctrl.onLineColorChange($index)"></color-picker>
<color-picker color="threshold.lineColor" on-change="ctrl.onLineColorChange($index)"></color-picker>
</span>
</div>

View File

@ -59,7 +59,7 @@
<div class="gf-form" ng-if="timeRegion.fill && timeRegion.colorMode === 'custom'">
<label class="gf-form-label">Fill color</label>
<span class="gf-form-label">
<color-picker color="timeRegion.fillColor" onChange="ctrl.onFillColorChange($index)"></color-picker>
<color-picker color="timeRegion.fillColor" on-change="ctrl.onFillColorChange($index)"></color-picker>
</span>
</div>
@ -68,7 +68,7 @@
<div class="gf-form" ng-if="timeRegion.line && timeRegion.colorMode === 'custom'">
<label class="gf-form-label">Line color</label>
<span class="gf-form-label">
<color-picker color="timeRegion.lineColor" onChange="ctrl.onLineColorChange($index)"></color-picker>
<color-picker color="timeRegion.lineColor" on-change="ctrl.onLineColorChange($index)"></color-picker>
</span>
</div>

View File

@ -3,7 +3,7 @@
<h5 class="section-heading">Y Axis</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Unit</label>
<unit-picker onChange="editor.setUnitFormat" value="ctrl.panel.yAxis.format" class="width-12"></unit-picker>
<unit-picker on-change="editor.setUnitFormat" value="ctrl.panel.yAxis.format" class="width-12"></unit-picker>
</div>
<div ng-if="ctrl.panel.dataFormat == 'timeseries'">
<div class="gf-form">

View File

@ -12,7 +12,7 @@
<div class="gf-form">
<label class="gf-form-label width-9">Color</label>
<span class="gf-form-label">
<color-picker color="ctrl.panel.color.cardColor" onChange="ctrl.onCardColorChange"></color-picker>
<color-picker color="ctrl.panel.color.cardColor" on-change="ctrl.onCardColorChange"></color-picker>
</span>
</div>
<div class="gf-form">

View File

@ -106,7 +106,7 @@
<div ng-if="style.type === 'number'">
<div class="gf-form">
<label class="gf-form-label width-8">Unit</label>
<unit-picker onChange="editor.setUnitFormat(style)" value="style.unit" width="16"></unit-picker>
<unit-picker on-change="editor.setUnitFormat(style)" value="style.unit" width="16"></unit-picker>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Decimals</label>
@ -220,13 +220,13 @@
<div class="gf-form">
<label class="gf-form-label width-8">Colors</label>
<span class="gf-form-label">
<color-picker color="style.colors[0]" onChange="editor.onColorChange(style, 0)"></color-picker>
<color-picker color="style.colors[0]" on-change="editor.onColorChange(style, 0)"></color-picker>
</span>
<span class="gf-form-label">
<color-picker color="style.colors[1]" onChange="editor.onColorChange(style, 1)"></color-picker>
<color-picker color="style.colors[1]" on-change="editor.onColorChange(style, 1)"></color-picker>
</span>
<span class="gf-form-label">
<color-picker color="style.colors[2]" onChange="editor.onColorChange(style, 2)"></color-picker>
<color-picker color="style.colors[2]" on-change="editor.onColorChange(style, 2)"></color-picker>
</span>
<div class="gf-form-label">
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>

View File

@ -262,8 +262,7 @@
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/robotomono/v13/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2)
format('woff2');
src: url(../fonts/roboto/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@ -274,8 +273,7 @@
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/robotomono/v13/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2)
format('woff2');
src: url(../fonts/roboto/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}