]> git.proxmox.com Git - ceph.git/blame - 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
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()
222 if scope_name != Scope.DASHBOARD_SETTINGS
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
520 def check_and_update_db(self):
9f95a23c
TL
521 logger.debug("Checking for previews DB versions")
522
523 def check_migrate_v0_to_current():
11fdf7f2
TL
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:
9f95a23c 528 logger.debug("Found single user credentials: user=%s", username)
11fdf7f2
TL
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()
9f95a23c
TL
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()
11fdf7f2
TL
558
559 @classmethod
560 def load(cls):
9f95a23c 561 logger.info("Loading user roles DB version=%s", cls.VERSION)
11fdf7f2
TL
562
563 json_db = mgr.get_store(cls.accessdb_config_key())
564 if json_db is None:
9f95a23c 565 logger.debug("No DB v%s found, creating new...", cls.VERSION)
11fdf7f2
TL
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
9f95a23c 571 dict_db = json.loads(json_db)
11fdf7f2 572 roles = {rn: Role.from_dict(r)
9f95a23c 573 for rn, r in dict_db.get('roles', {}).items()}
11fdf7f2 574 users = {un: User.from_dict(u, dict(roles, **SYSTEM_ROLES))
9f95a23c
TL
575 for un, u in dict_db.get('users', {}).items()}
576 return cls(dict_db['version'], users, roles)
11fdf7f2
TL
577
578
579def 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',
cd265ab1
TL
586 'name=username,type=CephString',
587 'Set the login credentials. Password read from -i <file>')
588@CLICheckNonemptyFileInput
589def set_login_credentials_cmd(_, username, inbuf):
590 password = inbuf
11fdf7f2
TL
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******************************************************************
605Username and password updated''', ''
606
607
608@CLIReadCommand('dashboard ac-role-show',
609 'name=rolename,type=CephString,req=false',
610 'Show role info')
611def 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')
630def 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')
642def 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')
661def 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')
687def 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')
706def 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 '
11fdf7f2
TL
720 'name=rolename,type=CephString,req=false '
721 'name=name,type=CephString,req=false '
9f95a23c
TL
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',
cd265ab1
TL
727 'Create a user. Password read from -i <file>')
728@CLICheckNonemptyFileInput
729def ac_user_create_cmd(_, username, inbuf, rolename=None, name=None,
9f95a23c
TL
730 email=None, enabled=True, force_password=False,
731 pwd_expiration_date=None, pwd_update_required=False):
cd265ab1 732 password = inbuf
11fdf7f2
TL
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:
9f95a23c
TL
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)
11fdf7f2 749 except UserAlreadyExists as ex:
801d1391 750 return 0, str(ex), ''
11fdf7f2
TL
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
9f95a23c
TL
758@CLIWriteCommand('dashboard ac-user-enable',
759 'name=username,type=CephString',
760 'Enable a user')
761def ac_user_enable(_, username):
762 try:
763 user = mgr.ACCESS_CTRL_DB.get_user(username)
764 user.enabled = True
adb31ebb 765 mgr.ACCESS_CTRL_DB.reset_attempt(username)
9f95a23c
TL
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')
776def 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
11fdf7f2
TL
787@CLIWriteCommand('dashboard ac-user-delete',
788 'name=username,type=CephString',
789 'Delete user')
790def 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')
803def 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')
826def 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')
849def 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 '
9f95a23c 872 'name=force_password,type=CephBool,req=false',
cd265ab1
TL
873 'Set user password from -i <file>')
874@CLICheckNonemptyFileInput
875def ac_user_set_password(_, username, inbuf, force_password=False):
876 password = inbuf
11fdf7f2
TL
877 try:
878 user = mgr.ACCESS_CTRL_DB.get_user(username)
9f95a23c
TL
879 if not force_password:
880 pw_check = PasswordPolicy(password, user.name)
881 pw_check.check_all()
11fdf7f2 882 user.set_password(password)
9f95a23c
TL
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',
cd265ab1
TL
892 'name=username,type=CephString',
893 'Set user password bcrypt hash from -i <file>')
894@CLICheckNonemptyFileInput
895def ac_user_set_password_hash(_, username, inbuf):
896 hashed_password = inbuf
9f95a23c
TL
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)
11fdf7f2
TL
902
903 mgr.ACCESS_CTRL_DB.save()
904 return 0, json.dumps(user.to_dict()), ''
9f95a23c
TL
905 except ValueError:
906 return -errno.EINVAL, '', 'Invalid password hash'
11fdf7f2
TL
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')
916def 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
929class 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:
9f95a23c
TL
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}
11fdf7f2
TL
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)