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_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('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',
|
||||
utils.abspath('auth/novarc.template'),
|
||||
@@ -68,6 +79,7 @@ flags.DEFINE_string('credential_rc_file', 'novarc',
|
||||
'Filename of rc in credentials zip')
|
||||
flags.DEFINE_string('vpn_ip', '127.0.0.1', 'Public IP for the cloudpipe VPN servers')
|
||||
|
||||
|
||||
class AuthBase(object):
|
||||
@classmethod
|
||||
def safe_id(cls, obj):
|
||||
@@ -105,9 +117,30 @@ class User(AuthBase):
|
||||
def vpn_ip(self):
|
||||
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):
|
||||
"""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):
|
||||
return UserManager.instance().is_project_member(self, project)
|
||||
@@ -192,18 +225,54 @@ class Project(Group):
|
||||
def has_manager(self, user):
|
||||
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):
|
||||
if not isinstance(user, User):
|
||||
user = UserManager.instance().get_user(user)
|
||||
rc = user.generate_rc(self.id)
|
||||
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()
|
||||
zf = os.path.join(tmpdir, "temp.zip")
|
||||
zippy = zipfile.ZipFile(zf, 'w')
|
||||
zippy.writestr(FLAGS.credential_rc_file, rc)
|
||||
zippy.writestr(FLAGS.credential_key_file, private_key)
|
||||
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.close()
|
||||
with open(zf, 'rb') as f:
|
||||
@@ -265,6 +334,32 @@ class UserManager(object):
|
||||
raise exception.NotAuthorized('Signature does not match')
|
||||
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):
|
||||
if 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)')
|
||||
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):
|
||||
attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(member=%s))' % dn )
|
||||
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)
|
||||
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):
|
||||
if self.group_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)
|
||||
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):
|
||||
if not self.user_exists(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 = [
|
||||
(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)
|
||||
for project in projects:
|
||||
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)
|
||||
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):
|
||||
project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
|
||||
self.delete_roles(project_dn)
|
||||
self.delete_group(project_dn)
|
||||
|
||||
def __to_user(self, attr):
|
||||
|
||||
Reference in New Issue
Block a user