package api import ( "context" "crypto/tls" "errors" "fmt" "net" "net/http" "os" "path" "path/filepath" "strings" "sync" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/middleware/csrf" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/services/querylibrary" "github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/stats" "github.com/grafana/grafana/pkg/services/store/entity/httpentitystore" "github.com/grafana/grafana/pkg/services/store/k8saccess" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/grafana/grafana/pkg/api/avatar" "github.com/grafana/grafana/pkg/api/routing" httpstatic "github.com/grafana/grafana/pkg/api/static" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/tracing" loginpkg "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/plugincontext" "github.com/grafana/grafana/pkg/registry/corekind" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/comments" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/correlations" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources/permissions" "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/export" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/live/pushhttp" "github.com/grafana/grafana/pkg/services/login" loginAttempt "github.com/grafana/grafana/pkg/services/loginattempt" "github.com/grafana/grafana/pkg/services/navtree" "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/playlist" "github.com/grafana/grafana/pkg/services/plugindashboards" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings" pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/services/provisioning" publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/queryhistory" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/searchusers" "github.com/grafana/grafana/pkg/services/secrets" secretsKV "github.com/grafana/grafana/pkg/services/secrets/kvstore" spm "github.com/grafana/grafana/pkg/services/secrets/kvstore/migrations" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/shorturls" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/tag" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/teamguardian" tempUser "github.com/grafana/grafana/pkg/services/temp_user" "github.com/grafana/grafana/pkg/services/thumbs" "github.com/grafana/grafana/pkg/services/updatechecker" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" ) type HTTPServer struct { log log.Logger web *web.Mux context context.Context httpSrv *http.Server middlewares []web.Handler namedMiddlewares []routing.RegisterNamedMiddleware bus bus.Bus PluginContextProvider *plugincontext.Provider RouteRegister routing.RouteRegister RenderService rendering.Service Cfg *setting.Cfg Features *featuremgmt.FeatureManager SettingsProvider setting.Provider HooksService *hooks.HooksService navTreeService navtree.Service CacheService *localcache.CacheService DataSourceCache datasources.CacheService AuthTokenService auth.UserTokenService QuotaService quota.Service RemoteCacheService *remotecache.RemoteCache ProvisioningService provisioning.ProvisioningService Login login.Service License models.Licensing AccessControl accesscontrol.AccessControl DataProxy *datasourceproxy.DataSourceProxyService PluginRequestValidator models.PluginRequestValidator pluginClient plugins.Client pluginStore plugins.Store pluginInstaller plugins.Installer pluginDashboardService plugindashboards.Service pluginStaticRouteResolver plugins.StaticRouteResolver pluginErrorResolver plugins.ErrorResolver SearchService search.Service ShortURLService shorturls.Service QueryHistoryService queryhistory.Service CorrelationsService correlations.Service Live *live.GrafanaLive LivePushGateway *pushhttp.Gateway ThumbService thumbs.Service ExportService export.ExportService StorageService store.StorageService httpEntityStore httpentitystore.HTTPEntityStore SearchV2HTTPService searchV2.SearchHTTPService QueryLibraryHTTPService querylibrary.HTTPService QueryLibraryService querylibrary.Service ContextHandler *contexthandler.ContextHandler SQLStore db.DB AlertEngine *alerting.AlertEngine AlertNG *ngalert.AlertNG LibraryPanelService librarypanels.Service LibraryElementService libraryelements.Service SocialService social.Service Listener net.Listener EncryptionService encryption.Internal SecretsService secrets.Service secretsPluginManager plugins.SecretsPluginManager secretsStore secretsKV.SecretsKVStore secretsMigrator secrets.Migrator secretsPluginMigrator spm.SecretMigrationProvider DataSourcesService datasources.DataSourceService cleanUpService *cleanup.CleanUpService tracer tracing.Tracer grafanaUpdateChecker *updatechecker.GrafanaService pluginsUpdateChecker *updatechecker.PluginsService searchUsersService searchusers.Service ldapGroups ldap.Groups teamGuardian teamguardian.TeamGuardian queryDataService *query.Service serviceAccountsService serviceaccounts.Service authInfoService login.AuthInfoService authenticator loginpkg.Authenticator teamPermissionsService accesscontrol.TeamPermissionsService NotificationService *notifications.NotificationService DashboardService dashboards.DashboardService dashboardProvisioningService dashboards.DashboardProvisioningService folderService folder.Service DatasourcePermissionsService permissions.DatasourcePermissionsService commentsService *comments.Service AlertNotificationService *alerting.AlertNotificationService dashboardsnapshotsService dashboardsnapshots.Service PluginSettings pluginSettings.Service AvatarCacheServer *avatar.AvatarCacheServer preferenceService pref.Service Csrf csrf.Service folderPermissionsService accesscontrol.FolderPermissionsService dashboardPermissionsService accesscontrol.DashboardPermissionsService dashboardVersionService dashver.Service PublicDashboardsApi *publicdashboardsApi.Api starService star.Service Kinds *corekind.Base playlistService playlist.Service apiKeyService apikey.Service kvStore kvstore.KVStore userService user.Service tempUserService tempUser.Service dashboardThumbsService thumbs.DashboardThumbService loginAttemptService loginAttempt.Service orgService org.Service teamService team.Service accesscontrolService accesscontrol.Service annotationsRepo annotations.Repository tagService tag.Service oauthTokenService oauthtoken.OAuthTokenService statsService stats.Service } type ServerOptions struct { Listener net.Listener } func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routing.RouteRegister, bus bus.Bus, renderService rendering.Service, licensing models.Licensing, hooksService *hooks.HooksService, cacheService *localcache.CacheService, sqlStore *sqlstore.SQLStore, alertEngine *alerting.AlertEngine, pluginRequestValidator models.PluginRequestValidator, pluginStaticRouteResolver plugins.StaticRouteResolver, pluginDashboardService plugindashboards.Service, pluginStore plugins.Store, pluginClient plugins.Client, pluginErrorResolver plugins.ErrorResolver, pluginInstaller plugins.Installer, settingsProvider setting.Provider, dataSourceCache datasources.CacheService, userTokenService auth.UserTokenService, cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, queryHistoryService queryhistory.Service, correlationsService correlations.Service, thumbService thumbs.Service, remoteCache *remotecache.RemoteCache, provisioningService provisioning.ProvisioningService, loginService login.Service, authenticator loginpkg.Authenticator, accessControl accesscontrol.AccessControl, dataSourceProxy *datasourceproxy.DataSourceProxyService, searchService *search.SearchService, live *live.GrafanaLive, livePushGateway *pushhttp.Gateway, plugCtxProvider *plugincontext.Provider, contextHandler *contexthandler.ContextHandler, features *featuremgmt.FeatureManager, alertNG *ngalert.AlertNG, libraryPanelService librarypanels.Service, libraryElementService libraryelements.Service, quotaService quota.Service, socialService social.Service, tracer tracing.Tracer, exportService export.ExportService, encryptionService encryption.Internal, grafanaUpdateChecker *updatechecker.GrafanaService, pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service, dataSourcesService datasources.DataSourceService, queryDataService *query.Service, ldapGroups ldap.Groups, teamGuardian teamguardian.TeamGuardian, serviceaccountsService serviceaccounts.Service, authInfoService login.AuthInfoService, storageService store.StorageService, httpEntityStore httpentitystore.HTTPEntityStore, notificationService *notifications.NotificationService, dashboardService dashboards.DashboardService, dashboardProvisioningService dashboards.DashboardProvisioningService, folderService folder.Service, datasourcePermissionsService permissions.DatasourcePermissionsService, alertNotificationService *alerting.AlertNotificationService, dashboardsnapshotsService dashboardsnapshots.Service, commentsService *comments.Service, pluginSettings pluginSettings.Service, avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service, teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service, starService star.Service, csrfService csrf.Service, basekinds *corekind.Base, playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore, secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service, secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore, publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service, loginAttemptService loginAttempt.Service, orgService org.Service, teamService team.Service, accesscontrolService accesscontrol.Service, dashboardThumbsService thumbs.DashboardThumbService, navTreeService navtree.Service, annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, queryLibraryHTTPService querylibrary.HTTPService, queryLibraryService querylibrary.Service, oauthTokenService oauthtoken.OAuthTokenService, statsService stats.Service, k8saccess k8saccess.K8SAccess, // required so that the router is registered ) (*HTTPServer, error) { web.Env = cfg.Env m := web.New() hs := &HTTPServer{ Cfg: cfg, RouteRegister: routeRegister, bus: bus, RenderService: renderService, License: licensing, HooksService: hooksService, CacheService: cacheService, SQLStore: sqlStore, AlertEngine: alertEngine, PluginRequestValidator: pluginRequestValidator, pluginInstaller: pluginInstaller, pluginClient: pluginClient, pluginStore: pluginStore, pluginStaticRouteResolver: pluginStaticRouteResolver, pluginDashboardService: pluginDashboardService, pluginErrorResolver: pluginErrorResolver, grafanaUpdateChecker: grafanaUpdateChecker, pluginsUpdateChecker: pluginsUpdateChecker, SettingsProvider: settingsProvider, DataSourceCache: dataSourceCache, AuthTokenService: userTokenService, cleanUpService: cleanUpService, ShortURLService: shortURLService, QueryHistoryService: queryHistoryService, CorrelationsService: correlationsService, Features: features, ThumbService: thumbService, StorageService: storageService, RemoteCacheService: remoteCache, ProvisioningService: provisioningService, Login: loginService, AccessControl: accessControl, DataProxy: dataSourceProxy, SearchV2HTTPService: searchv2HTTPService, SearchService: searchService, ExportService: exportService, Live: live, LivePushGateway: livePushGateway, PluginContextProvider: plugCtxProvider, ContextHandler: contextHandler, AlertNG: alertNG, LibraryPanelService: libraryPanelService, LibraryElementService: libraryElementService, QuotaService: quotaService, tracer: tracer, log: log.New("http.server"), web: m, Listener: opts.Listener, SocialService: socialService, EncryptionService: encryptionService, SecretsService: secretsService, secretsPluginManager: secretsPluginManager, secretsMigrator: secretsMigrator, secretsPluginMigrator: secretsPluginMigrator, secretsStore: secretsStore, httpEntityStore: httpEntityStore, DataSourcesService: dataSourcesService, searchUsersService: searchUsersService, ldapGroups: ldapGroups, teamGuardian: teamGuardian, queryDataService: queryDataService, serviceAccountsService: serviceaccountsService, authInfoService: authInfoService, authenticator: authenticator, NotificationService: notificationService, DashboardService: dashboardService, dashboardProvisioningService: dashboardProvisioningService, folderService: folderService, DatasourcePermissionsService: datasourcePermissionsService, commentsService: commentsService, teamPermissionsService: teamsPermissionsService, AlertNotificationService: alertNotificationService, dashboardsnapshotsService: dashboardsnapshotsService, PluginSettings: pluginSettings, AvatarCacheServer: avatarCacheServer, preferenceService: preferenceService, Csrf: csrfService, folderPermissionsService: folderPermissionsService, dashboardPermissionsService: dashboardPermissionsService, dashboardVersionService: dashboardVersionService, starService: starService, Kinds: basekinds, playlistService: playlistService, apiKeyService: apiKeyService, kvStore: kvStore, PublicDashboardsApi: publicDashboardsApi, userService: userService, tempUserService: tempUserService, dashboardThumbsService: dashboardThumbsService, loginAttemptService: loginAttemptService, orgService: orgService, teamService: teamService, navTreeService: navTreeService, accesscontrolService: accesscontrolService, annotationsRepo: annotationRepo, tagService: tagService, QueryLibraryHTTPService: queryLibraryHTTPService, QueryLibraryService: queryLibraryService, oauthTokenService: oauthTokenService, statsService: statsService, } if hs.Listener != nil { hs.log.Debug("Using provided listener") } hs.registerRoutes() // Register access control scope resolver for annotations hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo)) if err := hs.declareFixedRoles(); err != nil { return nil, err } return hs, nil } func (hs *HTTPServer) AddMiddleware(middleware web.Handler) { hs.middlewares = append(hs.middlewares, middleware) } func (hs *HTTPServer) AddNamedMiddleware(middleware routing.RegisterNamedMiddleware) { hs.namedMiddlewares = append(hs.namedMiddlewares, middleware) } func (hs *HTTPServer) Run(ctx context.Context) error { hs.context = ctx hs.applyRoutes() // Remove any square brackets enclosing IPv6 addresses, a format we support for backwards compatibility host := strings.TrimSuffix(strings.TrimPrefix(hs.Cfg.HTTPAddr, "["), "]") hs.httpSrv = &http.Server{ Addr: net.JoinHostPort(host, hs.Cfg.HTTPPort), Handler: hs.web, ReadTimeout: hs.Cfg.ReadTimeout, } switch hs.Cfg.Protocol { case setting.HTTP2Scheme: if err := hs.configureHttp2(); err != nil { return err } case setting.HTTPSScheme: if err := hs.configureHttps(); err != nil { return err } default: } listener, err := hs.getListener() if err != nil { return err } hs.log.Info("HTTP Server Listen", "address", listener.Addr().String(), "protocol", hs.Cfg.Protocol, "subUrl", hs.Cfg.AppSubURL, "socket", hs.Cfg.SocketPath) var wg sync.WaitGroup wg.Add(1) // handle http shutdown on server context done go func() { defer wg.Done() <-ctx.Done() if err := hs.httpSrv.Shutdown(context.Background()); err != nil { hs.log.Error("Failed to shutdown server", "error", err) } }() switch hs.Cfg.Protocol { case setting.HTTPScheme, setting.SocketScheme: if err := hs.httpSrv.Serve(listener); err != nil { if errors.Is(err, http.ErrServerClosed) { hs.log.Debug("server was shutdown gracefully") return nil } return err } case setting.HTTP2Scheme, setting.HTTPSScheme: if err := hs.httpSrv.ServeTLS(listener, hs.Cfg.CertFile, hs.Cfg.KeyFile); err != nil { if errors.Is(err, http.ErrServerClosed) { hs.log.Debug("server was shutdown gracefully") return nil } return err } default: panic(fmt.Sprintf("Unhandled protocol %q", hs.Cfg.Protocol)) } wg.Wait() return nil } func (hs *HTTPServer) getListener() (net.Listener, error) { if hs.Listener != nil { return hs.Listener, nil } switch hs.Cfg.Protocol { case setting.HTTPScheme, setting.HTTPSScheme, setting.HTTP2Scheme: listener, err := net.Listen("tcp", hs.httpSrv.Addr) if err != nil { return nil, fmt.Errorf("failed to open listener on address %s: %w", hs.httpSrv.Addr, err) } return listener, nil case setting.SocketScheme: listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: hs.Cfg.SocketPath, Net: "unix"}) if err != nil { return nil, fmt.Errorf("failed to open listener for socket %s: %w", hs.Cfg.SocketPath, err) } // Make socket writable by group // nolint:gosec if err := os.Chmod(hs.Cfg.SocketPath, os.FileMode(hs.Cfg.SocketMode)); err != nil { return nil, fmt.Errorf("failed to change socket mode %d: %w", hs.Cfg.SocketMode, err) } // golang.org/pkg/os does not have chgrp // Changing the gid of a file without privileges requires that the target group is in the group of the process and that the process is the file owner if err := os.Chown(hs.Cfg.SocketPath, -1, hs.Cfg.SocketGid); err != nil { return nil, fmt.Errorf("failed to change socket group id %d: %w", hs.Cfg.SocketGid, err) } return listener, nil default: hs.log.Error("Invalid protocol", "protocol", hs.Cfg.Protocol) return nil, fmt.Errorf("invalid protocol %q", hs.Cfg.Protocol) } } func (hs *HTTPServer) configureHttps() error { if hs.Cfg.CertFile == "" { return errors.New("cert_file cannot be empty when using HTTPS") } if hs.Cfg.KeyFile == "" { return errors.New("cert_key cannot be empty when using HTTPS") } if _, err := os.Stat(hs.Cfg.CertFile); os.IsNotExist(err) { return fmt.Errorf(`cannot find SSL cert_file at %q`, hs.Cfg.CertFile) } if _, err := os.Stat(hs.Cfg.KeyFile); os.IsNotExist(err) { return fmt.Errorf(`cannot find SSL key_file at %q`, hs.Cfg.KeyFile) } tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, } hs.httpSrv.TLSConfig = tlsCfg hs.httpSrv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) return nil } func (hs *HTTPServer) configureHttp2() error { if hs.Cfg.CertFile == "" { return errors.New("cert_file cannot be empty when using HTTP2") } if hs.Cfg.KeyFile == "" { return errors.New("cert_key cannot be empty when using HTTP2") } if _, err := os.Stat(hs.Cfg.CertFile); os.IsNotExist(err) { return fmt.Errorf("cannot find SSL cert_file at %q", hs.Cfg.CertFile) } if _, err := os.Stat(hs.Cfg.KeyFile); os.IsNotExist(err) { return fmt.Errorf("cannot find SSL key_file at %q", hs.Cfg.KeyFile) } tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, }, NextProtos: []string{"h2", "http/1.1"}, } hs.httpSrv.TLSConfig = tlsCfg return nil } func (hs *HTTPServer) applyRoutes() { // start with middlewares & static routes hs.addMiddlewaresAndStaticRoutes() // then add view routes & api routes hs.RouteRegister.Register(hs.web, hs.namedMiddlewares...) // lastly not found route hs.web.NotFound(middleware.ProvideRouteOperationName("notfound"), middleware.ReqSignedIn, hs.NotFoundHandler) } func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { m := hs.web m.Use(middleware.RequestTracing(hs.tracer)) m.Use(middleware.RequestMetrics(hs.Features)) m.UseMiddleware(middleware.Logger(hs.Cfg)) if hs.Cfg.EnableGzip { m.UseMiddleware(middleware.Gziper()) } m.UseMiddleware(middleware.Recovery(hs.Cfg)) m.UseMiddleware(hs.Csrf.Middleware()) hs.mapStatic(m, hs.Cfg.StaticRootPath, "build", "public/build") hs.mapStatic(m, hs.Cfg.StaticRootPath, "", "public", "/public/views/swagger.html") hs.mapStatic(m, hs.Cfg.StaticRootPath, "robots.txt", "robots.txt") if hs.Cfg.ImageUploadProvider == "local" { hs.mapStatic(m, hs.Cfg.ImagesDir, "", "/public/img/attachments") } if len(hs.Cfg.CustomResponseHeaders) > 0 { m.Use(middleware.AddCustomResponseHeaders(hs.Cfg)) } m.Use(middleware.AddDefaultResponseHeaders(hs.Cfg)) if hs.Cfg.ServeFromSubPath && hs.Cfg.AppSubURL != "" { m.SetURLPrefix(hs.Cfg.AppSubURL) } m.UseMiddleware(web.Renderer(filepath.Join(hs.Cfg.StaticRootPath, "views"), "[[", "]]")) // These endpoints are used for monitoring the Grafana instance // and should not be redirected or rejected. m.Use(hs.healthzHandler) m.Use(hs.apiHealthHandler) m.Use(hs.metricsEndpoint) m.Use(hs.pluginMetricsEndpoint) m.Use(hs.frontendLogEndpoints()) m.UseMiddleware(hs.ContextHandler.Middleware) m.Use(middleware.OrgRedirect(hs.Cfg, hs.userService)) m.Use(accesscontrol.LoadPermissionsMiddleware(hs.accesscontrolService)) // needs to be after context handler if hs.Cfg.EnforceDomain { m.Use(middleware.ValidateHostHeader(hs.Cfg)) } m.Use(middleware.HandleNoCacheHeader) if hs.Cfg.CSPEnabled || hs.Cfg.CSPReportOnlyEnabled { m.UseMiddleware(middleware.ContentSecurityPolicy(hs.Cfg, hs.log)) } for _, mw := range hs.middlewares { m.Use(mw) } } func (hs *HTTPServer) metricsEndpoint(ctx *web.Context) { if !hs.Cfg.MetricsEndpointEnabled { return } if ctx.Req.Method != http.MethodGet || ctx.Req.URL.Path != "/metrics" { return } if hs.metricsEndpointBasicAuthEnabled() && !BasicAuthenticatedRequest(ctx.Req, hs.Cfg.MetricsEndpointBasicAuthUsername, hs.Cfg.MetricsEndpointBasicAuthPassword) { ctx.Resp.WriteHeader(http.StatusUnauthorized) return } promhttp. HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}). ServeHTTP(ctx.Resp, ctx.Req) } // healthzHandler always return 200 - Ok if Grafana's web server is running func (hs *HTTPServer) healthzHandler(ctx *web.Context) { notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead if notHeadOrGet || ctx.Req.URL.Path != "/healthz" { return } ctx.Resp.WriteHeader(http.StatusOK) if _, err := ctx.Resp.Write([]byte("Ok")); err != nil { hs.log.Error("could not write to response", "err", err) } } // apiHealthHandler will return ok if Grafana's web server is running and it // can access the database. If the database cannot be accessed it will return // http status code 503. func (hs *HTTPServer) apiHealthHandler(ctx *web.Context) { notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead if notHeadOrGet || ctx.Req.URL.Path != "/api/health" { return } data := simplejson.New() data.Set("database", "ok") if !hs.Cfg.AnonymousHideVersion { data.Set("version", hs.Cfg.BuildVersion) data.Set("commit", hs.Cfg.BuildCommit) } if !hs.databaseHealthy(ctx.Req.Context()) { data.Set("database", "failing") ctx.Resp.Header().Set("Content-Type", "application/json; charset=UTF-8") ctx.Resp.WriteHeader(http.StatusServiceUnavailable) } else { ctx.Resp.Header().Set("Content-Type", "application/json; charset=UTF-8") ctx.Resp.WriteHeader(http.StatusOK) } dataBytes, err := data.EncodePretty() if err != nil { hs.log.Error("Failed to encode data", "err", err) return } if _, err := ctx.Resp.Write(dataBytes); err != nil { hs.log.Error("Failed to write to response", "err", err) } } func (hs *HTTPServer) mapStatic(m *web.Mux, rootDir string, dir string, prefix string, exclude ...string) { headers := func(c *web.Context) { c.Resp.Header().Set("Cache-Control", "public, max-age=3600") } if prefix == "public/build" { headers = func(c *web.Context) { c.Resp.Header().Set("Cache-Control", "public, max-age=31536000") } } if hs.Cfg.Env == setting.Dev { headers = func(c *web.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, Exclude: exclude, }, )) } func (hs *HTTPServer) metricsEndpointBasicAuthEnabled() bool { return hs.Cfg.MetricsEndpointBasicAuthUsername != "" && hs.Cfg.MetricsEndpointBasicAuthPassword != "" }