]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/services/access_control.py
import 15.2.0 Octopus source
[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 if roles is None:
298 self.roles = set()
299 else:
300 self.roles = roles
301 if last_update is None:
302 self.refresh_last_update()
303 else:
304 self.last_update = last_update
305 self._enabled = enabled
306 self.pwd_expiration_date = pwd_expiration_date
307 if self.pwd_expiration_date is None:
308 self.refresh_pwd_expiration_date()
309 self.pwd_update_required = pwd_update_required
310
311 def refresh_last_update(self):
312 self.last_update = int(time.time())
313
314 def refresh_pwd_expiration_date(self):
315 if Settings.USER_PWD_EXPIRATION_SPAN > 0:
316 expiration_date = datetime.utcnow() + timedelta(
317 days=Settings.USER_PWD_EXPIRATION_SPAN)
318 self.pwd_expiration_date = int(time.mktime(expiration_date.timetuple()))
319 else:
320 self.pwd_expiration_date = None
321
322 @property
323 def enabled(self):
324 return self._enabled
325
326 @enabled.setter
327 def enabled(self, value):
328 self._enabled = value
329 self.refresh_last_update()
330
331 def set_password(self, password):
332 self.set_password_hash(password_hash(password))
333
334 def set_password_hash(self, hashed_password):
335 self.password = hashed_password
336 self.refresh_last_update()
337 self.refresh_pwd_expiration_date()
338 self.pwd_update_required = False
339
340 def compare_password(self, password):
341 """
342 Compare the specified password with the user password.
343 :param password: The plain password to check.
344 :type password: str
345 :return: `True` if the passwords are equal, otherwise `False`.
346 :rtype: bool
347 """
348 pass_hash = password_hash(password, salt_password=self.password)
349 return pass_hash == self.password
350
351 def is_pwd_expired(self):
352 if self.pwd_expiration_date:
353 current_time = int(time.mktime(datetime.utcnow().timetuple()))
354 return self.pwd_expiration_date < current_time
355 return False
356
357 def set_roles(self, roles):
358 self.roles = set(roles)
359 self.refresh_last_update()
360
361 def add_roles(self, roles):
362 self.roles = self.roles.union(set(roles))
363 self.refresh_last_update()
364
365 def del_roles(self, roles):
366 for role in roles:
367 if role not in self.roles:
368 raise RoleNotInUser(role.name, self.username)
369 self.roles.difference_update(set(roles))
370 self.refresh_last_update()
371
372 def authorize(self, scope, permissions):
373 if self.pwd_update_required:
374 return False
375
376 for role in self.roles:
377 if role.authorize(scope, permissions):
378 return True
379 return False
380
381 def permissions_dict(self):
382 # type: () -> dict
383 perms = {} # type: dict
384 for role in self.roles:
385 for scope, perms_list in role.scopes_permissions.items():
386 if scope in perms:
387 perms_tmp = set(perms[scope]).union(set(perms_list))
388 perms[scope] = list(perms_tmp)
389 else:
390 perms[scope] = perms_list
391
392 return perms
393
394 def to_dict(self):
395 return {
396 'username': self.username,
397 'password': self.password,
398 'roles': sorted([r.name for r in self.roles]),
399 'name': self.name,
400 'email': self.email,
401 'lastUpdate': self.last_update,
402 'enabled': self.enabled,
403 'pwdExpirationDate': self.pwd_expiration_date,
404 'pwdUpdateRequired': self.pwd_update_required
405 }
406
407 @classmethod
408 def from_dict(cls, u_dict, roles):
409 return User(u_dict['username'], u_dict['password'], u_dict['name'],
410 u_dict['email'], {roles[r] for r in u_dict['roles']},
411 u_dict['lastUpdate'], u_dict['enabled'],
412 u_dict['pwdExpirationDate'], u_dict['pwdUpdateRequired'])
413
414
415 class AccessControlDB(object):
416 VERSION = 2
417 ACDB_CONFIG_KEY = "accessdb_v"
418
419 def __init__(self, version, users, roles):
420 self.users = users
421 self.version = version
422 self.roles = roles
423 self.lock = threading.RLock()
424
425 def create_role(self, name, description=None):
426 with self.lock:
427 if name in SYSTEM_ROLES or name in self.roles:
428 raise RoleAlreadyExists(name)
429 role = Role(name, description)
430 self.roles[name] = role
431 return role
432
433 def get_role(self, name):
434 with self.lock:
435 if name not in self.roles:
436 raise RoleDoesNotExist(name)
437 return self.roles[name]
438
439 def delete_role(self, name):
440 with self.lock:
441 if name not in self.roles:
442 raise RoleDoesNotExist(name)
443 role = self.roles[name]
444
445 # check if role is not associated with a user
446 for username, user in self.users.items():
447 if role in user.roles:
448 raise RoleIsAssociatedWithUser(name, username)
449
450 del self.roles[name]
451
452 def create_user(self, username, password, name, email, enabled=True,
453 pwd_expiration_date=None, pwd_update_required=False):
454 logger.debug("creating user: username=%s", username)
455 with self.lock:
456 if username in self.users:
457 raise UserAlreadyExists(username)
458 if pwd_expiration_date and \
459 (pwd_expiration_date < int(time.mktime(datetime.utcnow().timetuple()))):
460 raise PwdExpirationDateNotValid()
461 user = User(username, password_hash(password), name, email, enabled=enabled,
462 pwd_expiration_date=pwd_expiration_date,
463 pwd_update_required=pwd_update_required)
464 self.users[username] = user
465 return user
466
467 def get_user(self, username):
468 with self.lock:
469 if username not in self.users:
470 raise UserDoesNotExist(username)
471 return self.users[username]
472
473 def delete_user(self, username):
474 with self.lock:
475 if username not in self.users:
476 raise UserDoesNotExist(username)
477 del self.users[username]
478
479 def update_users_with_roles(self, role):
480 with self.lock:
481 if not role:
482 return
483 for _, user in self.users.items():
484 if role in user.roles:
485 user.refresh_last_update()
486
487 def save(self):
488 with self.lock:
489 db = {
490 'users': {un: u.to_dict() for un, u in self.users.items()},
491 'roles': {rn: r.to_dict() for rn, r in self.roles.items()},
492 'version': self.version
493 }
494 mgr.set_store(self.accessdb_config_key(), json.dumps(db))
495
496 @classmethod
497 def accessdb_config_key(cls, version=None):
498 if version is None:
499 version = cls.VERSION
500 return "{}{}".format(cls.ACDB_CONFIG_KEY, version)
501
502 def check_and_update_db(self):
503 logger.debug("Checking for previews DB versions")
504
505 def check_migrate_v0_to_current():
506 # check if there is username/password from previous version
507 username = mgr.get_module_option('username', None)
508 password = mgr.get_module_option('password', None)
509 if username and password:
510 logger.debug("Found single user credentials: user=%s", username)
511 # found user credentials
512 user = self.create_user(username, "", None, None)
513 # password is already hashed, so setting manually
514 user.password = password
515 user.add_roles([ADMIN_ROLE])
516 self.save()
517
518 def check_migrate_v1_to_current():
519 # Check if version 1 exists in the DB and migrate it to current version
520 v1_db = mgr.get_store(self.accessdb_config_key(1))
521 if v1_db:
522 logger.debug("Found database v1 credentials")
523 v1_db = json.loads(v1_db)
524
525 for user, _ in v1_db['users'].items():
526 v1_db['users'][user]['enabled'] = True
527 v1_db['users'][user]['pwdExpirationDate'] = None
528 v1_db['users'][user]['pwdUpdateRequired'] = False
529
530 self.roles = {rn: Role.from_dict(r) for rn, r in v1_db.get('roles', {}).items()}
531 self.users = {un: User.from_dict(u, dict(self.roles, **SYSTEM_ROLES))
532 for un, u in v1_db.get('users', {}).items()}
533
534 self.save()
535 else:
536 # If version 1 does not exist, check if migration of VERSION "0" needs to be done
537 check_migrate_v0_to_current()
538
539 check_migrate_v1_to_current()
540
541 @classmethod
542 def load(cls):
543 logger.info("Loading user roles DB version=%s", cls.VERSION)
544
545 json_db = mgr.get_store(cls.accessdb_config_key())
546 if json_db is None:
547 logger.debug("No DB v%s found, creating new...", cls.VERSION)
548 db = cls(cls.VERSION, {}, {})
549 # check if we can update from a previous version database
550 db.check_and_update_db()
551 return db
552
553 dict_db = json.loads(json_db)
554 roles = {rn: Role.from_dict(r)
555 for rn, r in dict_db.get('roles', {}).items()}
556 users = {un: User.from_dict(u, dict(roles, **SYSTEM_ROLES))
557 for un, u in dict_db.get('users', {}).items()}
558 return cls(dict_db['version'], users, roles)
559
560
561 def load_access_control_db():
562 mgr.ACCESS_CTRL_DB = AccessControlDB.load()
563
564
565 # CLI dashboard access control scope commands
566
567 @CLIWriteCommand('dashboard set-login-credentials',
568 'name=username,type=CephString '
569 'name=password,type=CephString',
570 'Set the login credentials')
571 def set_login_credentials_cmd(_, username, password):
572 try:
573 user = mgr.ACCESS_CTRL_DB.get_user(username)
574 user.set_password(password)
575 except UserDoesNotExist:
576 user = mgr.ACCESS_CTRL_DB.create_user(username, password, None, None)
577 user.set_roles([ADMIN_ROLE])
578
579 mgr.ACCESS_CTRL_DB.save()
580
581 return 0, '''\
582 ******************************************************************
583 *** WARNING: this command is deprecated. ***
584 *** Please use the ac-user-* related commands to manage users. ***
585 ******************************************************************
586 Username and password updated''', ''
587
588
589 @CLIReadCommand('dashboard ac-role-show',
590 'name=rolename,type=CephString,req=false',
591 'Show role info')
592 def ac_role_show_cmd(_, rolename=None):
593 if not rolename:
594 roles = dict(mgr.ACCESS_CTRL_DB.roles)
595 roles.update(SYSTEM_ROLES)
596 roles_list = [name for name, _ in roles.items()]
597 return 0, json.dumps(roles_list), ''
598 try:
599 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
600 except RoleDoesNotExist as ex:
601 if rolename not in SYSTEM_ROLES:
602 return -errno.ENOENT, '', str(ex)
603 role = SYSTEM_ROLES[rolename]
604 return 0, json.dumps(role.to_dict()), ''
605
606
607 @CLIWriteCommand('dashboard ac-role-create',
608 'name=rolename,type=CephString '
609 'name=description,type=CephString,req=false',
610 'Create a new access control role')
611 def ac_role_create_cmd(_, rolename, description=None):
612 try:
613 role = mgr.ACCESS_CTRL_DB.create_role(rolename, description)
614 mgr.ACCESS_CTRL_DB.save()
615 return 0, json.dumps(role.to_dict()), ''
616 except RoleAlreadyExists as ex:
617 return -errno.EEXIST, '', str(ex)
618
619
620 @CLIWriteCommand('dashboard ac-role-delete',
621 'name=rolename,type=CephString',
622 'Delete an access control role')
623 def ac_role_delete_cmd(_, rolename):
624 try:
625 mgr.ACCESS_CTRL_DB.delete_role(rolename)
626 mgr.ACCESS_CTRL_DB.save()
627 return 0, "Role '{}' deleted".format(rolename), ""
628 except RoleDoesNotExist as ex:
629 if rolename in SYSTEM_ROLES:
630 return -errno.EPERM, '', "Cannot delete system role '{}'" \
631 .format(rolename)
632 return -errno.ENOENT, '', str(ex)
633 except RoleIsAssociatedWithUser as ex:
634 return -errno.EPERM, '', str(ex)
635
636
637 @CLIWriteCommand('dashboard ac-role-add-scope-perms',
638 'name=rolename,type=CephString '
639 'name=scopename,type=CephString '
640 'name=permissions,type=CephString,n=N',
641 'Add the scope permissions for a role')
642 def ac_role_add_scope_perms_cmd(_, rolename, scopename, permissions):
643 try:
644 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
645 perms_array = [perm.strip() for perm in permissions]
646 role.set_scope_permissions(scopename, perms_array)
647 mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
648 mgr.ACCESS_CTRL_DB.save()
649 return 0, json.dumps(role.to_dict()), ''
650 except RoleDoesNotExist as ex:
651 if rolename in SYSTEM_ROLES:
652 return -errno.EPERM, '', "Cannot update system role '{}'" \
653 .format(rolename)
654 return -errno.ENOENT, '', str(ex)
655 except ScopeNotValid as ex:
656 return -errno.EINVAL, '', str(ex) + "\n Possible values: {}" \
657 .format(Scope.all_scopes())
658 except PermissionNotValid as ex:
659 return -errno.EINVAL, '', str(ex) + \
660 "\n Possible values: {}" \
661 .format(Permission.all_permissions())
662
663
664 @CLIWriteCommand('dashboard ac-role-del-scope-perms',
665 'name=rolename,type=CephString '
666 'name=scopename,type=CephString',
667 'Delete the scope permissions for a role')
668 def ac_role_del_scope_perms_cmd(_, rolename, scopename):
669 try:
670 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
671 role.del_scope_permissions(scopename)
672 mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
673 mgr.ACCESS_CTRL_DB.save()
674 return 0, json.dumps(role.to_dict()), ''
675 except RoleDoesNotExist as ex:
676 if rolename in SYSTEM_ROLES:
677 return -errno.EPERM, '', "Cannot update system role '{}'" \
678 .format(rolename)
679 return -errno.ENOENT, '', str(ex)
680 except ScopeNotInRole as ex:
681 return -errno.ENOENT, '', str(ex)
682
683
684 @CLIReadCommand('dashboard ac-user-show',
685 'name=username,type=CephString,req=false',
686 'Show user info')
687 def ac_user_show_cmd(_, username=None):
688 if not username:
689 users = mgr.ACCESS_CTRL_DB.users
690 users_list = [name for name, _ in users.items()]
691 return 0, json.dumps(users_list), ''
692 try:
693 user = mgr.ACCESS_CTRL_DB.get_user(username)
694 return 0, json.dumps(user.to_dict()), ''
695 except UserDoesNotExist as ex:
696 return -errno.ENOENT, '', str(ex)
697
698
699 @CLIWriteCommand('dashboard ac-user-create',
700 'name=username,type=CephString '
701 'name=password,type=CephString,req=false '
702 'name=rolename,type=CephString,req=false '
703 'name=name,type=CephString,req=false '
704 'name=email,type=CephString,req=false '
705 'name=enabled,type=CephBool,req=false '
706 'name=force_password,type=CephBool,req=false '
707 'name=pwd_expiration_date,type=CephInt,req=false '
708 'name=pwd_update_required,type=CephBool,req=false',
709 'Create a user')
710 def ac_user_create_cmd(_, username, password=None, rolename=None, name=None,
711 email=None, enabled=True, force_password=False,
712 pwd_expiration_date=None, pwd_update_required=False):
713 try:
714 role = mgr.ACCESS_CTRL_DB.get_role(rolename) if rolename else None
715 except RoleDoesNotExist as ex:
716 if rolename not in SYSTEM_ROLES:
717 return -errno.ENOENT, '', str(ex)
718 role = SYSTEM_ROLES[rolename]
719
720 try:
721 if not force_password:
722 pw_check = PasswordPolicy(password, username)
723 pw_check.check_all()
724 user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, email,
725 enabled, pwd_expiration_date,
726 pwd_update_required)
727 except PasswordPolicyException as ex:
728 return -errno.EINVAL, '', str(ex)
729 except UserAlreadyExists as ex:
730 return -errno.EEXIST, '', str(ex)
731
732 if role:
733 user.set_roles([role])
734 mgr.ACCESS_CTRL_DB.save()
735 return 0, json.dumps(user.to_dict()), ''
736
737
738 @CLIWriteCommand('dashboard ac-user-enable',
739 'name=username,type=CephString',
740 'Enable a user')
741 def ac_user_enable(_, username):
742 try:
743 user = mgr.ACCESS_CTRL_DB.get_user(username)
744 user.enabled = True
745
746 mgr.ACCESS_CTRL_DB.save()
747 return 0, json.dumps(user.to_dict()), ''
748 except UserDoesNotExist as ex:
749 return -errno.ENOENT, '', str(ex)
750
751
752 @CLIWriteCommand('dashboard ac-user-disable',
753 'name=username,type=CephString',
754 'Disable a user')
755 def ac_user_disable(_, username):
756 try:
757 user = mgr.ACCESS_CTRL_DB.get_user(username)
758 user.enabled = False
759
760 mgr.ACCESS_CTRL_DB.save()
761 return 0, json.dumps(user.to_dict()), ''
762 except UserDoesNotExist as ex:
763 return -errno.ENOENT, '', str(ex)
764
765
766 @CLIWriteCommand('dashboard ac-user-delete',
767 'name=username,type=CephString',
768 'Delete user')
769 def ac_user_delete_cmd(_, username):
770 try:
771 mgr.ACCESS_CTRL_DB.delete_user(username)
772 mgr.ACCESS_CTRL_DB.save()
773 return 0, "User '{}' deleted".format(username), ""
774 except UserDoesNotExist as ex:
775 return -errno.ENOENT, '', str(ex)
776
777
778 @CLIWriteCommand('dashboard ac-user-set-roles',
779 'name=username,type=CephString '
780 'name=roles,type=CephString,n=N',
781 'Set user roles')
782 def ac_user_set_roles_cmd(_, username, roles):
783 rolesname = roles
784 roles = []
785 for rolename in rolesname:
786 try:
787 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
788 except RoleDoesNotExist as ex:
789 if rolename not in SYSTEM_ROLES:
790 return -errno.ENOENT, '', str(ex)
791 roles.append(SYSTEM_ROLES[rolename])
792 try:
793 user = mgr.ACCESS_CTRL_DB.get_user(username)
794 user.set_roles(roles)
795 mgr.ACCESS_CTRL_DB.save()
796 return 0, json.dumps(user.to_dict()), ''
797 except UserDoesNotExist as ex:
798 return -errno.ENOENT, '', str(ex)
799
800
801 @CLIWriteCommand('dashboard ac-user-add-roles',
802 'name=username,type=CephString '
803 'name=roles,type=CephString,n=N',
804 'Add roles to user')
805 def ac_user_add_roles_cmd(_, username, roles):
806 rolesname = roles
807 roles = []
808 for rolename in rolesname:
809 try:
810 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
811 except RoleDoesNotExist as ex:
812 if rolename not in SYSTEM_ROLES:
813 return -errno.ENOENT, '', str(ex)
814 roles.append(SYSTEM_ROLES[rolename])
815 try:
816 user = mgr.ACCESS_CTRL_DB.get_user(username)
817 user.add_roles(roles)
818 mgr.ACCESS_CTRL_DB.save()
819 return 0, json.dumps(user.to_dict()), ''
820 except UserDoesNotExist as ex:
821 return -errno.ENOENT, '', str(ex)
822
823
824 @CLIWriteCommand('dashboard ac-user-del-roles',
825 'name=username,type=CephString '
826 'name=roles,type=CephString,n=N',
827 'Delete roles from user')
828 def ac_user_del_roles_cmd(_, username, roles):
829 rolesname = roles
830 roles = []
831 for rolename in rolesname:
832 try:
833 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
834 except RoleDoesNotExist as ex:
835 if rolename not in SYSTEM_ROLES:
836 return -errno.ENOENT, '', str(ex)
837 roles.append(SYSTEM_ROLES[rolename])
838 try:
839 user = mgr.ACCESS_CTRL_DB.get_user(username)
840 user.del_roles(roles)
841 mgr.ACCESS_CTRL_DB.save()
842 return 0, json.dumps(user.to_dict()), ''
843 except UserDoesNotExist as ex:
844 return -errno.ENOENT, '', str(ex)
845 except RoleNotInUser as ex:
846 return -errno.ENOENT, '', str(ex)
847
848
849 @CLIWriteCommand('dashboard ac-user-set-password',
850 'name=username,type=CephString '
851 'name=password,type=CephString '
852 'name=force_password,type=CephBool,req=false',
853 'Set user password')
854 def ac_user_set_password(_, username, password, force_password=False):
855 try:
856 user = mgr.ACCESS_CTRL_DB.get_user(username)
857 if not force_password:
858 pw_check = PasswordPolicy(password, user.name)
859 pw_check.check_all()
860 user.set_password(password)
861 mgr.ACCESS_CTRL_DB.save()
862 return 0, json.dumps(user.to_dict()), ''
863 except PasswordPolicyException as ex:
864 return -errno.EINVAL, '', str(ex)
865 except UserDoesNotExist as ex:
866 return -errno.ENOENT, '', str(ex)
867
868
869 @CLIWriteCommand('dashboard ac-user-set-password-hash',
870 'name=username,type=CephString '
871 'name=hashed_password,type=CephString',
872 'Set user password bcrypt hash')
873 def ac_user_set_password_hash(_, username, hashed_password):
874 try:
875 # make sure the hashed_password is actually a bcrypt hash
876 bcrypt.checkpw(b'', hashed_password.encode('utf-8'))
877 user = mgr.ACCESS_CTRL_DB.get_user(username)
878 user.set_password_hash(hashed_password)
879
880 mgr.ACCESS_CTRL_DB.save()
881 return 0, json.dumps(user.to_dict()), ''
882 except ValueError:
883 return -errno.EINVAL, '', 'Invalid password hash'
884 except UserDoesNotExist as ex:
885 return -errno.ENOENT, '', str(ex)
886
887
888 @CLIWriteCommand('dashboard ac-user-set-info',
889 'name=username,type=CephString '
890 'name=name,type=CephString '
891 'name=email,type=CephString',
892 'Set user info')
893 def ac_user_set_info(_, username, name, email):
894 try:
895 user = mgr.ACCESS_CTRL_DB.get_user(username)
896 if name:
897 user.name = name
898 if email:
899 user.email = email
900 mgr.ACCESS_CTRL_DB.save()
901 return 0, json.dumps(user.to_dict()), ''
902 except UserDoesNotExist as ex:
903 return -errno.ENOENT, '', str(ex)
904
905
906 class LocalAuthenticator(object):
907 def __init__(self):
908 load_access_control_db()
909
910 def get_user(self, username):
911 return mgr.ACCESS_CTRL_DB.get_user(username)
912
913 def authenticate(self, username, password):
914 try:
915 user = mgr.ACCESS_CTRL_DB.get_user(username)
916 if user.password:
917 if user.enabled and user.compare_password(password) \
918 and not user.is_pwd_expired():
919 return {'permissions': user.permissions_dict(),
920 'pwdExpirationDate': user.pwd_expiration_date,
921 'pwdUpdateRequired': user.pwd_update_required}
922 except UserDoesNotExist:
923 logger.debug("User '%s' does not exist", username)
924 return None
925
926 def authorize(self, username, scope, permissions):
927 user = mgr.ACCESS_CTRL_DB.get_user(username)
928 return user.authorize(scope, permissions)