Export TLS version (#538)
authorSilke Hofstra <silkeh@users.noreply.github.com>
Mon, 14 Oct 2019 08:27:37 +0000 (10:27 +0200)
committerBrian Brazil <brian.brazil@robustperception.io>
Mon, 14 Oct 2019 08:27:37 +0000 (09:27 +0100)
* Add a metric for the TLS version used

Signed-off-by: Silke Hofstra <silke@slxh.eu>
prober/http.go
prober/tcp.go
prober/tcp_test.go
prober/tls.go
prober/utils_test.go

index bd9722d0f43a2c1866aab07b456bd0f351767753..f4abdbe119e3c678151be186a6f2099a5cee13eb 100644 (file)
@@ -261,6 +261,14 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
                        Help: "Returns earliest SSL cert expiry in unixtime",
                })
 
+               probeTLSVersion = prometheus.NewGaugeVec(
+                       prometheus.GaugeOpts{
+                               Name: "probe_tls_version_info",
+                               Help: "Contains the TLS version used",
+                       },
+                       []string{"version"},
+               )
+
                probeHTTPVersionGauge = prometheus.NewGauge(prometheus.GaugeOpts{
                        Name: "probe_http_version",
                        Help: "Returns the version of HTTP of the probe response",
@@ -538,8 +546,9 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
 
        if resp.TLS != nil {
                isSSLGauge.Set(float64(1))
-               registry.MustRegister(probeSSLEarliestCertExpiryGauge)
+               registry.MustRegister(probeSSLEarliestCertExpiryGauge, probeTLSVersion)
                probeSSLEarliestCertExpiryGauge.Set(float64(getEarliestCertExpiry(resp.TLS).Unix()))
+               probeTLSVersion.WithLabelValues(getTLSVersion(resp.TLS)).Set(1)
                if httpConfig.FailIfSSL {
                        level.Error(logger).Log("msg", "Final request was over SSL")
                        success = false
index e4976256689ae00799878520569f33486b8ee555..a450fd28de328af2051f64de75f2a46705633083 100644 (file)
@@ -94,6 +94,13 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
                Name: "probe_ssl_earliest_cert_expiry",
                Help: "Returns earliest SSL cert expiry date",
        })
+       probeTLSVersion := prometheus.NewGaugeVec(
+               prometheus.GaugeOpts{
+                       Name: "probe_tls_version_info",
+                       Help: "Returns the TLS version used, or NaN when unknown",
+               },
+               []string{"version"},
+       )
        probeFailedDueToRegex := prometheus.NewGauge(prometheus.GaugeOpts{
                Name: "probe_failed_due_to_regex",
                Help: "Indicates if probe failed due to regex",
@@ -118,8 +125,9 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
        }
        if module.TCP.TLS {
                state := conn.(*tls.Conn).ConnectionState()
-               registry.MustRegister(probeSSLEarliestCertExpiry)
+               registry.MustRegister(probeSSLEarliestCertExpiry, probeTLSVersion)
                probeSSLEarliestCertExpiry.Set(float64(getEarliestCertExpiry(&state).Unix()))
+               probeTLSVersion.WithLabelValues(getTLSVersion(&state)).Set(1)
        }
        scanner := bufio.NewScanner(conn)
        for i, qr := range module.TCP.QueryResponse {
@@ -188,6 +196,7 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
                        state := tlsConn.ConnectionState()
                        registry.MustRegister(probeSSLEarliestCertExpiry)
                        probeSSLEarliestCertExpiry.Set(float64(getEarliestCertExpiry(&state).Unix()))
+                       probeTLSVersion.WithLabelValues(getTLSVersion(&state)).Set(1)
                }
        }
        return true
index 82013003ab5dee2f6ee61965b9d2344d392f5365..e2fcbd4d3a970ff45f8e9c083c3c960f6ecc18b9 100644 (file)
@@ -118,6 +118,8 @@ func TestTCPConnectionWithTLS(t *testing.T) {
                tlsConfig := &tls.Config{
                        ServerName:   "localhost",
                        Certificates: []tls.Certificate{testcert},
+                       MinVersion:   tls.VersionTLS12,
+                       MaxVersion:   tls.VersionTLS12,
                }
                tlsConn := tls.Server(conn, tlsConfig)
                defer tlsConn.Close()
@@ -168,13 +170,24 @@ func TestTCPConnectionWithTLS(t *testing.T) {
        }
        <-ch
 
-       // Check the probe_ssl_earliest_cert_expiry.
+       // Check the resulting metrics.
        mfs, err := registry.Gather()
        if err != nil {
                t.Fatal(err)
        }
+
+       // Check labels
+       expectedLabels := map[string]map[string]string{
+               "probe_tls_version_info": {
+                       "version": "TLS 1.2",
+               },
+       }
+       checkRegistryLabels(expectedLabels, mfs, t)
+
+       // Check values
        expectedResults := map[string]float64{
                "probe_ssl_earliest_cert_expiry": float64(certExpiry.Unix()),
+               "probe_tls_version_info":         1,
        }
        checkRegistryResults(expectedResults, mfs, t)
 }
index ea21dd15bf825b8cf7d12541aeda0b4b93306cf3..758cbf35d44888960557f2d752c9d17df48048f7 100644 (file)
@@ -27,3 +27,18 @@ func getEarliestCertExpiry(state *tls.ConnectionState) time.Time {
        }
        return earliest
 }
+
+func getTLSVersion(state *tls.ConnectionState) string {
+       switch state.Version {
+       case tls.VersionTLS10:
+               return "TLS 1.0"
+       case tls.VersionTLS11:
+               return "TLS 1.1"
+       case tls.VersionTLS12:
+               return "TLS 1.2"
+       case tls.VersionTLS13:
+               return "TLS 1.3"
+       default:
+               return "unknown"
+       }
+}
index 38b95ee972f12dbe8e9faf84747d6d7045f92d57..326da5d81af0d9532a53bfb2fd20e385cf9fda59 100644 (file)
@@ -45,6 +45,35 @@ func checkRegistryResults(expRes map[string]float64, mfs []*dto.MetricFamily, t
        }
 }
 
+// Check if expected labels are in the registry
+func checkRegistryLabels(expRes map[string]map[string]string, mfs []*dto.MetricFamily, t *testing.T) {
+       results := make(map[string]map[string]string)
+       for _, mf := range mfs {
+               result := make(map[string]string)
+               for _, metric := range mf.Metric {
+                       for _, l := range metric.GetLabel() {
+                               result[l.GetName()] = l.GetValue()
+                       }
+               }
+               results[mf.GetName()] = result
+       }
+
+       for metric, labelValues := range expRes {
+               if _, ok := results[metric]; !ok {
+                       t.Fatalf("Expected metric %v not found in returned metrics", metric)
+               }
+               for name, exp := range labelValues {
+                       val, ok := results[metric][name]
+                       if !ok {
+                               t.Fatalf("Expected label %v for metric %v not found in returned metrics", val, name)
+                       }
+                       if val != exp {
+                               t.Fatalf("Expected: %v{%q=%q}, got: %v{%q=%q}", metric, name, exp, metric, name, val)
+                       }
+               }
+       }
+}
+
 // Create test certificate with specified expiry date
 // Certificate will be self-signed and use localhost/127.0.0.1
 // Generated certificate and key are returned in PEM encoding