Skip to content

Commit

Permalink
Support TLS client authentication (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
joesis authored and oliver006 committed Jun 21, 2019
1 parent d140a26 commit e949f34
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 8 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ redis-only-metrics | REDIS_EXPORTER_REDIS_ONLY_METRICS | Whether to also
include-system-metrics | REDIS_EXPORTER_INCL_SYSTEM_METRICS | Whether to include system metrics like `total_system_memory_bytes`, defaults to false.
is-tile38 | REDIS_EXPORTER_IS_TILE38 | Whether to scrape Tile38 specific metrics, defaults to false.
skip-tls-verification | REDIS_EXPORTER_SKIP_TLS_VERIFICATION | Whether to to skip TLS verification
tls-client-key-file | REDIS_EXPORTER_TLS_CLIENT_KEY_FILE | Name of the client key file (including full path) if the server requires TLS client authentication
tls-client-cert-file | REDIS_EXPORTER_TLS_CLIENT_CERT_FILE | Name the client cert file (including full path) if the server requires TLS client authentication

Redis instance addresses can be tcp addresses: `redis://localhost:6379`, `redis.example.com:6379` or e.g. unix sockets: `unix:///tmp/redis.sock`.\
SSL is supported by using the `rediss://` schema, for example: `rediss://azure-ssl-enabled-host.redis.cache.windows.net:6380` (note that the port is required when connecting to a non-standard 6379 port, e.g. with Azure Redis instances).\
Expand Down
8 changes: 5 additions & 3 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ExporterOptions struct {
ConfigCommandName string
CheckSingleKeys string
CheckKeys string
ClientCertificates []tls.Certificate
InclSystemMetrics bool
SkipTLSVerification bool
IsTile38 bool
Expand Down Expand Up @@ -970,14 +971,15 @@ func getKeysFromPatterns(c redis.Conn, keys []dbKeyPair) (expandedKeys []dbKeyPa
return expandedKeys, err
}

func (e *Exporter) connectToRedis(skipTLSVerification bool) (redis.Conn, error) {
func (e *Exporter) connectToRedis() (redis.Conn, error) {
options := []redis.DialOption{
redis.DialConnectTimeout(e.options.ConnectionTimeouts),
redis.DialReadTimeout(e.options.ConnectionTimeouts),
redis.DialWriteTimeout(e.options.ConnectionTimeouts),

redis.DialTLSConfig(&tls.Config{
InsecureSkipVerify: skipTLSVerification,
InsecureSkipVerify: e.options.SkipTLSVerification,
Certificates: e.options.ClientCertificates,
}),
}

Expand Down Expand Up @@ -1005,7 +1007,7 @@ func (e *Exporter) connectToRedis(skipTLSVerification bool) (redis.Conn, error)
}

func (e *Exporter) scrapeRedisHost(ch chan<- prometheus.Metric) error {
c, err := e.connectToRedis(e.options.SkipTLSVerification)
c, err := e.connectToRedis()
if err != nil {
log.Errorf("Couldn't connect to redis instance")
log.Debugf("connectToRedis( %s ) err: %s", e.redisAddr, err)
Expand Down
10 changes: 5 additions & 5 deletions exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ func TestHostVariations(t *testing.T) {
host := strings.ReplaceAll(os.Getenv("TEST_REDIS_URI"), "redis://", "")

for _, prefix := range []string{"", "redis://", "tcp://", ""} {
e, _ := NewRedisExporter(prefix+host, ExporterOptions{})
c, err := e.connectToRedis(true)
e, _ := NewRedisExporter(prefix+host, ExporterOptions{SkipTLSVerification: true})
c, err := e.connectToRedis()
if err != nil {
t.Errorf("connectToRedis() err: %s", err)
continue
Expand Down Expand Up @@ -586,7 +586,7 @@ func TestScanForKeys(t *testing.T) {

c, err := redis.DialURL(addr)
if err != nil {
t.Errorf("Couldn't connect to %#v: %#v", addr, err)
t.Fatalf("Couldn't connect to %#v: %#v", addr, err)
}
_, err = c.Do("SELECT", db)
if err != nil {
Expand Down Expand Up @@ -643,7 +643,7 @@ func TestGetKeysFromPatterns(t *testing.T) {

c, err := redis.DialURL(addr)
if err != nil {
t.Errorf("Couldn't connect to %#v: %#v", addr, err)
t.Fatalf("Couldn't connect to %#v: %#v", addr, err)
}

defer func() {
Expand Down Expand Up @@ -707,7 +707,7 @@ func TestGetKeyInfo(t *testing.T) {

c, err := redis.DialURL(addr)
if err != nil {
t.Errorf("Couldn't connect to %#v: %#v", addr, err)
t.Fatalf("Couldn't connect to %#v: %#v", addr, err)
}
_, err = c.Do("SELECT", db)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"crypto/tls"
"flag"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -48,6 +49,8 @@ func main() {
logFormat = flag.String("log-format", getEnv("REDIS_EXPORTER_LOG_FORMAT", "txt"), "Log format, valid options are txt and json")
configCommand = flag.String("config-command", getEnv("REDIS_EXPORTER_CONFIG_COMMAND", "CONFIG"), "What to use for the CONFIG command")
connectionTimeout = flag.String("connection-timeout", getEnv("REDIS_EXPORTER_CONNECTION_TIMEOUT", "15s"), "Timeout for connection to Redis instance")
tlsClientKeyFile = flag.String("tls-client-key-file", getEnv("REDIS_EXPORTER_TLS_CLIENT_KEY_FILE", ""), "Name of the client key file (including full path) if the server requires TLS client authentication")
tlsClientCertFile = flag.String("tls-client-cert-file", getEnv("REDIS_EXPORTER_TLS_CLIENT_CERT_FILE", ""), "Name of the client certificate file (including full path) if the server requires TLS client authentication")
isDebug = flag.Bool("debug", getEnvBool("REDIS_EXPORTER_DEBUG"), "Output verbose debug information")
isTile38 = flag.Bool("is-tile38", getEnvBool("REDIS_EXPORTER_IS_TILE38"), "Whether to scrape Tile38 specific metrics")
showVersion = flag.Bool("version", false, "Show version information and exit")
Expand Down Expand Up @@ -83,6 +86,18 @@ func main() {
log.Fatalf("Couldn't parse connection timeout duration, err: %s", err)
}

var tlsClientCertificates []tls.Certificate
if (*tlsClientKeyFile != "") != (*tlsClientCertFile != "") {
log.Fatal("TLS client key file and cert file should both be present")
}
if *tlsClientKeyFile != "" && *tlsClientCertFile != "" {
cert, err := tls.LoadX509KeyPair(*tlsClientCertFile, *tlsClientKeyFile)
if err != nil {
log.Fatalf("Couldn't load TLS client key pair, err: %s", err)
}
tlsClientCertificates = append(tlsClientCertificates, cert)
}

exp, err := NewRedisExporter(
*redisAddr,
ExporterOptions{
Expand All @@ -94,6 +109,7 @@ func main() {
InclSystemMetrics: *inclSystemMetrics,
IsTile38: *isTile38,
SkipTLSVerification: *skipTLSVerification,
ClientCertificates: tlsClientCertificates,
ConnectionTimeouts: to,
},
)
Expand Down

0 comments on commit e949f34

Please sign in to comment.