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
This commit is contained in:
dengzhaosen 2020-07-21 10:55:25 +08:00
parent d2aef19023
commit d229d3edb7
8 changed files with 134 additions and 16 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -0,0 +1,5 @@
---
features:
- |
Add the feature to support SASL for olso.cache to improve the security
of authority.