mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 05:41:49 +08:00

Changes to the http_server class meant that the TLS settings were not getting applied anymore. This fixes so that the minimum TLS version is 1.2 again.
220 lines
5.9 KiB
Go
220 lines
5.9 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
|
|
macaron "gopkg.in/macaron.v1"
|
|
|
|
"github.com/grafana/grafana/pkg/api/live"
|
|
httpstatic "github.com/grafana/grafana/pkg/api/static"
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/log"
|
|
"github.com/grafana/grafana/pkg/middleware"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
type HttpServer struct {
|
|
log log.Logger
|
|
macaron *macaron.Macaron
|
|
context context.Context
|
|
streamManager *live.StreamManager
|
|
|
|
httpSrv *http.Server
|
|
}
|
|
|
|
func NewHttpServer() *HttpServer {
|
|
return &HttpServer{
|
|
log: log.New("http.server"),
|
|
}
|
|
}
|
|
|
|
func (hs *HttpServer) Start(ctx context.Context) error {
|
|
var err error
|
|
|
|
hs.context = ctx
|
|
hs.streamManager = live.NewStreamManager()
|
|
hs.macaron = hs.newMacaron()
|
|
hs.registerRoutes()
|
|
|
|
hs.streamManager.Run(ctx)
|
|
|
|
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
|
hs.log.Info("Initializing HTTP Server", "address", listenAddr, "protocol", setting.Protocol, "subUrl", setting.AppSubUrl, "socket", setting.SocketPath)
|
|
|
|
hs.httpSrv = &http.Server{Addr: listenAddr, Handler: hs.macaron}
|
|
switch setting.Protocol {
|
|
case setting.HTTP:
|
|
err = hs.httpSrv.ListenAndServe()
|
|
if err == http.ErrServerClosed {
|
|
hs.log.Debug("server was shutdown gracefully")
|
|
return nil
|
|
}
|
|
case setting.HTTPS:
|
|
err = hs.listenAndServeTLS(setting.CertFile, setting.KeyFile)
|
|
if err == http.ErrServerClosed {
|
|
hs.log.Debug("server was shutdown gracefully")
|
|
return nil
|
|
}
|
|
case setting.SOCKET:
|
|
ln, err := net.Listen("unix", setting.SocketPath)
|
|
if err != nil {
|
|
hs.log.Debug("server was shutdown gracefully")
|
|
return nil
|
|
}
|
|
|
|
err = hs.httpSrv.Serve(ln)
|
|
if err != nil {
|
|
hs.log.Debug("server was shutdown gracefully")
|
|
return nil
|
|
}
|
|
default:
|
|
hs.log.Error("Invalid protocol", "protocol", setting.Protocol)
|
|
err = errors.New("Invalid Protocol")
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (hs *HttpServer) Shutdown(ctx context.Context) error {
|
|
err := hs.httpSrv.Shutdown(ctx)
|
|
hs.log.Info("stopped http server")
|
|
return err
|
|
}
|
|
|
|
func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
|
|
if certfile == "" {
|
|
return fmt.Errorf("cert_file cannot be empty when using HTTPS")
|
|
}
|
|
|
|
if keyfile == "" {
|
|
return fmt.Errorf("cert_key cannot be empty when using HTTPS")
|
|
}
|
|
|
|
if _, err := os.Stat(setting.CertFile); os.IsNotExist(err) {
|
|
return fmt.Errorf(`Cannot find SSL cert_file at %v`, setting.CertFile)
|
|
}
|
|
|
|
if _, err := os.Stat(setting.KeyFile); os.IsNotExist(err) {
|
|
return fmt.Errorf(`Cannot find SSL key_file at %v`, setting.KeyFile)
|
|
}
|
|
|
|
tlsCfg := &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
PreferServerCipherSuites: true,
|
|
CipherSuites: []uint16{
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
},
|
|
}
|
|
|
|
hs.httpSrv.TLSConfig = tlsCfg
|
|
hs.httpSrv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0)
|
|
|
|
return hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
|
|
}
|
|
|
|
func (hs *HttpServer) newMacaron() *macaron.Macaron {
|
|
macaron.Env = setting.Env
|
|
m := macaron.New()
|
|
|
|
m.Use(middleware.Logger())
|
|
m.Use(middleware.Recovery())
|
|
|
|
if setting.EnableGzip {
|
|
m.Use(middleware.Gziper())
|
|
}
|
|
|
|
for _, route := range plugins.StaticRoutes {
|
|
pluginRoute := path.Join("/public/plugins/", route.PluginId)
|
|
logger.Debug("Plugins: Adding route", "route", pluginRoute, "dir", route.Directory)
|
|
hs.mapStatic(m, route.Directory, "", pluginRoute)
|
|
}
|
|
|
|
hs.mapStatic(m, setting.StaticRootPath, "", "public")
|
|
hs.mapStatic(m, setting.StaticRootPath, "robots.txt", "robots.txt")
|
|
|
|
m.Use(macaron.Renderer(macaron.RenderOptions{
|
|
Directory: path.Join(setting.StaticRootPath, "views"),
|
|
IndentJSON: macaron.Env != macaron.PROD,
|
|
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
|
}))
|
|
|
|
m.Use(hs.healthHandler)
|
|
m.Use(middleware.GetContextHandler())
|
|
m.Use(middleware.Sessioner(&setting.SessionOptions))
|
|
m.Use(middleware.RequestMetrics())
|
|
m.Use(middleware.OrgRedirect())
|
|
|
|
// needs to be after context handler
|
|
if setting.EnforceDomain {
|
|
m.Use(middleware.ValidateHostHeader(setting.Domain))
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
|
|
if ctx.Req.Method != "GET" || ctx.Req.URL.Path != "/api/health" {
|
|
return
|
|
}
|
|
|
|
data := simplejson.New()
|
|
data.Set("database", "ok")
|
|
data.Set("version", setting.BuildVersion)
|
|
data.Set("commit", setting.BuildCommit)
|
|
|
|
if err := bus.Dispatch(&models.GetDBHealthQuery{}); err != nil {
|
|
data.Set("database", "failing")
|
|
ctx.Resp.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
|
ctx.Resp.WriteHeader(503)
|
|
} else {
|
|
ctx.Resp.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
|
ctx.Resp.WriteHeader(200)
|
|
}
|
|
|
|
dataBytes, _ := data.EncodePretty()
|
|
ctx.Resp.Write(dataBytes)
|
|
}
|
|
|
|
func (hs *HttpServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
|
|
headers := func(c *macaron.Context) {
|
|
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
|
|
}
|
|
|
|
if setting.Env == setting.DEV {
|
|
headers = func(c *macaron.Context) {
|
|
c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache")
|
|
}
|
|
}
|
|
|
|
m.Use(httpstatic.Static(
|
|
path.Join(rootDir, dir),
|
|
httpstatic.StaticOptions{
|
|
SkipLogging: true,
|
|
Prefix: prefix,
|
|
AddHeaders: headers,
|
|
},
|
|
))
|
|
}
|