summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/controllers/ceph_users.py
blob: 022f8f36c420b95dd69f2643875aed80ae047f6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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', routerLink='/cluster/user/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',
    resource='user',
    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