Skip to content

Commit

Permalink
Add certificate verification status to x509_cert input (influxdata#6143)
Browse files Browse the repository at this point in the history
  • Loading branch information
glinton authored and idohalevi committed Sep 23, 2020
1 parent e66bc88 commit 328920a
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 25 deletions.
12 changes: 7 additions & 5 deletions plugins/inputs/x509_cert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ file or network connection.
# 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
```


Expand All @@ -35,7 +32,10 @@ file or network connection.
- country
- province
- locality
- verification
- fields:
- verification_code (int)
- verification_error (string)
- expiry (int, seconds)
- age (int, seconds)
- startdate (int, seconds)
Expand All @@ -45,6 +45,8 @@ file or network connection.
### 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
x509_cert,common_name=ubuntu,source=/etc/ssl/certs/ssl-cert-snakeoil.pem,verification=valid age=7693222i,enddate=1871249033i,expiry=307666777i,startdate=1555889033i,verification_code=0i 1563582256000000000
x509_cert,common_name=www.example.org,country=US,locality=Los\ Angeles,organization=Internet\ Corporation\ for\ Assigned\ Names\ and\ Numbers,organizational_unit=Technology,province=California,source=https://example.org:443,verification=invalid age=20219055i,enddate=1606910400i,expiry=43328144i,startdate=1543363200i,verification_code=1i,verification_error="x509: certificate signed by unknown authority" 1563582256000000000
x509_cert,common_name=DigiCert\ SHA2\ Secure\ Server\ CA,country=US,organization=DigiCert\ Inc,source=https://example.org:443,verification=valid age=200838255i,enddate=1678276800i,expiry=114694544i,startdate=1362744000i,verification_code=0i 1563582256000000000
x509_cert,common_name=DigiCert\ Global\ Root\ CA,country=US,organization=DigiCert\ Inc,organizational_unit=www.digicert.com,source=https://example.org:443,verification=valid age=400465455i,enddate=1952035200i,expiry=388452944i,startdate=1163116800i,verification_code=0i 1563582256000000000
```
3 changes: 1 addition & 2 deletions plugins/inputs/x509_cert/dev/telegraf.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[[inputs.x509_cert]]
sources = ["https://www.influxdata.com:443"]
sources = ["https://expired.badssl.com:443", "https://wrong.host.badssl.com:443"]

[[outputs.file]]
files = ["stdout"]
71 changes: 53 additions & 18 deletions plugins/inputs/x509_cert/x509_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,14 @@ const sampleConfig = `
# 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"`
tlsCfg *tls.Config
_tls.ClientConfig
}

Expand All @@ -53,39 +51,36 @@ func (c *X509Cert) SampleConfig() string {
return sampleConfig
}

func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Certificate, error) {
func (c *X509Cert) locationToURL(location string) (*url.URL, 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())
return nil, fmt.Errorf("failed to parse cert location - %s", err.Error())
}

return u, nil
}

func (c *X509Cert) getCert(u *url.URL, timeout time.Duration) ([]*x509.Certificate, 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()

if tlsCfg == nil {
tlsCfg = &tls.Config{}
}
tlsCfg.ServerName = u.Hostname()
conn := tls.Client(ipConn, tlsCfg)
c.tlsCfg.ServerName = u.Hostname()
c.tlsCfg.InsecureSkipVerify = true
conn := tls.Client(ipConn, c.tlsCfg)
defer conn.Close()

hsErr := conn.Handshake()
Expand Down Expand Up @@ -114,7 +109,7 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert

return []*x509.Certificate{cert}, nil
default:
return nil, fmt.Errorf("unsuported scheme '%s' in location %s\n", u.Scheme, location)
return nil, fmt.Errorf("unsuported scheme '%s' in location %s", u.Scheme, u.String())
}
}

Expand Down Expand Up @@ -164,22 +159,62 @@ 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)
u, err := c.locationToURL(location)
if err != nil {
acc.AddError(err)
return nil
}

certs, err := c.getCert(u, c.Timeout.Duration*time.Second)
if err != nil {
acc.AddError(fmt.Errorf("cannot get SSL cert '%s': %s", location, err.Error()))
}

for _, cert := range certs {
for i, cert := range certs {
fields := getFields(cert, now)
tags := getTags(cert.Subject, location)

// The first certificate is the leaf/end-entity certificate which needs DNS
// name validation against the URL hostname.
opts := x509.VerifyOptions{}
if i == 0 {
opts.DNSName = u.Hostname()
}
if c.tlsCfg.RootCAs != nil {
opts.Roots = c.tlsCfg.RootCAs
}

_, err = cert.Verify(opts)
if err == nil {
tags["verification"] = "valid"
fields["verification_code"] = 0
} else {
tags["verification"] = "invalid"
fields["verification_code"] = 1
fields["verification_error"] = err.Error()
}

acc.AddFields("x509_cert", fields, tags)
}
}

return nil
}

func (c *X509Cert) Init() error {
tlsCfg, err := c.ClientConfig.TLSConfig()
if err != nil {
return err
}
if tlsCfg == nil {
tlsCfg = &tls.Config{}
}

c.tlsCfg = tlsCfg

return nil
}

func init() {
inputs.Add("x509_cert", func() telegraf.Input {
return &X509Cert{
Expand Down
5 changes: 5 additions & 0 deletions plugins/inputs/x509_cert/x509_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func TestGatherRemote(t *testing.T) {
Sources: []string{test.server},
Timeout: internal.Duration{Duration: test.timeout},
}
sc.Init()

sc.InsecureSkipVerify = true
testErr := false
Expand Down Expand Up @@ -169,6 +170,7 @@ func TestGatherLocal(t *testing.T) {
sc := X509Cert{
Sources: []string{f.Name()},
}
sc.Init()

error := false

Expand Down Expand Up @@ -218,6 +220,7 @@ func TestGatherChain(t *testing.T) {
sc := X509Cert{
Sources: []string{f.Name()},
}
sc.Init()

error := false

Expand All @@ -237,6 +240,7 @@ func TestGatherChain(t *testing.T) {

func TestStrings(t *testing.T) {
sc := X509Cert{}
sc.Init()

tests := []struct {
name string
Expand Down Expand Up @@ -265,6 +269,7 @@ func TestGatherCert(t *testing.T) {
m := &X509Cert{
Sources: []string{"https://www.influxdata.com:443"},
}
m.Init()

var acc testutil.Accumulator
err := m.Gather(&acc)
Expand Down

0 comments on commit 328920a

Please sign in to comment.