From 5cd8fd60bfe3165d78f6c3652f3bcc60113d288e Mon Sep 17 00:00:00 2001 From: Brent Hepburn Date: Thu, 21 Oct 2021 14:35:08 -0400 Subject: [PATCH] feat: enhance TLS support by taking path to CA file for redis conn --- Makefile | 2 +- src/settings/settings.go | 12 +++++++++++- src/utils/utilities.go | 29 ++++++++++++++++++++++++++++ test/integration/integration_test.go | 29 +++++++++++++++++++++++++++- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ef6ffc0c..a1599af4 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/settings/settings.go b/src/settings/settings.go index 7cd232c9..7dc62843 100644 --- a/src/settings/settings.go +++ b/src/settings/settings.go @@ -3,7 +3,8 @@ package settings import ( "crypto/tls" "time" - + + "github.com/envoyproxy/ratelimit/src/utils" "github.com/kelseyhightower/envconfig" "google.golang.org/grpc" ) @@ -58,6 +59,8 @@ type Settings struct { 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 @@ -101,6 +104,13 @@ func NewSettings() Settings { if err != nil { panic(err) } + + if (s.RedisTlsCACerts != "") { + s.RedisTlsConfig, err = utils.GenerateTlsConfig(s.RedisTlsCACerts) + if err != nil { + panic(err) + } + } return s } diff --git a/src/utils/utilities.go b/src/utils/utilities.go index e6029f5b..4fd50b76 100644 --- a/src/utils/utilities.go +++ b/src/utils/utilities.go @@ -1,8 +1,13 @@ package utils import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v3" "github.com/golang/protobuf/ptypes/duration" + logger "github.com/sirupsen/logrus" ) // Interface for a time source. @@ -41,3 +46,27 @@ func Max(a uint32, b uint32) uint32 { } return b } + +func GenerateTlsConfig(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 +} diff --git a/test/integration/integration_test.go b/test/integration/integration_test.go index 83fb0ba6..f855d2d4 100644 --- a/test/integration/integration_test.go +++ b/test/integration/integration_test.go @@ -16,6 +16,7 @@ import ( "github.com/envoyproxy/ratelimit/src/memcached" "github.com/envoyproxy/ratelimit/src/service_cmd/runner" "github.com/envoyproxy/ratelimit/src/settings" + "github.com/envoyproxy/ratelimit/src/utils" "github.com/envoyproxy/ratelimit/test/common" "github.com/golang/protobuf/ptypes/duration" "github.com/kelseyhightower/envconfig" @@ -233,9 +234,35 @@ func TestMultiNodeMemcache(t *testing.T) { }) } +func TestTlsConfigFailure(t *testing.T) { + s := makeSimpleRedisSettings(16381, 16382, false, 0) + s.RedisTlsConfig = nil + s.RedisAuth = "password123" + s.RedisTls = true + s.RedisPerSecondAuth = "password123" + s.RedisPerSecondTls = true + + 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 testBasicConfigAuthTLS(perSecond bool, local_cache_size int) func(*testing.T) { s := makeSimpleRedisSettings(16381, 16382, perSecond, local_cache_size) - s.RedisTlsConfig = nil + s.RedisTlsConfig, _ = utils.GenerateTlsConfig("/usr/local/share/ca-certificates/redis-stunnel.crt") s.RedisAuth = "password123" s.RedisTls = true s.RedisPerSecondAuth = "password123"