From d229d3edb73610137d78779b12e6dc226a3c6e61 Mon Sep 17 00:00:00 2001 From: dengzhaosen Date: Tue, 21 Jul 2020 10:55:25 +0800 Subject: [PATCH] Support SASL protocol for memcached Add the SASL protocol for memcached, to improve the security of authority. SASL(Simple Authentication and Security Layer): is a memchanism used to extend the verification ability of C/S mode. SASL is only the authentication process, which integrates the application layer and the system authentication mechanism. However, the current memcached hasn't any authenticaction mechanism to protect the user's data cached in memcached server. Depends-On: 7828bed0febabfa11a0a8f6960f4c7cc8acec841 Implements: blueprint enable-sasl-protocol Change-Id: I40b9f4eac518f34a3dfb710b5c4ab3a76da7c00c --- oslo_cache/_bmemcache_pool.py | 62 +++++++++++++++++++ oslo_cache/_memcache_pool.py | 9 ++- oslo_cache/_opts.py | 10 +++ oslo_cache/backends/memcache_pool.py | 33 +++++----- oslo_cache/core.py | 3 +- .../memcache_pool/test_cache_backend.py | 17 +++++ oslo_cache/tests/unit/test_connection_pool.py | 11 ++++ ...enable-sasl-protocol-46d11530b87e7832.yaml | 5 ++ 8 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 oslo_cache/_bmemcache_pool.py create mode 100644 releasenotes/notes/enable-sasl-protocol-46d11530b87e7832.yaml diff --git a/oslo_cache/_bmemcache_pool.py b/oslo_cache/_bmemcache_pool.py new file mode 100644 index 00000000..a442757f --- /dev/null +++ b/oslo_cache/_bmemcache_pool.py @@ -0,0 +1,62 @@ +# Copyright 2022 Inspur +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Thread-safe connection pool for python-binary-memcached.""" +try: + import eventlet +except ImportError: + eventlet = None +import bmemcached +from oslo_cache._memcache_pool import MemcacheClientPool +from oslo_log import log + +LOG = log.getLogger(__name__) + + +class _BMemcacheClient(bmemcached.Client): + """Thread global memcache client + + As client is inherited from threading.local we have to restore object + methods overloaded by threading.local so we can reuse clients in + different threads + """ + __delattr__ = object.__delattr__ + __getattribute__ = object.__getattribute__ + __setattr__ = object.__setattr__ + + # Hack for lp 1812935 + if eventlet and eventlet.patcher.is_monkey_patched('thread'): + # NOTE(bnemec): I'm not entirely sure why this works in a + # monkey-patched environment and not with vanilla stdlib, but it does. + def __new__(cls, *args, **kwargs): + return object.__new__(cls) + else: + __new__ = object.__new__ + + def __del__(self): + pass + + +class BMemcacheClientPool(MemcacheClientPool): + def __init__(self, urls, arguments, **kwargs): + MemcacheClientPool.__init__(self, urls, arguments, **kwargs) + self._arguments = { + 'username': arguments.get('username', None), + 'password': arguments.get('password', None), + 'tls_context': arguments.get('tls_context', None), + } + + def _create_connection(self): + return _BMemcacheClient(self.urls, **self._arguments) diff --git a/oslo_cache/_memcache_pool.py b/oslo_cache/_memcache_pool.py index 3f0bacb5..21e1e0dd 100644 --- a/oslo_cache/_memcache_pool.py +++ b/oslo_cache/_memcache_pool.py @@ -204,7 +204,14 @@ class MemcacheClientPool(ConnectionPool): # old-style class ConnectionPool.__init__(self, **kwargs) self.urls = urls - self._arguments = arguments + self._arguments = { + 'dead_retry': arguments.get('dead_retry', 5 * 60), + 'socket_timeout': arguments.get('socket_timeout', 3.0), + 'server_max_value_length': + arguments.get('server_max_value_length'), + 'flush_on_reconnect': arguments.get( + 'pool_flush_on_reconnect', False), + } # NOTE(morganfainberg): The host objects expect an int for the # deaduntil value. Initialize this at 0 for each host with 0 indicating # the host is not dead. diff --git a/oslo_cache/_opts.py b/oslo_cache/_opts.py index 167446b4..8917bab0 100644 --- a/oslo_cache/_opts.py +++ b/oslo_cache/_opts.py @@ -116,6 +116,16 @@ FILE_OPTIONS = { help='Global toggle if memcache will be flushed' ' on reconnect.' ' (oslo_cache.memcache_pool backend only).'), + cfg.BoolOpt('memcache_sasl_enabled', + default=False, + help='Enable the SASL(Simple Authentication and Security' + 'Layer) if the SASL_enable is true, else disable.'), + cfg.StrOpt('memcache_username', + default='', + help='the user name for the memcached which SASL enabled'), + cfg.StrOpt('memcache_password', + default='', + help='the password for the memcached which SASL enabled'), cfg.BoolOpt('tls_enabled', default=False, help='Global toggle for TLS usage when comunicating with' diff --git a/oslo_cache/backends/memcache_pool.py b/oslo_cache/backends/memcache_pool.py index 449d48f9..fa640dc3 100644 --- a/oslo_cache/backends/memcache_pool.py +++ b/oslo_cache/backends/memcache_pool.py @@ -19,6 +19,7 @@ import functools from dogpile.cache.backends import memcached as memcached_backend +from oslo_cache import _bmemcache_pool from oslo_cache import _memcache_pool @@ -55,20 +56,24 @@ class PooledMemcachedBackend(memcached_backend.MemcachedBackend): # Composed from GenericMemcachedBackend's and MemcacheArgs's __init__ def __init__(self, arguments): super(PooledMemcachedBackend, self).__init__(arguments) - self.client_pool = _memcache_pool.MemcacheClientPool( - self.url, - arguments={ - 'dead_retry': arguments.get('dead_retry', 5 * 60), - 'socket_timeout': arguments.get('socket_timeout', 3.0), - 'server_max_value_length': - arguments.get('server_max_value_length'), - 'flush_on_reconnect': arguments.get('pool_flush_on_reconnect', - False), - }, - maxsize=arguments.get('pool_maxsize', 10), - unused_timeout=arguments.get('pool_unused_timeout', 60), - conn_get_timeout=arguments.get('pool_connection_get_timeout', 10), - ) + if arguments.get('sasl_enabled', False): + self.client_pool = _bmemcache_pool.BMemcacheClientPool( + self.url, + arguments, + maxsize=arguments.get('pool_maxsize', 10), + unused_timeout=arguments.get('pool_unused_timeout', 60), + conn_get_timeout=arguments.get('pool_connection_get_timeout', + 10), + ) + else: + self.client_pool = _memcache_pool.MemcacheClientPool( + self.url, + arguments, + maxsize=arguments.get('pool_maxsize', 10), + unused_timeout=arguments.get('pool_unused_timeout', 60), + conn_get_timeout=arguments.get('pool_connection_get_timeout', + 10), + ) # Since all methods in backend just call one of methods of client, this # lets us avoid need to hack it too much diff --git a/oslo_cache/core.py b/oslo_cache/core.py index 6279166c..d1c45cac 100644 --- a/oslo_cache/core.py +++ b/oslo_cache/core.py @@ -163,7 +163,8 @@ def _build_cache_config(conf): conf.cache.memcache_servers) for arg in ('dead_retry', 'socket_timeout', 'pool_maxsize', 'pool_unused_timeout', 'pool_connection_get_timeout', - 'pool_flush_on_reconnect'): + 'pool_flush_on_reconnect', 'sasl_enabled', 'username', + 'password'): value = getattr(conf.cache, 'memcache_' + arg) conf_dict['%s.arguments.%s' % (prefix, arg)] = value diff --git a/oslo_cache/tests/functional/memcache_pool/test_cache_backend.py b/oslo_cache/tests/functional/memcache_pool/test_cache_backend.py index 0a0b435f..9aca2afc 100644 --- a/oslo_cache/tests/functional/memcache_pool/test_cache_backend.py +++ b/oslo_cache/tests/functional/memcache_pool/test_cache_backend.py @@ -30,3 +30,20 @@ class TestMemcachePoolCacheBackend(test_base.BaseTestCaseCacheBackend): # config fixture is properly initialized with value related to # the current backend in use. super(TestMemcachePoolCacheBackend, self).setUp() + + +class TestBMemcachePoolCacheBackend(test_base.BaseTestCaseCacheBackend): + def setUp(self): + MEMCACHED_PORT = os.getenv("OSLO_CACHE_TEST_MEMCACHED_PORT", "11211") + # If the cache support the sasl, the memcache_sasl_enabled + # should be True. + self.config_fixture.config( + group='cache', + backend='oslo_cache.memcache_pool', + enabled=True, + memcache_servers=[f'localhost:{MEMCACHED_PORT}'], + memcache_sasl_enabled=False, + memcache_username='sasl_name', + memcache_password='sasl_pswd' + ) + super(TestBMemcachePoolCacheBackend, self).setUp() diff --git a/oslo_cache/tests/unit/test_connection_pool.py b/oslo_cache/tests/unit/test_connection_pool.py index 4046125a..5f2c7481 100644 --- a/oslo_cache/tests/unit/test_connection_pool.py +++ b/oslo_cache/tests/unit/test_connection_pool.py @@ -18,6 +18,7 @@ from unittest import mock import testtools from testtools import matchers +from oslo_cache import _bmemcache_pool from oslo_cache import _memcache_pool from oslo_cache import exception from oslo_cache.tests import test_cache @@ -161,3 +162,13 @@ class TestMemcacheClientOverrides(test_cache.BaseTestCase): self.assertFalse(client.do_check_key) # Make sure our __new__ override still results in the right type self.assertIsInstance(client, _memcache_pool._MemcacheClient) + + +class TestBMemcacheClient(test_cache.BaseTestCase): + + def test_can_create_with_kwargs(self): + client = _bmemcache_pool._BMemcacheClient('foo', password='123456') + # Make sure kwargs are properly processed by the client + self.assertEqual('123456', client.password) + # Make sure our __new__ override still results in the right type + self.assertIsInstance(client, _bmemcache_pool._BMemcacheClient) diff --git a/releasenotes/notes/enable-sasl-protocol-46d11530b87e7832.yaml b/releasenotes/notes/enable-sasl-protocol-46d11530b87e7832.yaml new file mode 100644 index 00000000..a5eaa22b --- /dev/null +++ b/releasenotes/notes/enable-sasl-protocol-46d11530b87e7832.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add the feature to support SASL for olso.cache to improve the security + of authority.