]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/services/access_control.py
import ceph 15.2.14
[ceph.git] / ceph / src / pybind / mgr / dashboard / services / access_control.py
CommitLineData
11fdf7f2
TL
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
4from __future__ import absolute_import
5
9f95a23c
TL
6from string import punctuation, ascii_lowercase, digits, ascii_uppercase
7
11fdf7f2
TL
8import errno
9import json
9f95a23c 10import logging
11fdf7f2
TL
11import threading
12import time
9f95a23c
TL
13import re
14
15from datetime import datetime, timedelta
11fdf7f2
TL
16
17import bcrypt
cd265ab1 18from mgr_module import CLICheckNonemptyFileInput, CLIReadCommand, CLIWriteCommand
11fdf7f2 19
9f95a23c 20from .. import mgr
11fdf7f2 21from ..security import Scope, Permission
9f95a23c 22from ..settings import Settings
11fdf7f2
TL
23from ..exceptions import RoleAlreadyExists, RoleDoesNotExist, ScopeNotValid, \
24 PermissionNotValid, RoleIsAssociatedWithUser, \
25 UserAlreadyExists, UserDoesNotExist, ScopeNotInRole, \
9f95a23c
TL
26 RoleNotInUser, PasswordPolicyException, PwdExpirationDateNotValid
27
28
29logger = logging.getLogger('access_control')
11fdf7f2
TL
30
31
32# password hashing algorithm
33def 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
9f95a23c
TL
46class 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
11fdf7f2
TL
154class 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
213ADMIN_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
220READ_ONLY_ROLE = Role('read-only', 'Read-Only', {
221 scope_name: [_P.READ] for scope_name in Scope.all_scopes()
7f7e6c64 222 if scope_name not in (Scope.DASHBOARD_SETTINGS, Scope.CONFIG_OPT)
11fdf7f2
TL
223})
224
225
226# block manager role provides all permission for block related scopes
227BLOCK_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],
eafe8130 232 Scope.GRAFANA: [_P.READ],
11fdf7f2
TL
233})
234
235
236# RadosGW manager role provides all permissions for block related scopes
237RGW_MGR_ROLE = Role('rgw-manager', 'RGW Manager', {
238 Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
eafe8130 239 Scope.GRAFANA: [_P.READ],
11fdf7f2
TL
240})
241
242
243# Cluster manager role provides all permission for OSDs, Monitors, and
244# Config options
245CLUSTER_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],
eafe8130 252 Scope.GRAFANA: [_P.READ],
11fdf7f2
TL
253})
254
255
256# Pool manager role provides all permissions for pool related scopes
257POOL_MGR_ROLE = Role('pool-manager', 'Pool Manager', {
258 Scope.POOL: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
eafe8130 259 Scope.GRAFANA: [_P.READ],
11fdf7f2
TL
260})
261
9f95a23c 262# CephFS manager role provides all permissions for CephFS related scopes
11fdf7f2
TL
263CEPHFS_MGR_ROLE = Role('cephfs-manager', 'CephFS Manager', {
264 Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
eafe8130 265 Scope.GRAFANA: [_P.READ],
11fdf7f2
TL
266})
267
268GANESHA_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],
eafe8130 272 Scope.GRAFANA: [_P.READ],
11fdf7f2
TL
273})
274
275
276SYSTEM_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
288class User(object):
289 def __init__(self, username, password, name=None, email=None, roles=None,
9f95a23c
TL
290 last_update=None, enabled=True, pwd_expiration_date=None,
291 pwd_update_required=False):
11fdf7f2
TL
292 self.username = username
293 self.password = password
294 self.name = name
295 self.email = email
adb31ebb 296 self.invalid_auth_attempt = 0
11fdf7f2
TL
297 if roles is None:
298 self.roles = set()
299 else:
300 self.roles = roles
9f95a23c
TL
301 if last_update is None:
302 self.refresh_last_update()
11fdf7f2 303 else:
9f95a23c
TL
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
11fdf7f2 325
9f95a23c
TL
326 @enabled.setter
327 def enabled(self, value):
328 self._enabled = value
329 self.refresh_last_update()
11fdf7f2
TL
330
331 def set_password(self, password):
9f95a23c
TL
332 self.set_password_hash(password_hash(password))
333
334 def set_password_hash(self, hashed_password):
adb31ebb 335 self.invalid_auth_attempt = 0
9f95a23c
TL
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
11fdf7f2
TL
357
358 def set_roles(self, roles):
359 self.roles = set(roles)
9f95a23c 360 self.refresh_last_update()
11fdf7f2
TL
361
362 def add_roles(self, roles):
363 self.roles = self.roles.union(set(roles))
9f95a23c 364 self.refresh_last_update()
11fdf7f2
TL
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))
9f95a23c 371 self.refresh_last_update()
11fdf7f2
TL
372
373 def authorize(self, scope, permissions):
9f95a23c
TL
374 if self.pwd_update_required:
375 return False
376
11fdf7f2
TL
377 for role in self.roles:
378 if role.authorize(scope, permissions):
379 return True
380 return False
381
382 def permissions_dict(self):
9f95a23c
TL
383 # type: () -> dict
384 perms = {} # type: dict
11fdf7f2
TL
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,
9f95a23c
TL
402 'lastUpdate': self.last_update,
403 'enabled': self.enabled,
404 'pwdExpirationDate': self.pwd_expiration_date,
405 'pwdUpdateRequired': self.pwd_update_required
11fdf7f2
TL
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']},
9f95a23c
TL
412 u_dict['lastUpdate'], u_dict['enabled'],
413 u_dict['pwdExpirationDate'], u_dict['pwdUpdateRequired'])
11fdf7f2
TL
414
415
416class AccessControlDB(object):
9f95a23c 417 VERSION = 2
11fdf7f2
TL
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
adb31ebb
TL
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
11fdf7f2
TL
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
9f95a23c
TL
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)
11fdf7f2
TL
473 with self.lock:
474 if username in self.users:
475 raise UserAlreadyExists(username)
9f95a23c
TL
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)
11fdf7f2
TL
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:
9f95a23c 503 user.refresh_last_update()
11fdf7f2
TL
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
ec96510d
FG
520 def check_and_update_db(self):
521 logger.debug("Checking for previous DB versions")
522
523 def check_migrate_v1_to_current():
524 # Check if version 1 exists in the DB and migrate it to current version
525 v1_db = mgr.get_store(self.accessdb_config_key(1))
526 if v1_db:
527 logger.debug("Found database v1 credentials")
528 v1_db = json.loads(v1_db)
529
530 for user, _ in v1_db['users'].items():
531 v1_db['users'][user]['enabled'] = True
532 v1_db['users'][user]['pwdExpirationDate'] = None
533 v1_db['users'][user]['pwdUpdateRequired'] = False
534
535 self.roles = {rn: Role.from_dict(r) for rn, r in v1_db.get('roles', {}).items()}
536 self.users = {un: User.from_dict(u, dict(self.roles, **SYSTEM_ROLES))
537 for un, u in v1_db.get('users', {}).items()}
538
539 self.save()
540
541 check_migrate_v1_to_current()
542
11fdf7f2
TL
543 @classmethod
544 def load(cls):
9f95a23c 545 logger.info("Loading user roles DB version=%s", cls.VERSION)
11fdf7f2
TL
546
547 json_db = mgr.get_store(cls.accessdb_config_key())
548 if json_db is None:
9f95a23c 549 logger.debug("No DB v%s found, creating new...", cls.VERSION)
11fdf7f2 550 db = cls(cls.VERSION, {}, {})
ec96510d
FG
551 # check if we can update from a previous version database
552 db.check_and_update_db()
11fdf7f2
TL
553 return db
554
9f95a23c 555 dict_db = json.loads(json_db)
11fdf7f2 556 roles = {rn: Role.from_dict(r)
9f95a23c 557 for rn, r in dict_db.get('roles', {}).items()}
11fdf7f2 558 users = {un: User.from_dict(u, dict(roles, **SYSTEM_ROLES))
9f95a23c
TL
559 for un, u in dict_db.get('users', {}).items()}
560 return cls(dict_db['version'], users, roles)
11fdf7f2
TL
561
562
563def load_access_control_db():
564 mgr.ACCESS_CTRL_DB = AccessControlDB.load()
565
566
567# CLI dashboard access control scope commands
568
569@CLIWriteCommand('dashboard set-login-credentials',
cd265ab1
TL
570 'name=username,type=CephString',
571 'Set the login credentials. Password read from -i <file>')
572@CLICheckNonemptyFileInput
573def set_login_credentials_cmd(_, username, inbuf):
574 password = inbuf
11fdf7f2
TL
575 try:
576 user = mgr.ACCESS_CTRL_DB.get_user(username)
577 user.set_password(password)
578 except UserDoesNotExist:
579 user = mgr.ACCESS_CTRL_DB.create_user(username, password, None, None)
580 user.set_roles([ADMIN_ROLE])
581
582 mgr.ACCESS_CTRL_DB.save()
583
584 return 0, '''\
585******************************************************************
586*** WARNING: this command is deprecated. ***
587*** Please use the ac-user-* related commands to manage users. ***
588******************************************************************
589Username and password updated''', ''
590
591
592@CLIReadCommand('dashboard ac-role-show',
593 'name=rolename,type=CephString,req=false',
594 'Show role info')
595def ac_role_show_cmd(_, rolename=None):
596 if not rolename:
597 roles = dict(mgr.ACCESS_CTRL_DB.roles)
598 roles.update(SYSTEM_ROLES)
599 roles_list = [name for name, _ in roles.items()]
600 return 0, json.dumps(roles_list), ''
601 try:
602 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
603 except RoleDoesNotExist as ex:
604 if rolename not in SYSTEM_ROLES:
605 return -errno.ENOENT, '', str(ex)
606 role = SYSTEM_ROLES[rolename]
607 return 0, json.dumps(role.to_dict()), ''
608
609
610@CLIWriteCommand('dashboard ac-role-create',
611 'name=rolename,type=CephString '
612 'name=description,type=CephString,req=false',
613 'Create a new access control role')
614def ac_role_create_cmd(_, rolename, description=None):
615 try:
616 role = mgr.ACCESS_CTRL_DB.create_role(rolename, description)
617 mgr.ACCESS_CTRL_DB.save()
618 return 0, json.dumps(role.to_dict()), ''
619 except RoleAlreadyExists as ex:
620 return -errno.EEXIST, '', str(ex)
621
622
623@CLIWriteCommand('dashboard ac-role-delete',
624 'name=rolename,type=CephString',
625 'Delete an access control role')
626def ac_role_delete_cmd(_, rolename):
627 try:
628 mgr.ACCESS_CTRL_DB.delete_role(rolename)
629 mgr.ACCESS_CTRL_DB.save()
630 return 0, "Role '{}' deleted".format(rolename), ""
631 except RoleDoesNotExist as ex:
632 if rolename in SYSTEM_ROLES:
633 return -errno.EPERM, '', "Cannot delete system role '{}'" \
634 .format(rolename)
635 return -errno.ENOENT, '', str(ex)
636 except RoleIsAssociatedWithUser as ex:
637 return -errno.EPERM, '', str(ex)
638
639
640@CLIWriteCommand('dashboard ac-role-add-scope-perms',
641 'name=rolename,type=CephString '
642 'name=scopename,type=CephString '
643 'name=permissions,type=CephString,n=N',
644 'Add the scope permissions for a role')
645def ac_role_add_scope_perms_cmd(_, rolename, scopename, permissions):
646 try:
647 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
648 perms_array = [perm.strip() for perm in permissions]
649 role.set_scope_permissions(scopename, perms_array)
650 mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
651 mgr.ACCESS_CTRL_DB.save()
652 return 0, json.dumps(role.to_dict()), ''
653 except RoleDoesNotExist as ex:
654 if rolename in SYSTEM_ROLES:
655 return -errno.EPERM, '', "Cannot update system role '{}'" \
656 .format(rolename)
657 return -errno.ENOENT, '', str(ex)
658 except ScopeNotValid as ex:
659 return -errno.EINVAL, '', str(ex) + "\n Possible values: {}" \
660 .format(Scope.all_scopes())
661 except PermissionNotValid as ex:
662 return -errno.EINVAL, '', str(ex) + \
663 "\n Possible values: {}" \
664 .format(Permission.all_permissions())
665
666
667@CLIWriteCommand('dashboard ac-role-del-scope-perms',
668 'name=rolename,type=CephString '
669 'name=scopename,type=CephString',
670 'Delete the scope permissions for a role')
671def ac_role_del_scope_perms_cmd(_, rolename, scopename):
672 try:
673 role = mgr.ACCESS_CTRL_DB.get_role(rolename)
674 role.del_scope_permissions(scopename)
675 mgr.ACCESS_CTRL_DB.update_users_with_roles(role)
676 mgr.ACCESS_CTRL_DB.save()
677 return 0, json.dumps(role.to_dict()), ''
678 except RoleDoesNotExist as ex:
679 if rolename in SYSTEM_ROLES:
680 return -errno.EPERM, '', "Cannot update system role '{}'" \
681 .format(rolename)
682 return -errno.ENOENT, '', str(ex)
683 except ScopeNotInRole as ex:
684 return -errno.ENOENT, '', str(ex)
685
686
687@CLIReadCommand('dashboard ac-user-show',
688 'name=username,type=CephString,req=false',
689 'Show user info')
690def ac_user_show_cmd(_, username=None):
691 if not username:
692 users = mgr.ACCESS_CTRL_DB.users
693 users_list = [name for name, _ in users.items()]
694 return 0, json.dumps(users_list), ''
695 try:
696 user = mgr.ACCESS_CTRL_DB.get_user(username)
697 return 0, json.dumps(user.to_dict()), ''
698 except UserDoesNotExist as ex:
699 return -errno.ENOENT, '', str(ex)
700
701
702@CLIWriteCommand('dashboard ac-user-create',
703 'name=username,type=CephString '
11fdf7f2
TL
704 'name=rolename,type=CephString,req=false '
705 'name=name,type=CephString,req=false '
9f95a23c
TL
706 'name=email,type=CephString,req=false '
707 'name=enabled,type=CephBool,req=false '
708 'name=force_password,type=CephBool,req=false '
709 'name=pwd_expiration_date,type=CephInt,req=false '
710 'name=pwd_update_required,type=CephBool,req=false',
cd265ab1
TL
711 'Create a user. Password read from -i <file>')
712@CLICheckNonemptyFileInput
713def ac_user_create_cmd(_, username, inbuf, rolename=None, name=None,
9f95a23c
TL
714 email=None, enabled=True, force_password=False,
715 pwd_expiration_date=None, pwd_update_required=False):
cd265ab1 716 password = inbuf
11fdf7f2
TL
717 try:
718 role = mgr.ACCESS_CTRL_DB.get_role(rolename) if rolename else None
719 except RoleDoesNotExist as ex:
720 if rolename not in SYSTEM_ROLES:
721 return -errno.ENOENT, '', str(ex)
722 role = SYSTEM_ROLES[rolename]
723
724 try:
9f95a23c
TL
725 if not force_password:
726 pw_check = PasswordPolicy(password, username)
727 pw_check.check_all()
728 user = mgr.ACCESS_CTRL_DB.create_user(username, password, name, email,
729 enabled, pwd_expiration_date,
730 pwd_update_required)
731 except PasswordPolicyException as ex:
732 return -errno.EINVAL, '', str(ex)
11fdf7f2 733 except UserAlreadyExists as ex:
801d1391 734 return 0, str(ex), ''
11fdf7f2
TL
735
736 if role:
737 user.set_roles([role])
738 mgr.ACCESS_CTRL_DB.save()
739 return 0, json.dumps(user.to_dict()), ''
740
741
9f95a23c
TL
742@CLIWriteCommand('dashboard ac-user-enable',
743 'name=username,type=CephString',
744 'Enable a user')
745def ac_user_enable(_, username):
746 try:
747 user = mgr.ACCESS_CTRL_DB.get_user(username)
748 user.enabled = True
adb31ebb 749 mgr.ACCESS_CTRL_DB.reset_attempt(username)
9f95a23c
TL
750
751 mgr.ACCESS_CTRL_DB.save()
752 return 0, json.dumps(user.to_dict()), ''
753 except UserDoesNotExist as ex:
754 return -errno.ENOENT, '', str(ex)
755
756
757@CLIWriteCommand('dashboard ac-user-disable',
758 'name=username,type=CephString',
759 'Disable a user')
760def ac_user_disable(_, username):
761 try:
762 user = mgr.ACCESS_CTRL_DB.get_user(username)
763 user.enabled = False
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
11fdf7f2
TL
771@CLIWriteCommand('dashboard ac-user-delete',
772 'name=username,type=CephString',
773 'Delete user')
774def ac_user_delete_cmd(_, username):
775 try:
776 mgr.ACCESS_CTRL_DB.delete_user(username)
777 mgr.ACCESS_CTRL_DB.save()
778 return 0, "User '{}' deleted".format(username), ""
779 except UserDoesNotExist as ex:
780 return -errno.ENOENT, '', str(ex)
781
782
783@CLIWriteCommand('dashboard ac-user-set-roles',
784 'name=username,type=CephString '
785 'name=roles,type=CephString,n=N',
786 'Set user roles')
787def ac_user_set_roles_cmd(_, username, roles):
788 rolesname = roles
789 roles = []
790 for rolename in rolesname:
791 try:
792 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
793 except RoleDoesNotExist as ex:
794 if rolename not in SYSTEM_ROLES:
795 return -errno.ENOENT, '', str(ex)
796 roles.append(SYSTEM_ROLES[rolename])
797 try:
798 user = mgr.ACCESS_CTRL_DB.get_user(username)
799 user.set_roles(roles)
800 mgr.ACCESS_CTRL_DB.save()
801 return 0, json.dumps(user.to_dict()), ''
802 except UserDoesNotExist as ex:
803 return -errno.ENOENT, '', str(ex)
804
805
806@CLIWriteCommand('dashboard ac-user-add-roles',
807 'name=username,type=CephString '
808 'name=roles,type=CephString,n=N',
809 'Add roles to user')
810def ac_user_add_roles_cmd(_, username, roles):
811 rolesname = roles
812 roles = []
813 for rolename in rolesname:
814 try:
815 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
816 except RoleDoesNotExist as ex:
817 if rolename not in SYSTEM_ROLES:
818 return -errno.ENOENT, '', str(ex)
819 roles.append(SYSTEM_ROLES[rolename])
820 try:
821 user = mgr.ACCESS_CTRL_DB.get_user(username)
822 user.add_roles(roles)
823 mgr.ACCESS_CTRL_DB.save()
824 return 0, json.dumps(user.to_dict()), ''
825 except UserDoesNotExist as ex:
826 return -errno.ENOENT, '', str(ex)
827
828
829@CLIWriteCommand('dashboard ac-user-del-roles',
830 'name=username,type=CephString '
831 'name=roles,type=CephString,n=N',
832 'Delete roles from user')
833def ac_user_del_roles_cmd(_, username, roles):
834 rolesname = roles
835 roles = []
836 for rolename in rolesname:
837 try:
838 roles.append(mgr.ACCESS_CTRL_DB.get_role(rolename))
839 except RoleDoesNotExist as ex:
840 if rolename not in SYSTEM_ROLES:
841 return -errno.ENOENT, '', str(ex)
842 roles.append(SYSTEM_ROLES[rolename])
843 try:
844 user = mgr.ACCESS_CTRL_DB.get_user(username)
845 user.del_roles(roles)
846 mgr.ACCESS_CTRL_DB.save()
847 return 0, json.dumps(user.to_dict()), ''
848 except UserDoesNotExist as ex:
849 return -errno.ENOENT, '', str(ex)
850 except RoleNotInUser as ex:
851 return -errno.ENOENT, '', str(ex)
852
853
854@CLIWriteCommand('dashboard ac-user-set-password',
855 'name=username,type=CephString '
9f95a23c 856 'name=force_password,type=CephBool,req=false',
cd265ab1
TL
857 'Set user password from -i <file>')
858@CLICheckNonemptyFileInput
859def ac_user_set_password(_, username, inbuf, force_password=False):
860 password = inbuf
11fdf7f2
TL
861 try:
862 user = mgr.ACCESS_CTRL_DB.get_user(username)
9f95a23c
TL
863 if not force_password:
864 pw_check = PasswordPolicy(password, user.name)
865 pw_check.check_all()
11fdf7f2 866 user.set_password(password)
9f95a23c
TL
867 mgr.ACCESS_CTRL_DB.save()
868 return 0, json.dumps(user.to_dict()), ''
869 except PasswordPolicyException as ex:
870 return -errno.EINVAL, '', str(ex)
871 except UserDoesNotExist as ex:
872 return -errno.ENOENT, '', str(ex)
873
874
875@CLIWriteCommand('dashboard ac-user-set-password-hash',
cd265ab1
TL
876 'name=username,type=CephString',
877 'Set user password bcrypt hash from -i <file>')
878@CLICheckNonemptyFileInput
879def ac_user_set_password_hash(_, username, inbuf):
880 hashed_password = inbuf
9f95a23c
TL
881 try:
882 # make sure the hashed_password is actually a bcrypt hash
883 bcrypt.checkpw(b'', hashed_password.encode('utf-8'))
884 user = mgr.ACCESS_CTRL_DB.get_user(username)
885 user.set_password_hash(hashed_password)
11fdf7f2
TL
886
887 mgr.ACCESS_CTRL_DB.save()
888 return 0, json.dumps(user.to_dict()), ''
9f95a23c
TL
889 except ValueError:
890 return -errno.EINVAL, '', 'Invalid password hash'
11fdf7f2
TL
891 except UserDoesNotExist as ex:
892 return -errno.ENOENT, '', str(ex)
893
894
895@CLIWriteCommand('dashboard ac-user-set-info',
896 'name=username,type=CephString '
897 'name=name,type=CephString '
898 'name=email,type=CephString',
899 'Set user info')
900def ac_user_set_info(_, username, name, email):
901 try:
902 user = mgr.ACCESS_CTRL_DB.get_user(username)
903 if name:
904 user.name = name
905 if email:
906 user.email = email
907 mgr.ACCESS_CTRL_DB.save()
908 return 0, json.dumps(user.to_dict()), ''
909 except UserDoesNotExist as ex:
910 return -errno.ENOENT, '', str(ex)
911
912
913class LocalAuthenticator(object):
914 def __init__(self):
915 load_access_control_db()
916
917 def get_user(self, username):
918 return mgr.ACCESS_CTRL_DB.get_user(username)
919
920 def authenticate(self, username, password):
921 try:
922 user = mgr.ACCESS_CTRL_DB.get_user(username)
923 if user.password:
9f95a23c
TL
924 if user.enabled and user.compare_password(password) \
925 and not user.is_pwd_expired():
926 return {'permissions': user.permissions_dict(),
927 'pwdExpirationDate': user.pwd_expiration_date,
928 'pwdUpdateRequired': user.pwd_update_required}
11fdf7f2
TL
929 except UserDoesNotExist:
930 logger.debug("User '%s' does not exist", username)
931 return None
932
933 def authorize(self, username, scope, permissions):
934 user = mgr.ACCESS_CTRL_DB.get_user(username)
935 return user.authorize(scope, permissions)