Skip to content

Commit

Permalink
Adding x509_cert input plugin (influxdata#3768)
Browse files Browse the repository at this point in the history
  • Loading branch information
jtyr authored and otherpirate committed Mar 15, 2019
1 parent 362ecfd commit e2aecac
Show file tree
Hide file tree
Showing 4 changed files with 414 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/webhooks"
_ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters"
_ "github.com/influxdata/telegraf/plugins/inputs/win_services"
_ "github.com/influxdata/telegraf/plugins/inputs/x509_cert"
_ "github.com/influxdata/telegraf/plugins/inputs/zfs"
_ "github.com/influxdata/telegraf/plugins/inputs/zipkin"
_ "github.com/influxdata/telegraf/plugins/inputs/zookeeper"
Expand Down
45 changes: 45 additions & 0 deletions plugins/inputs/x509_cert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# X509 Cert Input Plugin

This plugin provides information about X509 certificate accessible via local
file or network connection.


### Configuration

```toml
# Reads metrics from a SSL certificate
[[inputs.x509_cert]]
## List certificate sources
sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "https://example.org"]

## 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
```


### Metrics

- `x509_cert`
- tags:
- `source` - source of the certificate
- fields:
- `expiry` (int, seconds)
- `age` (int, seconds)
- `startdate` (int, seconds)
- `enddate` (int, seconds)


### Example output

```
x509_cert,host=myhost,source=https://example.org age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 1517845687000000000
x509_cert,host=myhost,source=/etc/ssl/certs/ssl-cert-snakeoil.pem age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000
```
163 changes: 163 additions & 0 deletions plugins/inputs/x509_cert/x509_cert.go
Original file line number Diff line number Diff line change
@@ -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},
}
})
}
Loading

0 comments on commit e2aecac

Please sign in to comment.