]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/services/access_control.py
75381cca10e67bd0dae2ddb4022daf58e5ba4054
[ceph.git] / ceph / src / pybind / mgr / dashboard / services / access_control.py
1 # -*- coding: utf-8 -*-
2 # pylint: disable=too-many-arguments,too-many-return-statements
3 # pylint: disable=too-many-branches, too-many-locals, too-many-statements
4 from __future__ import absolute_import
5
6 from string import punctuation, ascii_lowercase, digits, ascii_uppercase
7
8 import errno
9 import json
10 import logging
11 import threading
12 import time
13 import re
14
15 from datetime import datetime, timedelta
16
17 import bcrypt
18
19 from mgr_module import CLIReadCommand, CLIWriteCommand
20
21 from .. import mgr
22 from ..security import Scope, Permission
23 from ..settings import Settings
24 from ..exceptions import RoleAlreadyExists, RoleDoesNotExist, ScopeNotValid, \
25 PermissionNotValid, RoleIsAssociatedWithUser, \
26 UserAlreadyExists, UserDoesNotExist, ScopeNotInRole, \
27 RoleNotInUser, PasswordPolicyException, PwdExpirationDateNotValid
28
29
30 logger = logging.getLogger('access_control')
31
32
33 # password hashing algorithm
34 def password_hash(password, salt_password=None):
35 if not password:
36 return None
37 if not salt_password:
38 salt_password = bcrypt.gensalt()
39 else:
40 salt_password = salt_password.encode('utf8')
41 return bcrypt.hashpw(password.encode('utf8'), salt_password).decode('utf8')
42
43
44 _P = Permission # short alias
45
46
47 class PasswordPolicy(object):
48 def __init__(self, password, username=None, old_password=None):
49 """
50 :param password: The new plain password.
51 :type password: str
52 :param username: The name of the user.
53 :type username: str | None
54 :param old_password: The old plain password.
55 :type old_password: str | None
56 """
57 self.password = password
58 self.username = username
59 self.old_password = old_password
60 self.forbidden_words = Settings.PWD_POLICY_EXCLUSION_LIST.split(',')
61 self.complexity_credits = 0
62
63 @staticmethod
64 def _check_if_contains_word(password, word):
65 return re.compile('(?:{0})'.format(word),
66 flags=re.IGNORECASE).search(password)
67
68 def check_password_complexity(self):
69 if not Settings.PWD_POLICY_CHECK_COMPLEXITY_ENABLED:
70 return Settings.PWD_POLICY_MIN_COMPLEXITY
71 digit_credit = 1
72 small_letter_credit = 1
73 big_letter_credit = 2
74 special_character_credit = 3
75 other_character_credit = 5
76 self.complexity_credits = 0
77 for ch in self.password:
78 if ch in ascii_uppercase:
79 self.complexity_credits += big_letter_credit
80 elif ch in ascii_lowercase:
81 self.complexity_credits += small_letter_credit
82 elif ch in digits:
83 self.complexity_credits += digit_credit
84 elif ch in punctuation:
85 self.complexity_credits += special_character_credit
86 else:
87 self.complexity_credits += other_character_credit
88 return self.complexity_credits
89
90 def check_is_old_password(self):
91 if not Settings.PWD_POLICY_CHECK_OLDPWD_ENABLED:
92 return False
93 return self.old_password and self.password == self.old_password
94
95 def check_if_contains_username(self):
96 if not Settings.PWD_POLICY_CHECK_USERNAME_ENABLED:
97 return False
98 if not self.username:
99 return False
100 return self._check_if_contains_word(self.password, self.username)
101
102 def check_if_contains_forbidden_words(self):
103 if not Settings.PWD_POLICY_CHECK_EXCLUSION_LIST_ENABLED:
104 return False
105 return self._check_if_contains_word(self.password,
106 '|'.join(self.forbidden_words))
107
108 def check_if_sequential_characters(self):
109 if not Settings.PWD_POLICY_CHECK_SEQUENTIAL_CHARS_ENABLED:
110 return False
111 for i in range(1, len(self.password) - 1):
112 if ord(self.password[i - 1]) + 1 == ord(self.password[i])\
113 == ord(self.password[i + 1]) - 1:
114 return True
115 return False
116
117 def check_if_repetitive_characters(self):
118 if not Settings.PWD_POLICY_CHECK_REPETITIVE_CHARS_ENABLED:
119 return False
120 for i in range(1, len(self.password) - 1):
121 if self.password[i - 1] == self.password[i] == self.password[i + 1]:
122 return True
123 return False
124
125 def check_password_length(self):
126 if not Settings.PWD_POLICY_CHECK_LENGTH_ENABLED:
127 return True
128 return len(self.password) >= Settings.PWD_POLICY_MIN_LENGTH
129
130 def check_all(self):
131 """
132 Perform all password policy checks.
133 :raise PasswordPolicyException: If a password policy check fails.
134 """
135 if not Settings.PWD_POLICY_ENABLED:
136 return
137 if self.check_password_complexity() < Settings.PWD_POLICY_MIN_COMPLEXITY:
138 raise PasswordPolicyException('Password is too weak.')
139 if not self.check_password_length():
140 raise PasswordPolicyException('Password is too weak.')
141 if self.check_is_old_password():
142 raise PasswordPolicyException('Password must not be the same as the previous one.')
143 if self.check_if_contains_username():
144 raise PasswordPolicyException('Password must not contain username.')
145 result = self.check_if_contains_forbidden_words()
146 if result:
147 raise PasswordPolicyException('Password must not contain the keyword "{}".'.format(
148 result.group(0)))
149 if self.check_if_repetitive_characters():
150 raise PasswordPolicyException('Password must not contain repetitive characters.')
151 if self.check_if_sequential_characters():
152 raise PasswordPolicyException('Password must not contain sequential characters.')
153
154
155 class Role(object):
156 def __init__(self, name, description=None, scope_permissions=None):
157 self.name = name
158 self.description = description
159 if scope_permissions is None:
160 self.scopes_permissions = {}
161 else:
162 self.scopes_permissions = scope_permissions
163
164 def __hash__(self):
165 return hash(self.name)
166
167 def __eq__(self, other):
168 return self.name == other.name
169
170 def set_scope_permissions(self, scope, permissions):
171 if not Scope.valid_scope(scope):
172 raise ScopeNotValid(scope)
173 for perm in permissions:
174 if not Permission.valid_permission(perm):
175 raise PermissionNotValid(perm)
176
177 permissions.sort()
178 self.scopes_permissions[scope] = permissions
179
180 def del_scope_permissions(self, scope):
181 if scope not in self.scopes_permissions:
182 raise ScopeNotInRole(scope, self.name)
183 del self.scopes_permissions[scope]
184
185 def reset_scope_permissions(self):
186 self.scopes_permissions = {}
187
188 def authorize(self, scope, permissions):
189 if scope in self.scopes_permissions:
190 role_perms = self.scopes_permissions[scope]
191 for perm in permissions:
192 if perm not in role_perms:
193 return False
194 return True
195 return False
196
197 def to_dict(self):
198 return {
199 'name': self.name,
200 'description': self.description,
201 'scopes_permissions': self.scopes_permissions
202 }
203
204 @classmethod
205 def from_dict(cls, r_dict):
206 return Role(r_dict['name'], r_dict['description'],
207 r_dict['scopes_permissions'])
208
209
210 # static pre-defined system roles
211 # this roles cannot be deleted nor updated
212
213 # admin role provides all permissions for all scopes
214 ADMIN_ROLE = Role('administrator', 'Administrator', {
215 scope_name: Permission.all_permissions()
216 for scope_name in Scope.all_scopes()
217 })
218
219
220 # read-only role provides read-only permission for all scopes
221 READ_ONLY_ROLE = Role('read-only', 'Read-Only', {
222 scope_name: [_P.READ] for scope_name in Scope.all_scopes()
223 if scope_name != Scope.DASHBOARD_SETTINGS
224 })
225
226
227 # block manager role provides all permission for block related scopes
228 BLOCK_MGR_ROLE = Role('block-manager', 'Block Manager', {
229 Scope.RBD_IMAGE: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
230 Scope.POOL: [_P.READ],
231 Scope.ISCSI: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
232 Scope.RBD_MIRRORING: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
233 Scope.GRAFANA: [_P.READ],
234 })
235
236
237 # RadosGW manager role provides all permissions for block related scopes
238 RGW_MGR_ROLE = Role('rgw-manager', 'RGW Manager', {
239 Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
240 Scope.GRAFANA: [_P.READ],
241 })
242
243
244 # Cluster manager role provides all permission for OSDs, Monitors, and
245 # Config options
246 CLUSTER_MGR_ROLE = Role('cluster-manager', 'Cluster Manager', {
247 Scope.HOSTS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
248 Scope.OSD: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
249 Scope.MONITOR: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
250 Scope.MANAGER: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
251 Scope.CONFIG_OPT: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
252 Scope.LOG: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
253 Scope.GRAFANA: [_P.READ],
254 })
255
256
257 # Pool manager role provides all permissions for pool related scopes
258 POOL_MGR_ROLE = Role('pool-manager', 'Pool Manager', {
259 Scope.POOL: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
260 Scope.GRAFANA: [_P.READ],
261 })
262
263 # CephFS manager role provides all permissions for CephFS related scopes
264 CEPHFS_MGR_ROLE = Role('cephfs-manager', 'CephFS Manager', {
265 Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
266 Scope.GRAFANA: [_P.READ],
267 })
268
269 GANESHA_MGR_ROLE = Role('ganesha-manager', 'NFS Ganesha Manager', {
270 Scope.NFS_GANESHA: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
271 Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
272 Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
273 Scope.GRAFANA: [_P.READ],
274 })
275
276
277 SYSTEM_ROLES = {
278 ADMIN_ROLE.name: ADMIN_ROLE,
279 READ_ONLY_ROLE.name: READ_ONLY_ROLE,
280 BLOCK_MGR_ROLE.name: BLOCK_MGR_ROLE,
281 RGW_MGR_ROLE.name: RGW_MGR_ROLE,
282 CLUSTER_MGR_ROLE.name: CLUSTER_MGR_ROLE,
283 POOL_MGR_ROLE.name: POOL_MGR_ROLE,
284 CEPHFS_MGR_ROLE.name: CEPHFS_MGR_ROLE,
285 GANESHA_MGR_ROLE.name: GANESHA_MGR_ROLE,
286 }
287
288
289 class User(object):
290 def __init__(self, username, password, name=None, email=None, roles=None,
291 last_update=None, enabled=True, pwd_expiration_date=None,
292 pwd_update_required=False):
293 self.username = username
294 self.password = password
295 self.name = name
296 self.email = email
297 self.invalid_auth_attempt = 0
298 if roles is None:
299 self.roles = set()
300 else:
301 self.roles = roles
302 if last_update is None:
303 self.refresh_last_update()
304 else:
305 self.last_update = last_update
306 self._enabled = enabled
307 self.pwd_expiration_date = pwd_expiration_date
308 if self.pwd_expiration_date is None:
309 self.refresh_pwd_expiration_date()
310 self.pwd_update_required = pwd_update_required
311
312 def refresh_last_update(self):
313 self.last_update = int(time.time())
314
315 def refresh_pwd_expiration_date(self):
316 if Settings.USER_PWD_EXPIRATION_SPAN > 0:
317 expiration_date = datetime.utcnow() + timedelta(
318 days=Settings.USER_PWD_EXPIRATION_SPAN)
319 self.pwd_expiration_date = int(time.mktime(expiration_date.timetuple()))
320 else:
321 self.pwd_expiration_date = None
322
323 @property
324 def enabled(self):
325 return self._enabled
326
327 @enabled.setter
328 def enabled(self, value):
329 self._enabled = value
330 self.refresh_last_update()
331
332 def set_password(self, password):
333 self.set_password_hash(password_hash(password))
334
335 def set_password_hash(self, hashed_password):
336 self.invalid_auth_attempt = 0
337 self.password = hashed_password
338 self.refresh_last_update()
339 self.refresh_pwd_expiration_date()
340 self.pwd_update_required = False
341
342 def compare_password(self, password):
343 """
344 Compare the specified password with the user password.
345 :param password: The plain password to check.
346 :type password: str
347 :return: `True` if the passwords are equal, otherwise `False`.
348 :rtype: bool
349 """
350 pass_hash = password_hash(password, salt_password=self.password)
351 return pass_hash == self.password
352
353 def is_pwd_expired(self):
354 if self.pwd_expiration_date:
355 current_time = int(time.mktime(datetime.utcnow().timetuple()))
356 return self.pwd_expiration_date < current_time
357 return False
358
359 def set_roles(self, roles):
360 self.roles = set(roles)
361 self.refresh_last_update()
362
363 def add_roles(self, roles):
364 self.roles = self.roles.union(set(roles))
365 self.refresh_last_update()
366
367 def del_roles(self, roles):
368 for role in roles:
369 if role not in self.roles:
370 raise RoleNotInUser(role.name, self.username)
371 self.roles.difference_update(set(roles))
372 self.refresh_last_update()
373
374 def authorize(self, scope, permissions):
375 if self.pwd_update_required:
376 return False
377
378 for role in self.roles:
379 if role.authorize(scope, permissions):
380 return True
381 return False
382
383 def permissions_dict(self):
384 # type: () -> dict
385 perms = {} # type: dict
386 for role in self.roles:
387 for scope, perms_list in role.scopes_permissions.items():
388 if scope in perms:
389 perms_tmp = set(perms[scope]).union(set(perms_list))
390 perms[scope] = list(perms_tmp)
391 else:
392 perms[scope] = perms_list
393
394 return perms
395
396 def to_dict(self):
397 return {
398 'username': self.username,
399 'password': self.password,
400 'roles': sorted([r.name for r in self.roles]),
401 'name': self.name,
402 'email': self.email,
403 'lastUpdate': self.last_update,
404 'enabled': self.enabled,
405 'pwdExpirationDate': self.pwd_expiration_date,
406 'pwdUpdateRequired': self.pwd_update_required
407 }
408
409 @classmethod
410 def from_dict(cls, u_dict, roles):
411 return User(u_dict['username'], u_dict['password'], u_dict['name'],
412 u_dict['email'], {roles[r] for r in u_dict['roles']},
413 u_dict['lastUpdate'], u_dict['enabled'],
414 u_dict['pwdExpirationDate'], u_dict['pwdUpdateRequired'])
415
416
417 class AccessControlDB(object):
418 VERSION = 2
419 ACDB_CONFIG_KEY = "accessdb_v"
420
421 def __init__(self, version, users, roles):
422 self.users = users
423 self.version = version
424 self.roles = roles
425 self.lock = threading.RLock()
426
427 def create_role(self, name, description=None):
428 with self.lock:
429 if name in SYSTEM_ROLES or name in self.roles:
430 raise RoleAlreadyExists(name)
431 role = Role(name, description)
432 self.roles[name] = role
433 return role
434
435 def get_role(self, name):
436 with self.lock:
437 if name not in self.roles:
438 raise RoleDoesNotExist(name)
439 return self.roles[name]
440
441 def increment_attempt(self, username):
442 with self.lock:
443 if username in self.users:
444 self.users[username].invalid_auth_attempt += 1
445
446 def reset_attempt(self, username):
447 with self.lock:
448 if username in self.users:
449 self.users[username].invalid_auth_attempt = 0
450
451 def get_attempt(self, username):
452 with self.lock:
453 try:
454 return self.users[username].invalid_auth_attempt
455 except KeyError:
456 return 0
457
458 def delete_role(self, name):
459 with self.lock:
460 if name not in self.roles:
461 raise RoleDoesNotExist(name)
462 role = self.roles[name]
463
464 # check if role is not associated with a user
465 for username, user in self.users.items():
466 if role in user.roles:
467 raise RoleIsAssociatedWithUser(name, username)
468
469 del self.roles[name]
470
471 def create_user(self, username, password, name, email, enabled=True,
472 pwd_expiration_date=None, pwd_update_required=False):
473 logger.debug("creating user: username=%s", username)
474 with self.lock:
475 if username in self.users:
476 raise UserAlreadyExists(username)
477 if pwd_expiration_date and \
478 (pwd_expiration_date < int(time.mktime(datetime.utcnow().timetuple()))):
479 raise PwdExpirationDateNotValid()
480 user = User(username, password_hash(password), name, email, enabled=enabled,
481 pwd_expiration_date=pwd_expiration_date,
482 pwd_update_required=pwd_update_required)
483 self.users[username] = user
484 return user
485
486 def get_user(self, username):
487 with self.lock:
488 if username not in self.users:
489 raise UserDoesNotExist(username)
490 return self.users[username]
491
492 def delete_user(self, username):
493 with self.lock:
494 if username not in self.users:
495 raise UserDoesNotExist(username)
496 del self.users[username]
497
498 def update_users_with_roles(self, role):
499 with self.lock:
500 if not role:
501 return
502 for _, user in self.users.items():
503 if role in user.roles:
504 user.refresh_last_update()
505
506 def save(self):
507 with self.lock:
508 db = {
509 'users': {un: u.to_dict() for un, u in self.users.items()},
510 'roles': {rn: r.to_dict() for rn, r in self.roles.items()},
511 'version': self.version
512 }
513 mgr.set_store(self.accessdb_config_key(), json.dumps(db))
514
515 @classmethod
516 def accessdb_config_key(cls, version=None):
517 if version is None:
518 version = cls.VERSION
519 return "{}{}".format(cls.ACDB_CONFIG_KEY, version)
520
521 def check_and_update_db(self):
522 logger.debug("Checking for previews DB versions")
523
524 def check_migrate_v0_to_current():
525 # check if there is username/password from previous version
526 username = mgr.get_module_option('username', None)
527 password = mgr.get_module_option('password', None)
528 if username and password:
529 logger.debug("Found single user credentials: user=%s", username)
530 # found user credentials
531 user = self.create_user(username, "", None, None)
532 # password is already hashed, so setting manually
533 user.password = password
534 user.add_roles([ADMIN_ROLE])
535 self.save()
536
537 def check_migrate_v1_to_current():
538 # Check if version 1 exists in the DB and migrate it to current version
539 v1_db = mgr.get_store(self.accessdb_config_key(1))
540 if v1_db:
541 logger.debug("Found database v1 credentials")
542 v1_db = json.loads(v1_db)
543
544 for user, _ in v1_db['users'].items():
545 v1_db['users'][user]['enabled'] = True
546 v1_db['users'][user]['pwdExpirationDate'] = None
547 v1_db['users'][user]['pwdUpdateRequired'] = False
548
549 self.roles = {rn: Role.from_dict(r) for rn, r in v1_db.get('roles', {}).items()}
550 self.users = {un: User.from_dict(u, dict(self.roles, **SYSTEM_ROLES))
551 for un, u in v1_db.get('users', {}).items()}
552
553 self.save()
554 else:
555 # If version 1 does not exist, check if migration of VERSION "0" needs to be done
556 check_migrate_v0_to_current()
557
558 check_migrate_v1_to_current()
559
560 @classmethod
561 def load(cls):
562 logger.info("Loading user roles DB version=%s", cls.VERSION)
563
564 json_db = mgr.get_store(cls.accessdb_config_key())
565 if json_db is None:
566 logger.debug("No DB v%s found, creating new...", cls.VERSION)
567 db = cls(cls.VERSION, {}, {})
568 # check if we can update from a previous version database
569 db.check_and_update_db()
570 return db
571
572 dict_db = json.loads(json_db)
573 roles = {rn: Role.from_dict(r)
574 for rn, r in dict_db.get('roles', {}).items()}
575 users = {un: User.from_dict(u, dict(roles, **SYSTEM_ROLES))
576 for un, u in dict_db.get('users', {}).items()}
577 return cls(dict_db['version'], users, roles)
578
579
580 def load_access_control_db():
581 mgr.ACCESS_CTRL_DB = AccessControlDB.load()
582
583
584 # CLI dashboard access control scope commands
585
586 @CLIWriteCommand('dashboard set-login-credentials',
587 'name=username,type=CephString '
588 'name=password,type=CephString',
589 'Set the login credentials')
590 def set_login_credentials_cmd(_, username, password):
591 try:
592 user = mgr.ACCESS_CTRL_DB.get_user(username)
593 user.set_password(password)
594 except UserDoesNotExist:
595 user = mgr.ACCESS_CTRL_DB.create_user(username, password, None, None)
596 user.set_roles([ADMIN_ROLE])
597
598 mgr.ACCESS_CTRL_DB.save()
599
600 return 0, '''\
601 ******************************************************************
602 *** WARNING: this command is deprecated. ***
603 *** Please use the ac-user-* related commands to manage users. ***
604 ******************************************************************
605 Username and password updated''', ''
606
607
608 @CLIReadCommand('dashboard ac-role-show',
609 'name=rolename,type=CephString,req=false',
610 'Show role info')
611 def ac_role_show_cmd(_, rolename=None):
612 if not rolename:
613 roles = dict(mgr.ACCESS_CTRL_DB.roles)
614 roles.update(SYSTEM_ROLES)
615 roles_list = [name for name, _ in roles.items()]
616 return 0, json.dumps(roles_list), ''
617 try:
618 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
619 except RoleDoesNotExist as ex:
620 if rolename not in SYSTEM_ROLES:
621 return -errno.ENOENT, '', str(ex)
622 role = SYSTEM_ROLES[rolename]
623 return 0, json.dumps(role.to_dict()), ''
624
625
626 @CLIWriteCommand('dashboard ac-role-create',
627 'name=rolename,type=CephString '
628 'name=description,type=CephString,req=false',
629 'Create a new access control role')
630 def ac_role_create_cmd(_, rolename, description=None):
631 try:
632 role = mgr.ACCESS_CTRL_DB.create_role(rolename, description)
633 mgr.ACCESS_CTRL_DB.save()
634 return 0, json.dumps(role.to_dict()), ''
635 except RoleAlreadyExists as ex:
636 return -errno.EEXIST, '', str(ex)
637
638
639 @CLIWriteCommand('dashboard ac-role-delete',
640 'name=rolename,type=CephString',
641 'Delete an access control role')
642 def ac_role_delete_cmd(_, rolename):
643 try:
644 mgr.ACCESS_CTRL_DB.delete_role(rolename)
645 mgr.ACCESS_CTRL_DB.save()
646 return 0, "Role '{}' deleted".format(rolename), ""
647 except RoleDoesNotExist as ex:
648 if rolename in SYSTEM_ROLES:
649 return -errno.EPERM, '', "Cannot delete system role '{}'" \
650 .format(rolename)
651 return -errno.ENOENT, '', str(ex)
652 except RoleIsAssociatedWithUser as ex:
653 return -errno.EPERM, '', str(ex)
654
655
656 @CLIWriteCommand('dashboard ac-role-add-scope-perms',
657 'name=rolename,type=CephString '
658 'name=scopename,type=CephString '
659 'name=permissions,type=CephString,n=N',
660 'Add the scope permissions for a role')
661 def ac_role_add_scope_perms_cmd(_, rolename, scopename, permissions):
662 try:
663 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
664 perms_array = [perm.strip() for perm in permissions]
665 role.set_scope_permissions(scopename, perms_array)
666 mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
667 mgr.ACCESS_CTRL_DB.save()
668 return 0, json.dumps(role.to_dict()), ''
669 except RoleDoesNotExist as ex:
670 if rolename in SYSTEM_ROLES:
671 return -errno.EPERM, '', "Cannot update system role '{}'" \
672 .format(rolename)
673 return -errno.ENOENT, '', str(ex)
674 except ScopeNotValid as ex:
675 return -errno.EINVAL, '', str(ex) + "\n Possible values: {}" \
676 .format(Scope.all_scopes())
677 except PermissionNotValid as ex:
678 return -errno.EINVAL, '', str(ex) + \
679 "\n Possible values: {}" \
680 .format(Permission.all_permissions())
681
682
683 @CLIWriteCommand('dashboard ac-role-del-scope-perms',
684 'name=rolename,type=CephString '
685 'name=scopename,type=CephString',
686 'Delete the scope permissions for a role')
687 def ac_role_del_scope_perms_cmd(_, rolename, scopename):
688 try:
689 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
690 role.del_scope_permissions(scopename)
691 mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
692 mgr.ACCESS_CTRL_DB.save()
693 return 0, json.dumps(role.to_dict()), ''
694 except RoleDoesNotExist as ex:
695 if rolename in SYSTEM_ROLES:
696 return -errno.EPERM, '', "Cannot update system role '{}'" \
697 .format(rolename)
698 return -errno.ENOENT, '', str(ex)
699 except ScopeNotInRole as ex:
700 return -errno.ENOENT, '', str(ex)
701
702
703 @CLIReadCommand('dashboard ac-user-show',
704 'name=username,type=CephString,req=false',
705 'Show user info')
706 def ac_user_show_cmd(_, username=None):
707 if not username:
708 users = mgr.ACCESS_CTRL_DB.users
709 users_list = [name for name, _ in users.items()]
710 return 0, json.dumps(users_list), ''
711 try:
712 user = mgr.ACCESS_CTRL_DB.get_user(username)
713 return 0, json.dumps(user.to_dict()), ''
714 except UserDoesNotExist as ex:
715 return -errno.ENOENT, '', str(ex)
716
717
718 @CLIWriteCommand('dashboard ac-user-create',
719 'name=username,type=CephString '
720 'name=password,type=CephString,req=false '
721 'name=rolename,type=CephString,req=false '
722 'name=name,type=CephString,req=false '
723 'name=email,type=CephString,req=false '
724 'name=enabled,type=CephBool,req=false '
725 'name=force_password,type=CephBool,req=false '
726 'name=pwd_expiration_date,type=CephInt,req=false '
727 'name=pwd_update_required,type=CephBool,req=false',
728 'Create a user')
729 def ac_user_create_cmd(_, username, password=None, rolename=None, name=None,
730 email=None, enabled=True, force_password=False,
731 pwd_expiration_date=None, pwd_update_required=False):
732 try:
733 role = mgr.ACCESS_CTRL_DB.get_role(rolename) if rolename else None
734 except RoleDoesNotExist as ex:
735 if rolename not in SYSTEM_ROLES:
736 return -errno.ENOENT, '', str(ex)
737 role = SYSTEM_ROLES[rolename]
738
739 try:
740 if not force_password:
741 pw_check = PasswordPolicy(password, username)
742 pw_check.check_all()
743 user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, email,
744 enabled, pwd_expiration_date,
745 pwd_update_required)
746 except PasswordPolicyException as ex:
747 return -errno.EINVAL, '', str(ex)
748 except UserAlreadyExists as ex:
749 return 0, str(ex), ''
750
751 if role:
752 user.set_roles([role])
753 mgr.ACCESS_CTRL_DB.save()
754 return 0, json.dumps(user.to_dict()), ''
755
756
757 @CLIWriteCommand('dashboard ac-user-enable',
758 'name=username,type=CephString',
759 'Enable a user')
760 def ac_user_enable(_, username):
761 try:
762 user = mgr.ACCESS_CTRL_DB.get_user(username)
763 user.enabled = True
764 mgr.ACCESS_CTRL_DB.reset_attempt(username)
765
766 mgr.ACCESS_CTRL_DB.save()
767 return 0, json.dumps(user.to_dict()), ''
768 except UserDoesNotExist as ex:
769 return -errno.ENOENT, '', str(ex)
770
771
772 @CLIWriteCommand('dashboard ac-user-disable',
773 'name=username,type=CephString',
774 'Disable a user')
775 def ac_user_disable(_, username):
776 try:
777 user = mgr.ACCESS_CTRL_DB.get_user(username)
778 user.enabled = False
779
780 mgr.ACCESS_CTRL_DB.save()
781 return 0, json.dumps(user.to_dict()), ''
782 except UserDoesNotExist as ex:
783 return -errno.ENOENT, '', str(ex)
784
785
786 @CLIWriteCommand('dashboard ac-user-delete',
787 'name=username,type=CephString',
788 'Delete user')
789 def ac_user_delete_cmd(_, username):
790 try:
791 mgr.ACCESS_CTRL_DB.delete_user(username)
792 mgr.ACCESS_CTRL_DB.save()
793 return 0, "User '{}' deleted".format(username), ""
794 except UserDoesNotExist as ex:
795 return -errno.ENOENT, '', str(ex)
796
797
798 @CLIWriteCommand('dashboard ac-user-set-roles',
799 'name=username,type=CephString '
800 'name=roles,type=CephString,n=N',
801 'Set user roles')
802 def ac_user_set_roles_cmd(_, username, roles):
803 rolesname = roles
804 roles = []
805 for rolename in rolesname:
806 try:
807 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
808 except RoleDoesNotExist as ex:
809 if rolename not in SYSTEM_ROLES:
810 return -errno.ENOENT, '', str(ex)
811 roles.append(SYSTEM_ROLES[rolename])
812 try:
813 user = mgr.ACCESS_CTRL_DB.get_user(username)
814 user.set_roles(roles)
815 mgr.ACCESS_CTRL_DB.save()
816 return 0, json.dumps(user.to_dict()), ''
817 except UserDoesNotExist as ex:
818 return -errno.ENOENT, '', str(ex)
819
820
821 @CLIWriteCommand('dashboard ac-user-add-roles',
822 'name=username,type=CephString '
823 'name=roles,type=CephString,n=N',
824 'Add roles to user')
825 def ac_user_add_roles_cmd(_, username, roles):
826 rolesname = roles
827 roles = []
828 for rolename in rolesname:
829 try:
830 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
831 except RoleDoesNotExist as ex:
832 if rolename not in SYSTEM_ROLES:
833 return -errno.ENOENT, '', str(ex)
834 roles.append(SYSTEM_ROLES[rolename])
835 try:
836 user = mgr.ACCESS_CTRL_DB.get_user(username)
837 user.add_roles(roles)
838 mgr.ACCESS_CTRL_DB.save()
839 return 0, json.dumps(user.to_dict()), ''
840 except UserDoesNotExist as ex:
841 return -errno.ENOENT, '', str(ex)
842
843
844 @CLIWriteCommand('dashboard ac-user-del-roles',
845 'name=username,type=CephString '
846 'name=roles,type=CephString,n=N',
847 'Delete roles from user')
848 def ac_user_del_roles_cmd(_, username, roles):
849 rolesname = roles
850 roles = []
851 for rolename in rolesname:
852 try:
853 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
854 except RoleDoesNotExist as ex:
855 if rolename not in SYSTEM_ROLES:
856 return -errno.ENOENT, '', str(ex)
857 roles.append(SYSTEM_ROLES[rolename])
858 try:
859 user = mgr.ACCESS_CTRL_DB.get_user(username)
860 user.del_roles(roles)
861 mgr.ACCESS_CTRL_DB.save()
862 return 0, json.dumps(user.to_dict()), ''
863 except UserDoesNotExist as ex:
864 return -errno.ENOENT, '', str(ex)
865 except RoleNotInUser as ex:
866 return -errno.ENOENT, '', str(ex)
867
868
869 @CLIWriteCommand('dashboard ac-user-set-password',
870 'name=username,type=CephString '
871 'name=password,type=CephString '
872 'name=force_password,type=CephBool,req=false',
873 'Set user password')
874 def ac_user_set_password(_, username, password, force_password=False):
875 try:
876 user = mgr.ACCESS_CTRL_DB.get_user(username)
877 if not force_password:
878 pw_check = PasswordPolicy(password, user.name)
879 pw_check.check_all()
880 user.set_password(password)
881 mgr.ACCESS_CTRL_DB.save()
882 return 0, json.dumps(user.to_dict()), ''
883 except PasswordPolicyException as ex:
884 return -errno.EINVAL, '', str(ex)
885 except UserDoesNotExist as ex:
886 return -errno.ENOENT, '', str(ex)
887
888
889 @CLIWriteCommand('dashboard ac-user-set-password-hash',
890 'name=username,type=CephString '
891 'name=hashed_password,type=CephString',
892 'Set user password bcrypt hash')
893 def ac_user_set_password_hash(_, username, hashed_password):
894 try:
895 # make sure the hashed_password is actually a bcrypt hash
896 bcrypt.checkpw(b'', hashed_password.encode('utf-8'))
897 user = mgr.ACCESS_CTRL_DB.get_user(username)
898 user.set_password_hash(hashed_password)
899
900 mgr.ACCESS_CTRL_DB.save()
901 return 0, json.dumps(user.to_dict()), ''
902 except ValueError:
903 return -errno.EINVAL, '', 'Invalid password hash'
904 except UserDoesNotExist as ex:
905 return -errno.ENOENT, '', str(ex)
906
907
908 @CLIWriteCommand('dashboard ac-user-set-info',
909 'name=username,type=CephString '
910 'name=name,type=CephString '
911 'name=email,type=CephString',
912 'Set user info')
913 def ac_user_set_info(_, username, name, email):
914 try:
915 user = mgr.ACCESS_CTRL_DB.get_user(username)
916 if name:
917 user.name = name
918 if email:
919 user.email = email
920 mgr.ACCESS_CTRL_DB.save()
921 return 0, json.dumps(user.to_dict()), ''
922 except UserDoesNotExist as ex:
923 return -errno.ENOENT, '', str(ex)
924
925
926 class LocalAuthenticator(object):
927 def __init__(self):
928 load_access_control_db()
929
930 def get_user(self, username):
931 return mgr.ACCESS_CTRL_DB.get_user(username)
932
933 def authenticate(self, username, password):
934 try:
935 user = mgr.ACCESS_CTRL_DB.get_user(username)
936 if user.password:
937 if user.enabled and user.compare_password(password) \
938 and not user.is_pwd_expired():
939 return {'permissions': user.permissions_dict(),
940 'pwdExpirationDate': user.pwd_expiration_date,
941 'pwdUpdateRequired': user.pwd_update_required}
942 except UserDoesNotExist:
943 logger.debug("User '%s' does not exist", username)
944 return None
945
946 def authorize(self, username, scope, permissions):
947 user = mgr.ACCESS_CTRL_DB.get_user(username)
948 return user.authorize(scope, permissions)