diff options
author | Moisés Guimarães de Medeiros <moisesguimaraes@users.noreply.github.com> | 2020-04-07 12:33:08 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-07 08:33:08 -0700 |
commit | f35f21573ca816171e22b927925d76d5a47fc334 (patch) | |
tree | 47fdd7f73865270bc4269822966bcc8bd9b33d4e | |
parent | da89f48496493bf9075d8531e26dae42e0fd0dd9 (diff) | |
download | pymemcache-f35f21573ca816171e22b927925d76d5a47fc334.tar.gz |
Add TLS support for TCP sockets (#276)
-rw-r--r-- | .github/workflows/ci.yml | 9 | ||||
-rw-r--r-- | docs/getting_started.rst | 24 | ||||
-rw-r--r-- | extras/tls/ca-root.crt | 38 | ||||
-rw-r--r-- | extras/tls/client.crt | 28 | ||||
-rw-r--r-- | extras/tls/client.key | 27 | ||||
-rwxr-xr-x | extras/tls/update.sh | 8 | ||||
-rw-r--r-- | pymemcache/client/base.py | 17 | ||||
-rw-r--r-- | pymemcache/client/hash.py | 7 | ||||
-rw-r--r-- | pymemcache/test/conftest.py | 24 | ||||
-rw-r--r-- | pymemcache/test/test_integration.py | 16 |
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) |