Merge "[Native CephFS] Add messages for async ACL ops" into stable/ussuri

This commit is contained in:
Zuul 2021-06-01 19:20:07 +00:00 committed by Gerrit Code Review
commit db1dcdd325
5 changed files with 187 additions and 51 deletions

View File

@ -512,6 +512,10 @@ class InvalidShareAccessLevel(Invalid):
message = _("Invalid or unsupported share access level: %(level)s.")
class InvalidShareAccessType(Invalid):
message = _("Invalid or unsupported share access type: %(type)s.")
class ShareBackendException(ManilaException):
message = _("Share backend error: %(msg)s.")

View File

@ -33,7 +33,9 @@ class Action(object):
DELETE = ('007', _('delete'))
EXTEND = ('008', _('extend'))
SHRINK = ('009', _('shrink'))
ALL = (ALLOCATE_HOST,
UPDATE_ACCESS_RULES = ('010', _('update access rules'))
ALL = (
ALLOCATE_HOST,
CREATE,
DELETE_ACCESS_RULES,
PROMOTE,
@ -41,7 +43,9 @@ class Action(object):
REVERT_TO_SNAPSHOT,
DELETE,
EXTEND,
SHRINK)
SHRINK,
UPDATE_ACCESS_RULES,
)
class Detail(object):
@ -98,8 +102,18 @@ class Detail(object):
'019',
_("Share Driver does not support shrinking shares."
" Shrinking share operation failed."))
FORBIDDEN_CLIENT_ACCESS = (
'020',
_("Failed to grant access to client. The client ID used may be "
"forbidden. You may try again with a different client identifier."))
UNSUPPORTED_CLIENT_ACCESS = (
'021',
_("Failed to grant access to client. The access level or type may "
"be unsupported. You may try again with a different access level "
"or access type."))
ALL = (UNKNOWN_ERROR,
ALL = (
UNKNOWN_ERROR,
NO_VALID_HOST,
UNEXPECTED_NETWORK,
NO_SHARE_SERVER,
@ -117,7 +131,10 @@ class Detail(object):
FILTER_CREATE_FROM_SNAPSHOT,
DRIVER_FAILED_CREATING_FROM_SNAP,
DRIVER_REFUSED_SHRINK,
DRIVER_FAILED_SHRINK)
DRIVER_FAILED_SHRINK,
FORBIDDEN_CLIENT_ACCESS,
UNSUPPORTED_CLIENT_ACCESS,
)
# Exception and detail mappings
EXCEPTION_DETAIL_MAPPINGS = {

View File

@ -27,6 +27,8 @@ import six
from manila.common import constants
from manila import exception
from manila.i18n import _
from manila.message import api as message_api
from manila.message import message_field
from manila.share import driver
from manila.share.drivers import ganesha
from manila.share.drivers.ganesha import utils as ganesha_utils
@ -366,6 +368,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def __init__(self, execute, config, **kwargs):
self.volume_client = kwargs.pop('ceph_vol_client')
self.message_api = message_api.API()
super(NativeProtocolHelper, self).__init__(execute, config,
**kwargs)
@ -395,8 +398,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def _allow_access(self, context, share, access, share_server=None):
if access['access_type'] != CEPHX_ACCESS_TYPE:
raise exception.InvalidShareAccess(
reason=_("Only 'cephx' access type allowed."))
raise exception.InvalidShareAccessType(type=access['access_type'])
ceph_auth_id = access['access_to']
@ -409,7 +411,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
error_message = (_('Ceph authentication ID %s must be different '
'than the one the Manila service uses.') %
ceph_auth_id)
raise exception.InvalidInput(message=error_message)
raise exception.InvalidShareAccess(reason=error_message)
if not getattr(self.volume_client, 'version', None):
if access['access_level'] == constants.ACCESS_LEVEL_RO:
@ -422,9 +424,18 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
cephfs_share_path(share), ceph_auth_id)
else:
readonly = access['access_level'] == constants.ACCESS_LEVEL_RO
try:
auth_result = self.volume_client.authorize(
cephfs_share_path(share), ceph_auth_id, readonly=readonly,
tenant_id=share['project_id'])
except Exception as e:
if 'not allowed' in six.text_type(e).lower():
msg = ("Access to client %(client)s is not allowed. "
"Reason: %(reason)s")
msg_payload = {'client': ceph_auth_id, 'reason': e}
raise exception.InvalidShareAccess(
reason=msg % msg_payload)
raise
return auth_result['auth_key']
@ -443,7 +454,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
access_keys = {}
access_updates = {}
if not (add_rules or delete_rules): # recovery/maintenance mode
add_rules = access_rules
@ -475,13 +486,48 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
# access keys and ensure that after recovery, manila and the Ceph
# backend are in sync.
for rule in add_rules:
try:
access_key = self._allow_access(context, share, rule)
access_keys.update({rule['access_id']: {'access_key': access_key}})
except (exception.InvalidShareAccessLevel,
exception.InvalidShareAccessType):
self.message_api.create(
context,
message_field.Action.UPDATE_ACCESS_RULES,
share['project_id'],
resource_type=message_field.Resource.SHARE,
resource_id=share['share_id'],
detail=message_field.Detail.UNSUPPORTED_CLIENT_ACCESS)
log_args = {'id': rule['access_id'],
'access_level': rule['access_level'],
'access_to': rule['access_to']}
LOG.exception("Failed to provide %(access_level)s access to "
"%(access_to)s (Rule ID: %(id)s). Setting rule "
"to 'error' state.", log_args)
access_updates.update({rule['access_id']: {'state': 'error'}})
except exception.InvalidShareAccess:
self.message_api.create(
context,
message_field.Action.UPDATE_ACCESS_RULES,
share['project_id'],
resource_type=message_field.Resource.SHARE,
resource_id=share['share_id'],
detail=message_field.Detail.FORBIDDEN_CLIENT_ACCESS)
log_args = {'id': rule['access_id'],
'access_level': rule['access_level'],
'access_to': rule['access_to']}
LOG.exception("Failed to provide %(access_level)s access to "
"%(access_to)s (Rule ID: %(id)s). Setting rule "
"to 'error' state.", log_args)
access_updates.update({rule['access_id']: {'state': 'error'}})
else:
access_updates.update({
rule['access_id']: {'access_key': access_key},
})
for rule in delete_rules:
self._deny_access(context, share, rule)
return access_keys
return access_updates
def get_configured_ip_versions(self):
return [4]

