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). So here we go.
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 systems.
# 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
# TODO(nishanths): 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 3650 \
-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:
- This generates certificates with a roughly 10-YEAR EXPIRY! (see the
-days 3650
lines). You may want to adjust this. - 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. For the config above, I place 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
$ 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 client
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.
$ redis-cli --tls --cert tls/redis.crt --key tls/redis.key --cacert tls/ca.crt
redis> ping
"PONG"
As a soundness check, try omitting one of the options. It 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.
Side note: In hindsight, I would have like to have used ioredis (and it's what I'll do in the future) because I disagree with 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 (code below 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"
)
func main() {
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.