Skip to content

Commit

Permalink
Add starttls for smtp, imap and ftp (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
ribbybibby authored Jun 22, 2020
1 parent 1c8bd16 commit 89eff28
Show file tree
Hide file tree
Showing 9 changed files with 499 additions and 28 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ meaningful visualisations and consoles.
- [Configuration file](#configuration-file)
- [<module>](#module)
- [<tls_config>](#tls_config)
- [<https_probe>](#https_probe)
- [<tcp_probe>](#tcp_probe)
- [Example Queries](#example-queries)
- [Proxying](#proxying)
- [Grafana](#grafana)
Expand Down Expand Up @@ -152,14 +154,15 @@ modules: [<module>]
#### \<module\>
```
# The protocol over which the probe will take place (http, tcp)
# The protocol over which the probe will take place (https, tcp)
prober: <prober_string>

# Configuration for TLS
[ tls_config: <tls_config> ]

# The specific probe configuration
[ https: <https_probe> ]
[ tcp: <tcp_probe> ]
```
#### <tls_config>
Expand Down Expand Up @@ -188,6 +191,13 @@ prober: <prober_string>
[ proxy_url: <string> ]
```
#### <tcp_probe>
```
# Use the STARTTLS command before starting TLS for those protocols that support it (smtp, ftp, imap)
[ starttls: <string> ]
```
## Example Queries
Certificates that expire within 7 days:
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ type Module struct {
Prober string `yaml:"prober,omitempty"`
TLSConfig config.TLSConfig `yaml:"tls_config,omitempty"`
HTTPS HTTPSProbe `yaml:"https,omitempty"`
TCP TCPProbe `yaml:"tcp,omitempty"`
}

type TCPProbe struct {
StartTLS string `yaml:"starttls,omitempty"`
}

type HTTPSProbe struct {
Expand Down
4 changes: 4 additions & 0 deletions examples/ssl_exporter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ modules:
ca_file: /etc/tls/ca.crt
cert_file: /etc/tls/tls.crt
key_file: /etc/tls/tls.key
tcp_smtp_starttls:
prober: tcp
tcp:
starttls: smtp
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
Expand Down Expand Up @@ -69,6 +70,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -167,6 +169,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
145 changes: 141 additions & 4 deletions prober/tcp.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,166 @@
package prober

import (
"bufio"
"crypto/tls"
"fmt"
"net"
"regexp"
"time"

"github.com/ribbybibby/ssl_exporter/config"

pconfig "github.com/prometheus/common/config"
"github.com/prometheus/common/log"
)

// ProbeTCP performs a tcp probe
func ProbeTCP(target string, module config.Module, timeout time.Duration) (*tls.ConnectionState, error) {
tlsConfig, err := pconfig.NewTLSConfig(&module.TLSConfig)
dialer := &net.Dialer{Timeout: timeout}

conn, err := dialer.Dial("tcp", target)
if err != nil {
return nil, err
}
defer conn.Close()

if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
return nil, fmt.Errorf("Error setting deadline")
}

if module.TCP.StartTLS != "" {
err = startTLS(conn, module.TCP.StartTLS)
if err != nil {
return nil, err
}
}

conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", target, tlsConfig)
tlsConfig, err := pconfig.NewTLSConfig(&module.TLSConfig)
if err != nil {
return nil, err
}
defer conn.Close()

state := conn.ConnectionState()
if tlsConfig.ServerName == "" {
targetAddress, _, err := net.SplitHostPort(target)
if err != nil {
return nil, err
}
tlsConfig.ServerName = targetAddress
}

tlsConn := tls.Client(conn, tlsConfig)
defer tlsConn.Close()

if err := tlsConn.Handshake(); err != nil {
return nil, err
}

state := tlsConn.ConnectionState()

return &state, nil
}

type queryResponse struct {
expect string
send string
}

var (
// These are the protocols for which I had servers readily available to test
// against. There are plenty of other protocols that should be added here in
// the future.
//
// See openssl s_client for more examples:
// https://github.com/openssl/openssl/blob/openssl-3.0.0-alpha3/apps/s_client.c#L2229-L2728
startTLSqueryResponses = map[string][]queryResponse{
"smtp": []queryResponse{
queryResponse{
expect: "^220",
},
queryResponse{
send: "EHLO prober",
},
queryResponse{
expect: "^250-STARTTLS",
},
queryResponse{
send: "STARTTLS",
},
queryResponse{
expect: "^220",
},
},
"ftp": []queryResponse{
queryResponse{
expect: "^220",
},
queryResponse{
send: "AUTH TLS",
},
queryResponse{
expect: "^234",
},
},
"imap": []queryResponse{
queryResponse{
expect: "OK",
},
queryResponse{
send: ". CAPABILITY",
},
queryResponse{
expect: "STARTTLS",
},
queryResponse{
expect: "OK",
},
queryResponse{
send: ". STARTTLS",
},
queryResponse{
expect: "OK",
},
},
}
)

// startTLS will send the STARTTLS command for the given protocol
func startTLS(conn net.Conn, proto string) error {
var err error

qr, ok := startTLSqueryResponses[proto]
if !ok {
return fmt.Errorf("STARTTLS is not supported for %s", proto)
}

scanner := bufio.NewScanner(conn)
for _, qr := range qr {
if qr.expect != "" {
var match bool
for scanner.Scan() {
log.Debugf("read line: %s", scanner.Text())
match, err = regexp.Match(qr.expect, scanner.Bytes())
if err != nil {
return err
}
if match {
log.Debugf("regex: %s matched: %s", qr.expect, scanner.Text())
break
}
}
if scanner.Err() != nil {
return scanner.Err()
}
if !match {
return fmt.Errorf("regex: %s didn't match: %s", qr.expect, scanner.Text())
}
}
if qr.send != "" {
log.Debugf("sending line: %s", qr.send)
if _, err := fmt.Fprintf(conn, "%s\r\n", qr.send); err != nil {
return err
}
}
}
return nil
}
78 changes: 78 additions & 0 deletions prober/tcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,81 @@ func TestProbeTCPExpiredInsecure(t *testing.T) {
t.Fatalf("expected state but got nil")
}
}

// TestProbeTCPStartTLSSMTP tests STARTTLS against a mock SMTP server
func TestProbeTCPStartTLSSMTP(t *testing.T) {
server, _, _, caFile, teardown, err := test.SetupTCPServer()
if err != nil {
t.Fatalf(err.Error())
}
defer teardown()

server.StartSMTP()
defer server.Close()

module := config.Module{
TCP: config.TCPProbe{
StartTLS: "smtp",
},
TLSConfig: pconfig.TLSConfig{
CAFile: caFile,
InsecureSkipVerify: false,
},
}

if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
t.Fatalf("error: %s", err)
}
}

// TestProbeTCPStartTLSFTP tests STARTTLS against a mock FTP server
func TestProbeTCPStartTLSFTP(t *testing.T) {
server, _, _, caFile, teardown, err := test.SetupTCPServer()
if err != nil {
t.Fatalf(err.Error())
}
defer teardown()

server.StartFTP()
defer server.Close()

module := config.Module{
TCP: config.TCPProbe{
StartTLS: "ftp",
},
TLSConfig: pconfig.TLSConfig{
CAFile: caFile,
InsecureSkipVerify: false,
},
}

if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
t.Fatalf("error: %s", err)
}
}

// TestProbeTCPStartTLSIMAP tests STARTTLS against a mock IMAP server
func TestProbeTCPStartTLSIMAP(t *testing.T) {
server, _, _, caFile, teardown, err := test.SetupTCPServer()
if err != nil {
t.Fatalf(err.Error())
}
defer teardown()

server.StartIMAP()
defer server.Close()

module := config.Module{
TCP: config.TCPProbe{
StartTLS: "imap",
},
TLSConfig: pconfig.TLSConfig{
CAFile: caFile,
InsecureSkipVerify: false,
},
}

if _, err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second); err != nil {
t.Fatalf("error: %s", err)
}
}
1 change: 1 addition & 0 deletions ssl_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {

state, err := e.prober(e.target, e.module, e.timeout)
if err != nil {
log.Errorln(err)
ch <- prometheus.MustNewConstMetric(
tlsConnectSuccess, prometheus.GaugeValue, 0,
)
Expand Down
Loading

0 comments on commit 89eff28

Please sign in to comment.