Add certificate verification status to x509_cert input (#6143)
This commit is contained in:
parent
92cabcd323
commit
3e50db904a
|
@ -19,9 +19,6 @@ file or network connection.
|
||||||
# tls_ca = "/etc/telegraf/ca.pem"
|
# tls_ca = "/etc/telegraf/ca.pem"
|
||||||
# tls_cert = "/etc/telegraf/cert.pem"
|
# tls_cert = "/etc/telegraf/cert.pem"
|
||||||
# tls_key = "/etc/telegraf/key.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
|
- country
|
||||||
- province
|
- province
|
||||||
- locality
|
- locality
|
||||||
|
- verification
|
||||||
- fields:
|
- fields:
|
||||||
|
- verification_code (int)
|
||||||
|
- verification_error (string)
|
||||||
- expiry (int, seconds)
|
- expiry (int, seconds)
|
||||||
- age (int, seconds)
|
- age (int, seconds)
|
||||||
- startdate (int, seconds)
|
- startdate (int, seconds)
|
||||||
|
@ -45,6 +45,8 @@ file or network connection.
|
||||||
### Example output
|
### Example output
|
||||||
|
|
||||||
```
|
```
|
||||||
x509_cert,host=myhost,source=https://example.org age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 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,host=myhost,source=/etc/ssl/certs/ssl-cert-snakeoil.pem age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000
|
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
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
[[inputs.x509_cert]]
|
[[inputs.x509_cert]]
|
||||||
sources = ["https://www.influxdata.com:443"]
|
sources = ["https://expired.badssl.com:443", "https://wrong.host.badssl.com:443"]
|
||||||
|
|
||||||
[[outputs.file]]
|
[[outputs.file]]
|
||||||
files = ["stdout"]
|
|
||||||
|
|
|
@ -30,9 +30,6 @@ const sampleConfig = `
|
||||||
# tls_ca = "/etc/telegraf/ca.pem"
|
# tls_ca = "/etc/telegraf/ca.pem"
|
||||||
# tls_cert = "/etc/telegraf/cert.pem"
|
# tls_cert = "/etc/telegraf/cert.pem"
|
||||||
# tls_key = "/etc/telegraf/key.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"
|
const description = "Reads metrics from a SSL certificate"
|
||||||
|
|
||||||
|
@ -40,6 +37,7 @@ const description = "Reads metrics from a SSL certificate"
|
||||||
type X509Cert struct {
|
type X509Cert struct {
|
||||||
Sources []string `toml:"sources"`
|
Sources []string `toml:"sources"`
|
||||||
Timeout internal.Duration `toml:"timeout"`
|
Timeout internal.Duration `toml:"timeout"`
|
||||||
|
tlsCfg *tls.Config
|
||||||
_tls.ClientConfig
|
_tls.ClientConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,16 +51,20 @@ func (c *X509Cert) SampleConfig() string {
|
||||||
return sampleConfig
|
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, "/") {
|
if strings.HasPrefix(location, "/") {
|
||||||
location = "file://" + location
|
location = "file://" + location
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(location)
|
u, err := url.Parse(location)
|
||||||
if err != nil {
|
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 {
|
switch u.Scheme {
|
||||||
case "https":
|
case "https":
|
||||||
u.Scheme = "tcp"
|
u.Scheme = "tcp"
|
||||||
|
@ -70,22 +72,15 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert
|
||||||
case "udp", "udp4", "udp6":
|
case "udp", "udp4", "udp6":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "tcp", "tcp4", "tcp6":
|
case "tcp", "tcp4", "tcp6":
|
||||||
tlsCfg, err := c.ClientConfig.TLSConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ipConn, err := net.DialTimeout(u.Scheme, u.Host, timeout)
|
ipConn, err := net.DialTimeout(u.Scheme, u.Host, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer ipConn.Close()
|
defer ipConn.Close()
|
||||||
|
|
||||||
if tlsCfg == nil {
|
c.tlsCfg.ServerName = u.Hostname()
|
||||||
tlsCfg = &tls.Config{}
|
c.tlsCfg.InsecureSkipVerify = true
|
||||||
}
|
conn := tls.Client(ipConn, c.tlsCfg)
|
||||||
tlsCfg.ServerName = u.Hostname()
|
|
||||||
conn := tls.Client(ipConn, tlsCfg)
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
hsErr := conn.Handshake()
|
hsErr := conn.Handshake()
|
||||||
|
@ -114,7 +109,7 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert
|
||||||
|
|
||||||
return []*x509.Certificate{cert}, nil
|
return []*x509.Certificate{cert}, nil
|
||||||
default:
|
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()
|
now := time.Now()
|
||||||
|
|
||||||
for _, location := range c.Sources {
|
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 {
|
if err != nil {
|
||||||
acc.AddError(fmt.Errorf("cannot get SSL cert '%s': %s", location, err.Error()))
|
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)
|
fields := getFields(cert, now)
|
||||||
tags := getTags(cert.Subject, location)
|
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)
|
acc.AddFields("x509_cert", fields, tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +201,20 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error {
|
||||||
return nil
|
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() {
|
func init() {
|
||||||
inputs.Add("x509_cert", func() telegraf.Input {
|
inputs.Add("x509_cert", func() telegraf.Input {
|
||||||
return &X509Cert{
|
return &X509Cert{
|
||||||
|
|
|
@ -110,6 +110,7 @@ func TestGatherRemote(t *testing.T) {
|
||||||
Sources: []string{test.server},
|
Sources: []string{test.server},
|
||||||
Timeout: internal.Duration{Duration: test.timeout},
|
Timeout: internal.Duration{Duration: test.timeout},
|
||||||
}
|
}
|
||||||
|
sc.Init()
|
||||||
|
|
||||||
sc.InsecureSkipVerify = true
|
sc.InsecureSkipVerify = true
|
||||||
testErr := false
|
testErr := false
|
||||||
|
@ -169,6 +170,7 @@ func TestGatherLocal(t *testing.T) {
|
||||||
sc := X509Cert{
|
sc := X509Cert{
|
||||||
Sources: []string{f.Name()},
|
Sources: []string{f.Name()},
|
||||||
}
|
}
|
||||||
|
sc.Init()
|
||||||
|
|
||||||
error := false
|
error := false
|
||||||
|
|
||||||
|
@ -218,6 +220,7 @@ func TestGatherChain(t *testing.T) {
|
||||||
sc := X509Cert{
|
sc := X509Cert{
|
||||||
Sources: []string{f.Name()},
|
Sources: []string{f.Name()},
|
||||||
}
|
}
|
||||||
|
sc.Init()
|
||||||
|
|
||||||
error := false
|
error := false
|
||||||
|
|
||||||
|
@ -237,6 +240,7 @@ func TestGatherChain(t *testing.T) {
|
||||||
|
|
||||||
func TestStrings(t *testing.T) {
|
func TestStrings(t *testing.T) {
|
||||||
sc := X509Cert{}
|
sc := X509Cert{}
|
||||||
|
sc.Init()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -265,6 +269,7 @@ func TestGatherCert(t *testing.T) {
|
||||||
m := &X509Cert{
|
m := &X509Cert{
|
||||||
Sources: []string{"https://www.influxdata.com:443"},
|
Sources: []string{"https://www.influxdata.com:443"},
|
||||||
}
|
}
|
||||||
|
m.Init()
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
var acc testutil.Accumulator
|
||||||
err := m.Gather(&acc)
|
err := m.Gather(&acc)
|
||||||
|
|
Loading…
Reference in New Issue