Redis encryption in-transit — in k8s

Abstract

Many applications require encryption both at rest and in transit, while traditional databases provide this out of the box, redis require a bit of additional work.

This article shows you how to do it in zero effort on a running k8s cluster.

Many articles around the web show you how to do the above via manual configuration. In the age where you must spawn a new environment on every successful build — these are not friendly.

The approach I have running uses te sidecar ability of k8s, which initializes the ssh tunnel to the Redis cluster.

I am providing the example here based on AWS Redis ( ElasticCache) solely for the sake of simplicity to set up the server — the actual server can run anywhere. see the following resources

AWS redis in-transit encryption

Redis encryption tunnel

The missing piece here is creating the tunnel from each pod.

Trivial.

Dockerfile

FROM debian:buster-slim
RUN apt update && apt install -y stunnel
COPY redis-cli.conf /etc/stunnel/redis-cli.conf
CMD ["stunnel", "/etc/stunnel/redis-cli.conf"]

One can map your redis-cli.conf from a file into the volume, or as a config map, your choice.

The moment you have this simple image in place, the sidecar runs.

What is left is to create a Deployment on k8s in a similar manner to the following

apiVersion: apps/v1beta2
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "41"
field.cattle.io/creatorId: user-jntkb
field.cattle.io/publicEndpoints: '[<MULTIPLE META DATA PARAMS>]'
creationTimestamp: "2019-07-29T05:56:43Z"
generation: 61
labels:
cattle.io/creator: <>
workload.user.cattle.io/workloadselector: <DEPLOYMENT>
name: <DEPLOYMENT_NAME>
namespace: <DEPLOYMENT_NS>
resourceVersion: "10243889"
selfLink: /apis/apps/v1beta2/namespaces/api/deployments/<....>
uid: a40947ba-b1c5-11e9-942c-.....
spec:
progressDeadlineSeconds: 600
replicas: 4
revisionHistoryLimit: 10
selector:
matchLabels:
workload.user.cattle.io/workloadselector: <DEPLOYMENT>
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
cattle.io/timestamp: "2019-10-26T11:07:44Z"
field.cattle.io/ports: '[[{"containerPort":9000,"dnsName":"<....>","kind":"NodePort","name":"9000tcp01","protocol":"TCP","sourcePort":0}]]'
creationTimestamp: null
labels:
workload.user.cattle.io/workloadselector: <DEPLOYMENT>
spec:
containers:
- env:
- name: REDIS_HOST
value: localhost

- name: REDIS_PORT
value: "6379"
- name: REDIS_AUTH
valueFrom:
secretKeyRef:
key: REDIS_AUTH
name: redis
optional: false

image: <ECR>.dkr.ecr.amazonaws.com/<>IMAGE:<TAG>
imagePullPolicy: Always
name: <...>
ports:
- containerPort: 9000
name: 9000tcp01
protocol: TCP
resources:
requests:
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
capabilities: {}
privileged: false
procMount: Default
readOnlyRootFilesystem: false
runAsNonRoot: false
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
- image: <ECR>.dkr.ecr.amazonaws.com/stunnel:<TAG>
imagePullPolicy: Always
name: stunnel
resources: {}
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: false
runAsNonRoot: false
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
volumeMounts:
- mountPath: /etc/stunnel
name: stunnel

dnsConfig: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
name: stunnel
optional: false
name: stunnel

Everything in arrow brackets (<>) is not part of the script, but masked private data — replace/update with your case ( PM me for questions )

The main points highlighted in bold, let us break them down

- name: REDIS_HOST
value: localhost

you need to communicate with the locally running pipe of stunnel -since if you try to connect to the now encrypted instance of RDS — you will get rejection

stunnel running as a sidecar now via

- image: <ECR>.dkr.ecr.amazonaws.com/stunnel:<TAG>

Above points to the stunnel image that we created earlier ( as you can see I have everything auto built on an instance I run on-premise of https://drone.io/ ( and auto-published to ECR )

- name: REDIS_AUTH
valueFrom:
secretKeyRef:
key: REDIS_AUTH

The AUTH, the app needs to know somehow the key to use to connect with redis.

Above is not a great solution — since it is viewable as clear text in environment variables — but this is a topic for another post.

And the volume mapping from config map — which is what makes the container to be dynamic and change config without image rebuild.

Summary

You have an encrypted in traffic facility — built once and reused everywhere — with the importance of no added effort — as the redis client has no idea it is going through an encrypted tunnel and still assumes it is working with local redis.

--

--

Husband, Father, Software architect Proven record in all three

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Dan Kuida

Dan Kuida

Husband, Father, Software architect Proven record in all three