package middleware import ( "strings" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" ) var ( hostRedirectCounter = promauto.NewCounter(prometheus.CounterOpts{ Name: "host_redirect_total", Help: "Number of requests redirected due to host header mismatch", Namespace: "grafana", }) ) func ValidateHostHeader(cfg *setting.Cfg) web.Handler { return func(c *contextmodel.ReqContext) { // ignore local render calls if c.IsRenderCall { return } h := c.Req.Host if i := strings.Index(h, ":"); i >= 0 { h = h[:i] } if !strings.EqualFold(h, cfg.Domain) { // the normal redirecting logic doesn't work when running as frontend service, since it has no knowledge of the custom domain. // instead, we modify the single tenant `/bootdata` call with a 204 response and a header indicating the domain to redirect to // this is safe because only the frontend service calls `/bootdata` // the redirect is then handled client side. // see pkg/services/frontend/index.html if c.Req.URL.Path == "/bootdata" { c.Resp.Header().Set("Redirect-Domain", cfg.Domain) c.Resp.WriteHeader(204) return } hostRedirectCounter.Inc() c.Logger.Info("Enforcing Host header", "hosted", c.Req.Host, "expected", cfg.Domain) c.Redirect(strings.TrimSuffix(cfg.AppURL, "/")+c.Req.RequestURI, 301) return } } }