From 4e7ec75b2f6feb6985d7f3b41f14e0ede4de5e82 Mon Sep 17 00:00:00 2001 From: Luca Miccini Date: Wed, 19 Feb 2025 15:51:02 +0100 Subject: [PATCH] Switch from python-memcache to pymemcache To support TLS and MTLS this commit replaces python-memcache with pymemcache. Change-Id: I8a361af1658722eb2eac5592504925347a57020c --- keystonemiddleware/auth_token/__init__.py | 18 +++++++++++++ keystonemiddleware/auth_token/_cache.py | 13 ++++++---- keystonemiddleware/auth_token/_opts.py | 26 +++++++++++++++++++ .../tests/unit/auth_token/test_cache.py | 4 +-- keystonemiddleware/tests/unit/test_opts.py | 10 +++++++ ...switch-to-pymemcache-fcdad3326f954ea1.yaml | 4 +++ 6 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/switch-to-pymemcache-fcdad3326f954ea1.yaml diff --git a/keystonemiddleware/auth_token/__init__.py b/keystonemiddleware/auth_token/__init__.py index 9b8fb575..6a450da4 100644 --- a/keystonemiddleware/auth_token/__init__.py +++ b/keystonemiddleware/auth_token/__init__.py @@ -219,6 +219,7 @@ object is stored. import copy import re +import ssl from keystoneauth1 import access from keystoneauth1 import adapter @@ -877,6 +878,23 @@ class AuthProtocol(BaseAuthProtocol): password=self._conf.get('memcache_password'), ) + if self._conf.get('memcache_tls_enabled'): + tls_cafile = self._conf.get('memcache_tls_cafile') + tls_certfile = self._conf.get('memcache_tls_certfile') + tls_keyfile = self._conf.get('memcache_tls_keyfile') + tls_allowed_ciphers = self._conf.get( + 'memcache_tls_allowed_ciphers') + + tls_context = ssl.create_default_context(cafile=tls_cafile) + + if tls_certfile: + tls_context.load_cert_chain(tls_certfile, tls_keyfile) + + if tls_allowed_ciphers: + tls_context.set_ciphers(tls_allowed_ciphers) + + cache_kwargs['tls_context'] = tls_context + if security_strategy.lower() != 'none': secret_key = self._conf.get('memcache_secret_key') return _cache.SecureTokenCache(self.log, diff --git a/keystonemiddleware/auth_token/_cache.py b/keystonemiddleware/auth_token/_cache.py index 3a7e0c30..0fba019a 100644 --- a/keystonemiddleware/auth_token/_cache.py +++ b/keystonemiddleware/auth_token/_cache.py @@ -52,11 +52,12 @@ class _EnvCachePool(object): class _CachePool(list): """A lazy pool of cache references.""" - def __init__(self, memcached_servers, log, arguments): + def __init__(self, memcached_servers, log, arguments, tls_context=None): self._memcached_servers = memcached_servers self._sasl_enabled = arguments.get("sasl_enabled", False) self._username = arguments.get("username", None) self._password = arguments.get("password", None) + self._tls_context = tls_context if not self._memcached_servers: log.warning( "Using the in-process token cache is deprecated as of the " @@ -81,8 +82,9 @@ class _CachePool(list): c = bmemcached.Client(self._memcached_servers, self._username, self._password) else: - import memcache - c = memcache.Client(self._memcached_servers, debug=0) + import pymemcache + c = pymemcache.HashClient(self._memcached_servers, + tls_context=self._tls_context) else: c = _FakeClient() @@ -140,13 +142,14 @@ class TokenCache(object): _CACHE_KEY_TEMPLATE = 'tokens/%s' def __init__(self, log, cache_time=None, - env_cache_name=None, memcached_servers=None, + env_cache_name=None, memcached_servers=None, tls_context=None, use_advanced_pool=True, dead_retry=None, socket_timeout=None, **kwargs): self._LOG = log self._cache_time = cache_time self._env_cache_name = env_cache_name self._memcached_servers = memcached_servers + self._tls_context = tls_context self._use_advanced_pool = use_advanced_pool self._arguments = { 'dead_retry': dead_retry, @@ -178,7 +181,7 @@ class TokenCache(object): "through config option memcache_use_advanced_pool = True") return _CachePool(self._memcached_servers, self._LOG, - self._arguments) + self._arguments, tls_context=self._tls_context) def initialize(self, env): if self._initialized: diff --git a/keystonemiddleware/auth_token/_opts.py b/keystonemiddleware/auth_token/_opts.py index 70e233a0..b2621faf 100644 --- a/keystonemiddleware/auth_token/_opts.py +++ b/keystonemiddleware/auth_token/_opts.py @@ -124,6 +124,32 @@ _OPTS = [ secret=True, help='(Optional, mandatory if memcache_security_strategy is' ' defined) This string is used for key derivation.'), + cfg.BoolOpt('memcache_tls_enabled', + default=False, + help='(Optional) Global toggle for TLS usage when comunicating' + ' with the caching servers.'), + cfg.StrOpt('memcache_tls_cafile', + help='(Optional) Path to a file of concatenated CA certificates' + ' in PEM format necessary to establish the caching server\'s' + ' authenticity. If tls_enabled is False, this option is' + ' ignored.'), + cfg.StrOpt('memcache_tls_certfile', + help='(Optional) Path to a single file in PEM format containing' + ' the client\'s certificate as well as any number of CA' + ' certificates needed to establish the certificate\'s' + ' authenticity. This file is only required when client side' + ' authentication is necessary. If tls_enabled is False, this' + ' option is ignored.'), + cfg.StrOpt('memcache_tls_keyfile', + help='(Optional) Path to a single file containing the client\'s' + ' private key in. Otherwhise the private key will be taken from' + ' the file specified in tls_certfile. If tls_enabled is False,' + ' this option is ignored.'), + cfg.StrOpt('memcache_tls_allowed_ciphers', + help='(Optional) Set the available ciphers for sockets created' + ' with the TLS context. It should be a string in the OpenSSL' + ' cipher list format. If not specified, all OpenSSL enabled' + ' ciphers will be available.'), cfg.IntOpt('memcache_pool_dead_retry', default=5 * 60, help='(Optional) Number of seconds memcached server is' diff --git a/keystonemiddleware/tests/unit/auth_token/test_cache.py b/keystonemiddleware/tests/unit/auth_token/test_cache.py index c6fcbcbf..d438caaf 100644 --- a/keystonemiddleware/tests/unit/auth_token/test_cache.py +++ b/keystonemiddleware/tests/unit/auth_token/test_cache.py @@ -97,8 +97,8 @@ class TestLiveMemcache(base.BaseAuthTokenTestCase): if MEMCACHED_AVAILABLE is None: try: - import memcache - c = memcache.Client(MEMCACHED_SERVERS) + import pymemcache + c = pymemcache.HashClient(MEMCACHED_SERVERS) c.set('ping', 'pong', time=1) MEMCACHED_AVAILABLE = c.get('ping') == 'pong' except ImportError: diff --git a/keystonemiddleware/tests/unit/test_opts.py b/keystonemiddleware/tests/unit/test_opts.py index c9b1e2f5..7671a225 100644 --- a/keystonemiddleware/tests/unit/test_opts.py +++ b/keystonemiddleware/tests/unit/test_opts.py @@ -58,6 +58,11 @@ class OptsTestCase(utils.TestCase): 'memcache_security_strategy', 'memcache_secret_key', 'memcache_use_advanced_pool', + 'memcache_tls_enabled', + 'memcache_tls_cafile', + 'memcache_tls_certfile', + 'memcache_tls_keyfile', + 'memcache_tls_allowed_ciphers', 'memcache_pool_dead_retry', 'memcache_pool_maxsize', 'memcache_pool_unused_timeout', @@ -106,6 +111,11 @@ class OptsTestCase(utils.TestCase): 'memcache_security_strategy', 'memcache_secret_key', 'memcache_use_advanced_pool', + 'memcache_tls_enabled', + 'memcache_tls_cafile', + 'memcache_tls_certfile', + 'memcache_tls_keyfile', + 'memcache_tls_allowed_ciphers', 'memcache_pool_dead_retry', 'memcache_pool_maxsize', 'memcache_pool_unused_timeout', diff --git a/releasenotes/notes/switch-to-pymemcache-fcdad3326f954ea1.yaml b/releasenotes/notes/switch-to-pymemcache-fcdad3326f954ea1.yaml new file mode 100644 index 00000000..ca06c3ec --- /dev/null +++ b/releasenotes/notes/switch-to-pymemcache-fcdad3326f954ea1.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + python-memcached has been replaced by pymemcache to support TLS and MTLS.