Selaa lähdekoodia

Security: Add new setting allow_embedding (#16853)

When allow_embedding is false (default) the Grafana backend 
will set the http header `X-Frame-Options: deny` in all responses 
to non-static content which will instruct browser to not allow 
Grafana to be embedded in `<frame>`, `<iframe>`, 
`<embed>` or `<object>`.

Closes #14189
Marcus Efraimsson 6 vuotta sitten
vanhempi
commit
1c1427520d

+ 3 - 0
conf/defaults.ini

@@ -176,6 +176,9 @@ cookie_secure = false
 # set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
 cookie_samesite = lax
 
+# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
+allow_embedding = false
+
 #################################### Snapshots ###########################
 [snapshots]
 # snapshot sharing options

+ 3 - 0
conf/sample.ini

@@ -172,6 +172,9 @@ log_queries =
 # set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
 ;cookie_samesite = lax
 
+# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
+;allow_embedding = false
+
 #################################### Snapshots ###########################
 [snapshots]
 # snapshot sharing options

+ 6 - 0
docs/sources/installation/configuration.md

@@ -314,6 +314,12 @@ Set to `true` if you host Grafana behind HTTPS. Default is `false`.
 
 Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. The main goal is mitigate the risk of cross-origin information leakage. It also provides some protection against cross-site request forgery attacks (CSRF),  [read more here](https://www.owasp.org/index.php/SameSite). Valid values are `lax`, `strict` and `none`. Default is `lax`.
 
+### allow_embedding
+
+When `false`, the HTTP header `X-Frame-Options: deny` will be set in Grafana HTTP responses which will instruct
+browsers to not allow rendering Grafana in a `<frame>`, `<iframe>`, `<embed>` or `<object>`. The main goal is to
+mitigate the risk of [Clickjacking](https://www.owasp.org/index.php/Clickjacking). Default is `false`.
+
 <hr />
 
 ## [users]

+ 8 - 0
docs/sources/installation/upgrading.md

@@ -152,6 +152,8 @@ The default cookie name for storing the auth token is `grafana_session`. you can
 
 ## Upgrading to v6.2
 
+### Ensure encryption of datasource secrets
+
 Datasources store passwords and basic auth passwords in secureJsonData encrypted by default. Existing datasource
 will keep working with unencrypted passwords. If you want to migrate to encrypted storage for your existing datasources
 you can do that by:
@@ -160,3 +162,9 @@ password and save the datasource.
 - For datasources created by provisioning, you need to update your config file and use secureJsonData.password or
 secureJsonData.basicAuthPassword field. See [provisioning docs](/administration/provisioning) for example of current
 configuration.
+
+### Embedding Grafana
+
+If you're embedding Grafana in a `<frame>`, `<iframe>`, `<embed>` or `<object>` on a different website it will no longer work due to a new setting
+that per default instructs the browser to not allow Grafana to be embedded. Read more [here](/installation/configuration/#allow-embedding) about
+this new setting.

+ 8 - 0
pkg/middleware/middleware.go

@@ -237,6 +237,10 @@ func AddDefaultResponseHeaders() macaron.Handler {
 			if !strings.HasPrefix(ctx.Req.URL.Path, "/api/datasources/proxy/") {
 				AddNoCacheHeaders(ctx.Resp)
 			}
+
+			if !setting.AllowEmbedding {
+				AddXFrameOptionsDenyHeader(w)
+			}
 		})
 	}
 }
@@ -246,3 +250,7 @@ func AddNoCacheHeaders(w macaron.ResponseWriter) {
 	w.Header().Add("Pragma", "no-cache")
 	w.Header().Add("Expires", "-1")
 }
+
+func AddXFrameOptionsDenyHeader(w macaron.ResponseWriter) {
+	w.Header().Add("X-Frame-Options", "deny")
+}

+ 12 - 1
pkg/middleware/middleware_test.go

@@ -49,7 +49,7 @@ func TestMiddlewareContext(t *testing.T) {
 			So(sc.resp.Header().Get("Expires"), ShouldBeEmpty)
 		})
 
-		middlewareScenario(t, "middleware should add Cache-Control header for GET requests with html response", func(sc *scenarioContext) {
+		middlewareScenario(t, "middleware should add Cache-Control header for requests with html response", func(sc *scenarioContext) {
 			sc.handler(func(c *m.ReqContext) {
 				data := &dtos.IndexViewData{
 					User:     &dtos.CurrentUser{},
@@ -65,6 +65,17 @@ func TestMiddlewareContext(t *testing.T) {
 			So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1")
 		})
 
+		middlewareScenario(t, "middleware should add X-Frame-Options header with deny for request when not allowing embedding", func(sc *scenarioContext) {
+			sc.fakeReq("GET", "/api/search").exec()
+			So(sc.resp.Header().Get("X-Frame-Options"), ShouldEqual, "deny")
+		})
+
+		middlewareScenario(t, "middleware should not add X-Frame-Options header for request when allowing embedding", func(sc *scenarioContext) {
+			setting.AllowEmbedding = true
+			sc.fakeReq("GET", "/api/search").exec()
+			So(sc.resp.Header().Get("X-Frame-Options"), ShouldBeEmpty)
+		})
+
 		middlewareScenario(t, "Invalid api key", func(sc *scenarioContext) {
 			sc.apiKey = "invalid_key_test"
 			sc.fakeReq("GET", "/").exec()

+ 3 - 0
pkg/setting/setting.go

@@ -93,6 +93,7 @@ var (
 	DisableBruteForceLoginProtection bool
 	CookieSecure                     bool
 	CookieSameSite                   http.SameSite
+	AllowEmbedding                   bool
 
 	// Snapshots
 	ExternalSnapshotUrl   string
@@ -690,6 +691,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
 		cfg.CookieSameSite = CookieSameSite
 	}
 
+	AllowEmbedding = security.Key("allow_embedding").MustBool(false)
+
 	// read snapshots settings
 	snapshots := iniFile.Section("snapshots")
 	ExternalSnapshotUrl, err = valueAsString(snapshots, "external_snapshot_url", "")