From b38437da7c6341a82c70aa08176c5379bdf44cdc Mon Sep 17 00:00:00 2001 From: Chris Grindstaff Date: Fri, 23 Jun 2023 10:42:45 -0400 Subject: [PATCH 1/2] feat: Harvest Prometheus exporter should support TLS Fixes: #2070 --- cmd/admin/admin.go | 5 +- cmd/exporters/prometheus/httpd.go | 29 +++++- docs/assets/prometheus/PrometheusTLS.png | Bin 0 -> 44123 bytes docs/prometheus-exporter.md | 125 ++++++++++++++++++++--- harvest.cue | 60 +++++++---- pkg/conf/conf.go | 1 + 6 files changed, 180 insertions(+), 40 deletions(-) create mode 100644 docs/assets/prometheus/PrometheusTLS.png diff --git a/cmd/admin/admin.go b/cmd/admin/admin.go index b5482dfee..41daf6845 100644 --- a/cmd/admin/admin.go +++ b/cmd/admin/admin.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "fmt" "github.com/netapp/harvest/v2/pkg/conf" "github.com/netapp/harvest/v2/pkg/logging" @@ -68,7 +69,7 @@ func (a *Admin) startServer() { Msg("Admin node started") if a.httpSD.TLS.KeyFile != "" { - if err := server.ListenAndServeTLS(a.httpSD.TLS.CertFile, a.httpSD.TLS.KeyFile); err != nil && err != http.ErrServerClosed { + if err := server.ListenAndServeTLS(a.httpSD.TLS.CertFile, a.httpSD.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) { a.logger.Fatal().Err(err). Str("listen", a.listen). Str("ssl_cert", a.httpSD.TLS.CertFile). @@ -76,7 +77,7 @@ func (a *Admin) startServer() { Msg("Admin node could not listen") } } else { - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { a.logger.Fatal().Err(err). Str("listen", a.listen). Msg("Admin node could not listen") diff --git a/cmd/exporters/prometheus/httpd.go b/cmd/exporters/prometheus/httpd.go index f09174f29..7ffba1aac 100644 --- a/cmd/exporters/prometheus/httpd.go +++ b/cmd/exporters/prometheus/httpd.go @@ -8,9 +8,12 @@ package prometheus import ( "bytes" + "errors" "fmt" "github.com/netapp/harvest/v2/pkg/set" + "net" "net/http" + "strconv" "strings" "time" ) @@ -26,10 +29,30 @@ func (p *Prometheus) startHTTPD(addr string, port int) { Handler: mux, ReadHeaderTimeout: 60 * time.Second, } - p.Logger.Info().Str("addr", addr).Int("port", port).Msg("http server listen") - if err := server.ListenAndServe(); err != nil { - p.Logger.Fatal().Err(err).Msg("Failed to start server") + var url string + if p.Params.TLS.KeyFile != "" { + url = fmt.Sprintf("https://%s/metrics", net.JoinHostPort(addr, strconv.Itoa(port))) + } else { + url = fmt.Sprintf("%s://%s/metrics", "http", net.JoinHostPort(addr, strconv.Itoa(port))) + } + + p.Logger.Info().Str("url", url).Msg("server listen") + + if p.Params.TLS.KeyFile != "" { + if err := server.ListenAndServeTLS(p.Params.TLS.CertFile, p.Params.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) { + p.Logger.Fatal().Err(err). + Str("url", url). + Str("cert_file", p.Params.TLS.CertFile). + Str("key_file", p.Params.TLS.KeyFile). + Msg("Failed to start server") + } + } else { + if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + p.Logger.Fatal().Err(err). + Str("url", url). + Msg("Failed to start server") + } } } diff --git a/docs/assets/prometheus/PrometheusTLS.png b/docs/assets/prometheus/PrometheusTLS.png new file mode 100644 index 0000000000000000000000000000000000000000..bf2f7aad9fd167858e85eb91b66210006e59edfb GIT binary patch literal 44123 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

