mirror of
https://github.com/ribbybibby/ssl_exporter.git
synced 2024-11-27 08:31:02 +02:00
Refactor prober function and metrics collection
The existing implementation consists of a collector that exports information from a tls.ConnectionState returned by the prober function. This won't necessarily integrate well with additional probers that retrieve certs from sources other than a tls handshake (from file, for instance). I've made the probing more generically expandable by removing the collector and instead registering and collecting metrics inside the prober. This makes it possible to collect the same metrics in a different way, or collect different metrics depending on the prober.
This commit is contained in:
parent
e05745b959
commit
c74c0de901
@ -1,7 +1,6 @@
|
||||
package prober
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -10,16 +9,20 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
|
||||
pconfig "github.com/prometheus/common/config"
|
||||
)
|
||||
|
||||
// ProbeHTTPS performs a https probe
|
||||
func ProbeHTTPS(target string, module config.Module, timeout time.Duration) (*tls.ConnectionState, error) {
|
||||
func ProbeHTTPS(target string, module config.Module, timeout time.Duration, registry *prometheus.Registry) error {
|
||||
tlsConfig, err := newTLSConfig("", registry, &module.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(target, "http://") {
|
||||
return nil, fmt.Errorf("Target is using http scheme: %s", target)
|
||||
return fmt.Errorf("Target is using http scheme: %s", target)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(target, "https://") {
|
||||
@ -28,12 +31,7 @@ func ProbeHTTPS(target string, module config.Module, timeout time.Duration) (*tl
|
||||
|
||||
targetURL, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig, err := pconfig.NewTLSConfig(&module.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := http.ProxyFromEnvironment
|
||||
@ -56,7 +54,7 @@ func ProbeHTTPS(target string, module config.Module, timeout time.Duration) (*tl
|
||||
// Issue a GET request to the target
|
||||
resp, err := client.Get(targetURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_, err := io.Copy(ioutil.Discard, resp.Body)
|
||||
@ -68,8 +66,8 @@ func ProbeHTTPS(target string, module config.Module, timeout time.Duration) (*tl
|
||||
|
||||
// Check if the response from the target is encrypted
|
||||
if resp.TLS == nil {
|
||||
return nil, fmt.Errorf("The response from %s is unencrypted", targetURL.String())
|
||||
return fmt.Errorf("The response from %s is unencrypted", targetURL.String())
|
||||
}
|
||||
|
||||
return resp.TLS, nil
|
||||
return nil
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
pconfig "github.com/prometheus/common/config"
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
"github.com/ribbybibby/ssl_exporter/test"
|
||||
@ -34,13 +35,12 @@ func TestProbeHTTPS(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
state, err := ProbeHTTPS(server.URL, module, 5*time.Second)
|
||||
if err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
if state == nil {
|
||||
t.Fatalf("expected state but got nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestProbeHTTPSInvalidName tests hitting the server on an address which isn't
|
||||
@ -67,7 +67,9 @@ func TestProbeHTTPSInvalidName(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if _, err := ProbeHTTPS("https://localhost:"+u.Port(), module, 5*time.Second); err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS("https://localhost:"+u.Port(), module, 5*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error, but err was nil")
|
||||
}
|
||||
}
|
||||
@ -96,7 +98,9 @@ func TestProbeHTTPSNoScheme(t *testing.T) {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if _, err := ProbeHTTPS(u.Host, module, 5*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(u.Host, module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
@ -126,7 +130,9 @@ func TestProbeHTTPSServerName(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeHTTPS("https://localhost:"+u.Port(), module, 5*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS("https://localhost:"+u.Port(), module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
@ -139,7 +145,9 @@ func TestProbeHTTPSHTTP(t *testing.T) {
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
|
||||
if _, err := ProbeHTTPS(server.URL, config.Module{}, 5*time.Second); err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, config.Module{}, 5*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error, but err was nil")
|
||||
}
|
||||
}
|
||||
@ -186,13 +194,11 @@ func TestProbeHTTPSClientAuth(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
state, err := ProbeHTTPS(server.URL, module, 5*time.Second)
|
||||
if err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
if state == nil {
|
||||
t.Fatalf("expected state but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestProbeHTTPSClientAuthWrongClientCert tests that the probe fails with a bad
|
||||
@ -241,7 +247,9 @@ func TestProbeHTTPSClientAuthWrongClientCert(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeHTTPS(server.URL, module, 5*time.Second); err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error but err is nil")
|
||||
}
|
||||
}
|
||||
@ -272,7 +280,9 @@ func TestProbeHTTPSExpired(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeHTTPS(server.URL, module, 5*time.Second); err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error but err is nil")
|
||||
}
|
||||
}
|
||||
@ -304,13 +314,11 @@ func TestProbeHTTPSExpiredInsecure(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
state, err := ProbeHTTPS(server.URL, module, 5*time.Second)
|
||||
if err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
if state == nil {
|
||||
t.Fatalf("expected state but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestProbeHTTPSProxy tests the proxy_url field in the configuration
|
||||
@ -352,19 +360,17 @@ func TestProbeHTTPSProxy(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err = ProbeHTTPS(server.URL, module, 5*time.Second)
|
||||
if err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error but err was nil")
|
||||
}
|
||||
|
||||
// Test with the proxy url, this shouldn't return an error
|
||||
module.HTTPS.ProxyURL = config.URL{URL: proxyURL}
|
||||
|
||||
state, err := ProbeHTTPS(server.URL, module, 5*time.Second)
|
||||
if err != nil {
|
||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
if state == nil {
|
||||
t.Fatalf("expected state but got nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
268
prober/metrics.go
Normal file
268
prober/metrics.go
Normal file
@ -0,0 +1,268 @@
|
||||
package prober
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "ssl"
|
||||
)
|
||||
|
||||
func collectConnectionStateMetrics(state tls.ConnectionState, registry *prometheus.Registry) error {
|
||||
if err := collectTLSVersionMetrics(state.Version, registry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := collectCertificateMetrics(state.PeerCertificates, registry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := collectVerifiedChainMetrics(state.VerifiedChains, registry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return collectOCSPMetrics(state.OCSPResponse, registry)
|
||||
}
|
||||
|
||||
func collectTLSVersionMetrics(version uint16, registry *prometheus.Registry) error {
|
||||
var (
|
||||
tlsVersion = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "tls_version_info"),
|
||||
Help: "The TLS version used",
|
||||
},
|
||||
[]string{"version"},
|
||||
)
|
||||
)
|
||||
registry.MustRegister(tlsVersion)
|
||||
|
||||
var v string
|
||||
switch version {
|
||||
case tls.VersionTLS10:
|
||||
v = "TLS 1.0"
|
||||
case tls.VersionTLS11:
|
||||
v = "TLS 1.1"
|
||||
case tls.VersionTLS12:
|
||||
v = "TLS 1.2"
|
||||
case tls.VersionTLS13:
|
||||
v = "TLS 1.3"
|
||||
default:
|
||||
v = "unknown"
|
||||
}
|
||||
|
||||
tlsVersion.WithLabelValues(v).Set(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectCertificateMetrics(certs []*x509.Certificate, registry *prometheus.Registry) error {
|
||||
var (
|
||||
notAfter = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "cert_not_after"),
|
||||
Help: "NotAfter expressed as a Unix Epoch Time",
|
||||
},
|
||||
[]string{"serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"},
|
||||
)
|
||||
notBefore = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "cert_not_before"),
|
||||
Help: "NotBefore expressed as a Unix Epoch Time",
|
||||
},
|
||||
[]string{"serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"},
|
||||
)
|
||||
)
|
||||
registry.MustRegister(notAfter, notBefore)
|
||||
|
||||
for _, cert := range uniq(certs) {
|
||||
labels := labelValues(cert)
|
||||
|
||||
if !cert.NotAfter.IsZero() {
|
||||
notAfter.WithLabelValues(labels...).Set(float64(cert.NotAfter.Unix()))
|
||||
}
|
||||
|
||||
if !cert.NotBefore.IsZero() {
|
||||
notBefore.WithLabelValues(labels...).Set(float64(cert.NotBefore.Unix()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectVerifiedChainMetrics(verifiedChains [][]*x509.Certificate, registry *prometheus.Registry) error {
|
||||
var (
|
||||
verifiedNotAfter = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "verified_cert_not_after"),
|
||||
Help: "NotAfter expressed as a Unix Epoch Time",
|
||||
},
|
||||
[]string{"chain_no", "serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"},
|
||||
)
|
||||
verifiedNotBefore = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "verified_cert_not_before"),
|
||||
Help: "NotBefore expressed as a Unix Epoch Time",
|
||||
},
|
||||
[]string{"chain_no", "serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"},
|
||||
)
|
||||
)
|
||||
registry.MustRegister(verifiedNotAfter, verifiedNotBefore)
|
||||
|
||||
sort.Slice(verifiedChains, func(i, j int) bool {
|
||||
iExpiry := time.Time{}
|
||||
for _, cert := range verifiedChains[i] {
|
||||
if (iExpiry.IsZero() || cert.NotAfter.Before(iExpiry)) && !cert.NotAfter.IsZero() {
|
||||
iExpiry = cert.NotAfter
|
||||
}
|
||||
}
|
||||
jExpiry := time.Time{}
|
||||
for _, cert := range verifiedChains[j] {
|
||||
if (jExpiry.IsZero() || cert.NotAfter.Before(jExpiry)) && !cert.NotAfter.IsZero() {
|
||||
jExpiry = cert.NotAfter
|
||||
}
|
||||
}
|
||||
|
||||
return iExpiry.After(jExpiry)
|
||||
})
|
||||
|
||||
for i, chain := range verifiedChains {
|
||||
chain = uniq(chain)
|
||||
for _, cert := range chain {
|
||||
chainNo := strconv.Itoa(i)
|
||||
labels := append([]string{chainNo}, labelValues(cert)...)
|
||||
|
||||
if !cert.NotAfter.IsZero() {
|
||||
verifiedNotAfter.WithLabelValues(labels...).Set(float64(cert.NotAfter.Unix()))
|
||||
}
|
||||
|
||||
if !cert.NotBefore.IsZero() {
|
||||
verifiedNotBefore.WithLabelValues(labels...).Set(float64(cert.NotBefore.Unix()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectOCSPMetrics(ocspResponse []byte, registry *prometheus.Registry) error {
|
||||
var (
|
||||
ocspStapled = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "ocsp_response_stapled"),
|
||||
Help: "If the connection state contains a stapled OCSP response",
|
||||
},
|
||||
)
|
||||
ocspStatus = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "ocsp_response_status"),
|
||||
Help: "The status in the OCSP response 0=Good 1=Revoked 2=Unknown",
|
||||
},
|
||||
)
|
||||
ocspProducedAt = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "ocsp_response_produced_at"),
|
||||
Help: "The producedAt value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
},
|
||||
)
|
||||
ocspThisUpdate = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "ocsp_response_this_update"),
|
||||
Help: "The thisUpdate value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
},
|
||||
)
|
||||
ocspNextUpdate = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "ocsp_response_next_update"),
|
||||
Help: "The nextUpdate value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
},
|
||||
)
|
||||
ocspRevokedAt = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "ocsp_response_revoked_at"),
|
||||
Help: "The revocationTime value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
},
|
||||
)
|
||||
)
|
||||
registry.MustRegister(
|
||||
ocspStapled,
|
||||
ocspStatus,
|
||||
ocspProducedAt,
|
||||
ocspThisUpdate,
|
||||
ocspNextUpdate,
|
||||
ocspRevokedAt,
|
||||
)
|
||||
|
||||
if len(ocspResponse) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := ocsp.ParseResponse(ocspResponse, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ocspStapled.Set(1)
|
||||
ocspStatus.Set(float64(resp.Status))
|
||||
ocspProducedAt.Set(float64(resp.ProducedAt.Unix()))
|
||||
ocspThisUpdate.Set(float64(resp.ThisUpdate.Unix()))
|
||||
ocspNextUpdate.Set(float64(resp.NextUpdate.Unix()))
|
||||
ocspRevokedAt.Set(float64(resp.RevokedAt.Unix()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func labelValues(cert *x509.Certificate) []string {
|
||||
return []string{
|
||||
cert.SerialNumber.String(),
|
||||
cert.Issuer.CommonName,
|
||||
cert.Subject.CommonName,
|
||||
dnsNames(cert),
|
||||
ipAddresses(cert),
|
||||
emailAddresses(cert),
|
||||
organizationalUnits(cert),
|
||||
}
|
||||
}
|
||||
|
||||
func dnsNames(cert *x509.Certificate) string {
|
||||
if len(cert.DNSNames) > 0 {
|
||||
return "," + strings.Join(cert.DNSNames, ",") + ","
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func emailAddresses(cert *x509.Certificate) string {
|
||||
if len(cert.EmailAddresses) > 0 {
|
||||
return "," + strings.Join(cert.EmailAddresses, ",") + ","
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func ipAddresses(cert *x509.Certificate) string {
|
||||
if len(cert.IPAddresses) > 0 {
|
||||
ips := ","
|
||||
for _, ip := range cert.IPAddresses {
|
||||
ips = ips + ip.String() + ","
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func organizationalUnits(cert *x509.Certificate) string {
|
||||
if len(cert.Subject.OrganizationalUnit) > 0 {
|
||||
return "," + strings.Join(cert.Subject.OrganizationalUnit, ",") + ","
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package prober
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
)
|
||||
|
||||
@ -17,4 +17,4 @@ var (
|
||||
)
|
||||
|
||||
// ProbeFn probes
|
||||
type ProbeFn func(target string, module config.Module, timeout time.Duration) (*tls.ConnectionState, error)
|
||||
type ProbeFn func(target string, module config.Module, timeout time.Duration, registry *prometheus.Registry) error
|
||||
|
@ -8,56 +8,41 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
|
||||
pconfig "github.com/prometheus/common/config"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
)
|
||||
|
||||
// ProbeTCP performs a tcp probe
|
||||
func ProbeTCP(target string, module config.Module, timeout time.Duration) (*tls.ConnectionState, error) {
|
||||
func ProbeTCP(target string, module config.Module, timeout time.Duration, registry *prometheus.Registry) error {
|
||||
tlsConfig, err := newTLSConfig(target, registry, &module.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{Timeout: timeout}
|
||||
|
||||
conn, err := dialer.Dial("tcp", target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, fmt.Errorf("Error setting deadline")
|
||||
return fmt.Errorf("Error setting deadline")
|
||||
}
|
||||
|
||||
if module.TCP.StartTLS != "" {
|
||||
err = startTLS(conn, module.TCP.StartTLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig, err := pconfig.NewTLSConfig(&module.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tlsConfig.ServerName == "" {
|
||||
targetAddress, _, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ServerName = targetAddress
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
defer tlsConn.Close()
|
||||
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state := tlsConn.ConnectionState()
|
||||
|
||||
return &state, nil
|
||||
return tlsConn.Handshake()
|
||||
}
|
||||
|
||||
type queryResponse struct {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
"github.com/ribbybibby/ssl_exporter/test"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
pconfig "github.com/prometheus/common/config"
|
||||
)
|
||||
|
||||
@ -30,7 +31,9 @@ func TestProbeTCP(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
@ -56,7 +59,9 @@ func TestProbeTCPInvalidName(t *testing.T) {
|
||||
|
||||
_, listenPort, _ := net.SplitHostPort(server.Listener.Addr().String())
|
||||
|
||||
if _, err := ProbeTCP("localhost:"+listenPort, module, 10*time.Second); err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP("localhost:"+listenPort, module, 10*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error but err was nil")
|
||||
}
|
||||
}
|
||||
@ -83,7 +88,9 @@ func TestProbeTCPServerName(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeTCP("localhost:"+listenPort, module, 10*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP("localhost:"+listenPort, module, 10*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
@ -114,7 +121,9 @@ func TestProbeTCPExpired(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeTCP(server.Listener.Addr().String(), module, 5*time.Second); err == nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 5*time.Second, registry); err == nil {
|
||||
t.Fatalf("expected error but err is nil")
|
||||
}
|
||||
}
|
||||
@ -146,13 +155,12 @@ func TestProbeTCPExpiredInsecure(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
state, err := ProbeTCP(server.Listener.Addr().String(), module, 5*time.Second)
|
||||
if err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 5*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
if state == nil {
|
||||
t.Fatalf("expected state but got nil")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestProbeTCPStartTLSSMTP tests STARTTLS against a mock SMTP server
|
||||
@ -176,7 +184,9 @@ func TestProbeTCPStartTLSSMTP(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
@ -202,7 +212,9 @@ func TestProbeTCPStartTLSFTP(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
@ -228,7 +240,9 @@ func TestProbeTCPStartTLSIMAP(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
|
||||
registry := prometheus.NewRegistry()
|
||||
|
||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
}
|
||||
|
54
prober/tls.go
Normal file
54
prober/tls.go
Normal file
@ -0,0 +1,54 @@
|
||||
package prober
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
pconfig "github.com/prometheus/common/config"
|
||||
)
|
||||
|
||||
// newTLSConfig sets up TLS config and instruments it with a function that
|
||||
// collects metrics for the verified chain
|
||||
func newTLSConfig(target string, registry *prometheus.Registry, pTLSConfig *pconfig.TLSConfig) (*tls.Config, error) {
|
||||
tlsConfig, err := pconfig.NewTLSConfig(pTLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tlsConfig.ServerName == "" && target != "" {
|
||||
targetAddress, _, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ServerName = targetAddress
|
||||
}
|
||||
|
||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||
return collectConnectionStateMetrics(state, registry)
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func uniq(certs []*x509.Certificate) []*x509.Certificate {
|
||||
r := []*x509.Certificate{}
|
||||
|
||||
for _, c := range certs {
|
||||
if !contains(r, c) {
|
||||
r = append(r, c)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func contains(certs []*x509.Certificate, cert *x509.Certificate) bool {
|
||||
for _, c := range certs {
|
||||
if (c.SerialNumber.String() == cert.SerialNumber.String()) && (c.Issuer.CommonName == cert.Issuer.CommonName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
388
ssl_exporter.go
388
ssl_exporter.go
@ -1,297 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/version"
|
||||
"github.com/ribbybibby/ssl_exporter/config"
|
||||
"github.com/ribbybibby/ssl_exporter/prober"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "ssl"
|
||||
)
|
||||
|
||||
var (
|
||||
tlsConnectSuccess = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "tls_connect_success"),
|
||||
"If the TLS connection was a success",
|
||||
nil, nil,
|
||||
)
|
||||
tlsVersion = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "tls_version_info"),
|
||||
"The TLS version used",
|
||||
[]string{"version"}, nil,
|
||||
)
|
||||
proberType = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "prober"),
|
||||
"The prober used by the exporter to connect to the target",
|
||||
[]string{"prober"}, nil,
|
||||
)
|
||||
notBefore = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "cert_not_before"),
|
||||
"NotBefore expressed as a Unix Epoch Time",
|
||||
[]string{"serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"}, nil,
|
||||
)
|
||||
notAfter = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "cert_not_after"),
|
||||
"NotAfter expressed as a Unix Epoch Time",
|
||||
[]string{"serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"}, nil,
|
||||
)
|
||||
verifiedNotBefore = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "verified_cert_not_before"),
|
||||
"NotBefore expressed as a Unix Epoch Time for a certificate in the list of verified chains",
|
||||
[]string{"chain_no", "serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"}, nil,
|
||||
)
|
||||
verifiedNotAfter = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "verified_cert_not_after"),
|
||||
"NotAfter expressed as a Unix Epoch Time for a certificate in the list of verified chains",
|
||||
[]string{"chain_no", "serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"}, nil,
|
||||
)
|
||||
ocspStapled = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ocsp_response_stapled"),
|
||||
"If the connection state contains a stapled OCSP response",
|
||||
nil, nil,
|
||||
)
|
||||
ocspStatus = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ocsp_response_status"),
|
||||
"The status in the OCSP response 0=Good 1=Revoked 2=Unknown",
|
||||
nil, nil,
|
||||
)
|
||||
ocspProducedAt = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ocsp_response_produced_at"),
|
||||
"The producedAt value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
nil, nil,
|
||||
)
|
||||
ocspThisUpdate = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ocsp_response_this_update"),
|
||||
"The thisUpdate value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
nil, nil,
|
||||
)
|
||||
ocspNextUpdate = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ocsp_response_next_update"),
|
||||
"The nextUpdate value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
nil, nil,
|
||||
)
|
||||
ocspRevokedAt = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "ocsp_response_revoked_at"),
|
||||
"The revocationTime value in the OCSP response, expressed as a Unix Epoch Time",
|
||||
nil, nil,
|
||||
)
|
||||
)
|
||||
|
||||
// Exporter is the exporter type...
|
||||
type Exporter struct {
|
||||
target string
|
||||
prober prober.ProbeFn
|
||||
timeout time.Duration
|
||||
module config.Module
|
||||
}
|
||||
|
||||
// Describe metrics
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- tlsConnectSuccess
|
||||
ch <- tlsVersion
|
||||
ch <- proberType
|
||||
ch <- notAfter
|
||||
ch <- notBefore
|
||||
ch <- verifiedNotAfter
|
||||
ch <- verifiedNotBefore
|
||||
ch <- ocspStapled
|
||||
ch <- ocspStatus
|
||||
ch <- ocspProducedAt
|
||||
ch <- ocspThisUpdate
|
||||
ch <- ocspNextUpdate
|
||||
ch <- ocspRevokedAt
|
||||
}
|
||||
|
||||
// Collect metrics
|
||||
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
proberType, prometheus.GaugeValue, 1, e.module.Prober,
|
||||
)
|
||||
|
||||
state, err := e.prober(e.target, e.module, e.timeout)
|
||||
if err != nil {
|
||||
log.Errorf("error=%s target=%s prober=%s timeout=%s", err, e.target, e.module.Prober, e.timeout)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
tlsConnectSuccess, prometheus.GaugeValue, 0,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the TLS version from the connection state and export it as a metric
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
tlsVersion, prometheus.GaugeValue, 1, getTLSVersion(state),
|
||||
)
|
||||
|
||||
// Retrieve certificates from the connection state
|
||||
peerCertificates := state.PeerCertificates
|
||||
if len(peerCertificates) < 1 {
|
||||
log.Errorf("error=No certificates found in connection state. target=%s prober=%s", e.target, e.module.Prober)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
tlsConnectSuccess, prometheus.GaugeValue, 0,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// If there are peer certificates in the connection state then consider
|
||||
// the tls connection a success
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
tlsConnectSuccess, prometheus.GaugeValue, 1,
|
||||
)
|
||||
|
||||
// Remove duplicate certificates from the response
|
||||
peerCertificates = uniq(peerCertificates)
|
||||
|
||||
// Loop through peer certificates and create metrics
|
||||
for _, cert := range peerCertificates {
|
||||
if !cert.NotAfter.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
notAfter,
|
||||
prometheus.GaugeValue,
|
||||
float64(cert.NotAfter.UnixNano()/1e9),
|
||||
cert.SerialNumber.String(),
|
||||
cert.Issuer.CommonName,
|
||||
cert.Subject.CommonName,
|
||||
getDNSNames(cert),
|
||||
getIPAddresses(cert),
|
||||
getEmailAddresses(cert),
|
||||
getOrganizationalUnits(cert),
|
||||
)
|
||||
}
|
||||
|
||||
if !cert.NotBefore.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
notBefore,
|
||||
prometheus.GaugeValue,
|
||||
float64(cert.NotBefore.UnixNano()/1e9),
|
||||
cert.SerialNumber.String(),
|
||||
cert.Issuer.CommonName,
|
||||
cert.Subject.CommonName,
|
||||
getDNSNames(cert),
|
||||
getIPAddresses(cert),
|
||||
getEmailAddresses(cert),
|
||||
getOrganizationalUnits(cert),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the list of verified chains from the connection state
|
||||
verifiedChains := state.VerifiedChains
|
||||
|
||||
// Sort the verified chains from the chain that is valid for longest to the chain
|
||||
// that expires the soonest
|
||||
sort.Slice(verifiedChains, func(i, j int) bool {
|
||||
iExpiry := time.Time{}
|
||||
for _, cert := range verifiedChains[i] {
|
||||
if (iExpiry.IsZero() || cert.NotAfter.Before(iExpiry)) && !cert.NotAfter.IsZero() {
|
||||
iExpiry = cert.NotAfter
|
||||
}
|
||||
}
|
||||
jExpiry := time.Time{}
|
||||
for _, cert := range verifiedChains[j] {
|
||||
if (jExpiry.IsZero() || cert.NotAfter.Before(jExpiry)) && !cert.NotAfter.IsZero() {
|
||||
jExpiry = cert.NotAfter
|
||||
}
|
||||
}
|
||||
|
||||
return iExpiry.After(jExpiry)
|
||||
})
|
||||
|
||||
// Loop through the verified chains creating metrics. Label the metrics
|
||||
// with the index of the chain.
|
||||
for i, chain := range verifiedChains {
|
||||
chain = uniq(chain)
|
||||
for _, cert := range chain {
|
||||
chainNo := strconv.Itoa(i)
|
||||
|
||||
if !cert.NotAfter.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
verifiedNotAfter,
|
||||
prometheus.GaugeValue,
|
||||
float64(cert.NotAfter.UnixNano()/1e9),
|
||||
chainNo,
|
||||
cert.SerialNumber.String(),
|
||||
cert.Issuer.CommonName,
|
||||
cert.Subject.CommonName,
|
||||
getDNSNames(cert),
|
||||
getIPAddresses(cert),
|
||||
getEmailAddresses(cert),
|
||||
getOrganizationalUnits(cert),
|
||||
)
|
||||
}
|
||||
|
||||
if !cert.NotBefore.IsZero() {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
verifiedNotBefore,
|
||||
prometheus.GaugeValue,
|
||||
float64(cert.NotBefore.UnixNano()/1e9),
|
||||
chainNo,
|
||||
cert.SerialNumber.String(),
|
||||
cert.Issuer.CommonName,
|
||||
cert.Subject.CommonName,
|
||||
getDNSNames(cert),
|
||||
getIPAddresses(cert),
|
||||
getEmailAddresses(cert),
|
||||
getOrganizationalUnits(cert),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := collectOCSPMetrics(ch, state); err != nil {
|
||||
log.Errorf("error=%s target=%s prober=%s timeout=%s", err, e.target, e.module.Prober, e.timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func collectOCSPMetrics(ch chan<- prometheus.Metric, state *tls.ConnectionState) error {
|
||||
if len(state.OCSPResponse) > 0 {
|
||||
resp, err := ocsp.ParseResponse(state.OCSPResponse, nil)
|
||||
if err != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspStapled, prometheus.GaugeValue, 0,
|
||||
)
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspStapled, prometheus.GaugeValue, 1,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspStatus, prometheus.GaugeValue, float64(resp.Status),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspProducedAt, prometheus.GaugeValue, float64(resp.ProducedAt.Unix()),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspThisUpdate, prometheus.GaugeValue, float64(resp.ThisUpdate.Unix()),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspNextUpdate, prometheus.GaugeValue, float64(resp.NextUpdate.Unix()),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspRevokedAt, prometheus.GaugeValue, float64(resp.RevokedAt.Unix()),
|
||||
)
|
||||
} else {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
ocspStapled, prometheus.GaugeValue, 0,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func probeHandler(w http.ResponseWriter, r *http.Request, conf *config.Config) {
|
||||
moduleName := r.URL.Query().Get("module")
|
||||
if moduleName == "" {
|
||||
@ -331,99 +58,46 @@ func probeHandler(w http.ResponseWriter, r *http.Request, conf *config.Config) {
|
||||
return
|
||||
}
|
||||
|
||||
prober, ok := prober.Probers[module.Prober]
|
||||
probeFunc, ok := prober.Probers[module.Prober]
|
||||
if !ok {
|
||||
http.Error(w, fmt.Sprintf("Unknown prober %q", module.Prober), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
exporter := &Exporter{
|
||||
target: target,
|
||||
prober: prober,
|
||||
timeout: timeout,
|
||||
module: module,
|
||||
}
|
||||
var (
|
||||
tlsConnectSuccess = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "tls_connect_success"),
|
||||
Help: "If the TLS connection was a success",
|
||||
},
|
||||
)
|
||||
proberType = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: prometheus.BuildFQName(namespace, "", "prober"),
|
||||
Help: "The prober used by the exporter to connect to the target",
|
||||
},
|
||||
[]string{"prober"},
|
||||
)
|
||||
)
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(exporter)
|
||||
registry.MustRegister(tlsConnectSuccess, proberType)
|
||||
proberType.WithLabelValues(module.Prober).Set(1)
|
||||
|
||||
err := probeFunc(target, module, timeout, registry)
|
||||
if err != nil {
|
||||
log.Errorf("error=%s target=%s prober=%s timeout=%s", err, target, module.Prober, timeout)
|
||||
tlsConnectSuccess.Set(0)
|
||||
|
||||
} else {
|
||||
tlsConnectSuccess.Set(1)
|
||||
}
|
||||
|
||||
// Serve
|
||||
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func uniq(certs []*x509.Certificate) []*x509.Certificate {
|
||||
r := []*x509.Certificate{}
|
||||
|
||||
for _, c := range certs {
|
||||
if !contains(r, c) {
|
||||
r = append(r, c)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func contains(certs []*x509.Certificate, cert *x509.Certificate) bool {
|
||||
for _, c := range certs {
|
||||
if (c.SerialNumber.String() == cert.SerialNumber.String()) && (c.Issuer.CommonName == cert.Issuer.CommonName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
func getDNSNames(cert *x509.Certificate) string {
|
||||
if len(cert.DNSNames) > 0 {
|
||||
return "," + strings.Join(cert.DNSNames, ",") + ","
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getEmailAddresses(cert *x509.Certificate) string {
|
||||
if len(cert.EmailAddresses) > 0 {
|
||||
return "," + strings.Join(cert.EmailAddresses, ",") + ","
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getIPAddresses(cert *x509.Certificate) string {
|
||||
if len(cert.IPAddresses) > 0 {
|
||||
ips := ","
|
||||
for _, ip := range cert.IPAddresses {
|
||||
ips = ips + ip.String() + ","
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getOrganizationalUnits(cert *x509.Certificate) string {
|
||||
if len(cert.Subject.OrganizationalUnit) > 0 {
|
||||
return "," + strings.Join(cert.Subject.OrganizationalUnit, ",") + ","
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(version.NewCollector(namespace + "_exporter"))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user