mirror of
https://github.com/ribbybibby/ssl_exporter.git
synced 2024-11-24 08:22:17 +02:00
195 lines
4.3 KiB
Go
195 lines
4.3 KiB
Go
package prober
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"regexp"
|
|
|
|
"github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/ribbybibby/ssl_exporter/v2/config"
|
|
)
|
|
|
|
// ProbeTCP performs a tcp probe
|
|
func ProbeTCP(ctx context.Context, logger log.Logger, target string, module config.Module, registry *prometheus.Registry) error {
|
|
tlsConfig, err := newTLSConfig(target, registry, &module.TLSConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dialer := &net.Dialer{}
|
|
conn, err := dialer.DialContext(ctx, "tcp", target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
deadline, _ := ctx.Deadline()
|
|
if err := conn.SetDeadline(deadline); err != nil {
|
|
return fmt.Errorf("Error setting deadline")
|
|
}
|
|
|
|
if module.TCP.StartTLS != "" {
|
|
err = startTLS(logger, conn, module.TCP.StartTLS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
tlsConn := tls.Client(conn, tlsConfig)
|
|
defer tlsConn.Close()
|
|
|
|
return tlsConn.Handshake()
|
|
}
|
|
|
|
type queryResponse struct {
|
|
expect string
|
|
send string
|
|
sendBytes []byte
|
|
expectBytes []byte
|
|
}
|
|
|
|
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",
|
|
},
|
|
},
|
|
"postgres": []queryResponse{
|
|
queryResponse{
|
|
sendBytes: []byte{0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f},
|
|
},
|
|
queryResponse{
|
|
expectBytes: []byte{0x53},
|
|
},
|
|
},
|
|
"pop3": []queryResponse{
|
|
queryResponse{
|
|
expect: "OK",
|
|
},
|
|
queryResponse{
|
|
send: "STLS",
|
|
},
|
|
queryResponse{
|
|
expect: "OK",
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
// startTLS will send the STARTTLS command for the given protocol
|
|
func startTLS(logger log.Logger, 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() {
|
|
level.Debug(logger).Log("msg", fmt.Sprintf("read line: %s", scanner.Text()))
|
|
match, err = regexp.Match(qr.expect, scanner.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if match {
|
|
level.Debug(logger).Log("msg", fmt.Sprintf("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 len(qr.expectBytes) > 0 {
|
|
buffer := make([]byte, len(qr.expectBytes))
|
|
_, err = io.ReadFull(conn, buffer)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
level.Debug(logger).Log("msg", fmt.Sprintf("read bytes: %x", buffer))
|
|
if bytes.Compare(buffer, qr.expectBytes) != 0 {
|
|
return fmt.Errorf("read bytes %x didn't match with expected bytes %x", buffer, qr.expectBytes)
|
|
} else {
|
|
level.Debug(logger).Log("msg", fmt.Sprintf("expected bytes %x matched with read bytes %x", qr.expectBytes, buffer))
|
|
}
|
|
}
|
|
if qr.send != "" {
|
|
level.Debug(logger).Log("msg", fmt.Sprintf("sending line: %s", qr.send))
|
|
if _, err := fmt.Fprintf(conn, "%s\r\n", qr.send); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(qr.sendBytes) > 0 {
|
|
level.Debug(logger).Log("msg", fmt.Sprintf("sending bytes: %x", qr.sendBytes))
|
|
if _, err = conn.Write(qr.sendBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|