|
Packit |
4e8bc4 |
Securing Memcached with TLS
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Requirements
|
|
Packit |
4e8bc4 |
------------
|
|
Packit |
4e8bc4 |
We are required to encrypt Memcached network traffic as we deploy our servers in public cloud
|
|
Packit |
4e8bc4 |
environments. We decided to implement SSL/TLS for TCP at the network layer of Memcached
|
|
Packit |
4e8bc4 |
using OpenSSL libraries. This provides following benefits with the expense of added latency
|
|
Packit |
4e8bc4 |
and reduced throughput (to be quantified).
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# Encryption :Data is encrypted on the wire between Memcached client and server.
|
|
Packit |
4e8bc4 |
# Authentication : Optionally, both server and client authenticate each other.
|
|
Packit |
4e8bc4 |
# Integrity: Data is not tampered or altered when transmitted between client and server
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Following are a few additional features.
|
|
Packit |
4e8bc4 |
# Certificate refresh: when the server gets a new certificate, new connections
|
|
Packit |
4e8bc4 |
will use new certificates without a need of re-starting the server process.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# Multiple ports with and without TLS : by default all TCP ports are secured. Optionally we can setup
|
|
Packit |
4e8bc4 |
the server to secure a specific TCP port.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Note that initial implementation does not support session resumption or renegotiation.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Design
|
|
Packit |
4e8bc4 |
------
|
|
Packit |
4e8bc4 |
We experimented two options for implementing TLS, with SSL buffered events and directly using
|
|
Packit |
4e8bc4 |
OpenSSL API.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Bufferevents can use the OpenSSL library to implement SSL/TLS. Our experiment used
|
|
Packit |
4e8bc4 |
a socket-based bufferevent that tells OpenSSL to communicate with the network directly over.
|
|
Packit |
4e8bc4 |
Unlike a worker thread sets callback on the socket, this uses a “bufferevent” object for
|
|
Packit |
4e8bc4 |
callbacks. Memcached still has to setup the SSL Context but SSL handshake and object
|
|
Packit |
4e8bc4 |
management is done via the “bufferevent_” API. While this was fairly easy to implement,
|
|
Packit |
4e8bc4 |
we noticed a higher memory usage as we don’t have much control over allocating evbuffer
|
|
Packit |
4e8bc4 |
objects in bufferevents. More over there is a discussion on removing the libevent dependency
|
|
Packit |
4e8bc4 |
from Memcached; hence this option was not chosen.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
OpenSSL library provides APIs for us to directly read/write from a socket. With this option,
|
|
Packit |
4e8bc4 |
we create an SSL Context and many SSL objects. The SSL Context object, created at the process level,
|
|
Packit |
4e8bc4 |
holds certificates, a private key, and options regarding the TLS protocol and algorithms.
|
|
Packit |
4e8bc4 |
SSL objects, created at the connection level, represents SSL sessions. SSL objects are responsible
|
|
Packit |
4e8bc4 |
for encryption, and session handshake among other things.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
There are two ways to do network IO over TLS, either only use SSL_read/SSL_write with a network socket or
|
|
Packit |
4e8bc4 |
use the API along with an output/input buffer pair. These buffers are referred as BIO
|
|
Packit |
4e8bc4 |
(Basic Input Output) buffers.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
We started with the first option, create SSL objects with the socket and only interact with SSL_read/SSL_write.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
+------+ +-----+
|
|
Packit |
4e8bc4 |
|......|--> read(fd) --> BIO_write(rbio) -->|.....|--> SSL_read(ssl) --> IN
|
|
Packit |
4e8bc4 |
|......| |.....|
|
|
Packit |
4e8bc4 |
|.sock.| |.SSL.|
|
|
Packit |
4e8bc4 |
|......| |.....|
|
|
Packit |
4e8bc4 |
|......|<-- write(fd) <-- BIO_read(wbio) <--|.....|<-- SSL_write(ssl) <-- OUT
|
|
Packit |
4e8bc4 |
+------+ +-----+
|
|
Packit |
4e8bc4 |
| | | |
|
|
Packit |
4e8bc4 |
|<-------------------------------->| |<------------------->|
|
|
Packit |
4e8bc4 |
| encrypted bytes | | unencrypted bytes |
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Figure 1 : Network sockets, BIO buffers and SSL_read/SSL_write
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
(reference: https://gist.github.com/darrenjs/4645f115d10aa4b5cebf57483ec82eca)
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Memcached uses non blocking sockets and implements a rather complex state machine for
|
|
Packit |
4e8bc4 |
network IO. A listener thread does the TCP handshake and initiates the SSL handshake after
|
|
Packit |
4e8bc4 |
creating an SSL object based on the SSL Context object of the server. If there are no
|
|
Packit |
4e8bc4 |
fatal errors, the listener thread hands over the socket to a worker thread. A worker completes
|
|
Packit |
4e8bc4 |
the SSL handshake.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
----------- ----------------------
|
|
Packit |
4e8bc4 |
| |
|
|
Packit |
4e8bc4 |
Client | | Memcached Server
|
|
Packit |
4e8bc4 |
| |
|
|
Packit |
4e8bc4 |
| |---------------------
|
|
Packit |
4e8bc4 |
| | Listener thread |
|
|
Packit |
4e8bc4 |
| TCP connect | |
|
|
Packit |
4e8bc4 |
|---------------------> | (accept) |
|
|
Packit |
4e8bc4 |
| ClientHello | |
|
|
Packit |
4e8bc4 |
|---------------------> | (SSL_accept) |
|
|
Packit |
4e8bc4 |
| | |
|
|
Packit |
4e8bc4 |
| ServerHello and | |
|
|
Packit |
4e8bc4 |
| Certificate, | |
|
|
Packit |
4e8bc4 |
| ServerHelloDone | |
|
|
Packit |
4e8bc4 |
| <---------------------| |
|
|
Packit |
4e8bc4 |
| |---------------------
|
|
Packit |
4e8bc4 |
| | |
|
|
Packit |
4e8bc4 |
| | V
|
|
Packit |
4e8bc4 |
| |-------------------
|
|
Packit |
4e8bc4 |
| | Worker thread |
|
|
Packit |
4e8bc4 |
| ClientKeyExchange, | |
|
|
Packit |
4e8bc4 |
| ChangeCipherSpec, | |
|
|
Packit |
4e8bc4 |
| Finished | |
|
|
Packit |
4e8bc4 |
|---------------------> | (SSL_read) |
|
|
Packit |
4e8bc4 |
| | |
|
|
Packit |
4e8bc4 |
| | |
|
|
Packit |
4e8bc4 |
| NewSessionTicket, | |
|
|
Packit |
4e8bc4 |
| ChangeCipherSpec, | |
|
|
Packit |
4e8bc4 |
| Finished | |
|
|
Packit |
4e8bc4 |
| <---------------------| |
|
|
Packit |
4e8bc4 |
| | |
|
|
Packit |
4e8bc4 |
| Memcached request/ | |
|
|
Packit |
4e8bc4 |
| response | |
|
|
Packit |
4e8bc4 |
| <-------------------> | (SSL_read/ |
|
|
Packit |
4e8bc4 |
| | SSL_write) |
|
|
Packit |
4e8bc4 |
----------- -------------------------
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Figure 2 : The initial SSL handshake
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Setting-up callbacks when the socket is ready for reading/writing is the same
|
|
Packit |
4e8bc4 |
for both TLS and non-TLS connections. When the socket is ready, the state machine kicks off
|
|
Packit |
4e8bc4 |
and issues a SSL_read/ SSL_write. Note that we implement a SSL_sendmsg wrapper on top of
|
|
Packit |
4e8bc4 |
SSL_write to simulate the sendmsg API.
|
|
Packit |
4e8bc4 |
This way we don't explicitly use BIO buffers or do BIO_write/BIO_read, but let OpenSSL
|
|
Packit |
4e8bc4 |
library to do it on our behalf. Existing state machine takes care of reading the correct amount
|
|
Packit |
4e8bc4 |
of bytes and do the error handling when needed.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
As a best practice, server certificates and keys are periodically refreshed by the PKI.
|
|
Packit |
4e8bc4 |
When this happens we want server to use the new certificate without restarting the process.
|
|
Packit |
4e8bc4 |
Memcached is a cache and restarting servers affects the latency of applications. We implement
|
|
Packit |
4e8bc4 |
the automatic certificate refresh through a command. Upon receiving the "refresh_certs" command,
|
|
Packit |
4e8bc4 |
the server reloads the certificates and key to the SSL Context object. Existing connection won't be
|
|
Packit |
4e8bc4 |
interrupted but new connections will use the new certificate.
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
We understand not all users want to use TLS or have the OpenSSL dependency. Therefore
|
|
Packit |
4e8bc4 |
it's an optional module at the compile time. We can build a TLS capable Memcached server with
|
|
Packit |
4e8bc4 |
"./configure --enable-tls". Once the server is built with TLS support, we can enabled it with
|
|
Packit |
4e8bc4 |
"-Z" flag or "--enable-ssl". Certificate (-o ssl_chain_cert) and (-o ssl_key) are required
|
|
Packit |
4e8bc4 |
parameters while others are optional. Supported options can be listed through "memcached -h".
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
Developers need to have libio-socket-ssl-perl installed for running unit tests. When the server is
|
|
Packit |
4e8bc4 |
built with TLS support, we can use "test_tls" make target to run all existing tests over TLS and some
|
|
Packit |
4e8bc4 |
additional TLS specific tests. The minimum required OpenSSL version is 1.1.0g.
|