Skip to content

Commit

Permalink
feat: enhance TLS support by taking path to CA file for redis conn
Browse files Browse the repository at this point in the history
  • Loading branch information
Brent Hepburn committed Oct 19, 2021
1 parent 1294867 commit 9caa24e
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ bootstrap_redis_tls: redis.conf redis-per-second.conf
cat key.pem cert.pem > private.pem
sudo cp cert.pem /usr/local/share/ca-certificates/redis-stunnel.crt
chmod 640 key.pem cert.pem private.pem
sudo update-ca-certificates
#sudo update-ca-certificates - not needed with the RedisTlsCACerts feature
sudo stunnel redis.conf
sudo stunnel redis-per-second.conf
.PHONY: docs_format
Expand Down
4 changes: 2 additions & 2 deletions src/redis/cache_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ func NewRateLimiterCacheImplFromSettings(s settings.Settings, localCache *freeca
var perSecondPool Client
if s.RedisPerSecond {
perSecondPool = NewClientImpl(srv.Scope().Scope("redis_per_second_pool"), s.RedisPerSecondTls, s.RedisPerSecondAuth, s.RedisPerSecondSocketType,
s.RedisPerSecondType, s.RedisPerSecondUrl, s.RedisPerSecondPoolSize, s.RedisPerSecondPipelineWindow, s.RedisPerSecondPipelineLimit, s.RedisTlsConfig)
s.RedisPerSecondType, s.RedisPerSecondUrl, s.RedisPerSecondPoolSize, s.RedisPerSecondPipelineWindow, s.RedisPerSecondPipelineLimit, s.RedisTlsCACerts)
}
var otherPool Client
otherPool = NewClientImpl(srv.Scope().Scope("redis_pool"), s.RedisTls, s.RedisAuth, s.RedisSocketType, s.RedisType, s.RedisUrl, s.RedisPoolSize,
s.RedisPipelineWindow, s.RedisPipelineLimit, s.RedisTlsConfig)
s.RedisPipelineWindow, s.RedisPipelineLimit, s.RedisTlsCACerts)

return NewFixedRateLimitCacheImpl(
otherPool,
Expand Down
32 changes: 30 additions & 2 deletions src/redis/driver_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package redis

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"strings"
"time"

Expand Down Expand Up @@ -52,15 +54,41 @@ func checkError(err error) {
}
}

func GetClientTLSConfig(redisTlsCACerts string) (*tls.Config, error) {
// Get the SystemCertPool, continue with an empty pool on error
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}

// Read in the cert file
certs, err := ioutil.ReadFile(redisTlsCACerts)
if err != nil {
return nil, err
}

// Append our cert to the system pool
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
logger.Warnf("No certs appended, using system certs only")
}

// Trust the augmented cert pool in our client
return &tls.Config{
RootCAs: rootCAs,
}, nil
}

