2014-10-05 22:50:04 +08:00
|
|
|
package middleware
|
|
|
|
|
|
|
|
import (
|
2019-06-12 19:15:50 +08:00
|
|
|
"fmt"
|
2019-05-06 15:22:59 +08:00
|
|
|
"strings"
|
2014-10-05 22:50:04 +08:00
|
|
|
|
2023-04-05 15:13:24 +08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
2023-01-27 15:50:36 +08:00
|
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
2022-08-10 17:56:48 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2015-02-05 17:37:13 +08:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2021-10-11 20:30:59 +08:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2019-08-02 17:16:31 +08:00
|
|
|
)
|
|
|
|
|
2018-10-11 18:36:04 +08:00
|
|
|
var (
|
2019-08-02 17:16:31 +08:00
|
|
|
ReqGrafanaAdmin = Auth(&AuthOptions{
|
|
|
|
ReqSignedIn: true,
|
|
|
|
ReqGrafanaAdmin: true,
|
|
|
|
})
|
2021-02-28 01:04:28 +08:00
|
|
|
ReqSignedIn = Auth(&AuthOptions{ReqSignedIn: true})
|
|
|
|
ReqSignedInNoAnonymous = Auth(&AuthOptions{ReqSignedIn: true, ReqNoAnonynmous: true})
|
2022-08-10 17:56:48 +08:00
|
|
|
ReqEditorRole = RoleAuth(org.RoleEditor, org.RoleAdmin)
|
|
|
|
ReqOrgAdmin = RoleAuth(org.RoleAdmin)
|
2018-10-11 18:36:04 +08:00
|
|
|
)
|
|
|
|
|
2023-04-13 00:30:33 +08:00
|
|
|
func HandleNoCacheHeaders(ctx *contextmodel.ReqContext) {
|
|
|
|
// X-Grafana-NoCache tells Grafana to skip the cache while retrieving datasource instance metadata
|
|
|
|
ctx.SkipDSCache = ctx.Req.Header.Get("X-Grafana-NoCache") == "true"
|
|
|
|
// X-Cache-Skip tells Grafana to skip the Enterprise query/resource cache while issuing query and resource calls
|
|
|
|
ctx.SkipQueryCache = ctx.Req.Header.Get("X-Cache-Skip") == "true"
|
2021-01-12 14:42:32 +08:00
|
|
|
}
|
|
|
|
|
2021-10-11 20:30:59 +08:00
|
|
|
func AddDefaultResponseHeaders(cfg *setting.Cfg) web.Handler {
|
2023-02-03 04:34:55 +08:00
|
|
|
t := web.NewTree()
|
|
|
|
t.Add("/api/datasources/uid/:uid/resources/*", nil)
|
|
|
|
t.Add("/api/datasources/:id/resources/*", nil)
|
2024-09-17 16:28:38 +08:00
|
|
|
t.Add("/api/plugins/:id/resources/*", nil)
|
2023-04-05 15:13:24 +08:00
|
|
|
|
2021-10-11 20:30:59 +08:00
|
|
|
return func(c *web.Context) {
|
2023-02-03 04:34:55 +08:00
|
|
|
c.Resp.Before(func(w web.ResponseWriter) { // if response has already been written, skip.
|
2020-02-19 05:53:40 +08:00
|
|
|
if w.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-04-05 15:13:24 +08:00
|
|
|
traceId := tracing.TraceIDFromContext(c.Req.Context(), false)
|
|
|
|
if traceId != "" {
|
|
|
|
w.Header().Set("grafana-trace-id", traceId)
|
|
|
|
}
|
|
|
|
|
2023-02-03 04:34:55 +08:00
|
|
|
_, _, resourceURLMatch := t.Match(c.Req.URL.Path)
|
|
|
|
resourceCachable := resourceURLMatch && allowCacheControl(c.Resp)
|
2023-03-07 01:42:18 +08:00
|
|
|
if !strings.HasPrefix(c.Req.URL.Path, "/public/plugins/") &&
|
2023-11-23 00:41:54 +08:00
|
|
|
!strings.HasPrefix(c.Req.URL.Path, "/avatar/") &&
|
2023-12-18 20:21:57 +08:00
|
|
|
!strings.HasPrefix(c.Req.URL.Path, "/api/datasources/proxy/") &&
|
|
|
|
!strings.HasPrefix(c.Req.URL.Path, "/api/reports/render/") &&
|
2024-05-27 16:26:30 +08:00
|
|
|
!strings.HasPrefix(c.Req.URL.Path, "/render/d-solo/") &&
|
2025-04-10 20:42:23 +08:00
|
|
|
(!strings.HasPrefix(c.Req.URL.Path, "/api/gnet/plugins") || !strings.Contains(c.Req.URL.Path, "/logos/")) && !resourceCachable {
|
2021-01-12 14:42:32 +08:00
|
|
|
addNoCacheHeaders(c.Resp)
|
2019-05-06 15:22:59 +08:00
|
|
|
}
|
2019-05-06 15:56:23 +08:00
|
|
|
|
2023-07-06 22:43:20 +08:00
|
|
|
// X-Allow-Embedding header is set for specific URLs that need to be embedded in an iframe regardless
|
|
|
|
// of the configured allow_embedding setting.
|
|
|
|
embeddingHeader := w.Header().Get("X-Allow-Embedding")
|
|
|
|
if !cfg.AllowEmbedding && embeddingHeader != "allow" {
|
2020-12-11 18:44:44 +08:00
|
|
|
addXFrameOptionsDenyHeader(w)
|
2019-05-06 15:56:23 +08:00
|
|
|
}
|
2020-12-11 18:44:44 +08:00
|
|
|
addSecurityHeaders(w, cfg)
|
2019-05-06 15:22:59 +08:00
|
|
|
})
|
2017-07-04 22:33:37 +08:00
|
|
|
}
|
|
|
|
}
|
2019-05-06 15:22:59 +08:00
|
|
|
|
2020-12-11 18:44:44 +08:00
|
|
|
// addSecurityHeaders adds HTTP(S) response headers that enable various security protections in the client's browser.
|
2021-10-11 20:30:59 +08:00
|
|
|
func addSecurityHeaders(w web.ResponseWriter, cfg *setting.Cfg) {
|
2022-01-28 14:23:28 +08:00
|
|
|
if cfg.StrictTransportSecurity {
|
2020-12-11 18:44:44 +08:00
|
|
|
strictHeaderValues := []string{fmt.Sprintf("max-age=%v", cfg.StrictTransportSecurityMaxAge)}
|
|
|
|
if cfg.StrictTransportSecurityPreload {
|
2019-06-19 02:24:23 +08:00
|
|
|
strictHeaderValues = append(strictHeaderValues, "preload")
|
2019-06-12 19:15:50 +08:00
|
|
|
}
|
2020-12-11 18:44:44 +08:00
|
|
|
if cfg.StrictTransportSecuritySubDomains {
|
2019-06-19 02:24:23 +08:00
|
|
|
strictHeaderValues = append(strictHeaderValues, "includeSubDomains")
|
2019-06-12 19:15:50 +08:00
|
|
|
}
|
2020-12-14 22:13:01 +08:00
|
|
|
w.Header().Set("Strict-Transport-Security", strings.Join(strictHeaderValues, "; "))
|
2019-06-12 19:15:50 +08:00
|
|
|
}
|
|
|
|
|
2020-12-11 18:44:44 +08:00
|
|
|
if cfg.ContentTypeProtectionHeader {
|
2020-12-14 22:13:01 +08:00
|
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
2019-06-12 19:15:50 +08:00
|
|
|
}
|
|
|
|
|
2020-12-11 18:44:44 +08:00
|
|
|
if cfg.XSSProtectionHeader {
|
2020-12-14 22:13:01 +08:00
|
|
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
2019-06-12 19:15:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-11 20:30:59 +08:00
|
|
|
func addNoCacheHeaders(w web.ResponseWriter) {
|
2023-01-25 22:09:27 +08:00
|
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
|
|
w.Header().Del("Pragma")
|
|
|
|
w.Header().Del("Expires")
|
2019-05-06 15:22:59 +08:00
|
|
|
}
|
2019-05-06 15:56:23 +08:00
|
|
|
|
2021-10-11 20:30:59 +08:00
|
|
|
func addXFrameOptionsDenyHeader(w web.ResponseWriter) {
|
2020-12-14 22:13:01 +08:00
|
|
|
w.Header().Set("X-Frame-Options", "deny")
|
2019-05-06 15:56:23 +08:00
|
|
|
}
|
2022-12-01 01:12:34 +08:00
|
|
|
|
|
|
|
func AddCustomResponseHeaders(cfg *setting.Cfg) web.Handler {
|
|
|
|
return func(c *web.Context) {
|
|
|
|
c.Resp.Before(func(w web.ResponseWriter) {
|
|
|
|
if w.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for header, value := range cfg.CustomResponseHeaders {
|
|
|
|
// do not override existing headers
|
|
|
|
if w.Header().Get(header) != "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
w.Header().Set(header, value)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-02-03 04:34:55 +08:00
|
|
|
|
|
|
|
func allowCacheControl(rw web.ResponseWriter) bool {
|
|
|
|
ccHeaderValues := rw.Header().Values("Cache-Control")
|
|
|
|
|
|
|
|
if len(ccHeaderValues) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
foundPrivate := false
|
|
|
|
foundPublic := false
|
|
|
|
for _, val := range ccHeaderValues {
|
2023-02-28 04:55:22 +08:00
|
|
|
strings.Contains(val, "private")
|
|
|
|
if strings.Contains(val, "private") {
|
2023-02-03 04:34:55 +08:00
|
|
|
foundPrivate = true
|
|
|
|
}
|
2023-02-28 04:55:22 +08:00
|
|
|
if strings.Contains(val, "public") {
|
2023-02-03 04:34:55 +08:00
|
|
|
foundPublic = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return foundPrivate && !foundPublic && rw.Header().Get("X-Grafana-Cache") != ""
|
|
|
|
}
|