Wildcard support for x509_cert files (#6952)

* Accept standard unix glob matching rules

* comply with indentation

* update readme

* move globing expand and url parsing into Init()

* chore: rebase branch on upstream master

* rename refreshFilePaths to expandFilePaths
* expandFilePaths handles '/path/to/*.pem' and 'files:///path/to/*.pem'
* update sample config

* fix: recompile files globing pattern at every gather tic

 * add var globFilePathsToUrls to stack files path
 * add var globpaths to stack compiled globpath
 * rework sourcesToURLs to compile files path and stack them
 * rename expandFilePaths to expandFilePathsToUrls
 * rework expandFilePathsToUrls to only match compiled globpath
 * rework the `Gather` ticker to match globpath at each call

* fix: comply with requested changes

 * add specifics regarding relative paths in sample config
 * add logger and use it in expandFilePathsToUrls()
 * precompile glob for `files://`, `/` and `://`

* fix: update README to match last changes

* fix: comply with last requested changes

 * rename expandFilePathsToUrls() to collectCertURLs()
 * collectCertURLs() now returns []*url.URL to avoid extra field
globFilePathsToUrls in structure
 * update the Gather() ticker accordingly

* fix(windows): do not try to compile glopath for windows path as it's not supposed to be supported by the OS

* fix(ci): apply go fmt

* fix(ci): empty-lines/import-shadowing

Co-authored-by: Anthony LE BERRE <aleberre@vente-privee.com>
This commit is contained in:
jaroug 2021-03-23 22:31:15 +01:00 committed by GitHub
parent f267f342ae
commit b2b361356e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 25 deletions

View File

@ -9,8 +9,10 @@ file or network connection.
```toml ```toml
# Reads metrics from a SSL certificate # Reads metrics from a SSL certificate
[[inputs.x509_cert]] [[inputs.x509_cert]]
## List certificate sources ## List certificate sources, support wildcard expands for files
sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "https://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 for SSL connection
# timeout = "5s" # timeout = "5s"

View File

@ -16,13 +16,16 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/globpath"
_tls "github.com/influxdata/telegraf/plugins/common/tls" _tls "github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
) )
const sampleConfig = ` const sampleConfig = `
## List certificate sources ## 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 for SSL connection
# timeout = "5s" # timeout = "5s"
@ -45,6 +48,9 @@ type X509Cert struct {
ServerName string `toml:"server_name"` ServerName string `toml:"server_name"`
tlsCfg *tls.Config tlsCfg *tls.Config
_tls.ClientConfig _tls.ClientConfig
locations []*url.URL
globpaths []*globpath.GlobPath
Log telegraf.Logger
} }
// Description returns description of the plugin. // Description returns description of the plugin.
@ -57,20 +63,31 @@ func (c *X509Cert) SampleConfig() string {
return sampleConfig return sampleConfig
} }
func (c *X509Cert) locationToURL(location string) (*url.URL, error) { func (c *X509Cert) sourcesToURLs() error {
if strings.HasPrefix(location, "/") { for _, source := range c.Sources {
location = "file://" + location if strings.HasPrefix(source, "file://") ||
} strings.HasPrefix(source, "/") ||
if strings.Index(location, ":\\") == 1 { strings.Index(source, ":\\") != 1 {
location = "file://" + filepath.ToSlash(location) 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())
}
c.locations = append(c.locations, u)
}
} }
u, err := url.Parse(location) return nil
if err != nil {
return nil, fmt.Errorf("failed to parse cert location - %s", err.Error())
}
return u, nil
} }
func (c *X509Cert) serverName(u *url.URL) (string, error) { 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 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. // Gather adds metrics into the accumulator.
func (c *X509Cert) Gather(acc telegraf.Accumulator) error { func (c *X509Cert) Gather(acc telegraf.Accumulator) error {
now := time.Now() 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 { for _, location := range append(c.locations, collectedUrls...) {
u, err := c.locationToURL(location) certs, err := c.getCert(location, c.Timeout.Duration*time.Second)
if err != nil {
acc.AddError(err)
return nil
}
certs, err := c.getCert(u, c.Timeout.Duration)
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 i, cert := range certs { for i, cert := range certs {
fields := getFields(cert, now) 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 // The first certificate is the leaf/end-entity certificate which needs DNS
// name validation against the URL hostname. // name validation against the URL hostname.
@ -231,7 +268,7 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error {
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
} }
if i == 0 { if i == 0 {
opts.DNSName, err = c.serverName(u) opts.DNSName, err = c.serverName(location)
if err != nil { if err != nil {
return err return err
} }
@ -263,6 +300,11 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error {
} }
func (c *X509Cert) Init() error { func (c *X509Cert) Init() error {
err := c.sourcesToURLs()
if err != nil {
return err
}
tlsCfg, err := c.ClientConfig.TLSConfig() tlsCfg, err := c.ClientConfig.TLSConfig()
if err != nil { if err != nil {
return err return err