Patch to allow rbac
Conflicts: nova/auth/users.py
This commit is contained in:
		
							
								
								
									
										35
									
								
								nova/auth/rbac.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								nova/auth/rbac.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | from nova import exception | ||||||
|  | from nova.auth import users | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def allow(*roles): | ||||||
|  |     def wrap(f): | ||||||
|  |         def wrapped_f(self, context, *args, **kwargs): | ||||||
|  |             if context.user.is_superuser(): | ||||||
|  |                 return f(self, context, *args, **kwargs) | ||||||
|  |             for role in roles: | ||||||
|  |                 if __matches_role(context, role): | ||||||
|  |                     return f(self, context, *args, **kwargs) | ||||||
|  |             raise exception.NotAuthorized() | ||||||
|  |         return wrapped_f | ||||||
|  |     return wrap | ||||||
|  |  | ||||||
|  | def deny(*roles): | ||||||
|  |     def wrap(f): | ||||||
|  |         def wrapped_f(self, context, *args, **kwargs): | ||||||
|  |             if context.user.is_superuser(): | ||||||
|  |                 return f(self, context, *args, **kwargs) | ||||||
|  |             for role in roles: | ||||||
|  |                 if __matches_role(context, role): | ||||||
|  |                     raise exception.NotAuthorized() | ||||||
|  |             return f(self, context, *args, **kwargs) | ||||||
|  |         return wrapped_f | ||||||
|  |     return wrap | ||||||
|  |  | ||||||
|  | def __matches_role(context, role): | ||||||
|  |     if role == 'all': | ||||||
|  |         return True | ||||||
|  |     if role == 'none': | ||||||
|  |         return False | ||||||
|  |     return context.project.has_role(context.user.id, role) | ||||||
|  |  | ||||||
| @@ -53,6 +53,17 @@ flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user | |||||||
| flags.DEFINE_string('user_unit', 'Users', 'OID for Users') | flags.DEFINE_string('user_unit', 'Users', 'OID for Users') | ||||||
| flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users') | flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users') | ||||||
| flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Projects') | flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Projects') | ||||||
|  | flags.DEFINE_string('role_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Roles') | ||||||
|  |  | ||||||
|  | # mapping with these flags is necessary because we're going to tie in to an existing ldap schema | ||||||
|  | flags.DEFINE_string('ldap_cloudadmin', | ||||||
|  |     'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'cn for Cloud Admins') | ||||||
|  |  | ||||||
|  | # a user with one of these roles will be a superuser and have access to all api commands | ||||||
|  | flags.DEFINE_list('superuser_roles', ['cloudadmin'], 'roles that ignore rbac checking completely') | ||||||
|  |  | ||||||
|  | # a user with one of these roles will have it for every project, even if he or she is not a member of the project | ||||||
|  | flags.DEFINE_list('global_roles', ['cloudadmin'], 'roles that apply to all projects') | ||||||
|  |  | ||||||
| flags.DEFINE_string('credentials_template', | flags.DEFINE_string('credentials_template', | ||||||
|                     utils.abspath('auth/novarc.template'), |                     utils.abspath('auth/novarc.template'), | ||||||
| @@ -68,6 +79,7 @@ flags.DEFINE_string('credential_rc_file', 'novarc', | |||||||
|                     'Filename of rc in credentials zip') |                     'Filename of rc in credentials zip') | ||||||
| flags.DEFINE_string('vpn_ip', '127.0.0.1', 'Public IP for the cloudpipe VPN servers') | flags.DEFINE_string('vpn_ip', '127.0.0.1', 'Public IP for the cloudpipe VPN servers') | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthBase(object): | class AuthBase(object): | ||||||
|     @classmethod |     @classmethod | ||||||
|     def safe_id(cls, obj): |     def safe_id(cls, obj): | ||||||
| @@ -105,9 +117,30 @@ class User(AuthBase): | |||||||
|     def vpn_ip(self): |     def vpn_ip(self): | ||||||
|         return FLAGS.vpn_ip |         return FLAGS.vpn_ip | ||||||
|  |  | ||||||
|  |     def is_superuser(self): | ||||||
|  |         """allows user to bypass rbac completely""" | ||||||
|  |         if self.admin: | ||||||
|  |             return True | ||||||
|  |         for role in FLAGS.superuser_roles: | ||||||
|  |             if self.has_role(role): | ||||||
|  |                 return True | ||||||
|  |  | ||||||
|     def is_admin(self): |     def is_admin(self): | ||||||
|         """allows user to see objects from all projects""" |         """allows user to see objects from all projects""" | ||||||
|         return self.admin |         if self.is_superuser(): | ||||||
|  |             return True | ||||||
|  |         for role in FLAGS.global_roles: | ||||||
|  |             if self.has_role(role): | ||||||
|  |                 return True | ||||||
|  |  | ||||||
|  |     def has_role(self, role): | ||||||
|  |         return UserManager.instance().has_role(self, role) | ||||||
|  |  | ||||||
|  |     def add_role(self, role): | ||||||
|  |         return UserManager.instance().add_role(self, role) | ||||||
|  |  | ||||||
|  |     def remove_role(self, role): | ||||||
|  |         return UserManager.instance().remove_role(self, role) | ||||||
|  |  | ||||||
|     def is_project_member(self, project): |     def is_project_member(self, project): | ||||||
|         return UserManager.instance().is_project_member(self, project) |         return UserManager.instance().is_project_member(self, project) | ||||||
| @@ -192,18 +225,54 @@ class Project(Group): | |||||||
|     def has_manager(self, user): |     def has_manager(self, user): | ||||||
|         return User.safe_id(user) == self.project_manager_id |         return User.safe_id(user) == self.project_manager_id | ||||||
|  |  | ||||||
|  |     def add_role(self, user, role): | ||||||
|  |         return UserManager.instance().add_role(user, role, self) | ||||||
|  |  | ||||||
|  |     def remove_role(self, user, role): | ||||||
|  |         return UserManager.instance().remove_role(user, role, self) | ||||||
|  |  | ||||||
|  |     def has_role(self, user, role): | ||||||
|  |         return UserManager.instance().has_role(user, role, self) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def vpn_port(self): | ||||||
|  |         port_map = self.keeper['vpn_ports'] | ||||||
|  |         if not port_map: port_map = {} | ||||||
|  |         if not port_map.has_key(self.id): | ||||||
|  |             ports = port_map.values() | ||||||
|  |             if len(ports) > 0: | ||||||
|  |                 port_map[self.id] = max(ports) + 1 | ||||||
|  |             else: | ||||||
|  |                 port_map[self.id] = 8000 | ||||||
|  |         self.keeper['vpn_ports'] = port_map | ||||||
|  |         return self.keeper['vpn_ports'][self.id] | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def vpn_ip(self): | ||||||
|  |         return FLAGS.vpn_ip | ||||||
|  |  | ||||||
|     def get_credentials(self, user): |     def get_credentials(self, user): | ||||||
|         if not isinstance(user, User): |         if not isinstance(user, User): | ||||||
|             user = UserManager.instance().get_user(user) |             user = UserManager.instance().get_user(user) | ||||||
|         rc = user.generate_rc(self.id) |         rc = user.generate_rc(self.id) | ||||||
|         private_key, signed_cert = self.generate_x509_cert(user) |         private_key, signed_cert = self.generate_x509_cert(user) | ||||||
|  |  | ||||||
|  |         configfile = open(FLAGS.vpn_client_template,"r") | ||||||
|  |         s = string.Template(configfile.read()) | ||||||
|  |         configfile.close() | ||||||
|  |         config = s.substitute(keyfile=FLAGS.credential_key_file, | ||||||
|  |                               certfile=FLAGS.credential_cert_file, | ||||||
|  |                               ip=self.vpn_ip, | ||||||
|  |                               port=self.vpn_port) | ||||||
|  |  | ||||||
|         tmpdir = tempfile.mkdtemp() |         tmpdir = tempfile.mkdtemp() | ||||||
|         zf = os.path.join(tmpdir, "temp.zip") |         zf = os.path.join(tmpdir, "temp.zip") | ||||||
|         zippy = zipfile.ZipFile(zf, 'w') |         zippy = zipfile.ZipFile(zf, 'w') | ||||||
|         zippy.writestr(FLAGS.credential_rc_file, rc) |         zippy.writestr(FLAGS.credential_rc_file, rc) | ||||||
|         zippy.writestr(FLAGS.credential_key_file, private_key) |         zippy.writestr(FLAGS.credential_key_file, private_key) | ||||||
|         zippy.writestr(FLAGS.credential_cert_file, signed_cert) |         zippy.writestr(FLAGS.credential_cert_file, signed_cert) | ||||||
|  |         zippy.writestr("nebula-client.conf", config) | ||||||
|         zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id)) |         zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id)) | ||||||
|         zippy.close() |         zippy.close() | ||||||
|         with open(zf, 'rb') as f: |         with open(zf, 'rb') as f: | ||||||
| @@ -265,6 +334,32 @@ class UserManager(object): | |||||||
|                 raise exception.NotAuthorized('Signature does not match') |                 raise exception.NotAuthorized('Signature does not match') | ||||||
|         return (user, project) |         return (user, project) | ||||||
|  |  | ||||||
|  |     def has_role(self, user, role, project=None): | ||||||
|  |         with LDAPWrapper() as conn: | ||||||
|  |             if project and role == 'projectmanager': | ||||||
|  |                 return self.is_project_manager(user, project) | ||||||
|  |  | ||||||
|  |             global_role = conn.has_role(User.safe_id(user), | ||||||
|  |                                         role, | ||||||
|  |                                         None) | ||||||
|  |             if not global_role: | ||||||
|  |                 return global_role | ||||||
|  |  | ||||||
|  |             if not project or role in FLAGS.global_roles: | ||||||
|  |                 return global_role | ||||||
|  |  | ||||||
|  |             return conn.has_role(User.safe_id(user), | ||||||
|  |                                  role, | ||||||
|  |                                  Project.safe_id(project)) | ||||||
|  |  | ||||||
|  |     def add_role(self, user, role, project=None): | ||||||
|  |         with LDAPWrapper() as conn: | ||||||
|  |             return conn.add_role(User.safe_id(user), role, Project.safe_id(project)) | ||||||
|  |  | ||||||
|  |     def remove_role(self, user, role, project=None): | ||||||
|  |         with LDAPWrapper() as conn: | ||||||
|  |             return conn.remove_role(User.safe_id(user), role, Project.safe_id(project)) | ||||||
|  |  | ||||||
|     def create_project(self, name, manager_user, description=None, member_users=None): |     def create_project(self, name, manager_user, description=None, member_users=None): | ||||||
|         if member_users: |         if member_users: | ||||||
|             member_users = [User.safe_id(u) for u in member_users] |             member_users = [User.safe_id(u) for u in member_users] | ||||||
| @@ -428,6 +523,10 @@ class LDAPWrapper(object): | |||||||
|         attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)') |         attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)') | ||||||
|         return [self.__to_project(attr) for attr in attrs] |         return [self.__to_project(attr) for attr in attrs] | ||||||
|  |  | ||||||
|  |     def find_roles(self, tree): | ||||||
|  |         attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(!(objectclass=NovaProject)))') | ||||||
|  |         return [self.__to_group(attr) for attr in attrs] | ||||||
|  |  | ||||||
|     def find_groups_with_member(self, tree, dn): |     def find_groups_with_member(self, tree, dn): | ||||||
|         attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(member=%s))' % dn ) |         attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(member=%s))' % dn ) | ||||||
|         return [self.__to_group(attr) for attr in attrs] |         return [self.__to_group(attr) for attr in attrs] | ||||||
| @@ -530,6 +629,12 @@ class LDAPWrapper(object): | |||||||
|         dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree) |         dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree) | ||||||
|         return self.is_in_group(uid, dn) |         return self.is_in_group(uid, dn) | ||||||
|  |  | ||||||
|  |     def __role_to_dn(self, role, project_id=None): | ||||||
|  |         if project_id == None: | ||||||
|  |             return FLAGS.__getitem__("ldap_%s" % role).value | ||||||
|  |         else: | ||||||
|  |             return 'cn=%s,cn=%s,%s' % (role, project_id, FLAGS.project_ldap_subtree) | ||||||
|  |  | ||||||
|     def __create_group(self, group_dn, name, uid, description, member_uids = None): |     def __create_group(self, group_dn, name, uid, description, member_uids = None): | ||||||
|         if self.group_exists(name): |         if self.group_exists(name): | ||||||
|             raise exception.Duplicate("Group can't be created because group %s already exists" % name) |             raise exception.Duplicate("Group can't be created because group %s already exists" % name) | ||||||
| @@ -551,6 +656,27 @@ class LDAPWrapper(object): | |||||||
|         self.conn.add_s(group_dn, attr) |         self.conn.add_s(group_dn, attr) | ||||||
|         return self.__to_group(dict(attr)) |         return self.__to_group(dict(attr)) | ||||||
|  |  | ||||||
|  |     def has_role(self, uid, role, project_id=None): | ||||||
|  |         role_dn = self.__role_to_dn(role, project_id) | ||||||
|  |         return self.is_in_group(uid, role_dn) | ||||||
|  |  | ||||||
|  |     def add_role(self, uid, role, project_id=None): | ||||||
|  |         role_dn = self.__role_to_dn(role, project_id) | ||||||
|  |         if not self.group_exists(role_dn): | ||||||
|  |             # create the role if it doesn't exist | ||||||
|  |             description = '%s role for %s' % (role, project_id) | ||||||
|  |             self.__create_group(role_dn, role, uid, description) | ||||||
|  |         else: | ||||||
|  |             return self.add_to_group(uid, role_dn) | ||||||
|  |  | ||||||
|  |     def remove_role(self, uid, role, project_id=None): | ||||||
|  |         role_dn = self.__role_to_dn(role, project_id) | ||||||
|  |         try: | ||||||
|  |             return self.remove_from_group(uid, role_dn) | ||||||
|  |         except Exception, ex: | ||||||
|  |             print type(ex), ex | ||||||
|  |  | ||||||
|  |  | ||||||
|     def is_in_group(self, uid, group_dn): |     def is_in_group(self, uid, group_dn): | ||||||
|         if not self.user_exists(uid): |         if not self.user_exists(uid): | ||||||
|             raise exception.NotFound("User %s can't be searched in group becuase the user doesn't exist" % (uid,)) |             raise exception.NotFound("User %s can't be searched in group becuase the user doesn't exist" % (uid,)) | ||||||
| @@ -596,6 +722,9 @@ class LDAPWrapper(object): | |||||||
|         attr = [ |         attr = [ | ||||||
|             (ldap.MOD_DELETE, 'member', dn) |             (ldap.MOD_DELETE, 'member', dn) | ||||||
|         ] |         ] | ||||||
|  |         roles = self.find_groups_with_member(FLAGS.role_ldap_subtree, dn) | ||||||
|  |         for role in roles: | ||||||
|  |             self.conn.modify_s('cn=%s,%s' % (role.id, FLAGS.role_ldap_subtree), attr) | ||||||
|         projects = self.find_groups_with_member(FLAGS.project_ldap_subtree, dn) |         projects = self.find_groups_with_member(FLAGS.project_ldap_subtree, dn) | ||||||
|         for project in projects: |         for project in projects: | ||||||
|             self.conn.modify_s('cn=%s,%s' % (project.id, FLAGS.project_ldap_subtree), attr) |             self.conn.modify_s('cn=%s,%s' % (project.id, FLAGS.project_ldap_subtree), attr) | ||||||
| @@ -640,8 +769,15 @@ class LDAPWrapper(object): | |||||||
|             raise exception.NotFound("Group at dn %s doesn't exist" % group_dn) |             raise exception.NotFound("Group at dn %s doesn't exist" % group_dn) | ||||||
|         self.conn.delete_s(group_dn) |         self.conn.delete_s(group_dn) | ||||||
|  |  | ||||||
|  |     def delete_roles(self, project_dn): | ||||||
|  |         roles = self.find_roles(project_dn) | ||||||
|  |         if roles != None: | ||||||
|  |             for role in roles: | ||||||
|  |                 self.delete_group('cn=%s,%s' % (role.id, project_dn)) | ||||||
|  |  | ||||||
|     def delete_project(self, name): |     def delete_project(self, name): | ||||||
|         project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree) |         project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree) | ||||||
|  |         self.delete_roles(project_dn) | ||||||
|         self.delete_group(project_dn) |         self.delete_group(project_dn) | ||||||
|  |  | ||||||
|     def __to_user(self, attr): |     def __to_user(self, attr): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 andy
					andy