func NewClientImpl(scope stats.Scope, useTls bool, auth, redisSocketType, redisType, url string, poolSize int,
pipelineWindow time.Duration, pipelineLimit int, tlsConfig *tls.Config) Client {
pipelineWindow time.Duration, pipelineLimit int, tlsCACerts string) Client {
logger.Warnf("connecting to redis on %s with pool size %d", url, poolSize)

df := func(network, addr string) (radix.Conn, error) {
var dialOpts []radix.DialOpt

if useTls {
if tlsConfig != nil {
if tlsCACerts != "" {
tlsConfig, err := GetClientTLSConfig(tlsCACerts)
checkError(err)
dialOpts = append(dialOpts, radix.DialUseTLS(tlsConfig))
} else {
dialOpts = append(dialOpts, radix.DialUseTLS(&tls.Config{}))
Expand Down
5 changes: 2 additions & 3 deletions src/settings/settings.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package settings

import (
"crypto/tls"
"time"

"github.com/kelseyhightower/envconfig"
Expand Down Expand Up @@ -56,8 +55,8 @@ type Settings struct {
RedisPoolSize int `envconfig:"REDIS_POOL_SIZE" default:"10"`
RedisAuth string `envconfig:"REDIS_AUTH" default:""`
RedisTls bool `envconfig:"REDIS_TLS" default:"false"`
// TODO: Make this setting configurable out of the box instead of having to provide it through code.
RedisTlsConfig *tls.Config
// Custom logic to allow CA Certs to be specified
RedisTlsCACerts string `envconfig:"REDIS_TLS_CA_CERTS" default:""`

// RedisPipelineWindow sets the duration after which internal pipelines will be flushed.
// If window is zero then implicit pipelining will be disabled. Radix use 150us for the
Expand Down
28 changes: 27 additions & 1 deletion test/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,32 @@ func TestBasicConfig_ExtraTags(t *testing.T) {
})
}

func TestTlsConfigFailure(t *testing.T) {
s := makeSimpleRedisSettings(16381, 16382, false, 0)
s.RedisAuth = "password123"
s.RedisTls = true
s.RedisPerSecondAuth = "password123"
s.RedisPerSecondTls = true
s.RedisTlsCACerts = ""

enable_local_cache := s.LocalCacheSizeInBytes > 0

// HACK: Wait for the server to come up. Make a hook that we can wait on
common.WaitForTcpPort(context.Background(), s.GrpcPort, 1*time.Second)

assert := assert.New(t)
conn, err := grpc.Dial(fmt.Sprintf("localhost:%v", s.GrpcPort), grpc.WithInsecure())
assert.NoError(err)
defer conn.Close()
c := pb.NewRateLimitServiceClient(conn)

// Assert this fails because of missing TLS CA Config Definition and the test Redis certs are self-signed
_, err = c.ShouldRateLimit(
context.Background(),
common.NewRateLimitRequest("reload", [][][2]string{{{getCacheKey("block", enable_local_cache), "foo"}}}, 1))
assert.EqualError(err, "rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing dial tcp 127.0.0.1:8083: connect: connection refused\"")
}

func TestBasicTLSConfig(t *testing.T) {
t.Run("WithoutPerSecondRedisTLS", testBasicConfigAuthTLS(false, 0))
t.Run("WithPerSecondRedisTLS", testBasicConfigAuthTLS(true, 0))
Expand Down Expand Up @@ -235,11 +261,11 @@ func TestMultiNodeMemcache(t *testing.T) {

func testBasicConfigAuthTLS(perSecond bool, local_cache_size int) func(*testing.T) {
s := makeSimpleRedisSettings(16381, 16382, perSecond, local_cache_size)
s.RedisTlsConfig = nil
s.RedisAuth = "password123"
s.RedisTls = true
s.RedisPerSecondAuth = "password123"
s.RedisPerSecondTls = true
s.RedisTlsCACerts = "/usr/local/share/ca-certificates/redis-stunnel.crt"

return testBasicBaseConfig(s)
}
Expand Down
2 changes: 1 addition & 1 deletion test/redis/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func BenchmarkParallelDoLimit(b *testing.B) {
return func(b *testing.B) {
statsStore := gostats.NewStore(gostats.NewNullSink(), false)
sm := stats.NewMockStatManager(statsStore)
client := redis.NewClientImpl(statsStore, false, "", "tcp", "single", "127.0.0.1:6379", poolSize, pipelineWindow, pipelineLimit, nil)
client := redis.NewClientImpl(statsStore, false, "", "tcp", "single", "127.0.0.1:6379", poolSize, pipelineWindow, pipelineLimit, "")
defer client.Close()

cache := redis.NewFixedRateLimitCacheImpl(client, nil, utils.NewTimeSourceImpl(), rand.New(utils.NewLockedSource(time.Now().Unix())), 10, nil, 0.8, "", sm)
Expand Down
6 changes: 3 additions & 3 deletions test/redis/driver_impl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func testNewClientImpl(t *testing.T, pipelineWindow time.Duration, pipelineLimit
statsStore := stats.NewStore(stats.NewNullSink(), false)

mkRedisClient := func(auth, addr string) redis.Client {
return redis.NewClientImpl(statsStore, false, auth, "tcp", "single", addr, 1, pipelineWindow, pipelineLimit, nil)
return redis.NewClientImpl(statsStore, false, auth, "tcp", "single", addr, 1, pipelineWindow, pipelineLimit, "")
}

t.Run("connection refused", func(t *testing.T) {
Expand Down Expand Up @@ -103,7 +103,7 @@ func TestDoCmd(t *testing.T) {
statsStore := stats.NewStore(stats.NewNullSink(), false)

mkRedisClient := func(addr string) redis.Client {
return redis.NewClientImpl(statsStore, false, "", "tcp", "single", addr, 1, 0, 0, nil)
return redis.NewClientImpl(statsStore, false, "", "tcp", "single", addr, 1, 0, 0, "")
}

t.Run("SETGET ok", func(t *testing.T) {
Expand Down Expand Up @@ -148,7 +148,7 @@ func testPipeDo(t *testing.T, pipelineWindow time.Duration, pipelineLimit int) f
statsStore := stats.NewStore(stats.NewNullSink(), false)

mkRedisClient := func(addr string) redis.Client {
return redis.NewClientImpl(statsStore, false, "", "tcp", "single", addr, 1, pipelineWindow, pipelineLimit, nil)
return redis.NewClientImpl(statsStore, false, "", "tcp", "single", addr, 1, pipelineWindow, pipelineLimit, "")
}

t.Run("SETGET ok", func(t *testing.T) {
Expand Down

0 comments on commit 9caa24e

Please sign in to comment.