Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add leaf certificate details in probe_tls_certificate_info metric #943

Merged
merged 3 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions prober/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ func ProbeGRPC(ctx context.Context, target string, module config.Module, registr
},
[]string{"version"},
)

probeSSLLastInformation = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "probe_ssl_last_chain_info",
Help: "Contains SSL leaf certificate information",
},
[]string{"fingerprint_sha256", "subject", "issuer", "subjectalternative"},
)
)

for _, lv := range []string{"resolve"} {
Expand All @@ -120,6 +128,7 @@ func ProbeGRPC(ctx context.Context, target string, module config.Module, registr
registry.MustRegister(healthCheckResponseGaugeVec)
registry.MustRegister(probeSSLEarliestCertExpiryGauge)
registry.MustRegister(probeTLSVersion)
registry.MustRegister(probeSSLLastInformation)

if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") {
target = "http://" + target
Expand Down Expand Up @@ -202,6 +211,7 @@ func ProbeGRPC(ctx context.Context, target string, module config.Module, registr
isSSLGauge.Set(float64(1))
probeSSLEarliestCertExpiryGauge.Set(float64(getEarliestCertExpiry(&tlsInfo.State).Unix()))
probeTLSVersion.WithLabelValues(getTLSVersion(&tlsInfo.State)).Set(1)
probeSSLLastInformation.WithLabelValues(getFingerprint(&tlsInfo.State), getSubject(&tlsInfo.State), getIssuer(&tlsInfo.State), getDNSNames(&tlsInfo.State)).Set(1)
} else {
isSSLGauge.Set(float64(0))
}
Expand Down
4 changes: 2 additions & 2 deletions prober/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
Name: "probe_ssl_last_chain_info",
Help: "Contains SSL leaf certificate information",
},
[]string{"fingerprint_sha256"},
[]string{"fingerprint_sha256", "subject", "issuer", "subjectalternative"},
)

probeTLSVersion = prometheus.NewGaugeVec(
Expand Down Expand Up @@ -646,7 +646,7 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
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)
probeSSLLastInformation.WithLabelValues(getFingerprint(resp.TLS), getSubject(resp.TLS), getIssuer(resp.TLS), getDNSNames(resp.TLS)).Set(1)
if httpConfig.FailIfSSL {
level.Error(logger).Log("msg", "Final request was over SSL")
success = false
Expand Down
6 changes: 3 additions & 3 deletions prober/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func ProbeTCP(ctx context.Context, target string, module config.Module, registry
Name: "probe_ssl_last_chain_info",
Help: "Contains SSL leaf certificate information",
},
[]string{"fingerprint_sha256"},
[]string{"fingerprint_sha256", "subject", "issuer", "subjectalternative"},
)
probeTLSVersion := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Expand Down Expand Up @@ -139,7 +139,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)
probeSSLLastInformation.WithLabelValues(getFingerprint(&state), getSubject(&state), getIssuer(&state), getDNSNames(&state)).Set(1)
}
scanner := bufio.NewScanner(conn)
for i, qr := range module.TCP.QueryResponse {
Expand Down Expand Up @@ -205,7 +205,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)
probeSSLLastInformation.WithLabelValues(getFingerprint(&state), getSubject(&state), getIssuer(&state), getDNSNames(&state)).Set(1)
}
}
return true
Expand Down
16 changes: 16 additions & 0 deletions prober/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"strings"
"time"
)

Expand All @@ -36,6 +37,21 @@ func getFingerprint(state *tls.ConnectionState) string {
return hex.EncodeToString(fingerprint[:])
}

func getSubject(state *tls.ConnectionState) string {
cert := state.PeerCertificates[0]
return cert.Subject.String()
}

func getIssuer(state *tls.ConnectionState) string {
cert := state.PeerCertificates[0]
return cert.Issuer.String()
}

func getDNSNames(state *tls.ConnectionState) string {
cert := state.PeerCertificates[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I understand correctly, PeerCertificates[0] will return leaf certificate.

do we want to only export details of leaf certificate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question! The details of this certificate in my opinion are the most important, if we were to add others, that wil require rethink in how these values are extracted.

I reused the logic behind the "probe_ssl_last_chain_info" metric and in hindsight, that metric name is more succinct and applicable for the info the RFE requested so maybe it would be better to just add these labels there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, probe_ssl_last_chain_info makes it explicit that this is info of last (i.e leaf) certificate with the metric name.

I think we can proceed with one of the following way:

  1. keep the new metric, and update the PR to add details of all certificates in the chain.
  2. rename the new metric to include last in the metric name, and only add last cert. info.
  3. skip this new metric, and add the labels into probe_ssl_last_chain_info metric.

I am fine with any of the above solution.

@SuperQ @roidelapluie @mem what do you folks think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt option 1 would need a pretty significant rewrite of the logic to iterate through all the certificates.
Option 2 might cause confusion for end users between the metric names so I opted to try option 3 and moved these labels into the probe_ssl_last_chain_info metric.
I also expanded the subject and issuer labels to return all their respective information, not just the CN of both.
Example

$ curl -s 'http://172.17.0.2:9115/probe?target=twitter.com' | grep probe_ssl_last_chain_info
# HELP probe_ssl_last_chain_info Contains SSL leaf certificate information
# TYPE probe_ssl_last_chain_info gauge
probe_ssl_last_chain_info{fingerprint_sha256="1d0020d1d0b632e78625219e9e2fdcf3c3a0f641240c4ecc46768cd0644884e3",issuer="CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US",subject="CN=twitter.com,O=Twitter\\, Inc.,L=San Francisco,ST=California,C=US",subjectalternative="twitter.com,www.twitter.com"} 1

return strings.Join(cert.DNSNames, ",")
}

func getLastChainExpiry(state *tls.ConnectionState) time.Time {
lastChainExpiry := time.Time{}
for _, chain := range state.VerifiedChains {
Expand Down
1 change: 1 addition & 0 deletions prober/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func generateCertificateTemplate(expiry time.Time, IPAddressSAN bool) *x509.Cert
SubjectKeyId: []byte{1},
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "Example",
Organization: []string{"Example Org"},
},
NotBefore: time.Now(),
Expand Down