Skip to content

Commit

Permalink
Add TLS support for TCP sockets (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
moisesguimaraes committed Apr 7, 2020
1 parent da89f48 commit f35f215
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 6 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,17 @@ jobs:
- name: Tests
run: |
pip install -r test-requirements.txt
py.test pymemcache/test/ -m unit,integration --port ${{ job.services.memcached.ports[11211] }}
py.test pymemcache/test/ \
-m unit,integration \
--port ${{ job.services.memcached.ports[11211] }} \
--tls-port ${{ job.services.tls_memcached.ports[11211] }}
services:
memcached:
image: memcached:latest
ports:
- 11211/tcp
tls_memcached:
image: scoriacorp/tls_memcached:latest
ports:
- 11211/tcp
24 changes: 24 additions & 0 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ on if a server goes down.
client.set('some_key', 'some value')
result = client.get('some_key')
Using TLS
---------
**Memcached** `supports <https://github.com/memcached/memcached/wiki/TLS>`_
authentication and encryption via TLS since version **1.5.13**.

A Memcached server running with TLS enabled will only accept TLS connections.

To enable TLS in pymemcache, pass a valid TLS context to the client's
``tls_context`` parameter:

.. code-block:: python
import ssl
from pymemcache.client.base import Client
context = ssl.create_default_context(
cafile="my-ca-root.crt",
)
client = Client(('localhost', 11211), tls_context=context)
client.set('some_key', 'some_value')
result = client.get('some_key')
Serialization
--------------

Expand Down
38 changes: 38 additions & 0 deletions extras/tls/ca-root.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIGozCCBIugAwIBAgIJAM58RO9sXvoHMA0GCSqGSIb3DQEBCwUAMIGNMQswCQYD
VQQGEwJDWjEaMBgGA1UECAwRSmlob21vcmF2c2t5IGtyYWoxDTALBgNVBAcMBEJy
bm8xGzAZBgNVBAoMElNjb3JpYSBDb3Jwb3JhdGlvbjE2MDQGA1UEAwwtU2Nvcmlh
IENvcnBvcmF0aW9uIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTIwMDQw
MzE0NDAzOVoXDTQwMDMyOTE0NDAzOVowgY0xCzAJBgNVBAYTAkNaMRowGAYDVQQI
DBFKaWhvbW9yYXZza3kga3JhajENMAsGA1UEBwwEQnJubzEbMBkGA1UECgwSU2Nv
cmlhIENvcnBvcmF0aW9uMTYwNAYDVQQDDC1TY29yaWEgQ29ycG9yYXRpb24gUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDJqBOt19LipwyEq8YYnWe8SOJcDSE6fc+3gSggOSisJvDcjDZfgER2
eJmVdDutRbbeHoCTlA57buIy+3Dr1BkHbWpNrSlcBD3fgja6BhDZiH6Cuq3BvL5b
y2Yin96lk5JXmjNT5SP6vBmIe68lt+2BwjHgrbI6s8vOJwOy6gGZ8rVKGR6lHtbY
S7DznswyGoDuOlzHdf/9PNfbf1Jd72qn6qpAkf7GGvzqJaxqamhtB+V4QjSuv2Ts
em61+/7aeIN+MIF7IkiyVm+FwoVz505oAoeP8obXLFi2VKifinOrTMMMIoDd9I2m
FHraS5OhmlD4XaGNV9YhOYYu/gFgiHkQyjGBjtH+a4pZPwi9SyhsBHDRWx8HsWZV
6DWLjUyUhoM9yCUUYIPv+dA6zPhs5LKsmUfM5ASuhjTN/BBx+zpTUurX6Fmnz2Io
ypfiYjGWMdrwUdMLa6pY/5RcCysJHkrVLZSQi6hiC3yPqg0TlPVYBIcGP3vbkEcU
f7MBqdH6Tc8wdSAWSc+zgVD0ql5+TZ6MUXnL5wf2NYwuuzQDa1gT/VfjOZOjkv3H
lPC8isg926R6XuywPL4CynrL/qn6DRwNVelp31aD95HBS6YAVhJg7S4odQHDar4P
bA+qXqx0+syMyF9+c6liV2fmCHMKgRFFi6SfuwmpQ92gU53bFXPa1QIDAQABo4IB
AjCB/zAdBgNVHQ4EFgQUhVz9eXfMmqIaA4m3NVpJpI1tz1AwgcIGA1UdIwSBujCB
t4AUhVz9eXfMmqIaA4m3NVpJpI1tz1ChgZOkgZAwgY0xCzAJBgNVBAYTAkNaMRow
GAYDVQQIDBFKaWhvbW9yYXZza3kga3JhajENMAsGA1UEBwwEQnJubzEbMBkGA1UE
CgwSU2NvcmlhIENvcnBvcmF0aW9uMTYwNAYDVQQDDC1TY29yaWEgQ29ycG9yYXRp
b24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCCQDOfETvbF76BzAMBgNVHRME
BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAHkqrlcn7pzr/
UOsWkwtJkZaUgnejrryMsS24Oj7sWmpH23ZG//97gLibAjIhngZm3AOS4K7TVxvW
rkirvaRq5ZbehOnMqLhEBbAjumK2RjeM8SBzRqYBsvU7iELyN/IMgsHzeul/5/0R
vsBr0vtI6acKOAkUfMbpxN7m/gOL2CvGUmDy1NXtHWQTeDf6wxWkNGBb4E66sK66
auSP205xxKzlMCzRaf8nfDAx7oy4zQtjJKunMtglxjrpGDCEFMixT8wqIUbf46o+
+uK2AWqprBFL42+qGiu68gzMz1WS1iMmzbM0DUmAc3piDnBOz9YZa9iMegZekch5
OL52DDd6tId/eWVFrj/IcHYoCg7KNHQteZ004zUInCpjAT/e78IZFxG8k0lZR1Lc
87s8QXfhqm/GMzDIFMdZACrH8R90ubocK06iMcTahvI5EilH6LcLut28GGrRH8Og
C0YBAPaZ5cjhflc0grSjPK1dKqj/Vre3CQH/+lJ8qTOBPurXlxFL759bsi9Auath
GZ4bWhFTnykKCXJyzFbFgJObN/r/KrU4LI8q5MrkCseX5UTZ+P345WU6ZykjQqhJ
GPi/z+dXZDy8TQJD8gg07t/oyFlzlaqDkJNWOvU+Bf/zSUyY+WxvGKXb2l9Gd7/s
e2XISxvCzZK32s1mBNWSfl/tX0iw340=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions extras/tls/client.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEyzCCArOgAwIBAgIJAPPSvsWCQbfFMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
VQQGEwJDWjEaMBgGA1UECAwRSmlob21vcmF2c2t5IGtyYWoxDTALBgNVBAcMBEJy
bm8xGzAZBgNVBAoMElNjb3JpYSBDb3Jwb3JhdGlvbjE9MDsGA1UEAww0U2Nvcmlh
IENvcnBvcmF0aW9uIENsaWVudCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTAe
Fw0yMDA0MDMxNDQwNDBaFw0yMjA0MDMxNDQwNDBaMIGFMQswCQYDVQQGEwJDWjEa
MBgGA1UECAwRSmlob21vcmF2c2t5IGtyYWoxDTALBgNVBAcMBEJybm8xGzAZBgNV
BAoMElNjb3JpYSBDb3Jwb3JhdGlvbjEuMCwGA1UEAwwlU2NvcmlhIENvcnBvcmF0
aW9uIENsaWVudCBDZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALAJe+CxlDH9ajw9q7rpYOaXBZ7Z2t2qmRFChR9rySQVFft2mTsyeF9W
0zVNiR7wg1W74VvrrcQsv8OkbgEVeWt7e9lKIoIFzrQ1dJGUAs+vF4IQOKmlanWt
jjz42fuJVlwTn71rXHCxoyqd0jCaRd7BHtf/fl7Po9WEFRjUr5O1iZWHBIwIn7q+
edIwEUBs6qJN3vO42nqYmY7mQ/hG+vVzq7cL2WkN/EMGvj9SRVl0OMbmKnfxmUUi
FoVnB6KiREHt4Kb/4y1plZzAmEMI2QDpPp/keLSmHw55U2waTEo+BKJ//G4dp7Rs
K+CkdlOTIAEDM/AYvbM0/0rkPceovCMCAwEAAaMtMCswCQYDVR0TBAIwADARBglg
hkgBhvhCAQEEBAMCB4AwCwYDVR0PBAQDAgXgMA0GCSqGSIb3DQEBCwUAA4ICAQBV
M9wSpuC4zt5LhhXBHmxHuUVdIEIU+XXLTzMms3IC8r56rH4fFD6wfyVqvTlLVIyk
UeX/FrZ9P1uOt1H1nDeNLlK8ihVdw+JSLplCfjX7SevD8tXdnokcl95p3RMMHjXU
d46pY1StAU9fIm46WVsbtzfIPhejNlhn2L3DW3V2tkVXEKzdvaiFvmLWVlalxawY
CoyDh4m9E5s6l/B9RoLCAajSGeXQxMCm2L9DwAyUJhFPQYLO4YJT1fM7cvl7Irms
qjRAPq0rroebSP3bZDP0PXe7hwd01JcSnuLcQg6cOnsL9UOla8UpqJrMxG+rBD9o
nnIOoFA/2pjNsa0xTarRXa7C75H0f4TWlEzhsEvlTqT1eTVu/XfUcv2r2mL+jSVW
7iSQ37tlR8hN9L8/iYjIMlsf++3pdK1rvP0Mk8042pL8eqB+OYUQe/88KaNxTBeN
q1sqzkXtcJk7DqTBPXfHFJgzASpy7UR56sa/P7XmqTmBrpNDMP2XUkdNoAQjGae1
qiRmTiHP9e7d3bfWjW+odjbCxxZz5v4vfYY8FB6w2FfgLknfmnYKTOVR5ewT0d3T
01mLiKVtNDlMNHSBsOWvv72sH8Y1viQ09AzzrsCEFmyCGvQXQ4bps0ObIAITS98f
S1D9f+XM2TZJ/WxEB5VQP30iegfqEuKrwUTk8Lh6+g==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions extras/tls/client.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsAl74LGUMf1qPD2ruulg5pcFntna3aqZEUKFH2vJJBUV+3aZ
OzJ4X1bTNU2JHvCDVbvhW+utxCy/w6RuARV5a3t72UoiggXOtDV0kZQCz68XghA4
qaVqda2OPPjZ+4lWXBOfvWtccLGjKp3SMJpF3sEe1/9+Xs+j1YQVGNSvk7WJlYcE
jAifur550jARQGzqok3e87jaepiZjuZD+Eb69XOrtwvZaQ38Qwa+P1JFWXQ4xuYq
d/GZRSIWhWcHoqJEQe3gpv/jLWmVnMCYQwjZAOk+n+R4tKYfDnlTbBpMSj4Eon/8
bh2ntGwr4KR2U5MgAQMz8Bi9szT/SuQ9x6i8IwIDAQABAoIBAHiziATgvcQpBhaY
Eo/uRUrWcjwhFDi5KIr1GWIZ/aiH7LKm9xnn2TFFzzvVFhfowaSfVj44ssS4CiST
Mfn8R2yzFpA+jLqqULivjmXjHqpYW74KcU+g5AYcIlMcLhqSaGxp6DVwz8lVg5NM
8znwDchWkld4D6XiqWtVTUHhUyHrS74RR5KNEDSTJO+hwwWrviz9nzn5XO4vBa2C
w+SxFbQ3b4A/BCAIxEawYmBunizns29PFEgTqbmu+obRnjCHGzDH88Ob6R1uXn5f
4ofVOIGYpJi1X+0I9Io2fS9oOoaRU82gz26YLxKuE1XbZXrSchUGnfWpsVF0+yqi
TSy6cAECgYEA47OBhS1sDDg/TPwT26SVokGLhK30UxcOpxIaW9Dv+JnCGyfSffRD
BYBj2aiFLTZghJlqsumHjgRuZ4ZWW5tasioSbZ4IidIjtCkRTCv/M+eNVfaEjbZJ
Bg7uP3WnzcztYqdIbqgmyAq6ExqPr6WsICXka3SlEordOn1wuNT4NyMCgYEAxeo9
+sRyihydkNBrrcAJB5xCfPVG+THLAfUdTCZ9vC/GU31SN4CRsivvi6pwT0OKBFnz
OFjojW7Gb9c1SVgljMLubbpZfiDwT/JNzh6meEJTQnvsm3MrdNx6Zo7p2LDuOIZJ
2LQZzFKGckMxvk2xJXWHCzoBvAxecSxDe79INwECgYANE944e+dcvE5GaaPqVYWS
kBknQaZqr0RULCH/a/ycVphjXuIkAcdnpXwWoCsl8Z2RgA40wFzctzxwDbMgB8gp
u2jbitwKrlsGmeU4br51iLMBYOs0CGghRPJCCsvccgygQeNTF61Ch/sv5bKi7+z2
27ZGxahFbFxQY6v5saGf6QKBgACYTKllT8bUgTC/P6OdESnhsV14y0bSfH68AuOI
thYLurfjh4y9KTL06Nptn7rNRCvxLUb9FW3faF9LsVBQIITEzTytM7mqVa6X1t4I
v41a/a8UekiZVwcZ5pBKW6+YEI9A8BXjrLQth1Pumcatqxumt8oz2W98RghnDqjf
kVMBAoGBALbsVnmLnLiP2KnaYvYQyos8v7z43vdU1tknz04OxrMzPkBL7K0Mvk/0
yqD5jsR0cM/Fzc2RE7QBaSOkaShltIWIXlseO+kqPJ4XlLXmse3nmW8YG1ryokcG
LByhR57Kr6jHFGVcLqxrj2Bcgt6+oiCeREIjPgQMUH90W0wPM7XT
-----END RSA PRIVATE KEY-----
8 changes: 8 additions & 0 deletions extras/tls/update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

# extracting client credentials
docker run --rm scoriacorp/tls_memcached cat /opt/certs/key/client.key > client.key
docker run --rm scoriacorp/tls_memcached cat /opt/certs/crt/client.crt > client.crt

# extracting CA certificate
docker run --rm scoriacorp/tls_memcached cat /opt/certs/crt/ca-root.crt > ca-root.crt
17 changes: 14 additions & 3 deletions pymemcache/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ def __init__(self,
key_prefix=b'',
default_noreply=True,
allow_unicode_keys=False,
encoding='ascii'):
encoding='ascii',
tls_context=None):
"""
Constructor.
Expand Down Expand Up @@ -269,6 +270,7 @@ def __init__(self,
self.default_noreply = default_noreply
self.allow_unicode_keys = allow_unicode_keys
self.encoding = encoding
self.tls_context = tls_context

def check_key(self, key):
"""Checks key and add key_prefix."""
Expand All @@ -281,6 +283,11 @@ def _connect(self):
if isinstance(self.server, (list, tuple)):
sock = self.socket_module.socket(self.socket_module.AF_INET,
self.socket_module.SOCK_STREAM)

if self.tls_context:
sock = self.tls_context.wrap_socket(
sock, server_hostname=self.server[0]
)
else:
sock = self.socket_module.socket(self.socket_module.AF_UNIX,
self.socket_module.SOCK_STREAM)
Expand All @@ -291,6 +298,7 @@ def _connect(self):
if self.no_delay and sock.family == self.socket_module.AF_INET:
sock.setsockopt(self.socket_module.IPPROTO_TCP,
self.socket_module.TCP_NODELAY, 1)

except Exception:
sock.close()
raise
Expand Down Expand Up @@ -1016,7 +1024,8 @@ def __init__(self,
lock_generator=None,
default_noreply=True,
allow_unicode_keys=False,
encoding='ascii'):
encoding='ascii',
tls_context=None):
self.server = server
self.serde = serde or LegacyWrappingSerde(serializer, deserializer)
self.connect_timeout = connect_timeout
Expand All @@ -1037,6 +1046,7 @@ def __init__(self,
max_size=max_pool_size,
lock_generator=lock_generator)
self.encoding = encoding
self.tls_context = tls_context

def check_key(self, key):
"""Checks key and add key_prefix."""
Expand All @@ -1055,7 +1065,8 @@ def _create_client(self):
socket_module=self.socket_module,
key_prefix=self.key_prefix,
default_noreply=self.default_noreply,
allow_unicode_keys=self.allow_unicode_keys)
allow_unicode_keys=self.allow_unicode_keys,
tls_context=self.tls_context)
return client

def close(self):
Expand Down
7 changes: 5 additions & 2 deletions pymemcache/client/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def __init__(
ignore_exc=False,
allow_unicode_keys=False,
default_noreply=True,
encoding='ascii'
encoding='ascii',
tls_context=None
):
"""
Constructor.
Expand Down Expand Up @@ -87,7 +88,8 @@ def __init__(
'deserializer': deserializer,
'allow_unicode_keys': allow_unicode_keys,
'default_noreply': default_noreply,
'encoding': encoding
'encoding': encoding,
'tls_context': tls_context,
}

