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.") message = _("Invalid or unsupported share access level: %(level)s.")
class InvalidShareAccessType(Invalid):
message = _("Invalid or unsupported share access type: %(type)s.")
class ShareBackendException(ManilaException): class ShareBackendException(ManilaException):
message = _("Share backend error: %(msg)s.") message = _("Share backend error: %(msg)s.")

View File

@ -33,15 +33,19 @@ class Action(object):
DELETE = ('007', _('delete')) DELETE = ('007', _('delete'))
EXTEND = ('008', _('extend')) EXTEND = ('008', _('extend'))
SHRINK = ('009', _('shrink')) SHRINK = ('009', _('shrink'))
ALL = (ALLOCATE_HOST, UPDATE_ACCESS_RULES = ('010', _('update access rules'))
CREATE, ALL = (
DELETE_ACCESS_RULES, ALLOCATE_HOST,
PROMOTE, CREATE,
UPDATE, DELETE_ACCESS_RULES,
REVERT_TO_SNAPSHOT, PROMOTE,
DELETE, UPDATE,
EXTEND, REVERT_TO_SNAPSHOT,
SHRINK) DELETE,
EXTEND,
SHRINK,
UPDATE_ACCESS_RULES,
)
class Detail(object): class Detail(object):
@ -98,26 +102,39 @@ class Detail(object):
'019', '019',
_("Share Driver does not support shrinking shares." _("Share Driver does not support shrinking shares."
" Shrinking share operation failed.")) " 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 = (
NO_VALID_HOST, UNKNOWN_ERROR,
UNEXPECTED_NETWORK, NO_VALID_HOST,
NO_SHARE_SERVER, UNEXPECTED_NETWORK,
NO_ACTIVE_AVAILABLE_REPLICA, NO_SHARE_SERVER,
NO_ACTIVE_REPLICA, NO_ACTIVE_AVAILABLE_REPLICA,
FILTER_AVAILABILITY, NO_ACTIVE_REPLICA,
FILTER_CAPABILITIES, FILTER_AVAILABILITY,
FILTER_CAPACITY, FILTER_CAPABILITIES,
FILTER_DRIVER, FILTER_CAPACITY,
FILTER_IGNORE, FILTER_DRIVER,
FILTER_JSON, FILTER_IGNORE,
FILTER_RETRY, FILTER_JSON,
FILTER_REPLICATION, FILTER_RETRY,
DRIVER_FAILED_EXTEND, FILTER_REPLICATION,
FILTER_CREATE_FROM_SNAPSHOT, DRIVER_FAILED_EXTEND,
DRIVER_FAILED_CREATING_FROM_SNAP, FILTER_CREATE_FROM_SNAPSHOT,
DRIVER_REFUSED_SHRINK, DRIVER_FAILED_CREATING_FROM_SNAP,
DRIVER_FAILED_SHRINK) DRIVER_REFUSED_SHRINK,
DRIVER_FAILED_SHRINK,
FORBIDDEN_CLIENT_ACCESS,
UNSUPPORTED_CLIENT_ACCESS,
)
# Exception and detail mappings # Exception and detail mappings
EXCEPTION_DETAIL_MAPPINGS = { EXCEPTION_DETAIL_MAPPINGS = {

View File

@ -27,6 +27,8 @@ import six
from manila.common import constants from manila.common import constants
from manila import exception from manila import exception
from manila.i18n import _ 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 import driver
from manila.share.drivers import ganesha from manila.share.drivers import ganesha
from manila.share.drivers.ganesha import utils as ganesha_utils from manila.share.drivers.ganesha import utils as ganesha_utils
@ -366,6 +368,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def __init__(self, execute, config, **kwargs): def __init__(self, execute, config, **kwargs):
self.volume_client = kwargs.pop('ceph_vol_client') self.volume_client = kwargs.pop('ceph_vol_client')
self.message_api = message_api.API()
super(NativeProtocolHelper, self).__init__(execute, config, super(NativeProtocolHelper, self).__init__(execute, config,
**kwargs) **kwargs)
@ -395,8 +398,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def _allow_access(self, context, share, access, share_server=None): def _allow_access(self, context, share, access, share_server=None):
if access['access_type'] != CEPHX_ACCESS_TYPE: if access['access_type'] != CEPHX_ACCESS_TYPE:
raise exception.InvalidShareAccess( raise exception.InvalidShareAccessType(type=access['access_type'])
reason=_("Only 'cephx' access type allowed."))
ceph_auth_id = access['access_to'] ceph_auth_id = access['access_to']
@ -409,7 +411,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
error_message = (_('Ceph authentication ID %s must be different ' error_message = (_('Ceph authentication ID %s must be different '
'than the one the Manila service uses.') % 'than the one the Manila service uses.') %
ceph_auth_id) ceph_auth_id)
raise exception.InvalidInput(message=error_message) raise exception.InvalidShareAccess(reason=error_message)
if not getattr(self.volume_client, 'version', None): if not getattr(self.volume_client, 'version', None):
if access['access_level'] == constants.ACCESS_LEVEL_RO: if access['access_level'] == constants.ACCESS_LEVEL_RO:
@ -422,9 +424,18 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
cephfs_share_path(share), ceph_auth_id) cephfs_share_path(share), ceph_auth_id)
else: else:
readonly = access['access_level'] == constants.ACCESS_LEVEL_RO readonly = access['access_level'] == constants.ACCESS_LEVEL_RO
auth_result = self.volume_client.authorize( try:
cephfs_share_path(share), ceph_auth_id, readonly=readonly, auth_result = self.volume_client.authorize(
tenant_id=share['project_id']) 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'] return auth_result['auth_key']
@ -443,7 +454,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
def update_access(self, context, share, access_rules, add_rules, def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None): delete_rules, share_server=None):
access_keys = {} access_updates = {}
if not (add_rules or delete_rules): # recovery/maintenance mode if not (add_rules or delete_rules): # recovery/maintenance mode
add_rules = access_rules add_rules = access_rules
@ -475,13 +486,48 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
# access keys and ensure that after recovery, manila and the Ceph # access keys and ensure that after recovery, manila and the Ceph
# backend are in sync. # backend are in sync.
for rule in add_rules: for rule in add_rules:
access_key = self._allow_access(context, share, rule) try:
access_keys.update({rule['access_id']: {'access_key': access_key}}) access_key = self._allow_access(context, share, rule)
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: for rule in delete_rules:
self._deny_access(context, share, rule) self._deny_access(context, share, rule)
return access_keys return access_updates
def get_configured_ip_versions(self): def get_configured_ip_versions(self):
return [4] return [4]

