summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoisés Guimarães de Medeiros <moisesguimaraes@users.noreply.github.com>2020-04-07 12:33:08 -0300
committerGitHub <noreply@github.com>2020-04-07 08:33:08 -0700
commitf35f21573ca816171e22b927925d76d5a47fc334 (patch)
tree47fdd7f73865270bc4269822966bcc8bd9b33d4e
parentda89f48496493bf9075d8531e26dae42e0fd0dd9 (diff)
downloadpymemcache-f35f21573ca816171e22b927925d76d5a47fc334.tar.gz
Add TLS support for TCP sockets (#276)
-rw-r--r--.github/workflows/ci.yml9
-rw-r--r--docs/getting_started.rst24
-rw-r--r--extras/tls/ca-root.crt38
-rw-r--r--extras/tls/client.crt28
-rw-r--r--extras/tls/client.key27
-rwxr-xr-xextras/tls/update.sh8
-rw-r--r--pymemcache/client/base.py17
-rw-r--r--pymemcache/client/hash.py7
-rw-r--r--pymemcache/test/conftest.py24
-rw-r--r--pymemcache/test/test_integration.py16
10 files changed, 192 insertions, 6 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cc402f7..3e9125e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 4e1b9ef..25690e6 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -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
--------------
diff --git a/extras/tls/ca-root.crt b/extras/tls/ca-root.crt
new file mode 100644
index 0000000..41ac2e9
--- /dev/null
+++ b/extras/tls/ca-root.crt
@@ -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-----
diff --git a/extras/tls/client.crt b/extras/tls/client.crt
new file mode 100644
index 0000000..70481fc
--- /dev/null
+++ b/extras/tls/client.crt
@@ -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-----
diff --git a/extras/tls/client.key b/extras/tls/client.key
new file mode 100644
index 0000000..f81f757
--- /dev/null
+++ b/extras/tls/client.key
@@ -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-----
diff --git a/extras/tls/update.sh b/extras/tls/update.sh
new file mode 100755
index 0000000..d10a697
--- /dev/null
+++ b/extras/tls/update.sh
@@ -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
diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py
index 972cf4d..0387da4 100644
--- a/pymemcache/client/base.py
+++ b/pymemcache/client/base.py
@@ -219,7 +219,8 @@ class Client(object):
key_prefix=b'',
default_noreply=True,
allow_unicode_keys=False,
- encoding='ascii'):
+ encoding='ascii',
+ tls_context=None):
"""
Constructor.
@@ -269,6 +270,7 @@ class Client(object):
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."""
@@ -281,6 +283,11 @@ class Client(object):
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)
@@ -291,6 +298,7 @@ class Client(object):
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
@@ -1016,7 +1024,8 @@ class PooledClient(object):
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
@@ -1037,6 +1046,7 @@ class PooledClient(object):
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."""
@@ -1055,7 +1065,8 @@ class PooledClient(object):
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):
diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py
index fccb060..25e297e 100644
--- a/pymemcache/client/hash.py
+++ b/pymemcache/client/hash.py
@@ -36,7 +36,8 @@ class HashClient(object):
ignore_exc=False,
allow_unicode_keys=False,
default_noreply=True,
- encoding='ascii'
+ encoding='ascii',
+ tls_context=None
):
"""
Constructor.
@@ -87,7 +88,8 @@ class HashClient(object):
'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:
@@ -99,6 +101,7 @@ class HashClient(object):
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)
diff --git a/pymemcache/test/conftest.py b/pymemcache/test/conftest.py
index de82622..73f7939 100644
--- a/pymemcache/test/conftest.py
+++ b/pymemcache/test/conftest.py
@@ -1,5 +1,6 @@
import pytest
import socket
+import ssl
def pytest_addoption(parser):
@@ -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')
@@ -30,6 +37,16 @@ def port(request):
@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)
@@ -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]
diff --git a/pymemcache/test/test_integration.py b/pymemcache/test/test_integration.py
index 71b720e..ddd08cc 100644
--- a/pymemcache/test/test_integration.py
+++ b/pymemcache/test/test_integration.py
@@ -362,3 +362,19 @@ def test_errors(client_class, host, port, socket_module):
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)