J 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 literal 0 HcmV?d00001 diff --git a/docs/prometheus-exporter.md b/docs/prometheus-exporter.md index c7bd1bfdd..07a232d72 100644 --- a/docs/prometheus-exporter.md +++ b/docs/prometheus-exporter.md @@ -11,12 +11,12 @@ The Prometheus exporter is responsible for: - formatting metrics into the Prometheus [line protocol](https://prometheus.io/docs/instrumenting/exposition_formats/) -- creating a web-endpoint on `http://:/metrics` for Prometheus to scrape +- creating a web-endpoint on `http://:/metrics` (or `https:` if TLS is enabled) for Prometheus to scrape A web end-point is required because Prometheus scrapes Harvest by polling that end-point. In addition to the `/metrics` end-point, the Prometheus exporter also serves an overview of all metrics and collectors -available on its root address `http://:/`. +available on its root address `scheme://:/`. Because Prometheus polls Harvest, don't forget to [update your Prometheus configuration](#configure-prometheus-to-scrape-harvest-pollers) and tell Prometheus how to @@ -33,17 +33,19 @@ All parameters of the exporter are defined in the `Exporters` section of `harves An overview of all parameters: -| parameter | type | description | default | -|---------------------|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| -| `port_range` | int-int (range), overrides `port` if specified | lower port to upper port (inclusive) of the HTTP end-point to create when a poller specifies this exporter. Starting at lower port, each free port will be tried sequentially up to the upper port. | | -| `port` | int, required if port_range is not specified | port of the HTTP end-point | | -| `local_http_addr` | string, optional | address of the HTTP server Harvest starts for Prometheus to scrape:
use `localhost` to serve only on the local machine
use `0.0.0.0` (default) if Prometheus is scrapping from another machine | `0.0.0.0` | -| `global_prefix` | string, optional | add a prefix to all metrics (e.g. `netapp_`) | | -| `allow_addrs` | list of strings, optional | allow access only if host matches any of the provided addresses | | -| `allow_addrs_regex` | list of strings, optional | allow access only if host address matches at least one of the regular expressions | | -| `cache_max_keep` | string (Go duration format), optional | maximum amount of time metrics are cached (in case Prometheus does not timely collect the metrics) | `300s` | -| `add_meta_tags` | bool, optional | add `HELP` and `TYPE` [metatags](https://prometheus.io/docs/instrumenting/exposition_formats/#comments-help-text-and-type-information) to metrics (currently no useful information, but required by some tools) | `false` | -| `sort_labels` | bool, optional | sort metric labels before exporting. Some [open-metrics scrapers report](https://github.com/NetApp/harvest/issues/756) stale metrics when labels are not sorted. | `false` | +| parameter | type | description | default | +|-----------------------------|------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| `port_range` | int-int (range), overrides `port` if specified | lower port to upper port (inclusive) of the HTTP end-point to create when a poller specifies this exporter. Starting at lower port, each free port will be tried sequentially up to the upper port. | | +| `port` | int, required if port_range is not specified | port of the HTTP end-point | | +| `local_http_addr` | string, optional | address of the HTTP server Harvest starts for Prometheus to scrape:
use `localhost` to serve only on the local machine
use `0.0.0.0` (default) if Prometheus is scrapping from another machine | `0.0.0.0` | +| `global_prefix` | string, optional | add a prefix to all metrics (e.g. `netapp_`) | | +| `allow_addrs` | list of strings, optional | allow access only if host matches any of the provided addresses | | +| `allow_addrs_regex` | list of strings, optional | allow access only if host address matches at least one of the regular expressions | | +| `cache_max_keep` | string (Go duration format), optional | maximum amount of time metrics are cached (in case Prometheus does not timely collect the metrics) | `300s` | +| `add_meta_tags` | bool, optional | add `HELP` and `TYPE` [metatags](https://prometheus.io/docs/instrumenting/exposition_formats/#comments-help-text-and-type-information) to metrics (currently no useful information, but required by some tools) | `false` | +| `sort_labels` | bool, optional | sort metric labels before exporting. Some [open-metrics scrapers report](https://github.com/NetApp/harvest/issues/756) stale metrics when labels are not sorted. | `false` | +| `tls` | `tls` | optional | If present, enables TLS transport. If running in a container, see [note](https://github.com/NetApp/harvest/issues/672#issuecomment-1036338589) | +| tls `cert_file`, `key_file` | **required** child of `tls` | Relative or absolute path to TLS certificate and key file. TLS 1.3 certificates required.
FIPS complaint P-256 TLS 1.3 certificates can be created with `bin/harvest admin tls create server`, `openssl`, `mkcert`, etc. | | A few examples: @@ -297,7 +299,100 @@ Harvest machine. Also note the scrape interval above is set to 60s. That matches Harvest collectors. If you change the polling frequency of a Harvest collector to a lower value, you should also change the scrape interval. -# Prometheus Alerts +## Prometheus Exporter and TLS + +The Harvest Prometheus exporter can be configured to serve its metrics via `HTTPS` by configuring the `tls` section in +the `Exporters` section of `harvest.yml`. + +Let's walk through an example of how to set up Harvest's Prometheus exporter and how to configure Prometheus to use TLS. + +### Generate TLS Certificates + +We'll use Harvest's admin command line tool to create a self-signed TLS certificate key/pair for the exporter and Prometheus. +Note: If running in a container, see [note](https://github.com/NetApp/harvest/issues/672#issuecomment-1036338589). + +```bash +cd $Harvest_Install_Directory +bin/harvest admin tls create server +2023/06/23 09:39:48 wrote cert/admin-cert.pem +2023/06/23 09:39:48 wrote cert/admin-key.pem +``` + +Two files are created. Since we want to use these certificates for our Prometheus exporter, let's rename them to make that clearer. + +```bash +mv cert/admin-cert.pem cert/prom-cert.pem +mv cert/admin-key.pem cert/prom-key.pem +``` + +### Configure Harvest Prometheus Exporter to use TLS + +Edit your `harvest.yml` and add a TLS section to your exporter block like this: + +```yaml +Exporters: + my-exporter: + local_http_addr: localhost + exporter: Prometheus + port: 16001 + tls: + cert_file: cert/prom-cert.pem + key_file: cert/prom-key.pem +``` + +Update one of your Pollers to use this exporter and start the poller. + +```yaml +Pollers: + my-cluster: + datacenter: dc-1 + addr: 10.193.48.11 + exporters: + - my-exporter # Use TLS exporter we created above +``` + +When the poller is started, it will log whether `https` or `http` is being used as part of the `url` like so: + +```bash +bin/harvest start -f my-cluster +2023-06-23T10:02:03-04:00 INF prometheus/httpd.go:40 > server listen Poller=my-cluster exporter=my-exporter url=https://localhost:16001/metrics +``` + +If the `url` schema is `https`, TLS is being used. + +You can use curl to scrape the Prometheus exporter and verify that TLS is being used like so: + +```bash +curl --cacert cert/prom-cert.pem https://localhost:16001/metrics + +# or use --insecure to tell curl to skip certificate validation +# curl --insecure cert/prom-cert.pem https://localhost:16001/metrics +``` + +### Configure Prometheus to use TLS + +Let's configure Prometheus to use `HTTPs` to communicate with the exporter setup above. + +Edit your `prometheus.yml` and add or adapt your `scrape_configs` job. You need to add `scheme: https` and setup a `tls_config` +block to point to the earlier created `prom-cert.pem` like so: + +```yaml +scrape_configs: + - job_name: 'harvest-https' + scheme: https + tls_config: + ca_file: /path/to/prom-cert.pem + static_configs: + - targets: + - 'localhost:16001' +``` + +Start Prometheus and visit http://localhost:9090/targets with your browser. +You should see https://localhost:16001/metrics in the list of targets. + +![Prometheus Targets](assets/prometheus/PrometheusTLS.png) + +## Prometheus Alerts Prometheus includes out-of-the-box support for simple alerting. Alert rules are configured in your `prometheus.yml` file. Setup and details can be found in the Prometheus @@ -308,7 +403,7 @@ and [sample alerts](https://github.com/NetApp/harvest/blob/main/container/promet Refer [EMS Collector](https://github.com/NetApp/harvest/blob/main/cmd/collectors/ems/README.md) for more details about EMS events. -## Alertmanager +### Alertmanager Prometheus's builtin alerts are good for simple workflows. They do a nice job telling you what's happening at the moment. diff --git a/harvest.cue b/harvest.cue index da2758d52..d678de2e8 100644 --- a/harvest.cue +++ b/harvest.cue @@ -4,35 +4,55 @@ Exporters: [Name=_]: #Prom | #Influx label: [string]: string +#Auth: { + username: string + password: string +} + +#HTTPSD: { + listen: string + auth_basic?: #Auth + tls?: #TLS + heart_beat?: string + expire_after?: string +} + +#TLS: { + cert_file: string + key_file: string +} + #Admin: { - addr?: string + addr?: string + httpsd?: #HTTPSD } #Prom: { add_meta_tags?: bool - addr?: string // deprecated + addr?: string // deprecated allow_addrs_regex?: [...string] exporter: "Prometheus" local_http_addr?: "0.0.0.0" | "localhost" | "127.0.0.1" port?: int port_range?: string - sort_labels?: bool + sort_labels?: bool + tls?: #TLS } #Influx: { - addr?: string // one of addr|url + addr?: string // one of addr|url allow_addrs_regex: [...string] - bucket?: string + bucket?: string exporter: "InfluxDB" - org?: string + org?: string token?: string url?: string } #CredentialsScript: { - path: string - schedule?: string - timeout?: string + path: string + schedule?: string + timeout?: string } #CollectorDef: { @@ -46,20 +66,20 @@ Pollers: [Name=_]: #Poller auth_style?: "basic_auth" | "certificate_auth" client_timeout?: string collectors?: [...#CollectorDef] | [...string] - credentials_file?: string + credentials_file?: string credentials_script?: #CredentialsScript datacenter?: string exporters: [...string] - is_kfs?: bool + is_kfs?: bool labels?: [...label] log: [...string] - log_max_bytes?: int - log_max_files?: int - password?: string - prefer_zapi?: bool - ssl_cert?: string - ssl_key?: string - tls_min_version?: string - use_insecure_tls?: bool - username?: string + log_max_bytes?: int + log_max_files?: int + password?: string + prefer_zapi?: bool + ssl_cert?: string + ssl_key?: string + tls_min_version?: string + use_insecure_tls?: bool + username?: string } diff --git a/pkg/conf/conf.go b/pkg/conf/conf.go index 1efb1f078..78d386aa2 100644 --- a/pkg/conf/conf.go +++ b/pkg/conf/conf.go @@ -504,6 +504,7 @@ type Exporter struct { // Prometheus specific HeartBeatURL string `yaml:"heart_beat_url,omitempty"` SortLabels bool `yaml:"sort_labels,omitempty"` + TLS TLS `yaml:"tls,omitempty"` // InfluxDB specific Bucket *string `yaml:"bucket,omitempty"` From 4659365e15af71f05cfb1c61dcd03254d155c433 Mon Sep 17 00:00:00 2001 From: Chris Grindstaff Date: Fri, 23 Jun 2023 15:26:07 -0400 Subject: [PATCH 2/2] feat: Harvest Prometheus exporter should support TLS Fixes: #2070 --- docs/prometheus-exporter.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/prometheus-exporter.md b/docs/prometheus-exporter.md index 07a232d72..90cc14d0a 100644 --- a/docs/prometheus-exporter.md +++ b/docs/prometheus-exporter.md @@ -331,24 +331,24 @@ Edit your `harvest.yml` and add a TLS section to your exporter block like this: ```yaml Exporters: - my-exporter: - local_http_addr: localhost - exporter: Prometheus - port: 16001 - tls: - cert_file: cert/prom-cert.pem - key_file: cert/prom-key.pem + my-exporter: + local_http_addr: localhost + exporter: Prometheus + port: 16001 + tls: + cert_file: cert/prom-cert.pem + key_file: cert/prom-key.pem ``` Update one of your Pollers to use this exporter and start the poller. ```yaml Pollers: - my-cluster: - datacenter: dc-1 - addr: 10.193.48.11 - exporters: - - my-exporter # Use TLS exporter we created above + my-cluster: + datacenter: dc-1 + addr: 10.193.48.11 + exporters: + - my-exporter # Use TLS exporter we created above ``` When the poller is started, it will log whether `https` or `http` is being used as part of the `url` like so: