]>
git.proxmox.com Git - ceph.git/blob - 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
13 from mgr_module
import CLIReadCommand
, CLIWriteCommand
15 from .. import mgr
, logger
16 from ..security
import Scope
, Permission
17 from ..exceptions
import RoleAlreadyExists
, RoleDoesNotExist
, ScopeNotValid
, \
18 PermissionNotValid
, RoleIsAssociatedWithUser
, \
19 UserAlreadyExists
, UserDoesNotExist
, ScopeNotInRole
, \
23 # password hashing algorithm
24 def password_hash(password
, salt_password
=None):
28 salt_password
= bcrypt
.gensalt()
30 salt_password
= salt_password
.encode('utf8')
31 return bcrypt
.hashpw(password
.encode('utf8'), salt_password
).decode('utf8')
34 _P
= Permission
# short alias
38 def __init__(self
, name
, description
=None, scope_permissions
=None):
40 self
.description
= description
41 if scope_permissions
is None:
42 self
.scopes_permissions
= {}
44 self
.scopes_permissions
= scope_permissions
47 return hash(self
.name
)
49 def __eq__(self
, other
):
50 return self
.name
== other
.name
52 def set_scope_permissions(self
, scope
, permissions
):
53 if not Scope
.valid_scope(scope
):
54 raise ScopeNotValid(scope
)
55 for perm
in permissions
:
56 if not Permission
.valid_permission(perm
):
57 raise PermissionNotValid(perm
)
60 self
.scopes_permissions
[scope
] = permissions
62 def del_scope_permissions(self
, scope
):
63 if scope
not in self
.scopes_permissions
:
64 raise ScopeNotInRole(scope
, self
.name
)
65 del self
.scopes_permissions
[scope
]
67 def reset_scope_permissions(self
):
68 self
.scopes_permissions
= {}
70 def authorize(self
, scope
, permissions
):
71 if scope
in self
.scopes_permissions
:
72 role_perms
= self
.scopes_permissions
[scope
]
73 for perm
in permissions
:
74 if perm
not in role_perms
:
82 'description': self
.description
,
83 'scopes_permissions': self
.scopes_permissions
87 def from_dict(cls
, r_dict
):
88 return Role(r_dict
['name'], r_dict
['description'],
89 r_dict
['scopes_permissions'])
92 # static pre-defined system roles
93 # this roles cannot be deleted nor updated
95 # admin role provides all permissions for all scopes
96 ADMIN_ROLE
= Role('administrator', 'Administrator', {
97 scope_name
: Permission
.all_permissions()
98 for scope_name
in Scope
.all_scopes()
102 # read-only role provides read-only permission for all scopes
103 READ_ONLY_ROLE
= Role('read-only', 'Read-Only', {
104 scope_name
: [_P
.READ
] for scope_name
in Scope
.all_scopes()
105 if scope_name
!= Scope
.DASHBOARD_SETTINGS
109 # block manager role provides all permission for block related scopes
110 BLOCK_MGR_ROLE
= Role('block-manager', 'Block Manager', {
111 Scope
.RBD_IMAGE
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
112 Scope
.POOL
: [_P
.READ
],
113 Scope
.ISCSI
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
114 Scope
.RBD_MIRRORING
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
115 Scope
.GRAFANA
: [_P
.READ
],
119 # RadosGW manager role provides all permissions for block related scopes
120 RGW_MGR_ROLE
= Role('rgw-manager', 'RGW Manager', {
121 Scope
.RGW
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
122 Scope
.CONFIG_OPT
: [_P
.READ
],
123 Scope
.GRAFANA
: [_P
.READ
],
127 # Cluster manager role provides all permission for OSDs, Monitors, and
129 CLUSTER_MGR_ROLE
= Role('cluster-manager', 'Cluster Manager', {
130 Scope
.HOSTS
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
131 Scope
.OSD
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
132 Scope
.MONITOR
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
133 Scope
.MANAGER
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
134 Scope
.CONFIG_OPT
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
135 Scope
.LOG
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
136 Scope
.GRAFANA
: [_P
.READ
],
140 # Pool manager role provides all permissions for pool related scopes
141 POOL_MGR_ROLE
= Role('pool-manager', 'Pool Manager', {
142 Scope
.POOL
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
143 Scope
.CONFIG_OPT
: [_P
.READ
],
144 Scope
.GRAFANA
: [_P
.READ
],
147 # Pool manager role provides all permissions for CephFS related scopes
148 CEPHFS_MGR_ROLE
= Role('cephfs-manager', 'CephFS Manager', {
149 Scope
.CEPHFS
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
150 Scope
.CONFIG_OPT
: [_P
.READ
],
151 Scope
.GRAFANA
: [_P
.READ
],
154 GANESHA_MGR_ROLE
= Role('ganesha-manager', 'NFS Ganesha Manager', {
155 Scope
.NFS_GANESHA
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
156 Scope
.CEPHFS
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
157 Scope
.RGW
: [_P
.READ
, _P
.CREATE
, _P
.UPDATE
, _P
.DELETE
],
158 Scope
.CONFIG_OPT
: [_P
.READ
],
159 Scope
.GRAFANA
: [_P
.READ
],
164 ADMIN_ROLE
.name
: ADMIN_ROLE
,
165 READ_ONLY_ROLE
.name
: READ_ONLY_ROLE
,
166 BLOCK_MGR_ROLE
.name
: BLOCK_MGR_ROLE
,
167 RGW_MGR_ROLE
.name
: RGW_MGR_ROLE
,
168 CLUSTER_MGR_ROLE
.name
: CLUSTER_MGR_ROLE
,
169 POOL_MGR_ROLE
.name
: POOL_MGR_ROLE
,
170 CEPHFS_MGR_ROLE
.name
: CEPHFS_MGR_ROLE
,
171 GANESHA_MGR_ROLE
.name
: GANESHA_MGR_ROLE
,
176 def __init__(self
, username
, password
, name
=None, email
=None, roles
=None,
178 self
.username
= username
179 self
.password
= password
186 if lastUpdate
is None:
187 self
.refreshLastUpdate()
189 self
.lastUpdate
= lastUpdate
191 def refreshLastUpdate(self
):
192 self
.lastUpdate
= int(time
.time())
194 def set_password(self
, password
):
195 self
.password
= password_hash(password
)
196 self
.refreshLastUpdate()
198 def set_roles(self
, roles
):
199 self
.roles
= set(roles
)
200 self
.refreshLastUpdate()
202 def add_roles(self
, roles
):
203 self
.roles
= self
.roles
.union(set(roles
))
204 self
.refreshLastUpdate()
206 def del_roles(self
, roles
):
208 if role
not in self
.roles
:
209 raise RoleNotInUser(role
.name
, self
.username
)
210 self
.roles
.difference_update(set(roles
))
211 self
.refreshLastUpdate()
213 def authorize(self
, scope
, permissions
):
214 for role
in self
.roles
:
215 if role
.authorize(scope
, permissions
):
219 def permissions_dict(self
):
221 for role
in self
.roles
:
222 for scope
, perms_list
in role
.scopes_permissions
.items():
224 perms_tmp
= set(perms
[scope
]).union(set(perms_list
))
225 perms
[scope
] = list(perms_tmp
)
227 perms
[scope
] = perms_list
233 'username': self
.username
,
234 'password': self
.password
,
235 'roles': sorted([r
.name
for r
in self
.roles
]),
238 'lastUpdate': self
.lastUpdate
242 def from_dict(cls
, u_dict
, roles
):
243 return User(u_dict
['username'], u_dict
['password'], u_dict
['name'],
244 u_dict
['email'], {roles
[r
] for r
in u_dict
['roles']},
245 u_dict
['lastUpdate'])
248 class AccessControlDB(object):
250 ACDB_CONFIG_KEY
= "accessdb_v"
252 def __init__(self
, version
, users
, roles
):
254 self
.version
= version
256 self
.lock
= threading
.RLock()
258 def create_role(self
, name
, description
=None):
260 if name
in SYSTEM_ROLES
or name
in self
.roles
:
261 raise RoleAlreadyExists(name
)
262 role
= Role(name
, description
)
263 self
.roles
[name
] = role
266 def get_role(self
, name
):
268 if name
not in self
.roles
:
269 raise RoleDoesNotExist(name
)
270 return self
.roles
[name
]
272 def delete_role(self
, name
):
274 if name
not in self
.roles
:
275 raise RoleDoesNotExist(name
)
276 role
= self
.roles
[name
]
278 # check if role is not associated with a user
279 for username
, user
in self
.users
.items():
280 if role
in user
.roles
:
281 raise RoleIsAssociatedWithUser(name
, username
)
285 def create_user(self
, username
, password
, name
, email
):
286 logger
.debug("AC: creating user: username=%s", username
)
288 if username
in self
.users
:
289 raise UserAlreadyExists(username
)
290 user
= User(username
, password_hash(password
), name
, email
)
291 self
.users
[username
] = user
294 def get_user(self
, username
):
296 if username
not in self
.users
:
297 raise UserDoesNotExist(username
)
298 return self
.users
[username
]
300 def delete_user(self
, username
):
302 if username
not in self
.users
:
303 raise UserDoesNotExist(username
)
304 del self
.users
[username
]
306 def update_users_with_roles(self
, role
):
310 for _
, user
in self
.users
.items():
311 if role
in user
.roles
:
312 user
.refreshLastUpdate()
317 'users': {un
: u
.to_dict() for un
, u
in self
.users
.items()},
318 'roles': {rn
: r
.to_dict() for rn
, r
in self
.roles
.items()},
319 'version': self
.version
321 mgr
.set_store(self
.accessdb_config_key(), json
.dumps(db
))
324 def accessdb_config_key(cls
, version
=None):
326 version
= cls
.VERSION
327 return "{}{}".format(cls
.ACDB_CONFIG_KEY
, version
)
329 def check_and_update_db(self
):
330 logger
.debug("AC: Checking for previews DB versions")
331 if self
.VERSION
== 1: # current version
332 # check if there is username/password from previous version
333 username
= mgr
.get_module_option('username', None)
334 password
= mgr
.get_module_option('password', None)
335 if username
and password
:
336 logger
.debug("AC: Found single user credentials: user=%s",
338 # found user credentials
339 user
= self
.create_user(username
, "", None, None)
340 # password is already hashed, so setting manually
341 user
.password
= password
342 user
.add_roles([ADMIN_ROLE
])
345 raise NotImplementedError()
349 logger
.info("AC: Loading user roles DB version=%s", cls
.VERSION
)
351 json_db
= mgr
.get_store(cls
.accessdb_config_key())
353 logger
.debug("AC: No DB v%s found, creating new...", cls
.VERSION
)
354 db
= cls(cls
.VERSION
, {}, {})
355 # check if we can update from a previous version database
356 db
.check_and_update_db()
359 db
= json
.loads(json_db
)
360 roles
= {rn
: Role
.from_dict(r
)
361 for rn
, r
in db
.get('roles', {}).items()}
362 users
= {un
: User
.from_dict(u
, dict(roles
, **SYSTEM_ROLES
))
363 for un
, u
in db
.get('users', {}).items()}
364 return cls(db
['version'], users
, roles
)
367 def load_access_control_db():
368 mgr
.ACCESS_CTRL_DB
= AccessControlDB
.load()
371 # CLI dashboard access control scope commands
373 @CLIWriteCommand('dashboard set-login-credentials',
374 'name=username,type=CephString '
375 'name=password,type=CephString',
376 'Set the login credentials')
377 def set_login_credentials_cmd(_
, username
, password
):
379 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
380 user
.set_password(password
)
381 except UserDoesNotExist
:
382 user
= mgr
.ACCESS_CTRL_DB
.create_user(username
, password
, None, None)
383 user
.set_roles([ADMIN_ROLE
])
385 mgr
.ACCESS_CTRL_DB
.save()
388 ******************************************************************
389 *** WARNING: this command is deprecated. ***
390 *** Please use the ac-user-* related commands to manage users. ***
391 ******************************************************************
392 Username and password updated''', ''
395 @CLIReadCommand('dashboard ac-role-show',
396 'name=rolename,type=CephString,req=false',
398 def ac_role_show_cmd(_
, rolename
=None):
400 roles
= dict(mgr
.ACCESS_CTRL_DB
.roles
)
401 roles
.update(SYSTEM_ROLES
)
402 roles_list
= [name
for name
, _
in roles
.items()]
403 return 0, json
.dumps(roles_list
), ''
405 role
= mgr
.ACCESS_CTRL_DB
.get_role(rolename
)
406 except RoleDoesNotExist
as ex
:
407 if rolename
not in SYSTEM_ROLES
:
408 return -errno
.ENOENT
, '', str(ex
)
409 role
= SYSTEM_ROLES
[rolename
]
410 return 0, json
.dumps(role
.to_dict()), ''
413 @CLIWriteCommand('dashboard ac-role-create',
414 'name=rolename,type=CephString '
415 'name=description,type=CephString,req=false',
416 'Create a new access control role')
417 def ac_role_create_cmd(_
, rolename
, description
=None):
419 role
= mgr
.ACCESS_CTRL_DB
.create_role(rolename
, description
)
420 mgr
.ACCESS_CTRL_DB
.save()
421 return 0, json
.dumps(role
.to_dict()), ''
422 except RoleAlreadyExists
as ex
:
423 return -errno
.EEXIST
, '', str(ex
)
426 @CLIWriteCommand('dashboard ac-role-delete',
427 'name=rolename,type=CephString',
428 'Delete an access control role')
429 def ac_role_delete_cmd(_
, rolename
):
431 mgr
.ACCESS_CTRL_DB
.delete_role(rolename
)
432 mgr
.ACCESS_CTRL_DB
.save()
433 return 0, "Role '{}' deleted".format(rolename
), ""
434 except RoleDoesNotExist
as ex
:
435 if rolename
in SYSTEM_ROLES
:
436 return -errno
.EPERM
, '', "Cannot delete system role '{}'" \
438 return -errno
.ENOENT
, '', str(ex
)
439 except RoleIsAssociatedWithUser
as ex
:
440 return -errno
.EPERM
, '', str(ex
)
443 @CLIWriteCommand('dashboard ac-role-add-scope-perms',
444 'name=rolename,type=CephString '
445 'name=scopename,type=CephString '
446 'name=permissions,type=CephString,n=N',
447 'Add the scope permissions for a role')
448 def ac_role_add_scope_perms_cmd(_
, rolename
, scopename
, permissions
):
450 role
= mgr
.ACCESS_CTRL_DB
.get_role(rolename
)
451 perms_array
= [perm
.strip() for perm
in permissions
]
452 role
.set_scope_permissions(scopename
, perms_array
)
453 mgr
.ACCESS_CTRL_DB
.update_users_with_roles(role
)
454 mgr
.ACCESS_CTRL_DB
.save()
455 return 0, json
.dumps(role
.to_dict()), ''
456 except RoleDoesNotExist
as ex
:
457 if rolename
in SYSTEM_ROLES
:
458 return -errno
.EPERM
, '', "Cannot update system role '{}'" \
460 return -errno
.ENOENT
, '', str(ex
)
461 except ScopeNotValid
as ex
:
462 return -errno
.EINVAL
, '', str(ex
) + "\n Possible values: {}" \
463 .format(Scope
.all_scopes())
464 except PermissionNotValid
as ex
:
465 return -errno
.EINVAL
, '', str(ex
) + \
466 "\n Possible values: {}" \
467 .format(Permission
.all_permissions())
470 @CLIWriteCommand('dashboard ac-role-del-scope-perms',
471 'name=rolename,type=CephString '
472 'name=scopename,type=CephString',
473 'Delete the scope permissions for a role')
474 def ac_role_del_scope_perms_cmd(_
, rolename
, scopename
):
476 role
= mgr
.ACCESS_CTRL_DB
.get_role(rolename
)
477 role
.del_scope_permissions(scopename
)
478 mgr
.ACCESS_CTRL_DB
.update_users_with_roles(role
)
479 mgr
.ACCESS_CTRL_DB
.save()
480 return 0, json
.dumps(role
.to_dict()), ''
481 except RoleDoesNotExist
as ex
:
482 if rolename
in SYSTEM_ROLES
:
483 return -errno
.EPERM
, '', "Cannot update system role '{}'" \
485 return -errno
.ENOENT
, '', str(ex
)
486 except ScopeNotInRole
as ex
:
487 return -errno
.ENOENT
, '', str(ex
)
490 @CLIReadCommand('dashboard ac-user-show',
491 'name=username,type=CephString,req=false',
493 def ac_user_show_cmd(_
, username
=None):
495 users
= mgr
.ACCESS_CTRL_DB
.users
496 users_list
= [name
for name
, _
in users
.items()]
497 return 0, json
.dumps(users_list
), ''
499 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
500 return 0, json
.dumps(user
.to_dict()), ''
501 except UserDoesNotExist
as ex
:
502 return -errno
.ENOENT
, '', str(ex
)
505 @CLIWriteCommand('dashboard ac-user-create',
506 'name=username,type=CephString '
507 'name=password,type=CephString,req=false '
508 'name=rolename,type=CephString,req=false '
509 'name=name,type=CephString,req=false '
510 'name=email,type=CephString,req=false',
512 def ac_user_create_cmd(_
, username
, password
=None, rolename
=None, name
=None,
515 role
= mgr
.ACCESS_CTRL_DB
.get_role(rolename
) if rolename
else None
516 except RoleDoesNotExist
as ex
:
517 if rolename
not in SYSTEM_ROLES
:
518 return -errno
.ENOENT
, '', str(ex
)
519 role
= SYSTEM_ROLES
[rolename
]
522 user
= mgr
.ACCESS_CTRL_DB
.create_user(username
, password
, name
, email
)
523 except UserAlreadyExists
as ex
:
524 return -errno
.EEXIST
, '', str(ex
)
527 user
.set_roles([role
])
528 mgr
.ACCESS_CTRL_DB
.save()
529 return 0, json
.dumps(user
.to_dict()), ''
532 @CLIWriteCommand('dashboard ac-user-delete',
533 'name=username,type=CephString',
535 def ac_user_delete_cmd(_
, username
):
537 mgr
.ACCESS_CTRL_DB
.delete_user(username
)
538 mgr
.ACCESS_CTRL_DB
.save()
539 return 0, "User '{}' deleted".format(username
), ""
540 except UserDoesNotExist
as ex
:
541 return -errno
.ENOENT
, '', str(ex
)
544 @CLIWriteCommand('dashboard ac-user-set-roles',
545 'name=username,type=CephString '
546 'name=roles,type=CephString,n=N',
548 def ac_user_set_roles_cmd(_
, username
, roles
):
551 for rolename
in rolesname
:
553 roles
.append(mgr
.ACCESS_CTRL_DB
.get_role(rolename
))
554 except RoleDoesNotExist
as ex
:
555 if rolename
not in SYSTEM_ROLES
:
556 return -errno
.ENOENT
, '', str(ex
)
557 roles
.append(SYSTEM_ROLES
[rolename
])
559 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
560 user
.set_roles(roles
)
561 mgr
.ACCESS_CTRL_DB
.save()
562 return 0, json
.dumps(user
.to_dict()), ''
563 except UserDoesNotExist
as ex
:
564 return -errno
.ENOENT
, '', str(ex
)
567 @CLIWriteCommand('dashboard ac-user-add-roles',
568 'name=username,type=CephString '
569 'name=roles,type=CephString,n=N',
571 def ac_user_add_roles_cmd(_
, username
, roles
):
574 for rolename
in rolesname
:
576 roles
.append(mgr
.ACCESS_CTRL_DB
.get_role(rolename
))
577 except RoleDoesNotExist
as ex
:
578 if rolename
not in SYSTEM_ROLES
:
579 return -errno
.ENOENT
, '', str(ex
)
580 roles
.append(SYSTEM_ROLES
[rolename
])
582 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
583 user
.add_roles(roles
)
584 mgr
.ACCESS_CTRL_DB
.save()
585 return 0, json
.dumps(user
.to_dict()), ''
586 except UserDoesNotExist
as ex
:
587 return -errno
.ENOENT
, '', str(ex
)
590 @CLIWriteCommand('dashboard ac-user-del-roles',
591 'name=username,type=CephString '
592 'name=roles,type=CephString,n=N',
593 'Delete roles from user')
594 def ac_user_del_roles_cmd(_
, username
, roles
):
597 for rolename
in rolesname
:
599 roles
.append(mgr
.ACCESS_CTRL_DB
.get_role(rolename
))
600 except RoleDoesNotExist
as ex
:
601 if rolename
not in SYSTEM_ROLES
:
602 return -errno
.ENOENT
, '', str(ex
)
603 roles
.append(SYSTEM_ROLES
[rolename
])
605 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
606 user
.del_roles(roles
)
607 mgr
.ACCESS_CTRL_DB
.save()
608 return 0, json
.dumps(user
.to_dict()), ''
609 except UserDoesNotExist
as ex
:
610 return -errno
.ENOENT
, '', str(ex
)
611 except RoleNotInUser
as ex
:
612 return -errno
.ENOENT
, '', str(ex
)
615 @CLIWriteCommand('dashboard ac-user-set-password',
616 'name=username,type=CephString '
617 'name=password,type=CephString',
619 def ac_user_set_password(_
, username
, password
):
621 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
622 user
.set_password(password
)
624 mgr
.ACCESS_CTRL_DB
.save()
625 return 0, json
.dumps(user
.to_dict()), ''
626 except UserDoesNotExist
as ex
:
627 return -errno
.ENOENT
, '', str(ex
)
630 @CLIWriteCommand('dashboard ac-user-set-info',
631 'name=username,type=CephString '
632 'name=name,type=CephString '
633 'name=email,type=CephString',
635 def ac_user_set_info(_
, username
, name
, email
):
637 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
642 mgr
.ACCESS_CTRL_DB
.save()
643 return 0, json
.dumps(user
.to_dict()), ''
644 except UserDoesNotExist
as ex
:
645 return -errno
.ENOENT
, '', str(ex
)
648 class LocalAuthenticator(object):
650 load_access_control_db()
652 def get_user(self
, username
):
653 return mgr
.ACCESS_CTRL_DB
.get_user(username
)
655 def authenticate(self
, username
, password
):
657 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
659 pass_hash
= password_hash(password
, user
.password
)
660 if pass_hash
== user
.password
:
661 return user
.permissions_dict()
662 except UserDoesNotExist
:
663 logger
.debug("User '%s' does not exist", username
)
666 def authorize(self
, username
, scope
, permissions
):
667 user
= mgr
.ACCESS_CTRL_DB
.get_user(username
)
668 return user
.authorize(scope
, permissions
)