]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/services/access_control.py
import ceph 15.2.10
[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 from mgr_module import CLICheckNonemptyFileInput, CLIReadCommand, CLIWriteCommand
19
20 from .. import mgr
21 from ..security import Scope, Permission
22 from ..settings import Settings
23 from ..exceptions import RoleAlreadyExists, RoleDoesNotExist, ScopeNotValid, \
24 PermissionNotValid, RoleIsAssociatedWithUser, \
25 UserAlreadyExists, UserDoesNotExist, ScopeNotInRole, \
26 RoleNotInUser, PasswordPolicyException, PwdExpirationDateNotValid
27
28
29 logger = logging.getLogger('access_control')
30
31
32 # password hashing algorithm
33 def password_hash(password, salt_password=None):
34 if not password:
35 return None
36 if not salt_password:
37 salt_password = bcrypt.gensalt()
38 else:
39 salt_password = salt_password.encode('utf8')
40 return bcrypt.hashpw(password.encode('utf8'), salt_password).decode('utf8')
41
42
43 _P = Permission # short alias
44
45
46 class PasswordPolicy(object):
47 def __init__(self, password, username=None, old_password=None):
48 """
49 :param password: The new plain password.
50 :type password: str
51 :param username: The name of the user.
52 :type username: str | None
53 :param old_password: The old plain password.
54 :type old_password: str | None
55 """
56 self.password = password
57 self.username = username
58 self.old_password = old_password
59 self.forbidden_words = Settings.PWD_POLICY_EXCLUSION_LIST.split(',')
60 self.complexity_credits = 0
61
62 @staticmethod
63 def _check_if_contains_word(password, word):
64 return re.compile('(?:{0})'.format(word),
65 flags=re.IGNORECASE).search(password)
66
67 def check_password_complexity(self):
68 if not Settings.PWD_POLICY_CHECK_COMPLEXITY_ENABLED:
69 return Settings.PWD_POLICY_MIN_COMPLEXITY
70 digit_credit = 1
71 small_letter_credit = 1
72 big_letter_credit = 2
73 special_character_credit = 3
74 other_character_credit = 5
75 self.complexity_credits = 0
76 for ch in self.password:
77 if ch in ascii_uppercase:
78 self.complexity_credits += big_letter_credit
79 elif ch in ascii_lowercase:
80 self.complexity_credits += small_letter_credit
81 elif ch in digits:
82 self.complexity_credits += digit_credit
83 elif ch in punctuation:
84 self.complexity_credits += special_character_credit
85 else:
86 self.complexity_credits += other_character_credit
87 return self.complexity_credits
88
89 def check_is_old_password(self):
90 if not Settings.PWD_POLICY_CHECK_OLDPWD_ENABLED:
91 return False
92 return self.old_password and self.password == self.old_password
93
94 def check_if_contains_username(self):
95 if not Settings.PWD_POLICY_CHECK_USERNAME_ENABLED:
96 return False
97 if not self.username:
98 return False
99 return self._check_if_contains_word(self.password, self.username)
100
101 def check_if_contains_forbidden_words(self):
102 if not Settings.PWD_POLICY_CHECK_EXCLUSION_LIST_ENABLED:
103 return False
104 return self._check_if_contains_word(self.password,
105 '|'.join(self.forbidden_words))
106
107 def check_if_sequential_characters(self):
108 if not Settings.PWD_POLICY_CHECK_SEQUENTIAL_CHARS_ENABLED:
109 return False
110 for i in range(1, len(self.password) - 1):
111 if ord(self.password[i - 1]) + 1 == ord(self.password[i])\
112 == ord(self.password[i + 1]) - 1:
113 return True
114 return False
115
116 def check_if_repetitive_characters(self):
117 if not Settings.PWD_POLICY_CHECK_REPETITIVE_CHARS_ENABLED:
118 return False
119 for i in range(1, len(self.password) - 1):
120 if self.password[i - 1] == self.password[i] == self.password[i + 1]:
121 return True
122 return False
123
124 def check_password_length(self):
125 if not Settings.PWD_POLICY_CHECK_LENGTH_ENABLED:
126 return True
127 return len(self.password) >= Settings.PWD_POLICY_MIN_LENGTH
128
129 def check_all(self):
130 """
131 Perform all password policy checks.
132 :raise PasswordPolicyException: If a password policy check fails.
133 """
134 if not Settings.PWD_POLICY_ENABLED:
135 return
136 if self.check_password_complexity() < Settings.PWD_POLICY_MIN_COMPLEXITY:
137 raise PasswordPolicyException('Password is too weak.')
138 if not self.check_password_length():
139 raise PasswordPolicyException('Password is too weak.')
140 if self.check_is_old_password():
141 raise PasswordPolicyException('Password must not be the same as the previous one.')
142 if self.check_if_contains_username():
143 raise PasswordPolicyException('Password must not contain username.')
144 result = self.check_if_contains_forbidden_words()
145 if result:
146 raise PasswordPolicyException('Password must not contain the keyword "{}".'.format(
147 result.group(0)))
148 if self.check_if_repetitive_characters():
149 raise PasswordPolicyException('Password must not contain repetitive characters.')
150 if self.check_if_sequential_characters():
151 raise PasswordPolicyException('Password must not contain sequential characters.')
152
153
154 class Role(object):
155 def __init__(self, name, description=None, scope_permissions=None):
156 self.name = name
157 self.description = description
158 if scope_permissions is None:
159 self.scopes_permissions = {}
160 else:
161 self.scopes_permissions = scope_permissions
162
163 def __hash__(self):
164 return hash(self.name)
165
166 def __eq__(self, other):
167 return self.name == other.name
168
169 def set_scope_permissions(self, scope, permissions):
170 if not Scope.valid_scope(scope):
171 raise ScopeNotValid(scope)
172 for perm in permissions:
173 if not Permission.valid_permission(perm):
174 raise PermissionNotValid(perm)
175
176 permissions.sort()
177 self.scopes_permissions[scope] = permissions
178
179 def del_scope_permissions(self, scope):
180 if scope not in self.scopes_permissions:
181 raise ScopeNotInRole(scope, self.name)
182 del self.scopes_permissions[scope]
183
184 def reset_scope_permissions(self):
185 self.scopes_permissions = {}
186
187 def authorize(self, scope, permissions):
188 if scope in self.scopes_permissions:
189 role_perms = self.scopes_permissions[scope]
190 for perm in permissions:
191 if perm not in role_perms:
192 return False
193 return True
194 return False
195
196 def to_dict(self):
197 return {
198 'name': self.name,
199 'description': self.description,
200 'scopes_permissions': self.scopes_permissions
201 }
202
203 @classmethod
204 def from_dict(cls, r_dict):
205 return Role(r_dict['name'], r_dict['description'],
206 r_dict['scopes_permissions'])
207
208
209 # static pre-defined system roles
210 # this roles cannot be deleted nor updated
211
212 # admin role provides all permissions for all scopes
213 ADMIN_ROLE = Role('administrator', 'Administrator', {
214 scope_name: Permission.all_permissions()
215 for scope_name in Scope.all_scopes()
216 })
217
218
219 # read-only role provides read-only permission for all scopes
220 READ_ONLY_ROLE = Role('read-only', 'Read-Only', {
221 scope_name: [_P.READ] for scope_name in Scope.all_scopes()
222 if scope_name != Scope.DASHBOARD_SETTINGS
223 })
224
225
226 # block manager role provides all permission for block related scopes
227 BLOCK_MGR_ROLE = Role('block-manager', 'Block Manager', {
228 Scope.RBD_IMAGE: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
229 Scope.POOL: [_P.READ],
230 Scope.ISCSI: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
231 Scope.RBD_MIRRORING: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
232 Scope.GRAFANA: [_P.READ],
233 })
234
235
236 # RadosGW manager role provides all permissions for block related scopes
237 RGW_MGR_ROLE = Role('rgw-manager', 'RGW Manager', {
238 Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
239 Scope.GRAFANA: [_P.READ],
240 })
241
242
243 # Cluster manager role provides all permission for OSDs, Monitors, and
244 # Config options
245 CLUSTER_MGR_ROLE = Role('cluster-manager', 'Cluster Manager', {
246 Scope.HOSTS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
247 Scope.OSD: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
248 Scope.MONITOR: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
249 Scope.MANAGER: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
250 Scope.CONFIG_OPT: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
251 Scope.LOG: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
252 Scope.GRAFANA: [_P.READ],
253 })
254
255
256 # Pool manager role provides all permissions for pool related scopes
257 POOL_MGR_ROLE = Role('pool-manager', 'Pool Manager', {
258 Scope.POOL: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
259 Scope.GRAFANA: [_P.READ],
260 })
261
262 # CephFS manager role provides all permissions for CephFS related scopes
263 CEPHFS_MGR_ROLE = Role('cephfs-manager', 'CephFS Manager', {
264 Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
265 Scope.GRAFANA: [_P.READ],
266 })
267
268 GANESHA_MGR_ROLE = Role('ganesha-manager', 'NFS Ganesha Manager', {
269 Scope.NFS_GANESHA: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
270 Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
271 Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
272 Scope.GRAFANA: [_P.READ],
273 })
274
275
276 SYSTEM_ROLES = {
277 ADMIN_ROLE.name: ADMIN_ROLE,
278 READ_ONLY_ROLE.name: READ_ONLY_ROLE,
279 BLOCK_MGR_ROLE.name: BLOCK_MGR_ROLE,
280 RGW_MGR_ROLE.name: RGW_MGR_ROLE,
281 CLUSTER_MGR_ROLE.name: CLUSTER_MGR_ROLE,
282 POOL_MGR_ROLE.name: POOL_MGR_ROLE,
283 CEPHFS_MGR_ROLE.name: CEPHFS_MGR_ROLE,
284 GANESHA_MGR_ROLE.name: GANESHA_MGR_ROLE,
285 }
286
287
288 class User(object):
289 def __init__(self, username, password, name=None, email=None, roles=None,
290 last_update=None, enabled=True, pwd_expiration_date=None,
291 pwd_update_required=False):
292 self.username = username
293 self.password = password
294 self.name = name
295 self.email = email
296 self.invalid_auth_attempt = 0
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.invalid_auth_attempt = 0
336 self.password = hashed_password
337 self.refresh_last_update()
338 self.refresh_pwd_expiration_date()
339 self.pwd_update_required = False
340
341 def compare_password(self, password):
342 """
343 Compare the specified password with the user password.
344 :param password: The plain password to check.
345 :type password: str
346 :return: `True` if the passwords are equal, otherwise `False`.
347 :rtype: bool
348 """
349 pass_hash = password_hash(password, salt_password=self.password)
350 return pass_hash == self.password
351
352 def is_pwd_expired(self):
353 if self.pwd_expiration_date:
354 current_time = int(time.mktime(datetime.utcnow().timetuple()))
355 return self.pwd_expiration_date < current_time
356 return False
357
358 def set_roles(self, roles):
359 self.roles = set(roles)
360 self.refresh_last_update()
361
362 def add_roles(self, roles):
363 self.roles = self.roles.union(set(roles))
364 self.refresh_last_update()
365
366 def del_roles(self, roles):
367 for role in roles:
368 if role not in self.roles:
369 raise RoleNotInUser(role.name, self.username)
370 self.roles.difference_update(set(roles))
371 self.refresh_last_update()
372
373 def authorize(self, scope, permissions):
374 if self.pwd_update_required:
375 return False
376
377 for role in self.roles:
378 if role.authorize(scope, permissions):
379 return True
380 return False
381
382 def permissions_dict(self):
383 # type: () -> dict
384 perms = {} # type: dict
385 for role in self.roles:
386 for scope, perms_list in role.scopes_permissions.items():
387 if scope in perms:
388 perms_tmp = set(perms[scope]).union(set(perms_list))
389 perms[scope] = list(perms_tmp)
390 else:
391 perms[scope] = perms_list
392
393 return perms
394
395 def to_dict(self):
396 return {
397 'username': self.username,
398 'password': self.password,
399 'roles': sorted([r.name for r in self.roles]),
400 'name': self.name,
401 'email': self.email,
402 'lastUpdate': self.last_update,
403 'enabled': self.enabled,
404 'pwdExpirationDate': self.pwd_expiration_date,
405 'pwdUpdateRequired': self.pwd_update_required
406 }
407
408 @classmethod
409 def from_dict(cls, u_dict, roles):
410 return User(u_dict['username'], u_dict['password'], u_dict['name'],
411 u_dict['email'], {roles[r] for r in u_dict['roles']},
412 u_dict['lastUpdate'], u_dict['enabled'],
413 u_dict['pwdExpirationDate'], u_dict['pwdUpdateRequired'])
414
415
416 class AccessControlDB(object):
417 VERSION = 2
418 ACDB_CONFIG_KEY = "accessdb_v"
419
420 def __init__(self, version, users, roles):
421 self.users = users
422 self.version = version
423 self.roles = roles
424 self.lock = threading.RLock()
425
426 def create_role(self, name, description=None):
427 with self.lock:
428 if name in SYSTEM_ROLES or name in self.roles:
429 raise RoleAlreadyExists(name)
430 role = Role(name, description)
431 self.roles[name] = role
432 return role
433
434 def get_role(self, name):
435 with self.lock:
436 if name not in self.roles:
437 raise RoleDoesNotExist(name)
438 return self.roles[name]
439
440 def increment_attempt(self, username):
441 with self.lock:
442 if username in self.users:
443 self.users[username].invalid_auth_attempt += 1
444
445 def reset_attempt(self, username):
446 with self.lock:
447 if username in self.users:
448 self.users[username].invalid_auth_attempt = 0
449
450 def get_attempt(self, username):
451 with self.lock:
452 try:
453 return self.users[username].invalid_auth_attempt
454 except KeyError:
455 return 0
456
457 def delete_role(self, name):
458 with self.lock:
459 if name not in self.roles:
460 raise RoleDoesNotExist(name)
461 role = self.roles[name]
462
463 # check if role is not associated with a user
464 for username, user in self.users.items():
465 if role in user.roles:
466 raise RoleIsAssociatedWithUser(name, username)
467
468 del self.roles[name]
469
470 def create_user(self, username, password, name, email, enabled=True,
471 pwd_expiration_date=None, pwd_update_required=False):
472 logger.debug("creating user: username=%s", username)
473 with self.lock:
474 if username in self.users:
475 raise UserAlreadyExists(username)
476 if pwd_expiration_date and \
477 (pwd_expiration_date < int(time.mktime(datetime.utcnow().timetuple()))):
478 raise PwdExpirationDateNotValid()
479 user = User(username, password_hash(password), name, email, enabled=enabled,
480 pwd_expiration_date=pwd_expiration_date,
481 pwd_update_required=pwd_update_required)
482 self.users[username] = user
483 return user
484
485 def get_user(self, username):
486 with self.lock:
487 if username not in self.users:
488 raise UserDoesNotExist(username)
489 return self.users[username]
490
491 def delete_user(self, username):
492 with self.lock:
493 if username not in self.users:
494 raise UserDoesNotExist(username)
495 del self.users[username]
496
497 def update_users_with_roles(self, role):
498 with self.lock:
499 if not role:
500 return
501 for _, user in self.users.items():
502 if role in user.roles:
503 user.refresh_last_update()
504
505 def save(self):
506 with self.lock:
507 db = {
508 'users': {un: u.to_dict() for un, u in self.users.items()},
509 'roles': {rn: r.to_dict() for rn, r in self.roles.items()},
510 'version': self.version
511 }
512 mgr.set_store(self.accessdb_config_key(), json.dumps(db))
513
514 @classmethod
515 def accessdb_config_key(cls, version=None):
516 if version is None:
517 version = cls.VERSION
518 return "{}{}".format(cls.ACDB_CONFIG_KEY, version)
519
520 def check_and_update_db(self):
521 logger.debug("Checking for previews DB versions")
522
523 def check_migrate_v0_to_current():
524 # check if there is username/password from previous version
525 username = mgr.get_module_option('username', None)
526 password = mgr.get_module_option('password', None)
527 if username and password:
528 logger.debug("Found single user credentials: user=%s", username)
529 # found user credentials
530 user = self.create_user(username, "", None, None)
531 # password is already hashed, so setting manually
532 user.password = password
533 user.add_roles([ADMIN_ROLE])
534 self.save()
535
536 def check_migrate_v1_to_current():
537 # Check if version 1 exists in the DB and migrate it to current version
538 v1_db = mgr.get_store(self.accessdb_config_key(1))
539 if v1_db:
540 logger.debug("Found database v1 credentials")
541 v1_db = json.loads(v1_db)
542
543 for user, _ in v1_db['users'].items():
544 v1_db['users'][user]['enabled'] = True
545 v1_db['users'][user]['pwdExpirationDate'] = None
546 v1_db['users'][user]['pwdUpdateRequired'] = False
547
548 self.roles = {rn: Role.from_dict(r) for rn, r in v1_db.get('roles', {}).items()}
549 self.users = {un: User.from_dict(u, dict(self.roles, **SYSTEM_ROLES))
550 for un, u in v1_db.get('users', {}).items()}
551
552 self.save()
553 else:
554 # If version 1 does not exist, check if migration of VERSION "0" needs to be done
555 check_migrate_v0_to_current()
556
557 check_migrate_v1_to_current()
558
559 @classmethod
560 def load(cls):
561 logger.info("Loading user roles DB version=%s", cls.VERSION)
562
563 json_db = mgr.get_store(cls.accessdb_config_key())
564 if json_db is None:
565 logger.debug("No DB v%s found, creating new...", cls.VERSION)
566 db = cls(cls.VERSION, {}, {})
567 # check if we can update from a previous version database
568 db.check_and_update_db()
569 return db
570
571 dict_db = json.loads(json_db)
572 roles = {rn: Role.from_dict(r)
573 for rn, r in dict_db.get('roles', {}).items()}
574 users = {un: User.from_dict(u, dict(roles, **SYSTEM_ROLES))
575 for un, u in dict_db.get('users', {}).items()}
576 return cls(dict_db['version'], users, roles)
577
578
579 def load_access_control_db():
580 mgr.ACCESS_CTRL_DB = AccessControlDB.load()
581
582
583 # CLI dashboard access control scope commands
584
585 @CLIWriteCommand('dashboard set-login-credentials',
586 'name=username,type=CephString',
587 'Set the login credentials. Password read from -i <file>')
588 @CLICheckNonemptyFileInput
589 def set_login_credentials_cmd(_, username, inbuf):
590 password = inbuf
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=rolename,type=CephString,req=false '
721 'name=name,type=CephString,req=false '
722 'name=email,type=CephString,req=false '
723 'name=enabled,type=CephBool,req=false '
724 'name=force_password,type=CephBool,req=false '
725 'name=pwd_expiration_date,type=CephInt,req=false '
726 'name=pwd_update_required,type=CephBool,req=false',
727 'Create a user. Password read from -i <file>')
728 @CLICheckNonemptyFileInput
729 def ac_user_create_cmd(_, username, inbuf, rolename=None, name=None,
730 email=None, enabled=True, force_password=False,
731 pwd_expiration_date=None, pwd_update_required=False):
732 password = inbuf
733 try:
734 role = mgr.ACCESS_CTRL_DB.get_role(rolename) if rolename else None
735 except RoleDoesNotExist as ex:
736 if rolename not in SYSTEM_ROLES:
737 return -errno.ENOENT, '', str(ex)
738 role = SYSTEM_ROLES[rolename]
739
740 try:
741 if not force_password:
742 pw_check = PasswordPolicy(password, username)
743 pw_check.check_all()
744 user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, email,
745 enabled, pwd_expiration_date,
746 pwd_update_required)
747 except PasswordPolicyException as ex:
748 return -errno.EINVAL, '', str(ex)
749 except UserAlreadyExists as ex:
750 return 0, str(ex), ''
751
752 if role:
753 user.set_roles([role])
754 mgr.ACCESS_CTRL_DB.save()
755 return 0, json.dumps(user.to_dict()), ''
756
757
758 @CLIWriteCommand('dashboard ac-user-enable',
759 'name=username,type=CephString',
760 'Enable a user')
761 def ac_user_enable(_, username):
762 try:
763 user = mgr.ACCESS_CTRL_DB.get_user(username)
764 user.enabled = True
765 mgr.ACCESS_CTRL_DB.reset_attempt(username)
766
767 mgr.ACCESS_CTRL_DB.save()
768 return 0, json.dumps(user.to_dict()), ''
769 except UserDoesNotExist as ex:
770 return -errno.ENOENT, '', str(ex)
771
772
773 @CLIWriteCommand('dashboard ac-user-disable',
774 'name=username,type=CephString',
775 'Disable a user')
776 def ac_user_disable(_, username):
777 try:
778 user = mgr.ACCESS_CTRL_DB.get_user(username)
779 user.enabled = False
780
781 mgr.ACCESS_CTRL_DB.save()
782 return 0, json.dumps(user.to_dict()), ''
783 except UserDoesNotExist as ex:
784 return -errno.ENOENT, '', str(ex)
785
786
787 @CLIWriteCommand('dashboard ac-user-delete',
788 'name=username,type=CephString',
789 'Delete user')
790 def ac_user_delete_cmd(_, username):
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 'name=username,type=CephString '
801 'name=roles,type=CephString,n=N',
802 'Set user roles')
803 def ac_user_set_roles_cmd(_, username, roles):
804 rolesname = roles
805 roles = []
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 'name=username,type=CephString '
824 'name=roles,type=CephString,n=N',
825 'Add roles to user')
826 def ac_user_add_roles_cmd(_, username, roles):
827 rolesname = roles
828 roles = []
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 'name=username,type=CephString '
847 'name=roles,type=CephString,n=N',
848 'Delete roles from user')
849 def ac_user_del_roles_cmd(_, username, roles):
850 rolesname = roles
851 roles = []
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 'name=username,type=CephString '
872 'name=force_password,type=CephBool,req=false',
873 'Set user password from -i <file>')
874 @CLICheckNonemptyFileInput
875 def ac_user_set_password(_, username, inbuf, force_password=False):
876 password = inbuf
877 try:
878 user = mgr.ACCESS_CTRL_DB.get_user(username)
879 if not force_password:
880 pw_check = PasswordPolicy(password, user.name)
881 pw_check.check_all()
882 user.set_password(password)
883 mgr.ACCESS_CTRL_DB.save()
884 return 0, json.dumps(user.to_dict()), ''
885 except PasswordPolicyException as ex:
886 return -errno.EINVAL, '', str(ex)
887 except UserDoesNotExist as ex:
888 return -errno.ENOENT, '', str(ex)
889
890
891 @CLIWriteCommand('dashboard ac-user-set-password-hash',
892 'name=username,type=CephString',
893 'Set user password bcrypt hash from -i <file>')
894 @CLICheckNonemptyFileInput
895 def ac_user_set_password_hash(_, username, inbuf):
896 hashed_password = inbuf
897 try:
898 # make sure the hashed_password is actually a bcrypt hash
899 bcrypt.checkpw(b'', hashed_password.encode('utf-8'))
900 user = mgr.ACCESS_CTRL_DB.get_user(username)
901 user.set_password_hash(hashed_password)
902
903 mgr.ACCESS_CTRL_DB.save()
904 return 0, json.dumps(user.to_dict()), ''
905 except ValueError:
906 return -errno.EINVAL, '', 'Invalid password hash'
907 except UserDoesNotExist as ex:
908 return -errno.ENOENT, '', str(ex)
909
910
911 @CLIWriteCommand('dashboard ac-user-set-info',
912 'name=username,type=CephString '
913 'name=name,type=CephString '
914 'name=email,type=CephString',
915 'Set user info')
916 def ac_user_set_info(_, username, name, email):
917 try:
918 user = mgr.ACCESS_CTRL_DB.get_user(username)
919 if name:
920 user.name = name
921 if email:
922 user.email = email
923 mgr.ACCESS_CTRL_DB.save()
924 return 0, json.dumps(user.to_dict()), ''
925 except UserDoesNotExist as ex:
926 return -errno.ENOENT, '', str(ex)
927
928
929 class LocalAuthenticator(object):
930 def __init__(self):
931 load_access_control_db()
932
933 def get_user(self, username):
934 return mgr.ACCESS_CTRL_DB.get_user(username)
935
936 def authenticate(self, username, password):
937 try:
938 user = mgr.ACCESS_CTRL_DB.get_user(username)
939 if user.password:
940 if user.enabled and user.compare_password(password) \
941 and not user.is_pwd_expired():
942 return {'permissions': user.permissions_dict(),
943 'pwdExpirationDate': user.pwd_expiration_date,
944 'pwdUpdateRequired': user.pwd_update_required}
945 except UserDoesNotExist:
946 logger.debug("User '%s' does not exist", username)
947 return None
948
949 def authorize(self, username, scope, permissions):
950 user = mgr.ACCESS_CTRL_DB.get_user(username)
951 return user.authorize(scope, permissions)