Add certificate verification status to x509_cert input (#6143)

This commit is contained in:
Greg 2019-07-22 17:10:40 -06:00 committed by Daniel Nelson
parent 92cabcd323
commit 3e50db904a
4 changed files with 66 additions and 25 deletions

View File

@ -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
```
@ -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)
@ -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
```

View File

@ -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"]

View File

@ -30,9 +30,6 @@ 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"
@ -40,6 +37,7 @@ const description = "Reads metrics from a SSL certificate"
type X509Cert struct {
Sources []string `toml:"sources"`
Timeout internal.Duration `toml:"timeout"`
tlsCfg *tls.Config
_tls.ClientConfig
}
@ -53,16 +51,20 @@ 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"
@ -70,22 +72,15 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert
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()
@ -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())
}
}
@ -164,15 +159,41 @@ 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)
}
}
@ -180,6 +201,20 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error {
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{

View File

@ -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
@ -169,6 +170,7 @@ func TestGatherLocal(t *testing.T) {
sc := X509Cert{
Sources: []string{f.Name()},
}
sc.Init()
error := false
@ -218,6 +220,7 @@ func TestGatherChain(t *testing.T) {
sc := X509Cert{
Sources: []string{f.Name()},
}
sc.Init()
error := false
@ -237,6 +240,7 @@ func TestGatherChain(t *testing.T) {
func TestStrings(t *testing.T) {
sc := X509Cert{}
sc.Init()
tests := []struct {
name string
@ -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)