if use_pooling is True:
Expand All @@ -99,6 +101,7 @@ def __init__(
for server, port in servers:
self.add_server(server, port)
self.encoding = encoding
self.tls_context = tls_context

def add_server(self, server, port):
key = '%s:%s' % (server, port)
Expand Down
24 changes: 24 additions & 0 deletions pymemcache/test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
import socket
import ssl


def pytest_addoption(parser):
Expand All @@ -9,6 +10,12 @@ def pytest_addoption(parser):
parser.addoption('--port', action='store', default='11211',
help='memcached server port')

parser.addoption('--tls-server', action='store', default='localhost',
help='TLS memcached server')

parser.addoption('--tls-port', action='store', default='11212',
help='TLS memcached server port')

parser.addoption('--size', action='store', default=1024,
help='size of data in benchmarks')

Expand All @@ -29,6 +36,16 @@ def port(request):
return int(request.config.option.port)


@pytest.fixture(scope='session')
def tls_host(request):
return request.config.option.tls_server


@pytest.fixture(scope='session')
def tls_port(request):
return int(request.config.option.tls_port)


@pytest.fixture(scope='session')
def size(request):
return int(request.config.option.size)
Expand All @@ -49,6 +66,13 @@ def pairs(size, keys):
return {'pymemcache_test:%d' % i: 'X' * size for i in range(keys)}


@pytest.fixture(scope='session')
def tls_context():
return ssl.create_default_context(
cafile="extras/tls/ca-root.crt"
)


def pytest_generate_tests(metafunc):
if 'socket_module' in metafunc.fixturenames:
socket_modules = [socket]
Expand Down
16 changes: 16 additions & 0 deletions pymemcache/test/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,19 @@ def _unicode_value_in_set():

with pytest.raises(MemcacheClientError):
_unicode_value_in_set()


@pytest.mark.integration()
def test_tls(client_class, tls_host, tls_port, socket_module, tls_context):
client = client_class(
(tls_host, tls_port),
socket_module=socket_module,
tls_context=tls_context
)
client.flush_all()

key = b'key'
value = b'value'
key2 = b'key2'
value2 = b'value2'
get_set_helper(client, key, value, key2, value2)

0 comments on commit f35f215

Please sign in to comment.