From 54abdc20ae151cb9dbd16833a3377793b20041b3 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Mon, 5 Feb 2018 22:52:27 +0000 Subject: [PATCH 01/14] Adding ssl_cert input plugin --- plugins/inputs/all/all.go | 1 + plugins/inputs/ssl_cert/README.md | 39 +++++ plugins/inputs/ssl_cert/ssl_cert.go | 155 +++++++++++++++++ plugins/inputs/ssl_cert/ssl_cert_test.go | 213 +++++++++++++++++++++++ 4 files changed, 408 insertions(+) create mode 100644 plugins/inputs/ssl_cert/README.md create mode 100644 plugins/inputs/ssl_cert/ssl_cert.go create mode 100644 plugins/inputs/ssl_cert/ssl_cert_test.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index b2be2be5a55ca..280558eccc40e 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -96,6 +96,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_cert" _ "github.com/influxdata/telegraf/plugins/inputs/statsd" _ "github.com/influxdata/telegraf/plugins/inputs/syslog" _ "github.com/influxdata/telegraf/plugins/inputs/sysstat" diff --git a/plugins/inputs/ssl_cert/README.md b/plugins/inputs/ssl_cert/README.md new file mode 100644 index 0000000000000..130b79c679886 --- /dev/null +++ b/plugins/inputs/ssl_cert/README.md @@ -0,0 +1,39 @@ +# SSL Cert Input Plugin + +This plugin provides information about SSL certificate accessible via local +file or network connection. + + +### Configuration + +```toml +# Reads metrics from a SSL certificate +[[inputs.ssl_cert]] + ## List of local SSL files + #files = [] + ## List of servers + #servers = [] + ## Timeout for SSL connection + #timeout = 5 +``` + + +### Metrics + +- `ssl_cert` + - tags: + - `server` (only if `servers` parameter is defined) + - `file` (only if `files` parameter is defined) + - fields: + - `expiry` (int, seconds) + - `age` (int, seconds) + - `startdate` (int, seconds) + - `enddate` (int, seconds) + + +### Example output + +``` +ssl_cert,server=google.com:443,host=myhost age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 1517845687000000000 +ssl_cert,host=myhost,file=/path/to/the.crt age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000 +``` diff --git a/plugins/inputs/ssl_cert/ssl_cert.go b/plugins/inputs/ssl_cert/ssl_cert.go new file mode 100644 index 0000000000000..e013feec5f1b3 --- /dev/null +++ b/plugins/inputs/ssl_cert/ssl_cert.go @@ -0,0 +1,155 @@ +package ssl_cert + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "net" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +const sampleConfig = ` + ## List of local SSL files + # files = [] + ## List of servers + # servers = [] + ## Timeout for SSL connection + # timeout = 5 +` +const description = "Reads metrics from a SSL certificate" + +type SSLCert struct { + Servers []string `toml:"servers"` + Files []string `toml:"files"` + Timeout time.Duration `toml:"timeout"` + + // For tests + CloseConn bool +} + +func (sc *SSLCert) Description() string { + return description +} + +func (sc *SSLCert) SampleConfig() string { + return sampleConfig +} + +func getRemoteCert(server string, timeout time.Duration, closeConn bool) (*x509.Certificate, error) { + tlsCfg := &tls.Config{ + InsecureSkipVerify: true, + } + + ipConn, err := net.DialTimeout("tcp", server, timeout) + if err != nil { + return nil, err + } + defer ipConn.Close() + + conn := tls.Client(ipConn, tlsCfg) + defer conn.Close() + + if closeConn { + conn.Close() + } + + hsErr := conn.Handshake() + if hsErr != nil { + return nil, hsErr + } + + certs := conn.ConnectionState().PeerCertificates + + if certs == nil || len(certs) < 1 { + return nil, errors.New("Couldn't get remote certificate.") + } + + return certs[0], nil +} + +func getLocalCert(filename string) (*x509.Certificate, error) { + content, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + block, _ := pem.Decode(content) + if block == nil { + return nil, errors.New("Failed to parse certificate PEM.") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + + return cert, nil +} + +func getMetrics(cert *x509.Certificate, now time.Time) map[string]interface{} { + age := int(now.Sub(cert.NotBefore).Seconds()) + expiry := int(cert.NotAfter.Sub(now).Seconds()) + startdate := int(cert.NotBefore.Unix()) + enddate := int(cert.NotAfter.Unix()) + + metrics := map[string]interface{}{ + "age": age, + "expiry": expiry, + "startdate": startdate, + "enddate": enddate, + } + + return metrics +} + +func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { + now := time.Now() + + for _, server := range sc.Servers { + cert, err := getRemoteCert(server, sc.Timeout*time.Second, sc.CloseConn) + if err != nil { + return errors.New(fmt.Sprintf("Cannot get remote SSL cert: %s", err)) + } + + tags := map[string]string{ + "server": server, + } + + fields := getMetrics(cert, now) + + acc.AddFields("ssl_cert", fields, tags) + } + + for _, file := range sc.Files { + cert, err := getLocalCert(file) + if err != nil { + return errors.New(fmt.Sprintf("Cannot get local SSL cert: %s", err)) + } + + tags := map[string]string{ + "file": file, + } + + fields := getMetrics(cert, now) + + acc.AddFields("ssl_cert", fields, tags) + } + + return nil +} + +func init() { + inputs.Add("ssl_cert", func() telegraf.Input { + return &SSLCert{ + Files: []string{}, + Servers: []string{}, + Timeout: 5, + } + }) +} diff --git a/plugins/inputs/ssl_cert/ssl_cert_test.go b/plugins/inputs/ssl_cert/ssl_cert_test.go new file mode 100644 index 0000000000000..8b866a70c7f1c --- /dev/null +++ b/plugins/inputs/ssl_cert/ssl_cert_test.go @@ -0,0 +1,213 @@ +package ssl_cert + +import ( + "crypto/tls" + "encoding/base64" + "fmt" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/influxdata/telegraf/testutil" +) + +const testCert = `-----BEGIN CERTIFICATE----- +MIID7DCCAtSgAwIBAgIBATANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJVSzEX +MBUGA1UECBMOVW5pdGVkIEtpbmdkb20xDzANBgNVBAcTBkxvbmRvbjEWMBQGA1UE +ChMNVGVsZWdyYWYgVGVzdDEQMA4GA1UECxMHVGVzdGluZzESMBAGA1UEAxMJbG9j +YWxob3N0MB4XDTE4MDIwNjA2MTIwMFoXDTE5MDIwNjA2MTIwMFowdTELMAkGA1UE +BhMCVUsxFzAVBgNVBAgTDlVuaXRlZCBLaW5nZG9tMQ8wDQYDVQQHEwZMb25kb24x +FjAUBgNVBAoTDVRlbGVncmFmIFRlc3QxEDAOBgNVBAsTB1Rlc3RpbmcxEjAQBgNV +BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKev +IBsUu3NdYod/jxPxpJug4p1M/qiGkFTffcrjbxzlZUIED5bAZNUutqVYQYT7/Chy +pt7U7B6coAbUoIbJLpQ9ktDcyF22LpV7E6TB2SFIemnNYPM0U9vcW2EEaUvFfCTT +RLiwd+S4/twfgdgP8RuCnXuqkRQXCJVwD1GnazuNHqlyYH4IznXmY/Ia6dlaTSKs +zuJp4UwYSAuO+AmoefmOUAJ0Q2l4khBZlXv5wHqROuX+3VdIK7JtoP0ydcPkrTt9 +zI/qt4AFBJvIdBZS9QVAwoBNnNAElHX5eRRWhuLgftHVQM+3JNpsXYp4OlcoAs/A +QowOSsKXJu4i8pIn7lkCAwEAAaOBhjCBgzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW +BBRfip83ocgzVxMCnsFHASt4CUY5qjALBgNVHQ8EBAMCBeAwFAYDVR0RBA0wC4IJ +bG9jYWxob3N0MBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh +IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4IBAQBOuCcjgiDL9OhSuLN3pewB +tE0garxFgPBGwkr8g+XpRktiensdhoxsQFwuqsIPv56GmqlqiqZb98HxnNFkQ0im +XgmHvSxHLiSKbD6DOgpKQ1I0FB7sHKb8qaJbxwPJDXMyXseja7PEzh5EVtQMimhQ +z46Fz+pZ4uUS2h2TgqiAoJpKZnb8mceGQY9qzGztWv67RFsWVK7R+JP0W2cZ+Tk+ +2NoeMhBdqV0fA13cFvvWSLZ2w3eKLldJMIni2hk/G9nHDWIAbGb83qdhGfR7PMkJ +jD/IOPXIcYxKB62aDCEWDcCGdLZCRgBP2VDzQW2J6MIFm6qv5H8llkNs3Qh1/VFj +-----END CERTIFICATE-----` + +const testKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAp68gGxS7c11ih3+PE/Gkm6DinUz+qIaQVN99yuNvHOVlQgQP +lsBk1S62pVhBhPv8KHKm3tTsHpygBtSghskulD2S0NzIXbYulXsTpMHZIUh6ac1g +8zRT29xbYQRpS8V8JNNEuLB35Lj+3B+B2A/xG4Kde6qRFBcIlXAPUadrO40eqXJg +fgjOdeZj8hrp2VpNIqzO4mnhTBhIC474Cah5+Y5QAnRDaXiSEFmVe/nAepE65f7d +V0grsm2g/TJ1w+StO33Mj+q3gAUEm8h0FlL1BUDCgE2c0ASUdfl5FFaG4uB+0dVA +z7ck2mxding6VygCz8BCjA5Kwpcm7iLykifuWQIDAQABAoIBADZeOLmvGiwIjkbC +nCBqS+XN30wDR9pabvel0wJyhXdIBXHHIUrOrKLWV4/6spusnBB9RA+h18EBJX2x +eS7akgisgirIOwrvY+FBm5fi5kS9XDtrxNB2Ge6CXvpw1Lclm9/QxEphpS36sV+r +s4zbdmBmFCuhnRJ3eWgCgmUGNGWFEG2hmYAT5cy1ViPRY5pBNMLz+zvWhDw1Zjwl +C7Oe/zqqc7R1vGmoUJ8KPHqPAkXF9Ouj61YT+WXrF1iRRhn4oJ7x9O4L1vkbYOxq +0PWh5gGGHcqe3SdvF6RI4AlhLBK1PNPeFU78k4VXBZoeBNg+Q7lQYYr1A56sE+kY +BbqGAdECgYEA2nq4GlcbzBSeH3NzAkxy+lPYgdswrGih7mJeCwmJby+IdkqK5DL9 +VyC95Bx2aut0hiv8fxVfAM+utWn5tyfBt1BVhtyMj5Fam8Deh/JjgmJi4GeaWcMg +lo4iTUFTOBQ5ibGzUeE8pOnViN0HkUwpzWE2cvWh1546UgQkQJCAHU0CgYEAxHs6 +zv4tCz1g9Hj5nlRINdTbMJwr2Es1dNVzYeDlvtU7IfT/82G/Z4Ub35QF0vcj/5uB +sBKrF6i023WgDpQ/pqNoIE4GpEzyNov07e4YoYU9ejBamBdI48gmJswegYRjCeHf +Gl/cMBbSYgTA+GKsn2I5cWIVAEu50f8XUqSXPz0CgYEAnc2VvDC+uyEJNN5Ga5qc +UYLOFr0i4uSQUYZrNr2krtI+VnJw73KE2bGkdma4gXGfsGmE7qWZARUAs7ffzhLB +MI6tt8MFI41xTJ56HOdOSJaXpE4whjUSDKyMyhAs84xoIrRfOPzeuJ7MxRYgqSnB +574XfeE9DGgU57hmFtxILOECgYEAm+f4kzVHQsryeyrfT84q+mQrhVf2xotvIIUb +KEiPpSyH3nsM+e/PNHJ/2poXQP6QVwvrDW7SylQ5JocgeVETbMPvJOslBAx2iefm +c0Hh05DpZmKmEFcxpGU2OMTxU+5btATBxqjYDGSfjd2dzbpmpZYIZLriVTjBeyuC +MzadOTUCgYEAk75Ioeb7zURwvgXbP25MUlm1dTE+vT1Q63QSRHAgMqVZ6seexv09 +JoiUjTAZegW1RkST3tB1an9zO0EcPvo/1yU7wKaMuNwmauPlltBdpaTwojeUBiFr +AoQUXWIiFBoFfpNVcvUgHyGGc1hv7TX8Eh8KGo2+VuPzxnFuRrbbYCs= +-----END RSA PRIVATE KEY-----` + +func getTestPrefix(testN int) string { + return fmt.Sprintf("Test [%d]: ", testN) +} + +func TestGatherRemote(t *testing.T) { + if testing.Short() { + t.Skip("Skipping network-dependent test in short mode.") + } + + tests := []struct { + server string + timeout time.Duration + close bool + error bool + }{ + {server: ":99999", timeout: 0, close: false, error: true}, + {server: "", timeout: 5, close: false, error: false}, + {server: "", timeout: 0, close: true, error: true}, + } + + pair, err := tls.X509KeyPair([]byte(testCert), []byte(testKey)) + if err != nil { + t.Error(err) + } + + config := &tls.Config{ + Certificates: []tls.Certificate{pair}, + } + + ln, err := tls.Listen("tcp", ":0", config) + if err != nil { + t.Error(err) + } + defer ln.Close() + + go func() { + sconn, err := ln.Accept() + if err != nil { + return + } + + serverConfig := config.Clone() + + srv := tls.Server(sconn, serverConfig) + if err := srv.Handshake(); err != nil { + return + } + }() + + for i, test := range tests { + if test.server == "" { + test.server = ln.Addr().String() + } + + sc := SSLCert{ + Servers: []string{test.server}, + Timeout: test.timeout, + CloseConn: test.close, + } + + error := false + + acc := testutil.Accumulator{} + err = sc.Gather(&acc) + if err != nil { + error = true + } + + if error != test.error { + t.Errorf("Test [%d]: %s.", i, err) + } + } +} + +func TestGatherLocal(t *testing.T) { + wrongCert := fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", base64.StdEncoding.EncodeToString([]byte("test"))) + + tests := []struct { + mode os.FileMode + content string + error bool + }{ + {mode: 0001, content: "", error: true}, + {mode: 0640, content: "test", error: true}, + {mode: 0640, content: wrongCert, error: true}, + {mode: 0640, content: testCert, error: false}, + } + + for i, test := range tests { + f, err := ioutil.TempFile("", "ssl_cert") + if err != nil { + t.Error(err) + } + + _, err = f.Write([]byte(test.content)) + if err != nil { + t.Error(err) + } + + err = f.Chmod(test.mode) + if err != nil { + t.Error(err) + } + + err = f.Close() + if err != nil { + t.Error(err) + } + + defer os.Remove(f.Name()) + + sc := SSLCert{ + Files: []string{f.Name()}, + } + + error := false + + acc := testutil.Accumulator{} + err = sc.Gather(&acc) + if err != nil { + error = true + } + + if error != test.error { + t.Errorf("Test [%d]: %s.", i, err) + } + } +} + +func TestStrings(t *testing.T) { + sc := SSLCert{} + + tests := []struct { + method string + returned string + expected string + }{ + {method: "Description", returned: sc.Description(), expected: description}, + {method: "SampleConfig", returned: sc.SampleConfig(), expected: sampleConfig}, + } + + for i, test := range tests { + if test.returned != test.expected { + t.Errorf("Test [%d]: Expected method %s to return '%s', found '%s'.", i, test.method, test.expected, test.returned) + } + } +} From 79c28d63e136a8e4685c5074286a747fe98c9ff7 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Mon, 12 Feb 2018 20:09:14 +0000 Subject: [PATCH 02/14] Implementing review feedback --- plugins/inputs/ssl_cert/ssl_cert.go | 23 +++++--- plugins/inputs/ssl_cert/ssl_cert_test.go | 75 ++++++++++++------------ 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/plugins/inputs/ssl_cert/ssl_cert.go b/plugins/inputs/ssl_cert/ssl_cert.go index e013feec5f1b3..f28b7d832f8d0 100644 --- a/plugins/inputs/ssl_cert/ssl_cert.go +++ b/plugins/inputs/ssl_cert/ssl_cert.go @@ -24,24 +24,28 @@ const sampleConfig = ` ` const description = "Reads metrics from a SSL certificate" +// SSLCert holds the configuration of the plugin. type SSLCert struct { Servers []string `toml:"servers"` Files []string `toml:"files"` Timeout time.Duration `toml:"timeout"` // For tests - CloseConn bool + CloseConn bool + UnsetCerts bool } +// Description returns description of the plugin. func (sc *SSLCert) Description() string { return description } +// SampleConfig returns configuration sample for the plugin. func (sc *SSLCert) SampleConfig() string { return sampleConfig } -func getRemoteCert(server string, timeout time.Duration, closeConn bool) (*x509.Certificate, error) { +func getRemoteCert(server string, timeout time.Duration, closeConn bool, unsetCerts bool) (*x509.Certificate, error) { tlsCfg := &tls.Config{ InsecureSkipVerify: true, } @@ -66,8 +70,12 @@ func getRemoteCert(server string, timeout time.Duration, closeConn bool) (*x509. certs := conn.ConnectionState().PeerCertificates + if unsetCerts { + certs = nil + } + if certs == nil || len(certs) < 1 { - return nil, errors.New("Couldn't get remote certificate.") + return nil, errors.New("couldn't get remote certificate") } return certs[0], nil @@ -81,7 +89,7 @@ func getLocalCert(filename string) (*x509.Certificate, error) { block, _ := pem.Decode(content) if block == nil { - return nil, errors.New("Failed to parse certificate PEM.") + return nil, errors.New("failed to parse certificate PEM") } cert, err := x509.ParseCertificate(block.Bytes) @@ -108,13 +116,14 @@ func getMetrics(cert *x509.Certificate, now time.Time) map[string]interface{} { return metrics } +// Gather adds metrics into the accumulator. func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { now := time.Now() for _, server := range sc.Servers { - cert, err := getRemoteCert(server, sc.Timeout*time.Second, sc.CloseConn) + cert, err := getRemoteCert(server, sc.Timeout*time.Second, sc.CloseConn, sc.UnsetCerts) if err != nil { - return errors.New(fmt.Sprintf("Cannot get remote SSL cert: %s", err)) + return fmt.Errorf("cannot get remote SSL cert '%s': %s", server, err) } tags := map[string]string{ @@ -129,7 +138,7 @@ func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { for _, file := range sc.Files { cert, err := getLocalCert(file) if err != nil { - return errors.New(fmt.Sprintf("Cannot get local SSL cert: %s", err)) + return fmt.Errorf("cannot get local SSL cert '%s': %s", file, err) } tags := map[string]string{ diff --git a/plugins/inputs/ssl_cert/ssl_cert_test.go b/plugins/inputs/ssl_cert/ssl_cert_test.go index 8b866a70c7f1c..5e7ce4bb41363 100644 --- a/plugins/inputs/ssl_cert/ssl_cert_test.go +++ b/plugins/inputs/ssl_cert/ssl_cert_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/testutil" ) @@ -64,9 +65,8 @@ JoiUjTAZegW1RkST3tB1an9zO0EcPvo/1yU7wKaMuNwmauPlltBdpaTwojeUBiFr AoQUXWIiFBoFfpNVcvUgHyGGc1hv7TX8Eh8KGo2+VuPzxnFuRrbbYCs= -----END RSA PRIVATE KEY-----` -func getTestPrefix(testN int) string { - return fmt.Sprintf("Test [%d]: ", testN) -} +// Make sure SSLCert implements telegraf.Input +var _ telegraf.Input = &SSLCert{} func TestGatherRemote(t *testing.T) { if testing.Short() { @@ -77,51 +77,54 @@ func TestGatherRemote(t *testing.T) { server string timeout time.Duration close bool + unset bool error bool }{ - {server: ":99999", timeout: 0, close: false, error: true}, - {server: "", timeout: 5, close: false, error: false}, - {server: "", timeout: 0, close: true, error: true}, - } - - pair, err := tls.X509KeyPair([]byte(testCert), []byte(testKey)) - if err != nil { - t.Error(err) + {server: ":99999", timeout: 0, close: false, unset: false, error: true}, + {server: "", timeout: 5, close: false, unset: false, error: false}, + {server: "", timeout: 5, close: false, unset: true, error: true}, + {server: "", timeout: 0, close: true, unset: false, error: true}, } - config := &tls.Config{ - Certificates: []tls.Certificate{pair}, - } + for i, test := range tests { + pair, err := tls.X509KeyPair([]byte(testCert), []byte(testKey)) + if err != nil { + t.Fatal(err) + } - ln, err := tls.Listen("tcp", ":0", config) - if err != nil { - t.Error(err) - } - defer ln.Close() + config := &tls.Config{ + Certificates: []tls.Certificate{pair}, + } - go func() { - sconn, err := ln.Accept() + ln, err := tls.Listen("tcp", ":0", config) if err != nil { - return + t.Fatal(err) } + defer ln.Close() - serverConfig := config.Clone() + go func() { + sconn, err := ln.Accept() + if err != nil { + return + } - srv := tls.Server(sconn, serverConfig) - if err := srv.Handshake(); err != nil { - return - } - }() + serverConfig := config.Clone() + + srv := tls.Server(sconn, serverConfig) + if err := srv.Handshake(); err != nil { + return + } + }() - for i, test := range tests { if test.server == "" { test.server = ln.Addr().String() } sc := SSLCert{ - Servers: []string{test.server}, - Timeout: test.timeout, - CloseConn: test.close, + Servers: []string{test.server}, + Timeout: test.timeout, + CloseConn: test.close, + UnsetCerts: test.unset, } error := false @@ -155,22 +158,22 @@ func TestGatherLocal(t *testing.T) { for i, test := range tests { f, err := ioutil.TempFile("", "ssl_cert") if err != nil { - t.Error(err) + t.Fatal(err) } _, err = f.Write([]byte(test.content)) if err != nil { - t.Error(err) + t.Fatal(err) } err = f.Chmod(test.mode) if err != nil { - t.Error(err) + t.Fatal(err) } err = f.Close() if err != nil { - t.Error(err) + t.Fatal(err) } defer os.Remove(f.Name()) From bd12da183b554aca47c3fbc34a72bb467fcc6a11 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Wed, 21 Mar 2018 23:38:09 +0000 Subject: [PATCH 03/14] Exclude test flags from the public API --- plugins/inputs/ssl_cert/ssl_cert.go | 14 ++++++++------ plugins/inputs/ssl_cert/ssl_cert_test.go | 9 +++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/inputs/ssl_cert/ssl_cert.go b/plugins/inputs/ssl_cert/ssl_cert.go index f28b7d832f8d0..8c5d9d5277f8b 100644 --- a/plugins/inputs/ssl_cert/ssl_cert.go +++ b/plugins/inputs/ssl_cert/ssl_cert.go @@ -29,12 +29,14 @@ type SSLCert struct { Servers []string `toml:"servers"` Files []string `toml:"files"` Timeout time.Duration `toml:"timeout"` - - // For tests - CloseConn bool - UnsetCerts bool } +// For tests +var ( + closeConn bool + unsetCerts bool +) + // Description returns description of the plugin. func (sc *SSLCert) Description() string { return description @@ -45,7 +47,7 @@ func (sc *SSLCert) SampleConfig() string { return sampleConfig } -func getRemoteCert(server string, timeout time.Duration, closeConn bool, unsetCerts bool) (*x509.Certificate, error) { +func getRemoteCert(server string, timeout time.Duration) (*x509.Certificate, error) { tlsCfg := &tls.Config{ InsecureSkipVerify: true, } @@ -121,7 +123,7 @@ func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { now := time.Now() for _, server := range sc.Servers { - cert, err := getRemoteCert(server, sc.Timeout*time.Second, sc.CloseConn, sc.UnsetCerts) + cert, err := getRemoteCert(server, sc.Timeout*time.Second) if err != nil { return fmt.Errorf("cannot get remote SSL cert '%s': %s", server, err) } diff --git a/plugins/inputs/ssl_cert/ssl_cert_test.go b/plugins/inputs/ssl_cert/ssl_cert_test.go index 5e7ce4bb41363..32c0dd93615ca 100644 --- a/plugins/inputs/ssl_cert/ssl_cert_test.go +++ b/plugins/inputs/ssl_cert/ssl_cert_test.go @@ -121,12 +121,13 @@ func TestGatherRemote(t *testing.T) { } sc := SSLCert{ - Servers: []string{test.server}, - Timeout: test.timeout, - CloseConn: test.close, - UnsetCerts: test.unset, + Servers: []string{test.server}, + Timeout: test.timeout, } + closeConn = test.close + unsetCerts = test.unset + error := false acc := testutil.Accumulator{} From 7001319b35a5578df4a0856a10e48c024e501ec8 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Sat, 9 Jun 2018 00:01:12 +0100 Subject: [PATCH 04/14] Implementing comments from @danielnelson --- plugins/inputs/all/all.go | 2 +- plugins/inputs/ssl_cert/README.md | 39 ---- plugins/inputs/ssl_cert/ssl_cert_test.go | 217 ------------------ plugins/inputs/x509_cert/README.md | 45 ++++ .../ssl_cert.go => x509_cert/x509_cert.go} | 82 ++++--- plugins/inputs/x509_cert/x509_cert_test.go | 169 ++++++++++++++ 6 files changed, 267 insertions(+), 287 deletions(-) delete mode 100644 plugins/inputs/ssl_cert/README.md delete mode 100644 plugins/inputs/ssl_cert/ssl_cert_test.go create mode 100644 plugins/inputs/x509_cert/README.md rename plugins/inputs/{ssl_cert/ssl_cert.go => x509_cert/x509_cert.go} (53%) create mode 100644 plugins/inputs/x509_cert/x509_cert_test.go diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 280558eccc40e..698f743410bc3 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -96,7 +96,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_cert" + _ "github.com/influxdata/telegraf/plugins/inputs/x509_cert" _ "github.com/influxdata/telegraf/plugins/inputs/statsd" _ "github.com/influxdata/telegraf/plugins/inputs/syslog" _ "github.com/influxdata/telegraf/plugins/inputs/sysstat" diff --git a/plugins/inputs/ssl_cert/README.md b/plugins/inputs/ssl_cert/README.md deleted file mode 100644 index 130b79c679886..0000000000000 --- a/plugins/inputs/ssl_cert/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# SSL Cert Input Plugin - -This plugin provides information about SSL certificate accessible via local -file or network connection. - - -### Configuration - -```toml -# Reads metrics from a SSL certificate -[[inputs.ssl_cert]] - ## List of local SSL files - #files = [] - ## List of servers - #servers = [] - ## Timeout for SSL connection - #timeout = 5 -``` - - -### Metrics - -- `ssl_cert` - - tags: - - `server` (only if `servers` parameter is defined) - - `file` (only if `files` parameter is defined) - - fields: - - `expiry` (int, seconds) - - `age` (int, seconds) - - `startdate` (int, seconds) - - `enddate` (int, seconds) - - -### Example output - -``` -ssl_cert,server=google.com:443,host=myhost age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 1517845687000000000 -ssl_cert,host=myhost,file=/path/to/the.crt age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000 -``` diff --git a/plugins/inputs/ssl_cert/ssl_cert_test.go b/plugins/inputs/ssl_cert/ssl_cert_test.go deleted file mode 100644 index 32c0dd93615ca..0000000000000 --- a/plugins/inputs/ssl_cert/ssl_cert_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package ssl_cert - -import ( - "crypto/tls" - "encoding/base64" - "fmt" - "io/ioutil" - "os" - "testing" - "time" - - "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/testutil" -) - -const testCert = `-----BEGIN CERTIFICATE----- -MIID7DCCAtSgAwIBAgIBATANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJVSzEX -MBUGA1UECBMOVW5pdGVkIEtpbmdkb20xDzANBgNVBAcTBkxvbmRvbjEWMBQGA1UE -ChMNVGVsZWdyYWYgVGVzdDEQMA4GA1UECxMHVGVzdGluZzESMBAGA1UEAxMJbG9j -YWxob3N0MB4XDTE4MDIwNjA2MTIwMFoXDTE5MDIwNjA2MTIwMFowdTELMAkGA1UE -BhMCVUsxFzAVBgNVBAgTDlVuaXRlZCBLaW5nZG9tMQ8wDQYDVQQHEwZMb25kb24x -FjAUBgNVBAoTDVRlbGVncmFmIFRlc3QxEDAOBgNVBAsTB1Rlc3RpbmcxEjAQBgNV -BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKev -IBsUu3NdYod/jxPxpJug4p1M/qiGkFTffcrjbxzlZUIED5bAZNUutqVYQYT7/Chy -pt7U7B6coAbUoIbJLpQ9ktDcyF22LpV7E6TB2SFIemnNYPM0U9vcW2EEaUvFfCTT -RLiwd+S4/twfgdgP8RuCnXuqkRQXCJVwD1GnazuNHqlyYH4IznXmY/Ia6dlaTSKs -zuJp4UwYSAuO+AmoefmOUAJ0Q2l4khBZlXv5wHqROuX+3VdIK7JtoP0ydcPkrTt9 -zI/qt4AFBJvIdBZS9QVAwoBNnNAElHX5eRRWhuLgftHVQM+3JNpsXYp4OlcoAs/A -QowOSsKXJu4i8pIn7lkCAwEAAaOBhjCBgzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW -BBRfip83ocgzVxMCnsFHASt4CUY5qjALBgNVHQ8EBAMCBeAwFAYDVR0RBA0wC4IJ -bG9jYWxob3N0MBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh -IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4IBAQBOuCcjgiDL9OhSuLN3pewB -tE0garxFgPBGwkr8g+XpRktiensdhoxsQFwuqsIPv56GmqlqiqZb98HxnNFkQ0im -XgmHvSxHLiSKbD6DOgpKQ1I0FB7sHKb8qaJbxwPJDXMyXseja7PEzh5EVtQMimhQ -z46Fz+pZ4uUS2h2TgqiAoJpKZnb8mceGQY9qzGztWv67RFsWVK7R+JP0W2cZ+Tk+ -2NoeMhBdqV0fA13cFvvWSLZ2w3eKLldJMIni2hk/G9nHDWIAbGb83qdhGfR7PMkJ -jD/IOPXIcYxKB62aDCEWDcCGdLZCRgBP2VDzQW2J6MIFm6qv5H8llkNs3Qh1/VFj ------END CERTIFICATE-----` - -const testKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAp68gGxS7c11ih3+PE/Gkm6DinUz+qIaQVN99yuNvHOVlQgQP -lsBk1S62pVhBhPv8KHKm3tTsHpygBtSghskulD2S0NzIXbYulXsTpMHZIUh6ac1g -8zRT29xbYQRpS8V8JNNEuLB35Lj+3B+B2A/xG4Kde6qRFBcIlXAPUadrO40eqXJg -fgjOdeZj8hrp2VpNIqzO4mnhTBhIC474Cah5+Y5QAnRDaXiSEFmVe/nAepE65f7d -V0grsm2g/TJ1w+StO33Mj+q3gAUEm8h0FlL1BUDCgE2c0ASUdfl5FFaG4uB+0dVA -z7ck2mxding6VygCz8BCjA5Kwpcm7iLykifuWQIDAQABAoIBADZeOLmvGiwIjkbC -nCBqS+XN30wDR9pabvel0wJyhXdIBXHHIUrOrKLWV4/6spusnBB9RA+h18EBJX2x -eS7akgisgirIOwrvY+FBm5fi5kS9XDtrxNB2Ge6CXvpw1Lclm9/QxEphpS36sV+r -s4zbdmBmFCuhnRJ3eWgCgmUGNGWFEG2hmYAT5cy1ViPRY5pBNMLz+zvWhDw1Zjwl -C7Oe/zqqc7R1vGmoUJ8KPHqPAkXF9Ouj61YT+WXrF1iRRhn4oJ7x9O4L1vkbYOxq -0PWh5gGGHcqe3SdvF6RI4AlhLBK1PNPeFU78k4VXBZoeBNg+Q7lQYYr1A56sE+kY -BbqGAdECgYEA2nq4GlcbzBSeH3NzAkxy+lPYgdswrGih7mJeCwmJby+IdkqK5DL9 -VyC95Bx2aut0hiv8fxVfAM+utWn5tyfBt1BVhtyMj5Fam8Deh/JjgmJi4GeaWcMg -lo4iTUFTOBQ5ibGzUeE8pOnViN0HkUwpzWE2cvWh1546UgQkQJCAHU0CgYEAxHs6 -zv4tCz1g9Hj5nlRINdTbMJwr2Es1dNVzYeDlvtU7IfT/82G/Z4Ub35QF0vcj/5uB -sBKrF6i023WgDpQ/pqNoIE4GpEzyNov07e4YoYU9ejBamBdI48gmJswegYRjCeHf -Gl/cMBbSYgTA+GKsn2I5cWIVAEu50f8XUqSXPz0CgYEAnc2VvDC+uyEJNN5Ga5qc -UYLOFr0i4uSQUYZrNr2krtI+VnJw73KE2bGkdma4gXGfsGmE7qWZARUAs7ffzhLB -MI6tt8MFI41xTJ56HOdOSJaXpE4whjUSDKyMyhAs84xoIrRfOPzeuJ7MxRYgqSnB -574XfeE9DGgU57hmFtxILOECgYEAm+f4kzVHQsryeyrfT84q+mQrhVf2xotvIIUb -KEiPpSyH3nsM+e/PNHJ/2poXQP6QVwvrDW7SylQ5JocgeVETbMPvJOslBAx2iefm -c0Hh05DpZmKmEFcxpGU2OMTxU+5btATBxqjYDGSfjd2dzbpmpZYIZLriVTjBeyuC -MzadOTUCgYEAk75Ioeb7zURwvgXbP25MUlm1dTE+vT1Q63QSRHAgMqVZ6seexv09 -JoiUjTAZegW1RkST3tB1an9zO0EcPvo/1yU7wKaMuNwmauPlltBdpaTwojeUBiFr -AoQUXWIiFBoFfpNVcvUgHyGGc1hv7TX8Eh8KGo2+VuPzxnFuRrbbYCs= ------END RSA PRIVATE KEY-----` - -// Make sure SSLCert implements telegraf.Input -var _ telegraf.Input = &SSLCert{} - -func TestGatherRemote(t *testing.T) { - if testing.Short() { - t.Skip("Skipping network-dependent test in short mode.") - } - - tests := []struct { - server string - timeout time.Duration - close bool - unset bool - error bool - }{ - {server: ":99999", timeout: 0, close: false, unset: false, error: true}, - {server: "", timeout: 5, close: false, unset: false, error: false}, - {server: "", timeout: 5, close: false, unset: true, error: true}, - {server: "", timeout: 0, close: true, unset: false, error: true}, - } - - for i, test := range tests { - pair, err := tls.X509KeyPair([]byte(testCert), []byte(testKey)) - if err != nil { - t.Fatal(err) - } - - config := &tls.Config{ - Certificates: []tls.Certificate{pair}, - } - - ln, err := tls.Listen("tcp", ":0", config) - if err != nil { - t.Fatal(err) - } - defer ln.Close() - - go func() { - sconn, err := ln.Accept() - if err != nil { - return - } - - serverConfig := config.Clone() - - srv := tls.Server(sconn, serverConfig) - if err := srv.Handshake(); err != nil { - return - } - }() - - if test.server == "" { - test.server = ln.Addr().String() - } - - sc := SSLCert{ - Servers: []string{test.server}, - Timeout: test.timeout, - } - - closeConn = test.close - unsetCerts = test.unset - - error := false - - acc := testutil.Accumulator{} - err = sc.Gather(&acc) - if err != nil { - error = true - } - - if error != test.error { - t.Errorf("Test [%d]: %s.", i, err) - } - } -} - -func TestGatherLocal(t *testing.T) { - wrongCert := fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", base64.StdEncoding.EncodeToString([]byte("test"))) - - tests := []struct { - mode os.FileMode - content string - error bool - }{ - {mode: 0001, content: "", error: true}, - {mode: 0640, content: "test", error: true}, - {mode: 0640, content: wrongCert, error: true}, - {mode: 0640, content: testCert, error: false}, - } - - for i, test := range tests { - f, err := ioutil.TempFile("", "ssl_cert") - if err != nil { - t.Fatal(err) - } - - _, err = f.Write([]byte(test.content)) - if err != nil { - t.Fatal(err) - } - - err = f.Chmod(test.mode) - if err != nil { - t.Fatal(err) - } - - err = f.Close() - if err != nil { - t.Fatal(err) - } - - defer os.Remove(f.Name()) - - sc := SSLCert{ - Files: []string{f.Name()}, - } - - error := false - - acc := testutil.Accumulator{} - err = sc.Gather(&acc) - if err != nil { - error = true - } - - if error != test.error { - t.Errorf("Test [%d]: %s.", i, err) - } - } -} - -func TestStrings(t *testing.T) { - sc := SSLCert{} - - tests := []struct { - method string - returned string - expected string - }{ - {method: "Description", returned: sc.Description(), expected: description}, - {method: "SampleConfig", returned: sc.SampleConfig(), expected: sampleConfig}, - } - - for i, test := range tests { - if test.returned != test.expected { - t.Errorf("Test [%d]: Expected method %s to return '%s', found '%s'.", i, test.method, test.expected, test.returned) - } - } -} diff --git a/plugins/inputs/x509_cert/README.md b/plugins/inputs/x509_cert/README.md new file mode 100644 index 0000000000000..44554b2a82d4a --- /dev/null +++ b/plugins/inputs/x509_cert/README.md @@ -0,0 +1,45 @@ +# X509 Cert Input Plugin + +This plugin provides information about X509 certificate accessible via local +file or network connection. + + +### Configuration + +```toml +# Reads metrics from a SSL certificate +[[inputs.x509_cert]] + ## List of local SSL files + # files = ["/etc/ssl/certs/ssl-cert-snakeoil.pem"] + ## List of servers + # servers = ["tcp://example.org:443"] + ## Timeout for SSL connection + # timeout = 5 + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.pem" + # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = false +``` + + +### Metrics + +- `x509_cert` + - tags: + - `server` (only if `servers` parameter is defined) + - `file` (only if `files` parameter is defined) + - fields: + - `expiry` (int, seconds) + - `age` (int, seconds) + - `startdate` (int, seconds) + - `enddate` (int, seconds) + + +### Example output + +``` +x509_cert,server=google.com:443,host=myhost age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 1517845687000000000 +x509_cert,host=myhost,file=/path/to/the.crt age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000 +``` diff --git a/plugins/inputs/ssl_cert/ssl_cert.go b/plugins/inputs/x509_cert/x509_cert.go similarity index 53% rename from plugins/inputs/ssl_cert/ssl_cert.go rename to plugins/inputs/x509_cert/x509_cert.go index 8c5d9d5277f8b..d36affd1fdba3 100644 --- a/plugins/inputs/ssl_cert/ssl_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -1,4 +1,4 @@ -package ssl_cert +package x509_cert import ( "crypto/tls" @@ -8,27 +8,37 @@ import ( "fmt" "io/ioutil" "net" + "strings" "time" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal" + _tls "github.com/influxdata/telegraf/internal/tls" "github.com/influxdata/telegraf/plugins/inputs" ) const sampleConfig = ` ## List of local SSL files - # files = [] + # files = ["/etc/ssl/certs/ssl-cert-snakeoil.pem"] ## List of servers - # servers = [] + # servers = ["tcp://example.org:443"] ## Timeout for SSL connection # timeout = 5 + ## Optional TLS Config + # tls_ca = "/etc/telegraf/ca.pem" + # tls_cert = "/etc/telegraf/cert.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" -// SSLCert holds the configuration of the plugin. -type SSLCert struct { - Servers []string `toml:"servers"` - Files []string `toml:"files"` - Timeout time.Duration `toml:"timeout"` +// X509Cert holds the configuration of the plugin. +type X509Cert struct { + Servers []string `toml:"servers"` + Files []string `toml:"files"` + Timeout internal.Duration `toml:"timeout"` + _tls.ClientConfig } // For tests @@ -38,21 +48,31 @@ var ( ) // Description returns description of the plugin. -func (sc *SSLCert) Description() string { +func (c *X509Cert) Description() string { return description } // SampleConfig returns configuration sample for the plugin. -func (sc *SSLCert) SampleConfig() string { +func (c *X509Cert) SampleConfig() string { return sampleConfig } -func getRemoteCert(server string, timeout time.Duration) (*x509.Certificate, error) { - tlsCfg := &tls.Config{ - InsecureSkipVerify: true, +func (c *X509Cert) getRemoteCert(server string, timeout time.Duration) (*x509.Certificate, error) { + tlsCfg, err := c.ClientConfig.TLSConfig() + if err != nil { + return nil, err + } + + network := "tcp" + host_port := server + vals := strings.Split(server, "://") + + if len(vals) > 1 { + network = vals[0] + host_port = vals[1] } - ipConn, err := net.DialTimeout("tcp", server, timeout) + ipConn, err := net.DialTimeout(network, host_port, timeout) if err != nil { return nil, err } @@ -102,28 +122,30 @@ func getLocalCert(filename string) (*x509.Certificate, error) { return cert, nil } -func getMetrics(cert *x509.Certificate, now time.Time) map[string]interface{} { +func getFields(cert *x509.Certificate, now time.Time) map[string]interface{} { age := int(now.Sub(cert.NotBefore).Seconds()) expiry := int(cert.NotAfter.Sub(now).Seconds()) - startdate := int(cert.NotBefore.Unix()) - enddate := int(cert.NotAfter.Unix()) + startdate := cert.NotBefore.Unix() + enddate := cert.NotAfter.Unix() + valid := expiry > 0 - metrics := map[string]interface{}{ + fields := map[string]interface{}{ "age": age, "expiry": expiry, "startdate": startdate, "enddate": enddate, + "valid": valid, } - return metrics + return fields } // Gather adds metrics into the accumulator. -func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { +func (c *X509Cert) Gather(acc telegraf.Accumulator) error { now := time.Now() - for _, server := range sc.Servers { - cert, err := getRemoteCert(server, sc.Timeout*time.Second) + for _, server := range c.Servers { + cert, err := c.getRemoteCert(server, c.Timeout.Duration*time.Second) if err != nil { return fmt.Errorf("cannot get remote SSL cert '%s': %s", server, err) } @@ -132,12 +154,12 @@ func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { "server": server, } - fields := getMetrics(cert, now) + fields := getFields(cert, now) - acc.AddFields("ssl_cert", fields, tags) + acc.AddFields("x509_cert", fields, tags) } - for _, file := range sc.Files { + for _, file := range c.Files { cert, err := getLocalCert(file) if err != nil { return fmt.Errorf("cannot get local SSL cert '%s': %s", file, err) @@ -147,20 +169,20 @@ func (sc *SSLCert) Gather(acc telegraf.Accumulator) error { "file": file, } - fields := getMetrics(cert, now) + fields := getFields(cert, now) - acc.AddFields("ssl_cert", fields, tags) + acc.AddFields("x509_cert", fields, tags) } return nil } func init() { - inputs.Add("ssl_cert", func() telegraf.Input { - return &SSLCert{ + inputs.Add("x509_cert", func() telegraf.Input { + return &X509Cert{ Files: []string{}, Servers: []string{}, - Timeout: 5, + Timeout: internal.Duration{Duration: 5}, } }) } diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go new file mode 100644 index 0000000000000..0e13349e75d0f --- /dev/null +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -0,0 +1,169 @@ +package x509_cert + +import ( + "crypto/tls" + "encoding/base64" + "fmt" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/testutil" +) + +var pki = testutil.NewPKI("../../../testutil/pki") + +// Make sure X509Cert implements telegraf.Input +var _ telegraf.Input = &X509Cert{} + +func TestGatherRemote(t *testing.T) { + if testing.Short() { + t.Skip("Skipping network-dependent test in short mode.") + } + + tests := []struct { + server string + timeout time.Duration + close bool + unset bool + error bool + }{ + {server: ":99999", timeout: 0, close: false, unset: false, error: true}, + {server: "", timeout: 5, close: false, unset: false, error: false}, + {server: "", timeout: 5, close: false, unset: true, error: true}, + {server: "", timeout: 0, close: true, unset: false, error: true}, + } + + for i, test := range tests { + pair, err := tls.X509KeyPair([]byte(pki.ReadServerCert()), []byte(pki.ReadServerKey())) + if err != nil { + t.Fatal(err) + } + + config := &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{pair}, + } + + ln, err := tls.Listen("tcp", ":0", config) + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + go func() { + sconn, err := ln.Accept() + if err != nil { + return + } + + serverConfig := config.Clone() + + srv := tls.Server(sconn, serverConfig) + if err := srv.Handshake(); err != nil { + return + } + }() + + if test.server == "" { + test.server = ln.Addr().String() + } + + sc := X509Cert{ + Servers: []string{test.server}, + Timeout: internal.Duration{Duration: test.timeout}, + } + + closeConn = test.close + unsetCerts = test.unset + + error := false + + acc := testutil.Accumulator{} + err = sc.Gather(&acc) + if err != nil { + error = true + } + + if error != test.error { + t.Errorf("Test [%d]: %s.", i, err) + } + } +} + +func TestGatherLocal(t *testing.T) { + wrongCert := fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", base64.StdEncoding.EncodeToString([]byte("test"))) + + tests := []struct { + mode os.FileMode + content string + error bool + }{ + {mode: 0001, content: "", error: true}, + {mode: 0640, content: "test", error: true}, + {mode: 0640, content: wrongCert, error: true}, + {mode: 0640, content: pki.ReadServerCert(), error: false}, + } + + for i, test := range tests { + f, err := ioutil.TempFile("", "x509_cert") + if err != nil { + t.Fatal(err) + } + + _, err = f.Write([]byte(test.content)) + if err != nil { + t.Fatal(err) + } + + err = f.Chmod(test.mode) + if err != nil { + t.Fatal(err) + } + + err = f.Close() + if err != nil { + t.Fatal(err) + } + + defer os.Remove(f.Name()) + + sc := X509Cert{ + Files: []string{f.Name()}, + } + + error := false + + acc := testutil.Accumulator{} + err = sc.Gather(&acc) + if err != nil { + error = true + } + + if error != test.error { + t.Errorf("Test [%d]: %s.", i, err) + } + } +} + +func TestStrings(t *testing.T) { + sc := X509Cert{} + + tests := []struct { + method string + returned string + expected string + }{ + {method: "Description", returned: sc.Description(), expected: description}, + {method: "SampleConfig", returned: sc.SampleConfig(), expected: sampleConfig}, + } + + for i, test := range tests { + if test.returned != test.expected { + t.Errorf("Test [%d]: Expected method %s to return '%s', found '%s'.", i, test.method, test.expected, test.returned) + } + } +} From b4a4908b8ca62548b6d9f7949ee0ce468450423d Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Tue, 26 Jun 2018 10:58:26 +0100 Subject: [PATCH 05/14] Fixing test (thanks @sepetrov) --- plugins/inputs/x509_cert/x509_cert_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go index 0e13349e75d0f..54e8b3bde0dda 100644 --- a/plugins/inputs/x509_cert/x509_cert_test.go +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -45,7 +45,7 @@ func TestGatherRemote(t *testing.T) { config := &tls.Config{ InsecureSkipVerify: true, - Certificates: []tls.Certificate{pair}, + Certificates: []tls.Certificate{pair}, } ln, err := tls.Listen("tcp", ":0", config) @@ -77,6 +77,7 @@ func TestGatherRemote(t *testing.T) { Timeout: internal.Duration{Duration: test.timeout}, } + sc.InsecureSkipVerify = true closeConn = test.close unsetCerts = test.unset From 7d93a69fb935aeca9847dc93212f16c605bb724b Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Tue, 26 Jun 2018 11:36:25 +0100 Subject: [PATCH 06/14] Ordering imports --- 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 698f743410bc3..4f2d5f600f6c9 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -96,7 +96,6 @@ 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/x509_cert" _ "github.com/influxdata/telegraf/plugins/inputs/statsd" _ "github.com/influxdata/telegraf/plugins/inputs/syslog" _ "github.com/influxdata/telegraf/plugins/inputs/sysstat" @@ -114,6 +113,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/webhooks" _ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters" _ "github.com/influxdata/telegraf/plugins/inputs/win_services" + _ "github.com/influxdata/telegraf/plugins/inputs/x509_cert" _ "github.com/influxdata/telegraf/plugins/inputs/zfs" _ "github.com/influxdata/telegraf/plugins/inputs/zipkin" _ "github.com/influxdata/telegraf/plugins/inputs/zookeeper" From 146d6167e12faabc47c6e77a627b468c04406b75 Mon Sep 17 00:00:00 2001 From: Greg Linton Date: Fri, 20 Jul 2018 12:45:47 -0600 Subject: [PATCH 07/14] Remove unnecessary vars for testing --- plugins/inputs/x509_cert/x509_cert.go | 19 ++-------- plugins/inputs/x509_cert/x509_cert_test.go | 40 ++++++++++++++-------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index d36affd1fdba3..cdba8dae310b4 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" - "errors" "fmt" "io/ioutil" "net" @@ -41,12 +40,6 @@ type X509Cert struct { _tls.ClientConfig } -// For tests -var ( - closeConn bool - unsetCerts bool -) - // Description returns description of the plugin. func (c *X509Cert) Description() string { return description @@ -81,10 +74,6 @@ func (c *X509Cert) getRemoteCert(server string, timeout time.Duration) (*x509.Ce conn := tls.Client(ipConn, tlsCfg) defer conn.Close() - if closeConn { - conn.Close() - } - hsErr := conn.Handshake() if hsErr != nil { return nil, hsErr @@ -92,12 +81,8 @@ func (c *X509Cert) getRemoteCert(server string, timeout time.Duration) (*x509.Ce certs := conn.ConnectionState().PeerCertificates - if unsetCerts { - certs = nil - } - if certs == nil || len(certs) < 1 { - return nil, errors.New("couldn't get remote certificate") + return nil, fmt.Errorf("couldn't get remote certificate") } return certs[0], nil @@ -111,7 +96,7 @@ func getLocalCert(filename string) (*x509.Certificate, error) { block, _ := pem.Decode(content) if block == nil { - return nil, errors.New("failed to parse certificate PEM") + return nil, fmt.Errorf("failed to parse certificate PEM") } cert, err := x509.ParseCertificate(block.Bytes) diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go index 54e8b3bde0dda..900fae915a4b5 100644 --- a/plugins/inputs/x509_cert/x509_cert_test.go +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -29,23 +29,32 @@ func TestGatherRemote(t *testing.T) { timeout time.Duration close bool unset bool + noshake bool error bool }{ {server: ":99999", timeout: 0, close: false, unset: false, error: true}, {server: "", timeout: 5, close: false, unset: false, error: false}, {server: "", timeout: 5, close: false, unset: true, error: true}, {server: "", timeout: 0, close: true, unset: false, error: true}, + {server: "", timeout: 5, close: false, unset: false, noshake: true, error: true}, } - for i, test := range tests { - pair, err := tls.X509KeyPair([]byte(pki.ReadServerCert()), []byte(pki.ReadServerKey())) - if err != nil { - t.Fatal(err) - } + pair, err := tls.X509KeyPair([]byte(pki.ReadServerCert()), []byte(pki.ReadServerKey())) + if err != nil { + t.Fatal(err) + } + + config := &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{pair}, + } - config := &tls.Config{ - InsecureSkipVerify: true, - Certificates: []tls.Certificate{pair}, + for i, test := range tests { + if test.unset { + config.Certificates = nil + config.GetCertificate = func(i *tls.ClientHelloInfo) (*tls.Certificate, error) { + return nil, nil + } } ln, err := tls.Listen("tcp", ":0", config) @@ -59,10 +68,16 @@ func TestGatherRemote(t *testing.T) { if err != nil { return } + if test.close { + sconn.Close() + } serverConfig := config.Clone() srv := tls.Server(sconn, serverConfig) + if test.noshake { + srv.Close() + } if err := srv.Handshake(); err != nil { return } @@ -78,18 +93,15 @@ func TestGatherRemote(t *testing.T) { } sc.InsecureSkipVerify = true - closeConn = test.close - unsetCerts = test.unset - - error := false + testErr := false acc := testutil.Accumulator{} err = sc.Gather(&acc) if err != nil { - error = true + testErr = true } - if error != test.error { + if testErr != test.error { t.Errorf("Test [%d]: %s.", i, err) } } From e53e1e7a7474fd9c31d09e6d57c6828fefafa222 Mon Sep 17 00:00:00 2001 From: Greg Linton Date: Fri, 20 Jul 2018 13:13:26 -0600 Subject: [PATCH 08/14] Unify cert gathering --- plugins/inputs/x509_cert/x509_cert.go | 133 ++++++++++----------- plugins/inputs/x509_cert/x509_cert_test.go | 6 +- 2 files changed, 65 insertions(+), 74 deletions(-) diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index cdba8dae310b4..70ffff5f64d7a 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -1,3 +1,4 @@ +// Package x509_cert reports metrics from an SSL certificate. package x509_cert import ( @@ -7,7 +8,7 @@ import ( "fmt" "io/ioutil" "net" - "strings" + "net/url" "time" "github.com/influxdata/telegraf" @@ -17,16 +18,17 @@ import ( ) const sampleConfig = ` - ## List of local SSL files - # files = ["/etc/ssl/certs/ssl-cert-snakeoil.pem"] - ## List of servers - # servers = ["tcp://example.org:443"] + ## List of servers and local SSL files + # sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443"] + ## Timeout for SSL connection # timeout = 5 + ## Optional TLS Config # tls_ca = "/etc/telegraf/ca.pem" # tls_cert = "/etc/telegraf/cert.pem" # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification # insecure_skip_verify = false ` @@ -34,8 +36,7 @@ const description = "Reads metrics from a SSL certificate" // X509Cert holds the configuration of the plugin. type X509Cert struct { - Servers []string `toml:"servers"` - Files []string `toml:"files"` + Sources []string `toml:"sources"` Timeout internal.Duration `toml:"timeout"` _tls.ClientConfig } @@ -50,61 +51,65 @@ func (c *X509Cert) SampleConfig() string { return sampleConfig } -func (c *X509Cert) getRemoteCert(server string, timeout time.Duration) (*x509.Certificate, error) { - tlsCfg, err := c.ClientConfig.TLSConfig() +func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Certificate, error) { + u, err := url.Parse(location) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse cert location - %s\n", err.Error()) } - network := "tcp" - host_port := server - vals := strings.Split(server, "://") - - if len(vals) > 1 { - network = vals[0] - host_port = vals[1] - } + switch u.Scheme { + case "https": + u.Scheme = "tcp" + fallthrough + case "udp": + fallthrough + case "tcp": + tlsCfg, err := c.ClientConfig.TLSConfig() + if err != nil { + return nil, err + } - ipConn, err := net.DialTimeout(network, host_port, timeout) - if err != nil { - return nil, err - } - defer ipConn.Close() + ipConn, err := net.DialTimeout(u.Scheme, u.Host, timeout) + if err != nil { + return nil, err + } + defer ipConn.Close() - conn := tls.Client(ipConn, tlsCfg) - defer conn.Close() + conn := tls.Client(ipConn, tlsCfg) + defer conn.Close() - hsErr := conn.Handshake() - if hsErr != nil { - return nil, hsErr - } + hsErr := conn.Handshake() + if hsErr != nil { + return nil, hsErr + } - certs := conn.ConnectionState().PeerCertificates + certs := conn.ConnectionState().PeerCertificates - if certs == nil || len(certs) < 1 { - return nil, fmt.Errorf("couldn't get remote certificate") - } + if certs == nil || len(certs) < 1 { + return nil, fmt.Errorf("couldn't get remote certificate") + } - return certs[0], nil -} + return certs, nil + case "file": + fallthrough + default: + content, err := ioutil.ReadFile(u.Path) + if err != nil { + return nil, err + } -func getLocalCert(filename string) (*x509.Certificate, error) { - content, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } + block, _ := pem.Decode(content) + if block == nil { + return nil, fmt.Errorf("failed to parse certificate PEM") + } - block, _ := pem.Decode(content) - if block == nil { - return nil, fmt.Errorf("failed to parse certificate PEM") - } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err + return []*x509.Certificate{cert}, nil } - - return cert, nil } func getFields(cert *x509.Certificate, now time.Time) map[string]interface{} { @@ -129,34 +134,21 @@ func getFields(cert *x509.Certificate, now time.Time) map[string]interface{} { func (c *X509Cert) Gather(acc telegraf.Accumulator) error { now := time.Now() - for _, server := range c.Servers { - cert, err := c.getRemoteCert(server, c.Timeout.Duration*time.Second) + for _, location := range c.Sources { + certs, err := c.getCert(location, c.Timeout.Duration*time.Second) if err != nil { - return fmt.Errorf("cannot get remote SSL cert '%s': %s", server, err) + return fmt.Errorf("cannot get SSL cert '%s': %s", location, err.Error()) } tags := map[string]string{ - "server": server, + "source": location, } - fields := getFields(cert, now) + for _, cert := range certs { + fields := getFields(cert, now) - acc.AddFields("x509_cert", fields, tags) - } - - for _, file := range c.Files { - cert, err := getLocalCert(file) - if err != nil { - return fmt.Errorf("cannot get local SSL cert '%s': %s", file, err) - } - - tags := map[string]string{ - "file": file, + acc.AddFields("x509_cert", fields, tags) } - - fields := getFields(cert, now) - - acc.AddFields("x509_cert", fields, tags) } return nil @@ -165,8 +157,7 @@ func (c *X509Cert) Gather(acc telegraf.Accumulator) error { func init() { inputs.Add("x509_cert", func() telegraf.Input { return &X509Cert{ - Files: []string{}, - Servers: []string{}, + Sources: []string{}, Timeout: internal.Duration{Duration: 5}, } }) diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go index 900fae915a4b5..113886b3d621a 100644 --- a/plugins/inputs/x509_cert/x509_cert_test.go +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -84,11 +84,11 @@ func TestGatherRemote(t *testing.T) { }() if test.server == "" { - test.server = ln.Addr().String() + test.server = "tcp://" + ln.Addr().String() } sc := X509Cert{ - Servers: []string{test.server}, + Sources: []string{test.server}, Timeout: internal.Duration{Duration: test.timeout}, } @@ -145,7 +145,7 @@ func TestGatherLocal(t *testing.T) { defer os.Remove(f.Name()) sc := X509Cert{ - Files: []string{f.Name()}, + Sources: []string{f.Name()}, } error := false From d0e540ad6fef94432ee1ba252311928c5e3a52b0 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Sun, 22 Jul 2018 21:23:18 +0100 Subject: [PATCH 09/14] Align README with the code --- plugins/inputs/x509_cert/README.md | 16 ++++++++-------- plugins/inputs/x509_cert/x509_cert.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/inputs/x509_cert/README.md b/plugins/inputs/x509_cert/README.md index 44554b2a82d4a..0758ecaaa3027 100644 --- a/plugins/inputs/x509_cert/README.md +++ b/plugins/inputs/x509_cert/README.md @@ -9,16 +9,17 @@ file or network connection. ```toml # Reads metrics from a SSL certificate [[inputs.x509_cert]] - ## List of local SSL files - # files = ["/etc/ssl/certs/ssl-cert-snakeoil.pem"] - ## List of servers - # servers = ["tcp://example.org:443"] + ## List certificate sources + # sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "https://example.org"] + ## Timeout for SSL connection # timeout = 5 + ## Optional TLS Config # tls_ca = "/etc/telegraf/ca.pem" # tls_cert = "/etc/telegraf/cert.pem" # tls_key = "/etc/telegraf/key.pem" + ## Use TLS but skip chain & host verification # insecure_skip_verify = false ``` @@ -28,8 +29,7 @@ file or network connection. - `x509_cert` - tags: - - `server` (only if `servers` parameter is defined) - - `file` (only if `files` parameter is defined) + - `source` - source of the certificate - fields: - `expiry` (int, seconds) - `age` (int, seconds) @@ -40,6 +40,6 @@ file or network connection. ### Example output ``` -x509_cert,server=google.com:443,host=myhost age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 1517845687000000000 -x509_cert,host=myhost,file=/path/to/the.crt age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000 +x509_cert,host=myhost,source=https://example.org age=1753627i,expiry=5503972i,startdate=1516092060i,enddate=1523349660i 1517845687000000000 +x509_cert,host=myhost,source=/etc/ssl/certs/ssl-cert-snakeoil.pem age=7522207i,expiry=308002732i,startdate=1510323480i,enddate=1825848420i 1517845687000000000 ``` diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index 70ffff5f64d7a..2de328290d77f 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -18,7 +18,7 @@ import ( ) const sampleConfig = ` - ## List of servers and local SSL files + ## List certificate sources # sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443"] ## Timeout for SSL connection From 4d28028ba1664865791dd2cf7e9a2b9a8ff366f9 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Sun, 22 Jul 2018 21:24:13 +0100 Subject: [PATCH 10/14] Improving test coverage --- plugins/inputs/x509_cert/x509_cert_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go index 113886b3d621a..2e9e2bdc84c68 100644 --- a/plugins/inputs/x509_cert/x509_cert_test.go +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -24,6 +24,17 @@ func TestGatherRemote(t *testing.T) { t.Skip("Skipping network-dependent test in short mode.") } + tmpfile, err := ioutil.TempFile("", "example") + if err != nil { + t.Fatal(err) + } + + defer os.Remove(tmpfile.Name()) + + if _, err := tmpfile.Write([]byte(pki.ReadServerCert())); err != nil { + t.Fatal(err) + } + tests := []struct { server string timeout time.Duration @@ -34,6 +45,8 @@ func TestGatherRemote(t *testing.T) { }{ {server: ":99999", timeout: 0, close: false, unset: false, error: true}, {server: "", timeout: 5, close: false, unset: false, error: false}, + {server: "https://example.org:443", timeout: 5, close: false, unset: false, error: false}, + {server: "file://" + tmpfile.Name(), timeout: 5, close: false, unset: false, error: false}, {server: "", timeout: 5, close: false, unset: true, error: true}, {server: "", timeout: 0, close: true, unset: false, error: true}, {server: "", timeout: 5, close: false, unset: false, noshake: true, error: true}, From 8649314dfa57fe87ad25463a1fa728ba29f5ade3 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Mon, 23 Jul 2018 16:05:00 +0100 Subject: [PATCH 11/14] Removing unnecessary check --- plugins/inputs/x509_cert/x509_cert.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index 2de328290d77f..5f6eac352718d 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -85,10 +85,6 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert certs := conn.ConnectionState().PeerCertificates - if certs == nil || len(certs) < 1 { - return nil, fmt.Errorf("couldn't get remote certificate") - } - return certs, nil case "file": fallthrough From 0e8cfe6e0e1a9ae4a522ff1bd6e0b1f9e6df084b Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Mon, 30 Jul 2018 00:50:03 +0100 Subject: [PATCH 12/14] Implementing review comments from danielnelson --- plugins/inputs/x509_cert/README.md | 4 ++-- plugins/inputs/x509_cert/x509_cert.go | 21 ++++++++++++--------- plugins/inputs/x509_cert/x509_cert_test.go | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/plugins/inputs/x509_cert/README.md b/plugins/inputs/x509_cert/README.md index 0758ecaaa3027..6b922f0e12606 100644 --- a/plugins/inputs/x509_cert/README.md +++ b/plugins/inputs/x509_cert/README.md @@ -10,10 +10,10 @@ file or network connection. # Reads metrics from a SSL certificate [[inputs.x509_cert]] ## List certificate sources - # sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "https://example.org"] + sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "https://example.org"] ## Timeout for SSL connection - # timeout = 5 + # timeout = 5s ## Optional TLS Config # tls_ca = "/etc/telegraf/ca.pem" diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index 5f6eac352718d..cb43b4e295075 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "net/url" + "strings" "time" "github.com/influxdata/telegraf" @@ -19,10 +20,10 @@ import ( const sampleConfig = ` ## List certificate sources - # sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443"] + sources = ["/etc/ssl/certs/ssl-cert-snakeoil.pem", "tcp://example.org:443"] ## Timeout for SSL connection - # timeout = 5 + # timeout = 5s ## Optional TLS Config # tls_ca = "/etc/telegraf/ca.pem" @@ -47,11 +48,15 @@ func (c *X509Cert) Description() string { } // SampleConfig returns configuration sample for the plugin. -func (c *X509Cert) SampleConfig() string { + func (c *X509Cert) SampleConfig() string { return sampleConfig } func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Certificate, error) { + if strings.HasPrefix(location, "/") { + location = "file://" + location + } + u, err := url.Parse(location) if err != nil { return nil, fmt.Errorf("failed to parse cert location - %s\n", err.Error()) @@ -61,9 +66,9 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert case "https": u.Scheme = "tcp" fallthrough - case "udp": + case "udp", "udp4", "udp6": fallthrough - case "tcp": + case "tcp", "tcp4", "tcp6": tlsCfg, err := c.ClientConfig.TLSConfig() if err != nil { return nil, err @@ -87,8 +92,6 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert return certs, nil case "file": - fallthrough - default: content, err := ioutil.ReadFile(u.Path) if err != nil { return nil, err @@ -105,6 +108,8 @@ func (c *X509Cert) getCert(location string, timeout time.Duration) ([]*x509.Cert } return []*x509.Certificate{cert}, nil + default: + return nil, fmt.Errorf("unsuported scheme '%s' in location %s\n", u.Scheme, location) } } @@ -113,14 +118,12 @@ func getFields(cert *x509.Certificate, now time.Time) map[string]interface{} { expiry := int(cert.NotAfter.Sub(now).Seconds()) startdate := cert.NotBefore.Unix() enddate := cert.NotAfter.Unix() - valid := expiry > 0 fields := map[string]interface{}{ "age": age, "expiry": expiry, "startdate": startdate, "enddate": enddate, - "valid": valid, } return fields diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go index 2e9e2bdc84c68..d0431718b1f01 100644 --- a/plugins/inputs/x509_cert/x509_cert_test.go +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -47,6 +47,7 @@ func TestGatherRemote(t *testing.T) { {server: "", timeout: 5, close: false, unset: false, error: false}, {server: "https://example.org:443", timeout: 5, close: false, unset: false, error: false}, {server: "file://" + tmpfile.Name(), timeout: 5, close: false, unset: false, error: false}, + {server: "foo://", timeout: 5, close: false, unset: false, error: true}, {server: "", timeout: 5, close: false, unset: true, error: true}, {server: "", timeout: 0, close: true, unset: false, error: true}, {server: "", timeout: 5, close: false, unset: false, noshake: true, error: true}, From 35b29c440e245f6b2e804c5e3c8ea854079cd058 Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Mon, 30 Jul 2018 01:33:25 +0100 Subject: [PATCH 13/14] Changing tests to use subtasks --- plugins/inputs/x509_cert/x509_cert_test.go | 195 +++++++++++---------- 1 file changed, 102 insertions(+), 93 deletions(-) diff --git a/plugins/inputs/x509_cert/x509_cert_test.go b/plugins/inputs/x509_cert/x509_cert_test.go index d0431718b1f01..f4c6c873876f2 100644 --- a/plugins/inputs/x509_cert/x509_cert_test.go +++ b/plugins/inputs/x509_cert/x509_cert_test.go @@ -36,6 +36,7 @@ func TestGatherRemote(t *testing.T) { } tests := []struct { + name string server string timeout time.Duration close bool @@ -43,14 +44,14 @@ func TestGatherRemote(t *testing.T) { noshake bool error bool }{ - {server: ":99999", timeout: 0, close: false, unset: false, error: true}, - {server: "", timeout: 5, close: false, unset: false, error: false}, - {server: "https://example.org:443", timeout: 5, close: false, unset: false, error: false}, - {server: "file://" + tmpfile.Name(), timeout: 5, close: false, unset: false, error: false}, - {server: "foo://", timeout: 5, close: false, unset: false, error: true}, - {server: "", timeout: 5, close: false, unset: true, error: true}, - {server: "", timeout: 0, close: true, unset: false, error: true}, - {server: "", timeout: 5, close: false, unset: false, noshake: true, error: true}, + {name: "wrong port", server: ":99999", error: true}, + {name: "no server", timeout: 5}, + {name: "successful https", server: "https://example.org:443", timeout: 5}, + {name: "successful file", server: "file://" + tmpfile.Name(), timeout: 5}, + {name: "unsupported scheme", server: "foo://", timeout: 5, error: true}, + {name: "no certificate", timeout: 5, unset: true, error: true}, + {name: "closed connection", close: true, error: true}, + {name: "no handshake", timeout: 5, noshake: true, error: true}, } pair, err := tls.X509KeyPair([]byte(pki.ReadServerCert()), []byte(pki.ReadServerKey())) @@ -63,61 +64,63 @@ func TestGatherRemote(t *testing.T) { Certificates: []tls.Certificate{pair}, } - for i, test := range tests { - if test.unset { - config.Certificates = nil - config.GetCertificate = func(i *tls.ClientHelloInfo) (*tls.Certificate, error) { - return nil, nil + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.unset { + config.Certificates = nil + config.GetCertificate = func(i *tls.ClientHelloInfo) (*tls.Certificate, error) { + return nil, nil + } } - } - ln, err := tls.Listen("tcp", ":0", config) - if err != nil { - t.Fatal(err) - } - defer ln.Close() - - go func() { - sconn, err := ln.Accept() + ln, err := tls.Listen("tcp", ":0", config) if err != nil { - return + t.Fatal(err) } - if test.close { - sconn.Close() + defer ln.Close() + + go func() { + sconn, err := ln.Accept() + if err != nil { + return + } + if test.close { + sconn.Close() + } + + serverConfig := config.Clone() + + srv := tls.Server(sconn, serverConfig) + if test.noshake { + srv.Close() + } + if err := srv.Handshake(); err != nil { + return + } + }() + + if test.server == "" { + test.server = "tcp://" + ln.Addr().String() } - serverConfig := config.Clone() - - srv := tls.Server(sconn, serverConfig) - if test.noshake { - srv.Close() - } - if err := srv.Handshake(); err != nil { - return + sc := X509Cert{ + Sources: []string{test.server}, + Timeout: internal.Duration{Duration: test.timeout}, } - }() - if test.server == "" { - test.server = "tcp://" + ln.Addr().String() - } + sc.InsecureSkipVerify = true + testErr := false - sc := X509Cert{ - Sources: []string{test.server}, - Timeout: internal.Duration{Duration: test.timeout}, - } - - sc.InsecureSkipVerify = true - testErr := false - - acc := testutil.Accumulator{} - err = sc.Gather(&acc) - if err != nil { - testErr = true - } + acc := testutil.Accumulator{} + err = sc.Gather(&acc) + if err != nil { + testErr = true + } - if testErr != test.error { - t.Errorf("Test [%d]: %s.", i, err) - } + if testErr != test.error { + t.Errorf("%s", err) + } + }) } } @@ -125,54 +128,57 @@ func TestGatherLocal(t *testing.T) { wrongCert := fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", base64.StdEncoding.EncodeToString([]byte("test"))) tests := []struct { + name string mode os.FileMode content string error bool }{ - {mode: 0001, content: "", error: true}, - {mode: 0640, content: "test", error: true}, - {mode: 0640, content: wrongCert, error: true}, - {mode: 0640, content: pki.ReadServerCert(), error: false}, + {name: "permission denied", mode: 0001, error: true}, + {name: "not a certificate", mode: 0640, content: "test", error: true}, + {name: "wrong certificate", mode: 0640, content: wrongCert, error: true}, + {name: "correct certificate", mode: 0640, content: pki.ReadServerCert()}, } - for i, test := range tests { - f, err := ioutil.TempFile("", "x509_cert") - if err != nil { - t.Fatal(err) - } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f, err := ioutil.TempFile("", "x509_cert") + if err != nil { + t.Fatal(err) + } - _, err = f.Write([]byte(test.content)) - if err != nil { - t.Fatal(err) - } + _, err = f.Write([]byte(test.content)) + if err != nil { + t.Fatal(err) + } - err = f.Chmod(test.mode) - if err != nil { - t.Fatal(err) - } + err = f.Chmod(test.mode) + if err != nil { + t.Fatal(err) + } - err = f.Close() - if err != nil { - t.Fatal(err) - } + err = f.Close() + if err != nil { + t.Fatal(err) + } - defer os.Remove(f.Name()) + defer os.Remove(f.Name()) - sc := X509Cert{ - Sources: []string{f.Name()}, - } + sc := X509Cert{ + Sources: []string{f.Name()}, + } - error := false + error := false - acc := testutil.Accumulator{} - err = sc.Gather(&acc) - if err != nil { - error = true - } + acc := testutil.Accumulator{} + err = sc.Gather(&acc) + if err != nil { + error = true + } - if error != test.error { - t.Errorf("Test [%d]: %s.", i, err) - } + if error != test.error { + t.Errorf("%s", err) + } + }) } } @@ -180,17 +186,20 @@ func TestStrings(t *testing.T) { sc := X509Cert{} tests := []struct { + name string method string returned string expected string }{ - {method: "Description", returned: sc.Description(), expected: description}, - {method: "SampleConfig", returned: sc.SampleConfig(), expected: sampleConfig}, + {name: "description", method: "Description", returned: sc.Description(), expected: description}, + {name: "sample config", method: "SampleConfig", returned: sc.SampleConfig(), expected: sampleConfig}, } - for i, test := range tests { - if test.returned != test.expected { - t.Errorf("Test [%d]: Expected method %s to return '%s', found '%s'.", i, test.method, test.expected, test.returned) - } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.returned != test.expected { + t.Errorf("Expected method %s to return '%s', found '%s'.", test.method, test.expected, test.returned) + } + }) } } From 8a1f0e42f405155bee3e7851b44f34b8972dbead Mon Sep 17 00:00:00 2001 From: Jiri Tyr Date: Mon, 30 Jul 2018 01:34:59 +0100 Subject: [PATCH 14/14] Fixing formatting --- plugins/inputs/x509_cert/x509_cert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/x509_cert/x509_cert.go b/plugins/inputs/x509_cert/x509_cert.go index cb43b4e295075..2e5d26996f6a3 100644 --- a/plugins/inputs/x509_cert/x509_cert.go +++ b/plugins/inputs/x509_cert/x509_cert.go @@ -48,7 +48,7 @@ func (c *X509Cert) Description() string { } // SampleConfig returns configuration sample for the plugin. - func (c *X509Cert) SampleConfig() string { +func (c *X509Cert) SampleConfig() string { return sampleConfig }