forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding x509_cert input plugin (influxdata#3768)
- Loading branch information
1 parent
362ecfd
commit e2aecac
Showing
4 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}, | ||
} | ||
}) | ||
} |
Oops, something went wrong.