From b2b361356e6248dc099842784f8fe4bd9ae007bf Mon Sep 17 00:00:00 2001 From: jaroug Date: Tue, 23 Mar 2021 22:31:15 +0100 Subject: [PATCH] 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 --- plugins/inputs/x509_cert/README.md | 6 +- plugins/inputs/x509_cert/x509_cert.go | 86 ++++++++++++++++++++------- 2 files changed, 68 insertions(+), 24 deletions(-) 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