View File

@ -387,7 +387,7 @@ class NativeProtocolHelperTestCase(test.TestCase):
super(NativeProtocolHelperTestCase, self).setUp()
self.fake_conf = configuration.Configuration(None)
self._context = context.get_admin_context()
self._share = fake_share.fake_share(share_proto='CEPHFS')
self._share = fake_share.fake_share_instance(share_proto='CEPHFS')
self.fake_conf.set_default('driver_handles_share_servers', False)
@ -470,7 +470,7 @@ class NativeProtocolHelperTestCase(test.TestCase):
tenant_id=self._share['project_id'])
def test_allow_access_wrong_type(self):
self.assertRaises(exception.InvalidShareAccess,
self.assertRaises(exception.InvalidShareAccessType,
self._native_protocol_helper._allow_access,
self._context, self._share, {
'access_level': constants.ACCESS_LEVEL_RW,
@ -479,7 +479,7 @@ class NativeProtocolHelperTestCase(test.TestCase):
})
def test_allow_access_same_cephx_id_as_manila_service(self):
self.assertRaises(exception.InvalidInput,
self.assertRaises(exception.InvalidShareAccess,
self._native_protocol_helper._allow_access,
self._context, self._share, {
'access_level': constants.ACCESS_LEVEL_RW,
@ -487,6 +487,23 @@ class NativeProtocolHelperTestCase(test.TestCase):
'access_to': 'manila',
})
def test_allow_access_to_preexisting_ceph_user(self):
vc = self._native_protocol_helper.volume_client
msg = ("auth ID: admin exists and not created by "
"ceph_volume_client. Not allowed to modify")
self.mock_object(vc, 'authorize',
mock.Mock(side_effect=Exception(msg)))
self.assertRaises(exception.InvalidShareAccess,
self._native_protocol_helper._allow_access,
self._context, self._share,
{
'access_level': constants.ACCESS_LEVEL_RW,
'access_type': 'cephx',
'access_to': 'admin'
})
def test_deny_access(self):
vc = self._native_protocol_helper.volume_client
self._native_protocol_helper._deny_access(self._context, self._share, {
@ -501,7 +518,6 @@ class NativeProtocolHelperTestCase(test.TestCase):
"alice", volume_path=driver.cephfs_share_path(self._share))
def test_update_access_add_rm(self):
vc = self._native_protocol_helper.volume_client
alice = {
'id': 'instance_mapping_id1',
'access_id': 'accessid1',
@ -512,22 +528,66 @@ class NativeProtocolHelperTestCase(test.TestCase):
bob = {
'id': 'instance_mapping_id2',
'access_id': 'accessid2',
'access_level': 'rw',
'access_level': 'ro',
'access_type': 'cephx',
'access_to': 'bob'
}
manila = {
'id': 'instance_mapping_id3',
'access_id': 'accessid3',
'access_level': 'ro',
'access_type': 'cephx',
'access_to': 'manila'
}
admin = {
'id': 'instance_mapping_id4',
'access_id': 'accessid4',
'access_level': 'rw',
'access_type': 'cephx',
'access_to': 'admin'
}
dabo = {
'id': 'instance_mapping_id5',
'access_id': 'accessid5',
'access_level': 'rwx',
'access_type': 'cephx',
'access_to': 'dabo'
}
allow_access_side_effects = [
'abc123',
exception.InvalidShareAccess(reason='not'),
exception.InvalidShareAccess(reason='allowed'),
exception.InvalidShareAccessLevel(level='rwx')
]
self.mock_object(self._native_protocol_helper.message_api, 'create')
self.mock_object(self._native_protocol_helper, '_deny_access')
self.mock_object(self._native_protocol_helper,
'_allow_access',
mock.Mock(side_effect=allow_access_side_effects))
access_updates = self._native_protocol_helper.update_access(
self._context, self._share, access_rules=[alice],
add_rules=[alice], delete_rules=[bob])
self._context,
self._share,
access_rules=[alice, manila, admin, dabo],
add_rules=[alice, manila, admin, dabo],
delete_rules=[bob])
expected_access_updates = {
'accessid1': {'access_key': 'abc123'},
'accessid3': {'state': 'error'},
'accessid4': {'state': 'error'},
'accessid5': {'state': 'error'}
}
self.assertEqual(expected_access_updates, access_updates)
self._native_protocol_helper._allow_access.assert_has_calls(
[mock.call(self._context, self._share, alice),
mock.call(self._context, self._share, manila),
mock.call(self._context, self._share, admin)])
self._native_protocol_helper._deny_access.assert_called_once_with(
self._context, self._share, bob)
self.assertEqual(
{'accessid1': {'access_key': 'abc123'}}, access_updates)
vc.authorize.assert_called_once_with(
driver.cephfs_share_path(self._share), "alice", readonly=False,
tenant_id=self._share['project_id'])
vc.deauthorize.assert_called_once_with(
driver.cephfs_share_path(self._share), "bob")
3, self._native_protocol_helper.message_api.create.call_count)
@ddt.data(None, 1)
def test_update_access_all(self, volume_client_version):

View File

@ -0,0 +1,9 @@
---
fixes:
- |
New user messages now alert users of possible remediations during access
rule creation errors with CephFS shares. This includes hints to users to
not use cephx client users that are prohibited by CephFS or the share
driver. See `CVE-2020-27781
<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-27781>`_ and
bug #1904015 <https://launchpad.net/bugs/1904015>`_ for more details.