From 330106873fec80e61139de74bd29b9c86e2dc864 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Mon, 29 Jan 2018 10:39:40 +0200 Subject: [PATCH 01/12] add inputs.ssl plugin --- plugins/inputs/all/all.go | 1 + plugins/inputs/ssl/README.md | 0 plugins/inputs/ssl/ssl.go | 109 +++++++++++++++++++++++++++++++++ plugins/inputs/ssl/ssl_test.go | 1 + 4 files changed, 111 insertions(+) create mode 100644 plugins/inputs/ssl/README.md create mode 100644 plugins/inputs/ssl/ssl.go create mode 100644 plugins/inputs/ssl/ssl_test.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index fa78b3ff01f60..12cd49d2c13ea 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -106,4 +106,5 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zipkin" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" + _ "github.com/influxdata/telegraf/plugins/inputs/ssl" ) diff --git a/plugins/inputs/ssl/README.md b/plugins/inputs/ssl/README.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go new file mode 100644 index 0000000000000..c0939cff5f533 --- /dev/null +++ b/plugins/inputs/ssl/ssl.go @@ -0,0 +1,109 @@ +package ssl + +import ( + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + "crypto/x509" + "time" + "net" + "crypto/tls" + "github.com/pkg/errors" + "strconv" +) + +type Ssl struct { + Servers []Server +} + +type Server struct { + Domain string + Port int + Timeout int +} + +var sampleConfig = ` + [[inputs.ssl.servers]] + domain = "google.com" + port = 443 + timeout = 5 + [[inputs.ssl.servers]] + domain = "github.com" + port = 443 + timeout = 5 +` + +func (s *Ssl) SampleConfig() string { + return sampleConfig +} + +func (s *Ssl) Description() string { + return "Check expiration date and domains of ssl certificate" +} + +func (s *Ssl) Gather(acc telegraf.Accumulator) error { + for _, server := range s.Servers { + certs, err := getServerCertsChain(server.Domain, server.Port, server.Timeout) + if err != nil { + acc.AddError(err) + } + cert := certs[0] + timeNow := time.Now() + timeToExp := int64(0) + + if cert.NotAfter.UnixNano() < timeNow.UnixNano() { + acc.AddError(errors.New("cert has expired")) + } else { + timeToExp = int64(cert.NotAfter.Sub(timeNow)) + } + if !isStringInSlice(server.Domain, cert.DNSNames) { + acc.AddError(errors.New("cert and domain mismatch")) + } + fields := make(map[string]interface{}) + tags := make(map[string]string) + + fields["time_to_expiration"] = timeToExp + + tags["domain"] = server.Domain + tags["port"] = strconv.FormatInt(int64(server.Port), 10) + + acc.AddFields("ssl", fields, tags) + } + return nil +} + +func getServerCertsChain(d string, p int, t int) ([]*x509.Certificate, error) { + h := d + ":" + strconv.FormatInt(int64(p), 10) + ipConn, err := net.DialTimeout("tcp", h, time.Duration(t) * time.Second) + if err != nil { + return nil, err + } + defer ipConn.Close() + + tlsConn := tls.Client(ipConn, &tls.Config{ServerName: d, InsecureSkipVerify: true}) + defer tlsConn.Close() + + err = tlsConn.Handshake() + if err != nil { + return nil, err + } + certs := tlsConn.ConnectionState().PeerCertificates + if certs == nil || len(certs) < 1 { + return nil, errors.New("cert receive error") + } + return certs, nil +} + +func isStringInSlice(n string, s []string) bool { + for _, d := range s { + if n == d { + return true + } + } + return false +} + +func init() { + inputs.Add("ssl", func() telegraf.Input { + return &Ssl{} + }) +} diff --git a/plugins/inputs/ssl/ssl_test.go b/plugins/inputs/ssl/ssl_test.go new file mode 100644 index 0000000000000..912c43fad3334 --- /dev/null +++ b/plugins/inputs/ssl/ssl_test.go @@ -0,0 +1 @@ +package ssl From e8b7b804ab19c167fac4f123cf438af2e2d3d7a3 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Mon, 5 Feb 2018 21:36:18 +0200 Subject: [PATCH 02/12] add readme. update config --- plugins/inputs/ssl/README.md | 52 ++++++++++++++++++++++++++++++++++++ plugins/inputs/ssl/ssl.go | 2 ++ 2 files changed, 54 insertions(+) diff --git a/plugins/inputs/ssl/README.md b/plugins/inputs/ssl/README.md index e69de29bb2d1d..34254b26ba7a2 100644 --- a/plugins/inputs/ssl/README.md +++ b/plugins/inputs/ssl/README.md @@ -0,0 +1,52 @@ +# Telegraf Plugin: SSL + +### Configuration: + +``` +# Check expiration date and domains of ssl certificate +[[inputs.ssl]] + ## Server to check + [[inputs.ssl.servers]] + domain = "google.com" + port = 443 + timeout = 5 + ## Server to check + [[inputs.ssl.servers]] + domain = "github.com" + port = 443 + timeout = 5 +``` + +### Tags: + +- domain +- port + +### Fields: + +- time_to_expiration(int) + +### Example Output: + +If ssl certificate is valid: + +``` +* Plugin: inputs.ssl, Collection 1 +> ssl,domain=example.com,port=443,host=host time_to_expiration=5620833395015000i 1517213967000000000 +``` + +If ssl certificate and domain mismatch: + +``` +* Plugin: inputs.ssl, Collection 1 +2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: cert and domain mismatch +> ssl,domain=example.com,port=443,host=host time_to_expiration=5620766895580000i 1517214033000000000 +``` + +If ssl certificate has expired: + +``` +* Plugin: inputs.ssl, Collection 1 +2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: cert has expired +> ssl,domain=example.com,port=443,host=host time_to_expiration=0i 1517214033000000000 +``` diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index c0939cff5f533..035b0e3835b0d 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -22,10 +22,12 @@ type Server struct { } var sampleConfig = ` + ## Server to check [[inputs.ssl.servers]] domain = "google.com" port = 443 timeout = 5 + ## Server to check [[inputs.ssl.servers]] domain = "github.com" port = 443 From 2a9aba7de93a5465271ffc42cbced9593acad559 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Mon, 12 Feb 2018 22:29:24 +0200 Subject: [PATCH 03/12] fix --- plugins/inputs/ssl/README.md | 6 ++--- plugins/inputs/ssl/ssl.go | 43 ++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/plugins/inputs/ssl/README.md b/plugins/inputs/ssl/README.md index 34254b26ba7a2..121d40774af9e 100644 --- a/plugins/inputs/ssl/README.md +++ b/plugins/inputs/ssl/README.md @@ -39,14 +39,14 @@ If ssl certificate and domain mismatch: ``` * Plugin: inputs.ssl, Collection 1 -2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: cert and domain mismatch -> ssl,domain=example.com,port=443,host=host time_to_expiration=5620766895580000i 1517214033000000000 +2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: [example.com:443] cert and domain mismatch +> ssl,domain=example.com,port=443,host=host time_to_expiration=0i 1517214033000000000 ``` If ssl certificate has expired: ``` * Plugin: inputs.ssl, Collection 1 -2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: cert has expired +2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: [example.com:443] cert has expired > ssl,domain=example.com,port=443,host=host time_to_expiration=0i 1517214033000000000 ``` diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index 035b0e3835b0d..e588c9c8be296 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -44,27 +44,28 @@ func (s *Ssl) Description() string { func (s *Ssl) Gather(acc telegraf.Accumulator) error { for _, server := range s.Servers { - certs, err := getServerCertsChain(server.Domain, server.Port, server.Timeout) - if err != nil { - acc.AddError(err) - } - cert := certs[0] + h := getServerAddress(server.Domain, server.Port) timeNow := time.Now() timeToExp := int64(0) - - if cert.NotAfter.UnixNano() < timeNow.UnixNano() { - acc.AddError(errors.New("cert has expired")) - } else { - timeToExp = int64(cert.NotAfter.Sub(timeNow)) - } - if !isStringInSlice(server.Domain, cert.DNSNames) { - acc.AddError(errors.New("cert and domain mismatch")) - } fields := make(map[string]interface{}) tags := make(map[string]string) + certs, err := getServerCertsChain(server.Domain, server.Port, server.Timeout) + if err != nil { + acc.AddError(err) + } else { + cert := certs[0] + if cert.NotAfter.UnixNano() < timeNow.UnixNano() { + acc.AddError(errors.New("[" + h + "] cert has expired")) + } else { + timeToExp = int64(cert.NotAfter.Sub(timeNow)) + } + if !isStringInSlice(server.Domain, cert.DNSNames) { + acc.AddError(errors.New("[" + h + "] cert and domain mismatch")) + timeToExp = int64(0) + } + } fields["time_to_expiration"] = timeToExp - tags["domain"] = server.Domain tags["port"] = strconv.FormatInt(int64(server.Port), 10) @@ -74,10 +75,10 @@ func (s *Ssl) Gather(acc telegraf.Accumulator) error { } func getServerCertsChain(d string, p int, t int) ([]*x509.Certificate, error) { - h := d + ":" + strconv.FormatInt(int64(p), 10) + h := getServerAddress(d, p) ipConn, err := net.DialTimeout("tcp", h, time.Duration(t) * time.Second) if err != nil { - return nil, err + return nil, errors.New("[" + h + "] " + err.Error()) } defer ipConn.Close() @@ -86,15 +87,19 @@ func getServerCertsChain(d string, p int, t int) ([]*x509.Certificate, error) { err = tlsConn.Handshake() if err != nil { - return nil, err + return nil, errors.New("[" + h + "] " + err.Error()) } certs := tlsConn.ConnectionState().PeerCertificates if certs == nil || len(certs) < 1 { - return nil, errors.New("cert receive error") + return nil, errors.New("[" + h + "] cert receive error") } return certs, nil } +func getServerAddress(d string, p int) string { + return d + ":" + strconv.FormatInt(int64(p), 10) +} + func isStringInSlice(n string, s []string) bool { for _, d := range s { if n == d { From 28ffbd5cef3a5086085932116bfd1fe598b10d39 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Mon, 19 Feb 2018 18:47:45 +0200 Subject: [PATCH 04/12] change time to expiration value fron nanosec to sec --- plugins/inputs/ssl/ssl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index e588c9c8be296..a8b97402ad9ff 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -58,7 +58,7 @@ func (s *Ssl) Gather(acc telegraf.Accumulator) error { if cert.NotAfter.UnixNano() < timeNow.UnixNano() { acc.AddError(errors.New("[" + h + "] cert has expired")) } else { - timeToExp = int64(cert.NotAfter.Sub(timeNow)) + timeToExp = int64(cert.NotAfter.Sub(timeNow) / time.Second) } if !isStringInSlice(server.Domain, cert.DNSNames) { acc.AddError(errors.New("[" + h + "] cert and domain mismatch")) From 33665fe875a169e1797410a9f5853b9759f9a5ae Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Mon, 19 Feb 2018 23:14:15 +0200 Subject: [PATCH 05/12] add wildcard support --- plugins/inputs/ssl/ssl.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index a8b97402ad9ff..48df9de10cd36 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -60,7 +60,7 @@ func (s *Ssl) Gather(acc telegraf.Accumulator) error { } else { timeToExp = int64(cert.NotAfter.Sub(timeNow) / time.Second) } - if !isStringInSlice(server.Domain, cert.DNSNames) { + if !isDomainInCertDnsNames(server.Domain, cert.DNSNames) { acc.AddError(errors.New("[" + h + "] cert and domain mismatch")) timeToExp = int64(0) } @@ -100,11 +100,20 @@ func getServerAddress(d string, p int) string { return d + ":" + strconv.FormatInt(int64(p), 10) } -func isStringInSlice(n string, s []string) bool { - for _, d := range s { - if n == d { +func isDomainInCertDnsNames(domain string, certDnsNames []string) bool { + for _, d := range certDnsNames { + if domain == d { return true } + if d[:1] == "*" && len(domain) >= len(d[2:]) { + d = d[2:] + if domain == d { + return true + } + if domain[len(domain)-len(d)-1:] == "."+d { + return true + } + } } return false } From 43ff3e1a82286d7e78d565e5c9e3279fa1d6f819 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Tue, 20 Feb 2018 09:30:16 +0200 Subject: [PATCH 06/12] implode domain and port in config --- plugins/inputs/ssl/ssl.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index 48df9de10cd36..1d7ddf426fe89 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -8,7 +8,7 @@ import ( "net" "crypto/tls" "github.com/pkg/errors" - "strconv" + "strings" ) type Ssl struct { @@ -16,21 +16,18 @@ type Ssl struct { } type Server struct { - Domain string - Port int + Host string Timeout int } var sampleConfig = ` ## Server to check [[inputs.ssl.servers]] - domain = "google.com" - port = 443 + host = "google.com:443" timeout = 5 ## Server to check [[inputs.ssl.servers]] - domain = "github.com" - port = 443 + host = "github.com" timeout = 5 ` @@ -44,12 +41,17 @@ func (s *Ssl) Description() string { func (s *Ssl) Gather(acc telegraf.Accumulator) error { for _, server := range s.Servers { - h := getServerAddress(server.Domain, server.Port) + slice := strings.Split(server.Host, ":") + domain, port := slice[0], "443" + if len(slice) > 1 { + port = slice[1] + } + h := getServerAddress(domain, port) timeNow := time.Now() timeToExp := int64(0) fields := make(map[string]interface{}) tags := make(map[string]string) - certs, err := getServerCertsChain(server.Domain, server.Port, server.Timeout) + certs, err := getServerCertsChain(domain, port, server.Timeout) if err != nil { acc.AddError(err) @@ -60,21 +62,21 @@ func (s *Ssl) Gather(acc telegraf.Accumulator) error { } else { timeToExp = int64(cert.NotAfter.Sub(timeNow) / time.Second) } - if !isDomainInCertDnsNames(server.Domain, cert.DNSNames) { + if !isDomainInCertDnsNames(domain, cert.DNSNames) { acc.AddError(errors.New("[" + h + "] cert and domain mismatch")) timeToExp = int64(0) } } fields["time_to_expiration"] = timeToExp - tags["domain"] = server.Domain - tags["port"] = strconv.FormatInt(int64(server.Port), 10) + tags["domain"] = domain + tags["port"] = port acc.AddFields("ssl", fields, tags) } return nil } -func getServerCertsChain(d string, p int, t int) ([]*x509.Certificate, error) { +func getServerCertsChain(d string, p string, t int) ([]*x509.Certificate, error) { h := getServerAddress(d, p) ipConn, err := net.DialTimeout("tcp", h, time.Duration(t) * time.Second) if err != nil { @@ -96,8 +98,8 @@ func getServerCertsChain(d string, p int, t int) ([]*x509.Certificate, error) { return certs, nil } -func getServerAddress(d string, p int) string { - return d + ":" + strconv.FormatInt(int64(p), 10) +func getServerAddress(d string, p string) string { + return d + ":" + p } func isDomainInCertDnsNames(domain string, certDnsNames []string) bool { From 1758e71c571e5a31d6a6fe03ed324fbfbe2a202b Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Tue, 20 Feb 2018 18:31:58 +0200 Subject: [PATCH 07/12] fix --- plugins/inputs/ssl/ssl.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index 1d7ddf426fe89..17e1c488bad2f 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -112,7 +112,8 @@ func isDomainInCertDnsNames(domain string, certDnsNames []string) bool { if domain == d { return true } - if domain[len(domain)-len(d)-1:] == "."+d { + start := len(domain)-len(d)-1 + if start >= 0 && domain[start:] == "."+d { return true } } From 4b5a0935e7483fbe4f54a219495f07f304ff8bd9 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Sun, 25 Feb 2018 15:08:59 +0200 Subject: [PATCH 08/12] update readme --- plugins/inputs/ssl/README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/ssl/README.md b/plugins/inputs/ssl/README.md index 121d40774af9e..170e6df0e57b9 100644 --- a/plugins/inputs/ssl/README.md +++ b/plugins/inputs/ssl/README.md @@ -7,13 +7,11 @@ [[inputs.ssl]] ## Server to check [[inputs.ssl.servers]] - domain = "google.com" - port = 443 + host = "google.com:443" timeout = 5 ## Server to check [[inputs.ssl.servers]] - domain = "github.com" - port = 443 + host = "github.com" timeout = 5 ``` @@ -32,7 +30,7 @@ If ssl certificate is valid: ``` * Plugin: inputs.ssl, Collection 1 -> ssl,domain=example.com,port=443,host=host time_to_expiration=5620833395015000i 1517213967000000000 +> ssl,domain=example.com,port=443,host=host time_to_expiration=3907728i 1517213967000000000 ``` If ssl certificate and domain mismatch: From 808576a8e2387c5940f7b399b9ea3a9eb8427db6 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Sun, 25 Feb 2018 15:09:36 +0200 Subject: [PATCH 09/12] add tests --- plugins/inputs/ssl/ssl_test.go | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/inputs/ssl/ssl_test.go b/plugins/inputs/ssl/ssl_test.go index 912c43fad3334..7177981990596 100644 --- a/plugins/inputs/ssl/ssl_test.go +++ b/plugins/inputs/ssl/ssl_test.go @@ -1 +1,36 @@ package ssl + +import ( + "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "github.com/influxdata/telegraf/testutil" +) + +func TestGathering(t *testing.T) { + if testing.Short() { + t.Skip("Skipping network-dependent test in short mode.") + } + var servers = []Server{ + { + Host: "github.com:443", + Timeout: 5, + }, + { + Host: "github.com", + Timeout: 5, + }, + } + var sslConfig = Ssl { + Servers: servers, + } + var acc testutil.Accumulator + + err := acc.GatherError(sslConfig.Gather) + assert.NoError(t, err) + metric, ok := acc.Get("ssl") + require.True(t, ok) + timeToExp, _ := metric.Fields["time_to_expiration"].(float64) + + assert.NotEqual(t, 0, timeToExp) +} From 179d8bd54be6509deec434318162ede6875f694f Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Sun, 25 Feb 2018 15:31:21 +0200 Subject: [PATCH 10/12] add input plugin for check ssl sert --- plugins/inputs/all/all.go | 1 + plugins/inputs/ssl/README.md | 50 +++++++++++++ plugins/inputs/ssl/ssl.go | 128 +++++++++++++++++++++++++++++++++ plugins/inputs/ssl/ssl_test.go | 36 ++++++++++ 4 files changed, 215 insertions(+) create mode 100644 plugins/inputs/ssl/README.md create mode 100644 plugins/inputs/ssl/ssl.go create mode 100644 plugins/inputs/ssl/ssl_test.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index fa78b3ff01f60..12cd49d2c13ea 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -106,4 +106,5 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zipkin" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" + _ "github.com/influxdata/telegraf/plugins/inputs/ssl" ) diff --git a/plugins/inputs/ssl/README.md b/plugins/inputs/ssl/README.md new file mode 100644 index 0000000000000..170e6df0e57b9 --- /dev/null +++ b/plugins/inputs/ssl/README.md @@ -0,0 +1,50 @@ +# Telegraf Plugin: SSL + +### Configuration: + +``` +# Check expiration date and domains of ssl certificate +[[inputs.ssl]] + ## Server to check + [[inputs.ssl.servers]] + host = "google.com:443" + timeout = 5 + ## Server to check + [[inputs.ssl.servers]] + host = "github.com" + timeout = 5 +``` + +### Tags: + +- domain +- port + +### Fields: + +- time_to_expiration(int) + +### Example Output: + +If ssl certificate is valid: + +``` +* Plugin: inputs.ssl, Collection 1 +> ssl,domain=example.com,port=443,host=host time_to_expiration=3907728i 1517213967000000000 +``` + +If ssl certificate and domain mismatch: + +``` +* Plugin: inputs.ssl, Collection 1 +2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: [example.com:443] cert and domain mismatch +> ssl,domain=example.com,port=443,host=host time_to_expiration=0i 1517214033000000000 +``` + +If ssl certificate has expired: + +``` +* Plugin: inputs.ssl, Collection 1 +2018-01-29T08:20:33Z E! Error in plugin [inputs.ssl]: [example.com:443] cert has expired +> ssl,domain=example.com,port=443,host=host time_to_expiration=0i 1517214033000000000 +``` diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go new file mode 100644 index 0000000000000..17e1c488bad2f --- /dev/null +++ b/plugins/inputs/ssl/ssl.go @@ -0,0 +1,128 @@ +package ssl + +import ( + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + "crypto/x509" + "time" + "net" + "crypto/tls" + "github.com/pkg/errors" + "strings" +) + +type Ssl struct { + Servers []Server +} + +type Server struct { + Host string + Timeout int +} + +var sampleConfig = ` + ## Server to check + [[inputs.ssl.servers]] + host = "google.com:443" + timeout = 5 + ## Server to check + [[inputs.ssl.servers]] + host = "github.com" + timeout = 5 +` + +func (s *Ssl) SampleConfig() string { + return sampleConfig +} + +func (s *Ssl) Description() string { + return "Check expiration date and domains of ssl certificate" +} + +func (s *Ssl) Gather(acc telegraf.Accumulator) error { + for _, server := range s.Servers { + slice := strings.Split(server.Host, ":") + domain, port := slice[0], "443" + if len(slice) > 1 { + port = slice[1] + } + h := getServerAddress(domain, port) + timeNow := time.Now() + timeToExp := int64(0) + fields := make(map[string]interface{}) + tags := make(map[string]string) + certs, err := getServerCertsChain(domain, port, server.Timeout) + + if err != nil { + acc.AddError(err) + } else { + cert := certs[0] + if cert.NotAfter.UnixNano() < timeNow.UnixNano() { + acc.AddError(errors.New("[" + h + "] cert has expired")) + } else { + timeToExp = int64(cert.NotAfter.Sub(timeNow) / time.Second) + } + if !isDomainInCertDnsNames(domain, cert.DNSNames) { + acc.AddError(errors.New("[" + h + "] cert and domain mismatch")) + timeToExp = int64(0) + } + } + fields["time_to_expiration"] = timeToExp + tags["domain"] = domain + tags["port"] = port + + acc.AddFields("ssl", fields, tags) + } + return nil +} + +func getServerCertsChain(d string, p string, t int) ([]*x509.Certificate, error) { + h := getServerAddress(d, p) + ipConn, err := net.DialTimeout("tcp", h, time.Duration(t) * time.Second) + if err != nil { + return nil, errors.New("[" + h + "] " + err.Error()) + } + defer ipConn.Close() + + tlsConn := tls.Client(ipConn, &tls.Config{ServerName: d, InsecureSkipVerify: true}) + defer tlsConn.Close() + + err = tlsConn.Handshake() + if err != nil { + return nil, errors.New("[" + h + "] " + err.Error()) + } + certs := tlsConn.ConnectionState().PeerCertificates + if certs == nil || len(certs) < 1 { + return nil, errors.New("[" + h + "] cert receive error") + } + return certs, nil +} + +func getServerAddress(d string, p string) string { + return d + ":" + p +} + +func isDomainInCertDnsNames(domain string, certDnsNames []string) bool { + for _, d := range certDnsNames { + if domain == d { + return true + } + if d[:1] == "*" && len(domain) >= len(d[2:]) { + d = d[2:] + if domain == d { + return true + } + start := len(domain)-len(d)-1 + if start >= 0 && domain[start:] == "."+d { + return true + } + } + } + return false +} + +func init() { + inputs.Add("ssl", func() telegraf.Input { + return &Ssl{} + }) +} diff --git a/plugins/inputs/ssl/ssl_test.go b/plugins/inputs/ssl/ssl_test.go new file mode 100644 index 0000000000000..7177981990596 --- /dev/null +++ b/plugins/inputs/ssl/ssl_test.go @@ -0,0 +1,36 @@ +package ssl + +import ( + "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "github.com/influxdata/telegraf/testutil" +) + +func TestGathering(t *testing.T) { + if testing.Short() { + t.Skip("Skipping network-dependent test in short mode.") + } + var servers = []Server{ + { + Host: "github.com:443", + Timeout: 5, + }, + { + Host: "github.com", + Timeout: 5, + }, + } + var sslConfig = Ssl { + Servers: servers, + } + var acc testutil.Accumulator + + err := acc.GatherError(sslConfig.Gather) + assert.NoError(t, err) + metric, ok := acc.Get("ssl") + require.True(t, ok) + timeToExp, _ := metric.Fields["time_to_expiration"].(float64) + + assert.NotEqual(t, 0, timeToExp) +} From e0be44d73bda34afc6a94fbf2af09051ec8821e4 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Sun, 25 Feb 2018 17:22:05 +0200 Subject: [PATCH 11/12] fix by gofmt --- plugins/inputs/ssl/ssl.go | 18 +++++++++--------- plugins/inputs/ssl/ssl_test.go | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/inputs/ssl/ssl.go b/plugins/inputs/ssl/ssl.go index 17e1c488bad2f..d770593fd765c 100644 --- a/plugins/inputs/ssl/ssl.go +++ b/plugins/inputs/ssl/ssl.go @@ -1,14 +1,14 @@ package ssl import ( + "crypto/tls" + "crypto/x509" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" - "crypto/x509" - "time" - "net" - "crypto/tls" "github.com/pkg/errors" + "net" "strings" + "time" ) type Ssl struct { @@ -16,11 +16,11 @@ type Ssl struct { } type Server struct { - Host string + Host string Timeout int } -var sampleConfig = ` +var sampleConfig = ` ## Server to check [[inputs.ssl.servers]] host = "google.com:443" @@ -39,7 +39,7 @@ func (s *Ssl) Description() string { return "Check expiration date and domains of ssl certificate" } -func (s *Ssl) Gather(acc telegraf.Accumulator) error { +func (s *Ssl) Gather(acc telegraf.Accumulator) error { for _, server := range s.Servers { slice := strings.Split(server.Host, ":") domain, port := slice[0], "443" @@ -78,7 +78,7 @@ func (s *Ssl) Gather(acc telegraf.Accumulator) error { func getServerCertsChain(d string, p string, t int) ([]*x509.Certificate, error) { h := getServerAddress(d, p) - ipConn, err := net.DialTimeout("tcp", h, time.Duration(t) * time.Second) + ipConn, err := net.DialTimeout("tcp", h, time.Duration(t)*time.Second) if err != nil { return nil, errors.New("[" + h + "] " + err.Error()) } @@ -112,7 +112,7 @@ func isDomainInCertDnsNames(domain string, certDnsNames []string) bool { if domain == d { return true } - start := len(domain)-len(d)-1 + start := len(domain) - len(d) - 1 if start >= 0 && domain[start:] == "."+d { return true } diff --git a/plugins/inputs/ssl/ssl_test.go b/plugins/inputs/ssl/ssl_test.go index 7177981990596..da449025ae969 100644 --- a/plugins/inputs/ssl/ssl_test.go +++ b/plugins/inputs/ssl/ssl_test.go @@ -1,10 +1,10 @@ package ssl import ( - "testing" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" ) func TestGathering(t *testing.T) { @@ -13,15 +13,15 @@ func TestGathering(t *testing.T) { } var servers = []Server{ { - Host: "github.com:443", + Host: "github.com:443", Timeout: 5, }, { - Host: "github.com", + Host: "github.com", Timeout: 5, }, } - var sslConfig = Ssl { + var sslConfig = Ssl{ Servers: servers, } var acc testutil.Accumulator From d2ea24779facd9b2128a3578593160ba16713335 Mon Sep 17 00:00:00 2001 From: Maxim Grabazey Date: Sun, 25 Feb 2018 17:27:45 +0200 Subject: [PATCH 12/12] fix by gofmt --- plugins/inputs/all/all.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 12cd49d2c13ea..ccf05d0d2b684 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -88,6 +88,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/socket_listener" _ "github.com/influxdata/telegraf/plugins/inputs/solr" _ "github.com/influxdata/telegraf/plugins/inputs/sqlserver" + _ "github.com/influxdata/telegraf/plugins/inputs/ssl" _ "github.com/influxdata/telegraf/plugins/inputs/statsd" _ "github.com/influxdata/telegraf/plugins/inputs/sysstat" _ "github.com/influxdata/telegraf/plugins/inputs/system" @@ -106,5 +107,4 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zipkin" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" - _ "github.com/influxdata/telegraf/plugins/inputs/ssl" )