View File

@ -387,7 +387,7 @@ class NativeProtocolHelperTestCase(test.TestCase):
super(NativeProtocolHelperTestCase, self).setUp() super(NativeProtocolHelperTestCase, self).setUp()
self.fake_conf = configuration.Configuration(None) self.fake_conf = configuration.Configuration(None)
self._context = context.get_admin_context() 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) self.fake_conf.set_default('driver_handles_share_servers', False)
@ -470,7 +470,7 @@ class NativeProtocolHelperTestCase(test.TestCase):
tenant_id=self._share['project_id']) tenant_id=self._share['project_id'])
def test_allow_access_wrong_type(self): def test_allow_access_wrong_type(self):
self.assertRaises(exception.InvalidShareAccess, self.assertRaises(exception.InvalidShareAccessType,
self._native_protocol_helper._allow_access, self._native_protocol_helper._allow_access,
self._context, self._share, { self._context, self._share, {
'access_level': constants.ACCESS_LEVEL_RW, '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): 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._native_protocol_helper._allow_access,
self._context, self._share, { self._context, self._share, {
'access_level': constants.ACCESS_LEVEL_RW, 'access_level': constants.ACCESS_LEVEL_RW,
@ -487,6 +487,23 @@ class NativeProtocolHelperTestCase(test.TestCase):
'access_to': 'manila', '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): def test_deny_access(self):
vc = self._native_protocol_helper.volume_client vc = self._native_protocol_helper.volume_client
self._native_protocol_helper._deny_access(self._context, self._share, { 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)) "alice", volume_path=driver.cephfs_share_path(self._share))
def test_update_access_add_rm(self): def test_update_access_add_rm(self):
vc = self._native_protocol_helper.volume_client
alice = { alice = {
'id': 'instance_mapping_id1', 'id': 'instance_mapping_id1',
'access_id': 'accessid1', 'access_id': 'accessid1',
@ -512,22 +528,66 @@ class NativeProtocolHelperTestCase(test.TestCase):
bob = { bob = {
'id': 'instance_mapping_id2', 'id': 'instance_mapping_id2',
'access_id': 'accessid2', 'access_id': 'accessid2',
'access_level': 'rw', 'access_level': 'ro',
'access_type': 'cephx', 'access_type': 'cephx',
'access_to': 'bob' '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( access_updates = self._native_protocol_helper.update_access(
self._context, self._share, access_rules=[alice], self._context,
add_rules=[alice], delete_rules=[bob]) 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( self.assertEqual(
{'accessid1': {'access_key': 'abc123'}}, access_updates) 3, self._native_protocol_helper.message_api.create.call_count)
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")
@ddt.data(None, 1) @ddt.data(None, 1)
def test_update_access_all(self, volume_client_version): 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.