prober/tls: adding metric to expose certificate fingerprint info (#678)
authorFelix Ehrenpfort <felix.ehrenpfort@codecentric.de>
Tue, 18 Aug 2020 22:55:48 +0000 (00:55 +0200)
committerGitHub <noreply@github.com>
Tue, 18 Aug 2020 22:55:48 +0000 (23:55 +0100)
this change adds a new metric `probe_ssl_fingerprint_info` to both tcp
and http probes. the metric always returns 1 similar to the tls version
metric and contains the leaf certificates sha256 fingerprint (hex) as a
label value.

this change allows users to validate in prometheus if a particular
certificate is being served.

Signed-off-by: xinau <felix.ehrenpfort@protonmail.com>
prober/http.go
prober/tcp.go
prober/tcp_test.go
prober/tls.go

index 7fa6c9c1a4e04daf53c7813f05a331131039e955..8362c0416f8a107f315f4b88871697cce7505ec7 100644 (file)
@@ -266,6 +266,14 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
                        Help: "Returns last SSL chain expiry in timestamp seconds",
                })
 
+               probeSSLLastInformation = prometheus.NewGaugeVec(
+                       prometheus.GaugeOpts{
+                               Name: "probe_ssl_last_chain_info",
+                               Help: "Contains SSL leaf certificate information",
+                       },
+                       []string{"fingerprint_sha256"},
+               )
+
                probeTLSVersion = prometheus.NewGaugeVec(
                        prometheus.GaugeOpts{
                                Name: "probe_tls_version_info",
@@ -549,10 +557,11 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
 
        if resp.TLS != nil {
                isSSLGauge.Set(float64(1))
-               registry.MustRegister(probeSSLEarliestCertExpiryGauge, probeTLSVersion, probeSSLLastChainExpiryTimestampSeconds)
+               registry.MustRegister(probeSSLEarliestCertExpiryGauge, probeTLSVersion, probeSSLLastChainExpiryTimestampSeconds, probeSSLLastInformation)
                probeSSLEarliestCertExpiryGauge.Set(float64(getEarliestCertExpiry(resp.TLS).Unix()))
                probeTLSVersion.WithLabelValues(getTLSVersion(resp.TLS)).Set(1)
                probeSSLLastChainExpiryTimestampSeconds.Set(float64(getLastChainExpiry(resp.TLS).Unix()))
+               probeSSLLastInformation.WithLabelValues(getFingerprint(resp.TLS)).Set(1)
                if httpConfig.FailIfSSL {
                        level.Error(logger).Log("msg", "Final request was over SSL")
                        success = false
index b5f5addd8bf8124a860d736e4c558ebff7eb1e99..3e8339b0f9d9c4b2e55edfdf20fd56f2ae56f020 100644 (file)
@@ -98,6 +98,13 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
                Name: "probe_ssl_last_chain_expiry_timestamp_seconds",
                Help: "Returns last SSL chain expiry in unixtime",
        })
+       probeSSLLastInformation := prometheus.NewGaugeVec(
+               prometheus.GaugeOpts{
+                       Name: "probe_ssl_last_chain_info",
+                       Help: "Contains SSL leaf certificate information",
+               },
+               []string{"fingerprint_sha256"},
+       )
        probeTLSVersion := prometheus.NewGaugeVec(
                prometheus.GaugeOpts{
                        Name: "probe_tls_version_info",
@@ -129,10 +136,11 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
        }
        if module.TCP.TLS {
                state := conn.(*tls.Conn).ConnectionState()
-               registry.MustRegister(probeSSLEarliestCertExpiry, probeTLSVersion, probeSSLLastChainExpiryTimestampSeconds)
+               registry.MustRegister(probeSSLEarliestCertExpiry, probeTLSVersion, probeSSLLastChainExpiryTimestampSeconds, probeSSLLastInformation)
                probeSSLEarliestCertExpiry.Set(float64(getEarliestCertExpiry(&state).Unix()))
                probeTLSVersion.WithLabelValues(getTLSVersion(&state)).Set(1)
                probeSSLLastChainExpiryTimestampSeconds.Set(float64(getLastChainExpiry(&state).Unix()))
+               probeSSLLastInformation.WithLabelValues(getFingerprint(&state)).Set(1)
        }
        scanner := bufio.NewScanner(conn)
        for i, qr := range module.TCP.QueryResponse {
@@ -203,6 +211,7 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
                        probeSSLEarliestCertExpiry.Set(float64(getEarliestCertExpiry(&state).Unix()))
                        probeTLSVersion.WithLabelValues(getTLSVersion(&state)).Set(1)
                        probeSSLLastChainExpiryTimestampSeconds.Set(float64(getLastChainExpiry(&state).Unix()))
+                       probeSSLLastInformation.WithLabelValues(getFingerprint(&state)).Set(1)
                }
        }
        return true
index d05f317454735e30fc538360018cadc92044810c..fd219f94649b9bf1476bfe67901d51a8f7d401ab 100644 (file)
@@ -196,6 +196,7 @@ func TestTCPConnectionWithTLS(t *testing.T) {
        // Check values
        expectedResults := map[string]float64{
                "probe_ssl_earliest_cert_expiry": float64(certExpiry.Unix()),
+               "probe_ssl_last_chain_info":      1,
                "probe_tls_version_info":         1,
        }
        checkRegistryResults(expectedResults, mfs, t)
@@ -317,6 +318,7 @@ func TestTCPConnectionWithTLSAndVerifiedCertificateChain(t *testing.T) {
        expectedResults := map[string]float64{
                "probe_ssl_earliest_cert_expiry":                float64(serverCertExpiry.Unix()),
                "probe_ssl_last_chain_expiry_timestamp_seconds": float64(serverCertExpiry.Unix()),
+               "probe_ssl_last_chain_info":                     1,
                "probe_tls_version_info":                        1,
        }
        checkRegistryResults(expectedResults, mfs, t)
index a4ef0f9a8a39d2c5ad548c8e61c0c0d91c2bd8a5..87107974b0f079270549ab63bc05d130966e7dfc 100644 (file)
@@ -14,7 +14,9 @@
 package prober
 
 import (
+       "crypto/sha256"
        "crypto/tls"
+       "encoding/hex"
        "time"
 )
 
@@ -28,6 +30,12 @@ func getEarliestCertExpiry(state *tls.ConnectionState) time.Time {
        return earliest
 }
 
+func getFingerprint(state *tls.ConnectionState) string {
+       cert := state.PeerCertificates[0]
+       fingerprint := sha256.Sum256(cert.Raw)
+       return hex.EncodeToString(fingerprint[:])
+}
+
 func getLastChainExpiry(state *tls.ConnectionState) time.Time {
        lastChainExpiry := time.Time{}
        for _, chain := range state.VerifiedChains {