diff --git a/README.md b/README.md index ec797cffcb..7c50297ee7 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,10 @@ To enable the redis-based config: When launched in redis-config mode, Refinery needs a redis host to use for managing the list of peers in the Refinery cluster. This hostname and port can be specified in one of two ways: -- set the `REFINERY_REDIS_HOST` environment variable -- set the `RedisHost` field in the config file +- set the `REFINERY_REDIS_HOST` environment variable (and optionally the `REFINERY_REDIS_PASSWORD` environment variable) +- set the `RedisHost` field in the config file (and optionally the `RedisPassword` field in the config file) -The redis host should be a hostname and a port, for example `redis.mydomain.com:6379`. The example config file has `localhost:6379` which obviously will not work with more than one host. +The redis host should be a hostname and a port, for example `redis.mydomain.com:6379`. The example config file has `localhost:6379` which obviously will not work with more than one host. When TLS is required to connect to the redis instance set the `UseTLS` config to `true`. ## How sampling decisions are made diff --git a/config/config.go b/config/config.go index ff7f114454..85f8780dfe 100644 --- a/config/config.go +++ b/config/config.go @@ -37,6 +37,14 @@ type Config interface { // management. GetRedisHost() (string, error) + // GetRedisPassword returns the password of a Redis instance to use for peer + // management. + GetRedisPassword() (string, error) + + // GetUseTLS returns true when TLS must be enabled to dial the Redis instance to + // use for peer management. + GetUseTLS() (bool, error) + // GetHoneycombAPI returns the base URL (protocol, hostname, and port) of // the upstream Honeycomb API server GetHoneycombAPI() (string, error) diff --git a/config/config_test.go b/config/config_test.go index 8ed47f0193..0fdea1f2eb 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRedisEnvVar(t *testing.T) { +func TestRedisHostEnvVar(t *testing.T) { host := "redis.magic:1337" os.Setenv("REFINERY_REDIS_HOST", host) defer os.Unsetenv("REFINERY_REDIS_HOST") @@ -28,6 +28,22 @@ func TestRedisEnvVar(t *testing.T) { } } +func TestRedisPasswordEnvVar(t *testing.T) { + password := "admin1234" + os.Setenv("REFINERY_REDIS_PASSWORD", password) + defer os.Unsetenv("REFINERY_REDIS_PASSWORD") + + c, err := NewConfig("../config.toml", "../rules.toml", func(err error) {}) + + if err != nil { + t.Error(err) + } + + if d, _ := c.GetRedisPassword(); d != password { + t.Error("received", d, "expected", password) + } +} + func TestReload(t *testing.T) { tmpDir, err := ioutil.TempDir("", "") assert.NoError(t, err) diff --git a/config/file_config.go b/config/file_config.go index f06b85f53e..48e55e0349 100644 --- a/config/file_config.go +++ b/config/file_config.go @@ -106,6 +106,8 @@ type PeerManagementConfig struct { Type string `validate:"required,oneof= file redis"` Peers []string `validate:"dive,url"` RedisHost string + RedisPassword string + UseTLS bool IdentifierInterfaceName string UseIPV6Identifier bool RedisIdentifier string @@ -116,11 +118,13 @@ func NewConfig(config, rules string, errorCallback func(error)) (Config, error) c := viper.New() c.BindEnv("PeerManagement.RedisHost", "REFINERY_REDIS_HOST") + c.BindEnv("PeerManagement.RedisPassword", "REFINERY_REDIS_PASSWORD") c.SetDefault("ListenAddr", "0.0.0.0:8080") c.SetDefault("PeerListenAddr", "0.0.0.0:8081") c.SetDefault("APIKeys", []string{"*"}) c.SetDefault("PeerManagement.Peers", []string{"http://127.0.0.1:8081"}) c.SetDefault("PeerManagement.Type", "file") + c.SetDefault("PeerManagement.UseTLS", false) c.SetDefault("PeerManagement.UseIPV6Identifier", false) c.SetDefault("HoneycombAPI", "https://api.honeycomb.io") c.SetDefault("Logger", "logrus") @@ -403,6 +407,20 @@ func (f *fileConfig) GetRedisHost() (string, error) { return f.config.GetString("PeerManagement.RedisHost"), nil } +func (f *fileConfig) GetRedisPassword() (string, error) { + f.mux.RLock() + defer f.mux.RUnlock() + + return f.config.GetString("PeerManagement.RedisPassword"), nil +} + +func (f *fileConfig) GetUseTLS() (bool, error) { + f.mux.RLock() + defer f.mux.RUnlock() + + return f.config.GetBool("PeerManagement.UseTLS"), nil +} + func (f *fileConfig) GetIdentifierInterfaceName() (string, error) { f.mux.RLock() defer f.mux.RUnlock() diff --git a/config/mock.go b/config/mock.go index 6064bffd97..16f2e3b47b 100644 --- a/config/mock.go +++ b/config/mock.go @@ -35,6 +35,10 @@ type MockConfig struct { GetPeersVal []string GetRedisHostErr error GetRedisHostVal string + GetRedisPasswordErr error + GetRedisPasswordVal string + GetUseTLSErr error + GetUseTLSVal bool GetSamplerTypeErr error GetSamplerTypeVal interface{} GetMetricsTypeErr error @@ -150,6 +154,18 @@ func (m *MockConfig) GetRedisHost() (string, error) { return m.GetRedisHostVal, m.GetRedisHostErr } +func (m *MockConfig) GetRedisPassword() (string, error) { + m.Mux.RLock() + defer m.Mux.RUnlock() + + return m.GetRedisPasswordVal, m.GetRedisPasswordErr +} +func (m *MockConfig) GetUseTLS() (bool, error) { + m.Mux.RLock() + defer m.Mux.RUnlock() + + return m.GetUseTLSVal, m.GetUseTLSErr +} func (m *MockConfig) GetMetricsType() (string, error) { m.Mux.RLock() defer m.Mux.RUnlock() diff --git a/config_complete.toml b/config_complete.toml index 8e37a591d5..e965ce9a77 100644 --- a/config_complete.toml +++ b/config_complete.toml @@ -126,6 +126,16 @@ Metrics = "honeycomb" # Not eligible for live reload. # RedisHost = "localhost:6379" +# RedisPassword is the password used to connect to redis for peer cluster membership management. +# If the environment variable 'REFINERY_REDIS_PASSWORD' is set it takes +# precedence and this value is ignored. +# Not eligible for live reload. +# RedisPassword = "" + +# UseTLS enables TLS when connecting to redis for peer cluster membership management, and sets the MinVersion to 1.2. +# Not eligible for live reload. +# UseTLS = false + # IdentifierInterfaceName is optional. By default, when using RedisHost, Refinery will use # the local hostname to identify itself to other peers in Redis. If your environment # requires that you use IPs as identifiers (for example, if peers can't resolve eachother diff --git a/internal/peer/redis.go b/internal/peer/redis.go index 1e947f90fa..70ba3c3d3b 100644 --- a/internal/peer/redis.go +++ b/internal/peer/redis.go @@ -2,6 +2,7 @@ package peer import ( "context" + "crypto/tls" "errors" "fmt" "net" @@ -51,18 +52,14 @@ func newRedisPeers(c config.Config) (Peers, error) { redisHost = "localhost:6379" } + options := buildOptions(c) pool := &redis.Pool{ MaxIdle: 3, MaxActive: 30, IdleTimeout: 5 * time.Minute, Wait: true, Dial: func() (redis.Conn, error) { - return redis.Dial( - "tcp", redisHost, - redis.DialReadTimeout(1*time.Second), - redis.DialConnectTimeout(1*time.Second), - redis.DialDatabase(0), // TODO enable multiple databases for multiple samproxies - ) + return redis.Dial("tcp", redisHost, options...) }, } @@ -167,6 +164,32 @@ func (p *redisPeers) watchPeers() { } } +func buildOptions(c config.Config) []redis.DialOption { + options := []redis.DialOption{ + redis.DialReadTimeout(1 * time.Second), + redis.DialConnectTimeout(1 * time.Second), + redis.DialDatabase(0), // TODO enable multiple databases for multiple samproxies + } + + password, _ := c.GetRedisPassword() + if password != "" { + options = append(options, redis.DialPassword(password)) + } + + useTLS, _ := c.GetUseTLS() + if useTLS { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + options = append(options, + redis.DialTLSConfig(tlsConfig), + redis.DialTLSSkipVerify(true), + redis.DialUseTLS(true)) + } + + return options +} + func publicAddr(c config.Config) (string, error) { // compute the public version of my peer listen address listenAddr, _ := c.GetPeerListenAddr()