diff --git a/plugins/inputs/x509_cert/README.md b/plugins/inputs/x509_cert/README.md index 42adc39217358..f206f6c0979a5 100644 --- a/plugins/inputs/x509_cert/README.md +++ b/plugins/inputs/x509_cert/README.md @@ -9,8 +9,10 @@ file or network connection. ```toml # Reads metrics from a SSL certificate [[inputs.x509_cert]] - ## List certificate sources - sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "https://example.org:443"] + ## List certificate sources, support wildcard expands for files + ## Prefix your entry with 'file://' if you intend to use relative paths + sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443", + "/etc/mycerts/*.mydomain.org.pem", "file:///path/to/*.pem"] ## Timeout for SSL connection # timeout = "5s" diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index 6ad87a9e0fdda..92fbcb4066e61 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -16,13 +16,16 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/internal/globpath" _tls "github.com/influxdata/telegraf/plugins/common/tls" "github.com/influxdata/telegraf/plugins/inputs" ) const sampleConfig = ` ## List certificate sources - sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443"] + ## Prefix your entry with 'file://' if you intend to use relative paths + sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443", + "/etc/mycerts/*.mydomain.org.pem", "file:///path/to/*.pem"] ## Timeout for SSL connection # timeout = "5s" @@ -45,6 +48,9 @@ type X509Cert struct { ServerName string `toml:"server_name"` tlsCfg *tls.Config _tls.ClientConfig + locations []*url.URL + globpaths []*globpath.GlobPath + Log telegraf.Logger } // Description returns description of the plugin. @@ -57,20 +63,31 @@ func (c *X509Cert) SampleConfig() string { return sampleConfig } -func (c *X509Cert) locationToURL(location string) (*url.URL, error) { - if strings.HasPrefix(location, "/") { - location = "file://" + location - } - if strings.Index(location, ":\\") == 1 { - location = "file://" + filepath.ToSlash(location) - } +func (c *X509Cert) sourcesToURLs() error { + for _, source := range c.Sources { + if strings.HasPrefix(source, "file://") || + strings.HasPrefix(source, "/") || + strings.Index(source, ":\\") != 1 { + source = filepath.ToSlash(strings.TrimPrefix(source, "file://")) + g, err := globpath.Compile(source) + if err != nil { + return fmt.Errorf("could not compile glob %v: %v", source, err) + } + c.globpaths = append(c.globpaths, g) + } else { + if strings.Index(source, ":\\") == 1 { + source = "file://" + filepath.ToSlash(source) + } + u, err := url.Parse(source) + if err != nil { + return fmt.Errorf("failed to parse cert location - %s", err.Error()) + } - u, err := url.Parse(location) - if err != nil { - return nil, fmt.Errorf("failed to parse cert location - %s", err.Error()) + c.locations = append(c.locations, u) + } } - return u, nil + return nil } func (c *X509Cert) serverName(u *url.URL) (string, error) { @@ -204,25 +221,45 @@ func getTags(cert *x509.Certificate, location string) map[string]string { return tags } +func (c *X509Cert) collectCertURLs() ([]*url.URL, error) { + var urls []*url.URL + + for _, path := range c.globpaths { + files := path.Match() + if len(files) <= 0 { + c.Log.Errorf("could not find file: %v", path) + continue + } + for _, file := range files { + file = "file://" + file + u, err := url.Parse(file) + if err != nil { + return urls, fmt.Errorf("failed to parse cert location - %s", err.Error()) + } + urls = append(urls, u) + } + } + + return urls, nil +} + // Gather adds metrics into the accumulator. func (c *X509Cert) Gather(acc telegraf.Accumulator) error { now := time.Now() + collectedUrls, err := c.collectCertURLs() + if err != nil { + acc.AddError(fmt.Errorf("cannot get file: %s", err.Error())) + } - for _, location := range c.Sources { - u, err := c.locationToURL(location) - if err != nil { - acc.AddError(err) - return nil - } - - certs, err := c.getCert(u, c.Timeout.Duration) + for _, location := range append(c.locations, collectedUrls...) { + certs, err := c.getCert(location, c.Timeout.Duration*time.Second) if err != nil { acc.AddError(fmt.Errorf("cannot get SSL cert '%s': %s", location, err.Error())) } for i, cert := range certs { fields := getFields(cert, now) - tags := getTags(cert, location) + tags := getTags(cert, location.String()) // The first certificate is the leaf/end-entity certificate which needs DNS // name validation against the URL hostname. @@ -231,7 +268,7 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error { KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } if i == 0 { - opts.DNSName, err = c.serverName(u) + opts.DNSName, err = c.serverName(location) if err != nil { return err } @@ -263,6 +300,11 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error { } func (c *X509Cert) Init() error { + err := c.sourcesToURLs() + if err != nil { + return err + } + tlsCfg, err := c.ClientConfig.TLSConfig() if err != nil { return err