summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/controllers/ceph_users.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/controllers/ceph_users.py')
-rw-r--r--src/pybind/mgr/dashboard/controllers/ceph_users.py216
1 files changed, 216 insertions, 0 deletions
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 <a ' # noqa: E501
+ 'href="https://docs.ceph.com/en/latest/rados/operations/user-management/#authorization-capabilities"' # noqa: E501
+ 'target="_blank">here.</a>'
+)
+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