A guide to using built-in TLS in Redis
Redis version 6 added TLS as a built-in feature. This makes me super happy, because I’m now able to use Redis as a store with my applications running on App Engine considering that the traffic is encrypted, without additional tools or a paid Redis Cloud plan.
Keep in mind that you will have to build Redis with TLS support at compile time. See https://redis.io/topics/encryption.
I couldn’t find an end-to-end example for setting up TLS (not very surprising considering that the feature was released April 30 this year), which would have really helped a novice like me. So here we are in case it helps someone else.
Generate cert files #
To use TLS with Redis, you’ll have to generate:
- a certificate-key pair for the server (
redis.crt
,redis.key
), and - a root CA certificate (
ca.crt
)
The TL;DR is that you run
FQDN=<redis-server-ip> make generate
with this Makefile.
FQDN ?= 127.0.0.1
OUTDIR ?= tls
# may be /etc/pki/tls in some machines.
# use `openssl version -a | grep OPENSSLDIR` to find out.
OPENSSLDIR ?= /etc/ssl
.PHONY: generate
generate: prepare redis.crt clean
.PHONY: prepare
prepare:
mkdir ${OUTDIR}
.PHONY: clean
clean:
rm -f ${OUTDIR}/openssl.cnf
openssl.cnf:
cat ${OPENSSLDIR}/openssl.cnf > ${OUTDIR}/openssl.cnf
echo "" >> ${OUTDIR}/openssl.cnf
echo "[ san_env ]" >> ${OUTDIR}/openssl.cnf
echo "subjectAltName = IP:${FQDN}" >> ${OUTDIR}/openssl.cnf
ca.key:
openssl genrsa 4096 > ${OUTDIR}/ca.key
ca.crt: ca.key
openssl req \
-new \
-x509 \
-nodes \
-sha256 \
-key ${OUTDIR}/ca.key \
-days 3650 \
-subj "/C=AU/CN=example" \
-out ${OUTDIR}/ca.crt
redis.csr: openssl.cnf
# is -extensions necessary?
# https://security.stackexchange.com/a/86999
SAN=IP:$(FQDN) openssl req \
-reqexts san_env \
-extensions san_env \
-config ${OUTDIR}/openssl.cnf \
-newkey rsa:4096 \
-nodes -sha256 \
-keyout ${OUTDIR}/redis.key \
-subj "/C=AU/CN=$(FQDN)" \
-out ${OUTDIR}/redis.csr
redis.crt: openssl.cnf ca.key ca.crt redis.csr
SAN=IP:$(FQDN) openssl x509 \
-req -sha256 \
-extfile ${OUTDIR}/openssl.cnf \
-extensions san_env \
-days 365 \
-in ${OUTDIR}/redis.csr \
-CA ${OUTDIR}/ca.crt \
-CAkey ${OUTDIR}/ca.key \
-CAcreateserial \
-out ${OUTDIR}/redis.crt
This will produce the required cert files in a directory named tls
by default. Optionally you can can set OUTDIR=<path>
to specify a custom output directory. A couple of notes:
- If your default openssl.cnf isn’t present at
/etc/ssl/openssl.cnf
you must setOPENSSLDIR=<path>
. It is present at/etc/pki/tls/openssl.cnf
on Fedora 32, so you’ll setOPENSSLDIR=/etc/pki/tls
. See the comments in the Makefile to find the location of OPENSSLDIR. - If you’re using a host like
redis.acmecorp.com
you may need to adjust the SAN/FQDN parts in the Makefile (this may require non-significant work on your part). If you’re only connecting locally, use127.0.0.1
.
The Makefile is adapted from Stack Overflow.
Start the server #
Once you have Redis built with TLS support, you’ll have to set TLS-specific options in your redis.conf. Here are the relevant options from mine.
port 0
tls-port 6379
tls-cert-file tls/redis.crt
tls-key-file tls/redis.key
tls-ca-cert-file tls/ca.crt
You can find these options with detailed comments in the “TLS/SSL” section of a new redis.conf.
Place the generated cert files in your server (you can scp
them over). For the config above, I placed my cert files in a directory named tls
, relative to the directory from where I would start the Redis server.
With that done, you can start the Redis server.
$ ls
redis.conf tls
$ ./path/to/redis-server redis.conf
Connect with a client #
With the server running, you’ll want to try connecting with a client. I’ve been able to connect successfully with redis-cli, Node.js programs, and Go programs.
redis-cli #
Likely the easiest client to connect with to a Redis TLS server. For simplicity and to potentially save debugging time, you should try this first, locally from the same machine running the Redis server.
Run redis-cli as you mostly would, with extra options specifying the cert files. Run a sample PING
command to make sure you’ve connected successfully.
$ ./path/to/redis-cli --tls --cert tls/redis.crt --key tls/redis.key --cacert tls/ca.crt
redis> ping
"PONG"
As a sanity check, try omitting one of the options and you should fail to connect.
Node.js client #
For Node, I was using https://github.com/NodeRedis/node-redis, but https://github.com/luin/ioredis should work equally well.
Complete side note: In hindsight, I would have like to have used ioredis (and it’s what I’ll do in the future) because of its Promise API, and I dislike node-redis’s handling of null/undefined in SET calls.
Both packages support a tls
property in their client config object. The type SecureContextOptions
is from tls.createSecureContext()
.
tls: SecureContextOptions
So you can do (uses some TypeScript syntax):
import * as fs from "fs"
import redis from "redis"
const redisHost = process.env["REDIS_HOST"]! // e.g. "1.2.3.4", "127.0.0.1", "localhost", "redis.acmecorp.com"
const client = redis.createClient({
host: redisHost,
port: 6379,
tls: {
cert: fs.readFileSync("redis/tls/redis.crt"),
key: fs.readFileSync("redis/tls/redis.key"),
ca: fs.readFileSync("redis/tls/ca.crt"),
},
})
Go client #
I used https://github.com/go-redis/redis. The configuration is similar to Node’s. The package’s *redis.Options
type has a field:
// TLS Config to use. When set TLS will be negotiated.
TLSConfig *tls.Config
You can do:
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"os"
"github.com/go-redis/redis"
)
redisHost := os.Getenv("REDIS_HOST") // e.g. "1.2.3.4", "127.0.0.1", "localhost", "redis.acmecorp.com"
cert, err := tls.LoadX509KeyPair("redis/tls/redis.crt", "redis/tls/redis.key")
if err != nil {
...
}
caCert, err := ioutil.ReadFile("redis/tls/ca.crt")
if err != nil {
...
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caCert)
client := redis.NewClient(&redis.Options{
Addr: net.JoinHostPort(redisHost, "6379"),
TLSConfig: &tls.Config{
ServerName: redisHost,
Certificates: []tls.Certificate{cert},
RootCAs: pool,
},
})
And you should hopefully have functioning clients at this stage.