diff --git a/conf/defaults.ini b/conf/defaults.ini index b0a085dd991..7259eb827ab 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -29,6 +29,10 @@ http_port = 3000 # The public facing domain name used to access grafana from a browser domain = localhost +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +enforce_domain = false + # The full public facing url root_url = %(protocol)s://%(domain)s:%(http_port)s/ diff --git a/conf/sample.ini b/conf/sample.ini index e86846da232..fc2d5a1c8f2 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -29,6 +29,10 @@ # The public facing domain name used to access grafana from a browser ;domain = localhost +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +;enforce_domain = false + # The full public facing url ;root_url = %(protocol)s://%(domain)s:%(http_port)s/ diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 7ccee50fbc9..a08873f552c 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -72,6 +72,9 @@ Another way is put nginx or apache infront of Grafana and have them proxy reques This setting is only used in as a part of the root_url setting (see below). Important if you use github or google oauth. +### enforce_domain +Redirect to correct domain if host header does not match domain. Prevents DNS rebinding attacks. Default is false. + ### root_url This is the full url used to access grafana from a web browser. This is important if you use google or github oauth authentication (for the callback url to be correct). diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go index a95d2af2498..6590c32fc93 100644 --- a/pkg/cmd/web.go +++ b/pkg/cmd/web.go @@ -40,6 +40,10 @@ func newMacaron() *macaron.Macaron { Delims: macaron.Delims{Left: "[[", Right: "]]"}, })) + if setting.EnforceDomain { + m.Use(middleware.ValidateHostHeader(setting.Domain)) + } + m.Use(middleware.GetContextHandler()) m.Use(middleware.Sessioner(&setting.SessionOptions)) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index bd04181da59..2a6873076c6 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -34,16 +34,6 @@ func GetContextHandler() macaron.Handler { AllowAnonymous: false, } - h := ctx.Req.Host - if i := strings.Index(h, ":"); i >= 0 { - h = h[:i] - } - - if !strings.EqualFold(h, setting.Domain) { - ctx.Redirect(strings.TrimSuffix(setting.AppUrl, "/")+ctx.Req.RequestURI, 301) - return - } - // the order in which these are tested are important // look for api key in Authorization header first // then init session and look for userId in session diff --git a/pkg/middleware/validate_host.go b/pkg/middleware/validate_host.go new file mode 100644 index 00000000000..56e0a8ee35a --- /dev/null +++ b/pkg/middleware/validate_host.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "strings" + + "github.com/Unknwon/macaron" + "github.com/grafana/grafana/pkg/setting" +) + +func ValidateHostHeader(domain string) macaron.Handler { + return func(c *macaron.Context) { + h := c.Req.Host + if i := strings.Index(h, ":"); i >= 0 { + h = h[:i] + } + + if !strings.EqualFold(h, domain) { + c.Redirect(strings.TrimSuffix(setting.AppUrl, "/")+c.Req.RequestURI, 301) + return + } + } +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index b451aad634f..8b6f83ec08a 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -65,6 +65,7 @@ var ( RouterLogging bool StaticRootPath string EnableGzip bool + EnforceDomain bool // Security settings. SecretKey string @@ -355,10 +356,10 @@ func NewConfigContext(args *CommandLineArgs) { Domain = server.Key("domain").MustString("localhost") HttpAddr = server.Key("http_addr").MustString("0.0.0.0") HttpPort = server.Key("http_port").MustString("3000") - StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath) RouterLogging = server.Key("router_logging").MustBool(false) EnableGzip = server.Key("enable_gzip").MustBool(false) + EnforceDomain = server.Key("enforce_domain").MustBool(false) security := Cfg.Section("security") SecretKey = security.Key("secret_key").String()