From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- src/pybind/mgr/dashboard/controllers/ceph_users.py | 216 +++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/pybind/mgr/dashboard/controllers/ceph_users.py (limited to 'src/pybind/mgr/dashboard/controllers/ceph_users.py') diff --git a/src/pybind/mgr/dashboard/controllers/ceph_users.py b/src/pybind/mgr/dashboard/controllers/ceph_users.py new file mode 100644 index 000000000..e1bdc1570 --- /dev/null +++ b/src/pybind/mgr/dashboard/controllers/ceph_users.py @@ -0,0 +1,216 @@ +import logging +from errno import EINVAL +from typing import List, NamedTuple, Optional + +from ..exceptions import DashboardException +from ..security import Scope +from ..services.ceph_service import CephService, SendCommandError +from . import APIDoc, APIRouter, CRUDCollectionMethod, CRUDEndpoint, \ + EndpointDoc, RESTController, SecretStr +from ._crud import ArrayHorizontalContainer, CRUDMeta, Form, FormField, \ + FormTaskInfo, Icon, MethodType, SelectionType, TableAction, Validator, \ + VerticalContainer + +logger = logging.getLogger("controllers.ceph_users") + + +class CephUserCaps(NamedTuple): + mon: str + osd: str + mgr: str + mds: str + + +class Cap(NamedTuple): + entity: str + cap: str + + +class CephUserEndpoints: + @staticmethod + def _run_auth_command(command: str, *args, **kwargs): + try: + return CephService.send_command('mon', command, *args, **kwargs) + except SendCommandError as ex: + msg = f'{ex} in command {ex.prefix}' + if ex.errno == -EINVAL: + raise DashboardException(msg, code=400) + raise DashboardException(msg, code=500) + + @staticmethod + def user_list(_): + """ + Get list of ceph users and its respective data + """ + return CephUserEndpoints._run_auth_command('auth ls')["auth_dump"] + + @staticmethod + def user_create(_, user_entity: str = '', capabilities: Optional[List[Cap]] = None, + import_data: str = ''): + """ + Add a ceph user with its defined capabilities. + :param user_entity: Entity to change + :param capabilities: List of capabilities to add to user_entity + """ + # Caps are represented as a vector in mon auth add commands. + # Look at AuthMonitor.cc::valid_caps for reference. + if import_data: + logger.debug("Sending import command 'auth import' \n%s", import_data) + CephUserEndpoints._run_auth_command('auth import', inbuf=import_data) + return "Successfully imported user" + + assert user_entity + caps = [] + for cap in capabilities: + caps.append(cap['entity']) + caps.append(cap['cap']) + + logger.debug("Sending command 'auth add' of entity '%s' with caps '%s'", + user_entity, str(caps)) + CephUserEndpoints._run_auth_command('auth add', entity=user_entity, caps=caps) + + return f"Successfully created user '{user_entity}'" + + @staticmethod + def user_delete(_, user_entity: str): + """ + Delete a ceph user and it's defined capabilities. + :param user_entity: Entity to delete + """ + logger.debug("Sending command 'auth del' of entity '%s'", user_entity) + CephUserEndpoints._run_auth_command('auth del', entity=user_entity) + return f"Successfully deleted user '{user_entity}'" + + @staticmethod + def export(_, entities: List[str]): + export_string = "" + for entity in entities: + out = CephUserEndpoints._run_auth_command('auth export', entity=entity, to_json=False) + export_string += f'{out}\n' + return export_string + + @staticmethod + def user_edit(_, user_entity: str = '', capabilities: List[Cap] = None): + """ + Change the ceph user capabilities. + Setting new capabilities will overwrite current ones. + :param user_entity: Entity to change + :param capabilities: List of updated capabilities to user_entity + """ + caps = [] + for cap in capabilities: + caps.append(cap['entity']) + caps.append(cap['cap']) + + logger.debug("Sending command 'auth caps' of entity '%s' with caps '%s'", + user_entity, str(caps)) + CephUserEndpoints._run_auth_command('auth caps', entity=user_entity, caps=caps) + return f"Successfully edited user '{user_entity}'" + + @staticmethod + def model(user_entity: str): + user_data = CephUserEndpoints._run_auth_command('auth get', entity=user_entity)[0] + model = {'user_entity': '', 'capabilities': []} + model['user_entity'] = user_data['entity'] + for entity, cap in user_data['caps'].items(): + model['capabilities'].append({'entity': entity, 'cap': cap}) + return model + + +cap_container = ArrayHorizontalContainer('Capabilities', 'capabilities', fields=[ + FormField('Entity', 'entity', + field_type=str), + FormField('Entity Capabilities', + 'cap', field_type=str) +], min_items=1) +create_container = VerticalContainer('Create User', 'create_user', fields=[ + FormField('User entity', 'user_entity', + field_type=str), + cap_container, +]) + +edit_container = VerticalContainer('Edit User', 'edit_user', fields=[ + FormField('User entity', 'user_entity', + field_type=str, readonly=True), + cap_container, +]) + +create_form = Form(path='/cluster/user/create', + root_container=create_container, + method_type=MethodType.POST.value, + task_info=FormTaskInfo("Ceph user '{user_entity}' successfully", + ['user_entity'])) + +# pylint: disable=C0301 +import_user_help = ( + 'The imported file should be a keyring file and it must follow the schema described here.' +) +import_container = VerticalContainer('Import User', 'import_user', fields=[ + FormField('User file import', 'import_data', + field_type="file", validators=[Validator.FILE], + help=import_user_help), +]) + +import_user_form = Form(path='/cluster/user/import', + root_container=import_container, + task_info=FormTaskInfo("successfully", []), + method_type=MethodType.POST.value) + +edit_form = Form(path='/cluster/user/edit', + root_container=edit_container, + method_type=MethodType.PUT.value, + task_info=FormTaskInfo("Ceph user '{user_entity}' successfully", + ['user_entity']), + model_callback=CephUserEndpoints.model) + + +@CRUDEndpoint( + router=APIRouter('/cluster/user', Scope.CONFIG_OPT), + doc=APIDoc("Get Ceph Users", "Cluster"), + set_column={"caps": {"cellTemplate": "badgeDict"}}, + actions=[ + TableAction(name='Create', permission='create', icon=Icon.ADD.value, + routerLink='/cluster/user/create'), + TableAction(name='Edit', permission='update', icon=Icon.EDIT.value, + click='edit'), + TableAction(name='Delete', permission='delete', icon=Icon.DESTROY.value, + click='delete', disable=True), + TableAction(name='Import', permission='create', icon=Icon.IMPORT.value, + routerLink='/cluster/user/import'), + TableAction(name='Export', permission='read', icon=Icon.EXPORT.value, + click='authExport', disable=True) + ], + permissions=[Scope.CONFIG_OPT], + forms=[create_form, edit_form, import_user_form], + column_key='entity', + get_all=CRUDCollectionMethod( + func=CephUserEndpoints.user_list, + doc=EndpointDoc("Get Ceph Users") + ), + create=CRUDCollectionMethod( + func=CephUserEndpoints.user_create, + doc=EndpointDoc("Create Ceph User") + ), + edit=CRUDCollectionMethod( + func=CephUserEndpoints.user_edit, + doc=EndpointDoc("Edit Ceph User") + ), + delete=CRUDCollectionMethod( + func=CephUserEndpoints.user_delete, + doc=EndpointDoc("Delete Ceph User") + ), + extra_endpoints=[ + ('export', CRUDCollectionMethod( + func=RESTController.Collection('POST', 'export')(CephUserEndpoints.export), + doc=EndpointDoc("Export Ceph Users") + )) + ], + selection_type=SelectionType.MULTI, + meta=CRUDMeta() +) +class CephUser(NamedTuple): + entity: str + caps: List[CephUserCaps] + key: SecretStr -- cgit v1.2.3