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