From 549ecb060e8475b52998d20c3a50bd7116e420fb Mon Sep 17 00:00:00 2001 From: Ingmar Stein <490610+IngmarStein@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:24:51 +0100 Subject: [PATCH] Server: Support HTTPS and HTTP/2 over Unix domain sockets (#118155) This commit introduces a new protocol scheme 'sockettls' (internal constant SocketHTTP2Scheme) that enables serving TLS-encrypted traffic over Unix domain sockets. Previously, Grafana supported plain HTTP over Unix sockets via the 'socket' protocol, but there was no way to enable TLS or HTTP/2 when using sockets. This is useful when Grafana is behind a reverse proxy that communicates via sockets and requires end-to-end encryption or HTTP/2 features. Key changes: - pkg/setting: Added SocketHTTP2Scheme and updated readServerSettings to parse the 'sockettls' protocol. This includes support for socket paths, GID/mode permissions, and TLS certificate/key configuration. - pkg/api: Modified HTTPServer to support listening on Unix sockets when TLS is enabled. Updated Run, getListener, and configureTLS to correctly handle the new scheme. - pkg/services/rendering: Updated getGrafanaCallbackURL to use 'https' scheme for the new socket-based scheme, ensuring correct callback URLs. - pkg/services/frontend: Enabled the Http2Enabled flag for the new scheme to inform the frontend about HTTP/2 availability. - conf: Updated defaults.ini and sample.ini to include 'sockettls' in the documented protocol options. - docs: Updated configuration documentation to reflect the new protocol. --- conf/defaults.ini | 2 +- conf/sample.ini | 2 +- .../setup-grafana/configure-grafana/_index.md | 2 +- pkg/api/frontendsettings.go | 2 +- pkg/api/http_server.go | 10 +++---- pkg/services/frontend/request_config.go | 2 +- pkg/services/rendering/rendering.go | 2 +- pkg/setting/setting.go | 27 ++++++++++++------- 8 files changed, 29 insertions(+), 20 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index f0d173d91b..667610fb44 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -37,7 +37,7 @@ permitted_provisioning_paths = devenv/dev-dashboards|conf/provisioning #################################### Server ############################## [server] -# Protocol (http, https, h2, socket) +# Protocol (http, https, h2, socket, socket_h2) protocol = http # Minimum TLS version allowed. By default, this value is empty. Accepted values are: TLS1.2, TLS1.3. If nothing is set TLS1.2 would be taken diff --git a/conf/sample.ini b/conf/sample.ini index f09448e573..e82ee9c982 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -35,7 +35,7 @@ #################################### Server #################################### [server] -# Protocol (http, https, h2, socket) +# Protocol (http, https, h2, socket, socket_h2) ;protocol = http # Minimum TLS version allowed. By default, this value is empty. Accepted values are: TLS1.2, TLS1.3. If nothing is set TLS1.2 would be taken diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index cab09d9c80..2fe36fdf40 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -222,7 +222,7 @@ Dashboards are reloaded when the JSON files change. #### `protocol` -`http`,`https`,`h2` or `socket` +`http`,`https`,`h2`,`socket` or `socket_h2` #### `min_tls_version` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 8c0c2486d4..c0605e9ce5 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -307,7 +307,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro RendererDefaultImageWidth: hs.Cfg.RendererDefaultImageWidth, RendererDefaultImageHeight: hs.Cfg.RendererDefaultImageHeight, RendererDefaultImageScale: hs.Cfg.RendererDefaultImageScale, - Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme, + Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme || hs.Cfg.Protocol == setting.SocketHTTP2Scheme, GrafanaJavascriptAgent: hs.Cfg.GrafanaJavascriptAgent, PluginCatalogURL: hs.Cfg.PluginCatalogURL, PluginAdminEnabled: hs.Cfg.PluginAdminEnabled, diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 4f04473595..ce0fb5994e 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -453,7 +453,7 @@ func (hs *HTTPServer) Run(ctx context.Context) error { hs.httpSrv.ErrorLog = stdlog.New(customErrorLogger, "", 0) switch hs.Cfg.Protocol { - case setting.HTTP2Scheme, setting.HTTPSScheme: + case setting.HTTP2Scheme, setting.HTTPSScheme, setting.SocketHTTP2Scheme: if err := hs.configureTLS(); err != nil { return err } @@ -499,7 +499,7 @@ func (hs *HTTPServer) Run(ctx context.Context) error { } return err } - case setting.HTTP2Scheme, setting.HTTPSScheme: + case setting.HTTP2Scheme, setting.HTTPSScheme, setting.SocketHTTP2Scheme: if err := hs.httpSrv.ServeTLS(listener, "", ""); err != nil { if errors.Is(err, http.ErrServerClosed) { hs.log.Debug("server was shutdown gracefully") @@ -528,7 +528,7 @@ func (hs *HTTPServer) getListener() (net.Listener, error) { return nil, fmt.Errorf("failed to open listener on address %s: %w", hs.httpSrv.Addr, err) } return listener, nil - case setting.SocketScheme: + case setting.SocketScheme, setting.SocketHTTP2Scheme: 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) @@ -840,7 +840,7 @@ func (hs *HTTPServer) getDefaultCiphers(tlsVersion uint16, protocol string) []ui tls.TLS_RSA_WITH_AES_256_GCM_SHA384, } } - if protocol == "h2" { + if protocol == "h2" || protocol == "socket_h2" { return []uint16{ tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_AES_128_GCM_SHA256, @@ -965,7 +965,7 @@ func (hs *HTTPServer) configureTLS() error { hs.httpSrv.TLSConfig = tlsCfg - if hs.Cfg.Protocol == setting.HTTP2Scheme { + if hs.Cfg.Protocol == setting.HTTP2Scheme || hs.Cfg.Protocol == setting.SocketHTTP2Scheme { hs.httpSrv.TLSConfig.NextProtos = []string{"h2", "http/1.1"} } diff --git a/pkg/services/frontend/request_config.go b/pkg/services/frontend/request_config.go index 4c15761d21..790f7e54b6 100644 --- a/pkg/services/frontend/request_config.go +++ b/pkg/services/frontend/request_config.go @@ -42,7 +42,7 @@ func NewFSRequestConfig(cfg *setting.Cfg, license licensing.Licensing) FSRequest GoogleAnalytics4SendManualPageViews: cfg.GoogleAnalytics4SendManualPageViews, GoogleAnalyticsId: cfg.GoogleAnalyticsID, GrafanaJavascriptAgent: cfg.GrafanaJavascriptAgent, - Http2Enabled: cfg.Protocol == setting.HTTP2Scheme, + Http2Enabled: cfg.Protocol == setting.HTTP2Scheme || cfg.Protocol == setting.SocketHTTP2Scheme, JwtHeaderName: cfg.JWTAuth.HeaderName, JwtUrlLogin: cfg.JWTAuth.URLLogin, LdapEnabled: cfg.LDAPAuthEnabled, diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go index 4acdcc0956..895a57c444 100644 --- a/pkg/services/rendering/rendering.go +++ b/pkg/services/rendering/rendering.go @@ -413,7 +413,7 @@ func (rs *RenderingService) getGrafanaCallbackURL(path string) string { switch protocol { case setting.HTTPScheme: protocol = "http" - case setting.HTTP2Scheme, setting.HTTPSScheme: + case setting.HTTP2Scheme, setting.HTTPSScheme, setting.SocketHTTP2Scheme: protocol = "https" default: // TODO: Handle other schemes? diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 75624228be..ca16b39eca 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -37,10 +37,11 @@ import ( type Scheme string const ( - HTTPScheme Scheme = "http" - HTTPSScheme Scheme = "https" - HTTP2Scheme Scheme = "h2" - SocketScheme Scheme = "socket" + HTTPScheme Scheme = "http" + HTTPSScheme Scheme = "https" + HTTP2Scheme Scheme = "h2" + SocketScheme Scheme = "socket" + SocketHTTP2Scheme Scheme = "socket_h2" ) const ( @@ -1983,23 +1984,31 @@ func (cfg *Cfg) readServerSettings(iniFile *ini.File) error { protocolStr := valueAsString(server, "protocol", "http") - if protocolStr == "https" { + switch protocolStr { + case "https": cfg.Protocol = HTTPSScheme cfg.CertFile = server.Key("cert_file").String() cfg.KeyFile = server.Key("cert_key").String() cfg.CertPassword = server.Key("cert_pass").String() - } - if protocolStr == "h2" { + case "h2": cfg.Protocol = HTTP2Scheme cfg.CertFile = server.Key("cert_file").String() cfg.KeyFile = server.Key("cert_key").String() cfg.CertPassword = server.Key("cert_pass").String() - } - if protocolStr == "socket" { + case "socket": cfg.Protocol = SocketScheme cfg.SocketGid = server.Key("socket_gid").MustInt(-1) cfg.SocketMode = server.Key("socket_mode").MustInt(0660) cfg.SocketPath = server.Key("socket").String() + case "socket_h2": + cfg.Protocol = SocketHTTP2Scheme + cfg.SocketGid = server.Key("socket_gid").MustInt(-1) + cfg.SocketMode = server.Key("socket_mode").MustInt(0660) + cfg.SocketPath = server.Key("socket").String() + cfg.CertFile = server.Key("cert_file").String() + cfg.KeyFile = server.Key("cert_key").String() + cfg.CertPassword = server.Key("cert_pass").String() + default: } cfg.MinTLSVersion = valueAsString(server, "min_tls_version", "TLS1.2")