Adding x509_cert input plugin (#3768)
This commit is contained in:
163
plugins/inputs/x509_cert/x509_cert.go
Normal file
163
plugins/inputs/x509_cert/x509_cert.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Package x509_cert reports metrics from an SSL certificate.
|
||||
package x509_cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
_tls "github.com/influxdata/telegraf/internal/tls"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
const sampleConfig = `
|
||||
## List certificate sources
|
||||
sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443"]
|
||||
|
||||
## Timeout for SSL connection
|
||||
# timeout = 5s
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
`
|
||||
const description = "Reads metrics from a SSL certificate"
|
||||
|
||||
// X509Cert holds the configuration of the plugin.
|
||||
type X509Cert struct {
|
||||
Sources []string `toml:"sources"`
|
||||
Timeout internal.Duration `toml:"timeout"`
|
||||
_tls.ClientConfig
|
||||
}
|
||||
|
||||
// Description returns description of the plugin.
|
||||
func (c *X509Cert) Description() string {
|
||||
return description
|
||||
}
|
||||
|
||||
// SampleConfig returns configuration sample for the plugin.
|
||||
func (c *X509Cert) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Certificate, error) {
|
||||
if strings.HasPrefix(location, "/") {
|
||||
location = "file://" + location
|
||||
}
|
||||
|
||||
u, err := url.Parse(location)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse cert location - %s\n", err.Error())
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
u.Scheme = "tcp"
|
||||
fallthrough
|
||||
case "udp", "udp4", "udp6":
|
||||
fallthrough
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
tlsCfg, err := c.ClientConfig.TLSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipConn, err := net.DialTimeout(u.Scheme, u.Host, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ipConn.Close()
|
||||
|
||||
conn := tls.Client(ipConn, tlsCfg)
|
||||
defer conn.Close()
|
||||
|
||||
hsErr := conn.Handshake()
|
||||
if hsErr != nil {
|
||||
return nil, hsErr
|
||||
}
|
||||
|
||||
certs := conn.ConnectionState().PeerCertificates
|
||||
|
||||
return certs, nil
|
||||
case "file":
|
||||
content, err := ioutil.ReadFile(u.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(content)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate PEM")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*x509.Certificate{cert}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsuported scheme '%s' in location %s\n", u.Scheme, location)
|
||||
}
|
||||
}
|
||||
|
||||
func getFields(cert *x509.Certificate, now time.Time) map[string]interface{} {
|
||||
age := int(now.Sub(cert.NotBefore).Seconds())
|
||||
expiry := int(cert.NotAfter.Sub(now).Seconds())
|
||||
startdate := cert.NotBefore.Unix()
|
||||
enddate := cert.NotAfter.Unix()
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"age": age,
|
||||
"expiry": expiry,
|
||||
"startdate": startdate,
|
||||
"enddate": enddate,
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// Gather adds metrics into the accumulator.
|
||||
func (c *X509Cert) Gather(acc telegraf.Accumulator) error {
|
||||
now := time.Now()
|
||||
|
||||
for _, location := range c.Sources {
|
||||
certs, err := c.getCert(location, c.Timeout.Duration*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get SSL cert '%s': %s", location, err.Error())
|
||||
}
|
||||
|
||||
tags := map[string]string{
|
||||
"source": location,
|
||||
}
|
||||
|
||||
for _, cert := range certs {
|
||||
fields := getFields(cert, now)
|
||||
|
||||
acc.AddFields("x509_cert", fields, tags)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("x509_cert", func() telegraf.Input {
|
||||
return &X509Cert{
|
||||
Sources: []string{},
|
||||
Timeout: internal.Duration{Duration: 5},
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user