From 682ac8f5c92b963627be1c4e7f7fcbd16d64409f Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Tue, 26 Sep 2023 23:12:12 +0200 Subject: [PATCH] Add TLS support to metrics server Add a link to metrics endpoint on root --- cmd/run.go | 62 +++++++++++++++++++++++++++++++++++++++------ config/constants.go | 7 ++--- config/getters.go | 7 +++++ config/types.go | 3 +++ gatewayd.yaml | 3 +++ 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 7dc4fcce..4d7bb407 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -343,7 +343,12 @@ var runCmd = &cobra.Command{ return } - fqdn, err := url.Parse("http://" + metricsConfig.Address) + scheme := "http://" + if metricsConfig.KeyFile != "" && metricsConfig.CertFile != "" { + scheme = "https://" + } + + fqdn, err := url.Parse(scheme + metricsConfig.Address) if err != nil { logger.Error().Err(err).Msg("Failed to parse metrics address") span.RecordError(err) @@ -386,7 +391,18 @@ var runCmd = &cobra.Command{ ) }() - logger.Info().Str("address", address).Msg("Metrics are exposed") + mux := http.NewServeMux() + mux.HandleFunc("/", func(responseWriter http.ResponseWriter, request *http.Request) { + // Serve a static page with a link to the metrics endpoint. + if _, err := responseWriter.Write([]byte(fmt.Sprintf( + `GatewayD Prometheus Metrics ServerMetrics`, + address, + ))); err != nil { + logger.Error().Err(err).Msg("Failed to write metrics") + span.RecordError(err) + sentry.CaptureException(err) + } + }) if conf.Plugin.EnableMetricsMerger && metricsMerger != nil { handler = mergedMetricsHandler(handler) @@ -394,7 +410,7 @@ var runCmd = &cobra.Command{ // Check if the metrics server is already running before registering the handler. if _, err = http.Get(address); err != nil { //nolint:gosec - http.Handle(metricsConfig.Path, gziphandler.GzipHandler(handler)) + mux.Handle(metricsConfig.Path, gziphandler.GzipHandler(handler)) } else { logger.Warn().Msg("Metrics server is already running, consider changing the port") span.RecordError(err) @@ -403,14 +419,44 @@ var runCmd = &cobra.Command{ // Create a new metrics server. metricsServer = &http.Server{ Addr: metricsConfig.Address, - Handler: handler, + Handler: mux, ReadHeaderTimeout: metricsConfig.GetReadHeaderTimeout(), } - // Start the metrics server. - if err = metricsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { - logger.Error().Err(err).Msg("Failed to start metrics server") - span.RecordError(err) + logger.Info().Str("address", address).Msg("Metrics are exposed") + + if metricsConfig.CertFile != "" && metricsConfig.KeyFile != "" { + // Set up TLS. + metricsServer.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS13, + CurvePreferences: []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + }, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + }, + } + metricsServer.TLSNextProto = make( + map[string]func(*http.Server, *tls.Conn, http.Handler), 0) + logger.Debug().Msg("Metrics server is running with TLS") + + // Start the metrics server with TLS. + if err = metricsServer.ListenAndServeTLS( + metricsConfig.CertFile, metricsConfig.KeyFile); !errors.Is(err, http.ErrServerClosed) { + logger.Error().Err(err).Msg("Failed to start metrics server") + span.RecordError(err) + } + } else { + // Start the metrics server without TLS. + if err = metricsServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + logger.Error().Err(err).Msg("Failed to start metrics server") + span.RecordError(err) + } } }(conf.Global.Metrics[config.Default], logger) diff --git a/config/constants.go b/config/constants.go index bb7445c7..a40175e8 100644 --- a/config/constants.go +++ b/config/constants.go @@ -123,9 +123,10 @@ const ( ChecksumBufferSize = 65536 // Metrics constants. - DefaultMetricsAddress = "localhost:9090" - DefaultMetricsPath = "/metrics" - DefaultReadHeaderTimeout = 10 * time.Second + DefaultMetricsAddress = "localhost:9090" + DefaultMetricsPath = "/metrics" + DefaultReadHeaderTimeout = 10 * time.Second + DefaultMetricsServerTimeout = 10 * time.Second // Sentry constants. DefaultTraceSampleRate = 0.2 diff --git a/config/getters.go b/config/getters.go index 47900ff1..11a8aa3b 100644 --- a/config/getters.go +++ b/config/getters.go @@ -276,3 +276,10 @@ func (m Metrics) GetReadHeaderTimeout() time.Duration { } return m.ReadHeaderTimeout } + +func (m Metrics) GetTimeout() time.Duration { + if m.Timeout <= 0 { + return DefaultMetricsServerTimeout + } + return m.Timeout +} diff --git a/config/types.go b/config/types.go index 17bb1ae7..5924e855 100644 --- a/config/types.go +++ b/config/types.go @@ -72,6 +72,9 @@ type Metrics struct { Address string `json:"address"` Path string `json:"path"` ReadHeaderTimeout time.Duration `json:"readHeaderTimeout" jsonschema:"oneof_type=string;integer"` + Timeout time.Duration `json:"timeout" jsonschema:"oneof_type=string;integer"` + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` } type Pool struct { diff --git a/gatewayd.yaml b/gatewayd.yaml index 77d5eb76..f0032f2d 100644 --- a/gatewayd.yaml +++ b/gatewayd.yaml @@ -25,6 +25,9 @@ metrics: address: localhost:9090 path: /metrics readHeaderTimeout: 10s # duration, prevents Slowloris attacks + timeout: 10s # duration + certFile: "" # Certificate file in PEM format + keyFile: "" # Private key file in PEM format clients: default: