diff options
author | Vishvananda Ishaya <vishvananda@gmail.com> | 2010-07-19 13:19:26 -0500 |
---|---|---|
committer | Vishvananda Ishaya <vishvananda@gmail.com> | 2010-07-19 13:19:26 -0500 |
commit | a3ca587654095ffd4b97103302fb0744e505e332 (patch) | |
tree | 5e7ef9e1e7fc7f35ca7c216ebfffea958c9d4d58 | |
parent | b09e69c5579526fcc1a08c7e3c3a3c880fa09297 (diff) | |
download | nova-a3ca587654095ffd4b97103302fb0744e505e332.tar.gz nova-a3ca587654095ffd4b97103302fb0744e505e332.tar.xz nova-a3ca587654095ffd4b97103302fb0744e505e332.zip |
Massive refactor of users.py
Split users.py into manager.py and ldpadriver.py
Added tons of docstrings
Cleaned up public methods
Simplified manager singleton handling
-rwxr-xr-x | bin/nova-api | 2 | ||||
-rwxr-xr-x | bin/nova-manage | 10 | ||||
-rwxr-xr-x | bin/nova-objectstore | 6 | ||||
-rwxr-xr-x | bin/nova-rsapi | 12 | ||||
-rw-r--r-- | nova/auth/ldapdriver.py | 428 | ||||
-rw-r--r-- | nova/auth/manager.py | 741 | ||||
-rw-r--r-- | nova/auth/rbac.py | 2 | ||||
-rw-r--r-- | nova/auth/users.py | 974 | ||||
-rw-r--r-- | nova/cloudpipe/api.py | 2 | ||||
-rw-r--r-- | nova/cloudpipe/pipelib.py | 4 | ||||
-rw-r--r-- | nova/compute/network.py | 14 | ||||
-rw-r--r-- | nova/endpoint/admin.py | 14 | ||||
-rwxr-xr-x | nova/endpoint/api.py | 4 | ||||
-rw-r--r-- | nova/endpoint/cloud.py | 6 | ||||
-rw-r--r-- | nova/endpoint/rackspace.py | 6 | ||||
-rw-r--r-- | nova/tests/access_unittest.py | 6 | ||||
-rw-r--r-- | nova/tests/api_unittest.py | 4 | ||||
-rw-r--r-- | nova/tests/auth_unittest.py (renamed from nova/tests/users_unittest.py) | 8 | ||||
-rw-r--r-- | nova/tests/cloud_unittest.py | 12 | ||||
-rw-r--r-- | nova/tests/network_unittest.py | 4 | ||||
-rw-r--r-- | nova/tests/objectstore_unittest.py | 6 | ||||
-rw-r--r-- | run_tests.py | 2 |
22 files changed, 1231 insertions, 1036 deletions
diff --git a/bin/nova-api b/bin/nova-api index 26f5dbc87..1f2009c30 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -29,7 +29,7 @@ from nova import flags from nova import rpc from nova import server from nova import utils -from nova.auth import users +from nova.auth import manager from nova.compute import model from nova.endpoint import admin from nova.endpoint import api diff --git a/bin/nova-manage b/bin/nova-manage index 56f89ce30..b0f0029ed 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -27,7 +27,7 @@ import time from nova import flags from nova import utils -from nova.auth import users +from nova.auth import manager from nova.compute import model from nova.compute import network from nova.cloudpipe import pipelib @@ -42,7 +42,7 @@ class NetworkCommands(object): class VpnCommands(object): def __init__(self): - self.manager = users.UserManager.instance() + self.manager = manager.AuthManager() self.instdir = model.InstanceDirectory() self.pipe = pipelib.CloudPipe(cloud.CloudController()) @@ -90,7 +90,7 @@ class VpnCommands(object): class RoleCommands(object): def __init__(self): - self.manager = users.UserManager.instance() + self.manager = manager.AuthManager() def add(self, user, role, project=None): """adds role to user @@ -113,7 +113,7 @@ class RoleCommands(object): class UserCommands(object): def __init__(self): - self.manager = users.UserManager.instance() + self.manager = manager.AuthManager() def __print_export(self, user): print 'export EC2_ACCESS_KEY=%s' % user.access @@ -153,7 +153,7 @@ class UserCommands(object): class ProjectCommands(object): def __init__(self): - self.manager = users.UserManager.instance() + self.manager = manager.AuthManager() def add(self, project, user): """adds user to project diff --git a/bin/nova-objectstore b/bin/nova-objectstore index 521f3d5d1..837eb2e0c 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -18,7 +18,7 @@ # under the License. """ - Tornado daemon for nova objectstore. Supports S3 API. + Tornado daemon for nova objectstore. Supports S3 API. """ import logging @@ -28,7 +28,7 @@ from tornado import ioloop from nova import flags from nova import server from nova import utils -from nova.auth import users +from nova.auth import manager from nova.objectstore import handler @@ -39,7 +39,7 @@ def main(argv): # FIXME: if this log statement isn't here, no logging # appears from other files and app won't start daemonized logging.debug('Started HTTP server on %s' % (FLAGS.s3_internal_port)) - app = handler.Application(users.UserManager()) + app = handler.Application(manager.AuthManager()) server = httpserver.HTTPServer(app) server.listen(FLAGS.s3_internal_port) ioloop.IOLoop.instance().start() diff --git a/bin/nova-rsapi b/bin/nova-rsapi index 5cbe2d8c1..306a1fc60 100755 --- a/bin/nova-rsapi +++ b/bin/nova-rsapi @@ -4,20 +4,20 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ - WSGI daemon for the main API endpoint. + WSGI daemon for the main API endpoint. """ import logging @@ -28,14 +28,14 @@ from nova import flags from nova import rpc from nova import server from nova import utils -from nova.auth import users +from nova.auth import manager from nova.endpoint import rackspace FLAGS = flags.FLAGS flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') def main(_argv): - user_manager = users.UserManager() + user_manager = manager.AuthManager() api_instance = rackspace.Api(user_manager) conn = rpc.Connection.instance() rpc_consumer = rpc.AdapterConsumer(connection=conn, diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py new file mode 100644 index 000000000..49443c99a --- /dev/null +++ b/nova/auth/ldapdriver.py @@ -0,0 +1,428 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Auth driver for ldap + +It should be easy to create a replacement for this driver supporting +other backends by creating another class that exposes the same +public methods. +""" + +import logging + +from nova import exception +from nova import flags +from nova.auth import manager + +try: + import ldap +except Exception, e: + from nova.auth import fakeldap as ldap +# NOTE(vish): this import is so we can use fakeldap even when real ldap +# is installed. +from nova.auth import fakeldap + +FLAGS = flags.FLAGS +flags.DEFINE_string('ldap_url', 'ldap://localhost', + 'Point this at your ldap server') +flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') +flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com', + 'DN of admin user') +flags.DEFINE_string('ldap_user_unit', 'Users', 'OID for Users') +flags.DEFINE_string('ldap_user_subtree', 'ou=Users,dc=example,dc=com', + 'OU for Users') +flags.DEFINE_string('ldap_project_subtree', 'ou=Groups,dc=example,dc=com', + 'OU for Projects') +flags.DEFINE_string('role_project_subtree', 'ou=Groups,dc=example,dc=com', + 'OU for Roles') + +# NOTE(vish): 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') +flags.DEFINE_string('ldap_itsec', + 'cn=itsec,ou=Groups,dc=example,dc=com', 'cn for ItSec') +flags.DEFINE_string('ldap_sysadmin', + 'cn=sysadmins,ou=Groups,dc=example,dc=com', 'cn for Sysadmins') +flags.DEFINE_string('ldap_netadmin', + 'cn=netadmins,ou=Groups,dc=example,dc=com', 'cn for NetAdmins') +flags.DEFINE_string('ldap_developer', + 'cn=developers,ou=Groups,dc=example,dc=com', 'cn for Developers') + + +class LdapDriver(object): + def __enter__(self): + """Creates the connection to LDAP""" + if FLAGS.fake_users: + self.NO_SUCH_OBJECT = fakeldap.NO_SUCH_OBJECT + self.OBJECT_CLASS_VIOLATION = fakeldap.OBJECT_CLASS_VIOLATION + self.conn = fakeldap.initialize(FLAGS.ldap_url) + else: + self.NO_SUCH_OBJECT = ldap.NO_SUCH_OBJECT + self.OBJECT_CLASS_VIOLATION = ldap.OBJECT_CLASS_VIOLATION + self.conn = ldap.initialize(FLAGS.ldap_url) + self.conn.simple_bind_s(FLAGS.ldap_user_dn, FLAGS.ldap_password) + return self + + def __exit__(self, type, value, traceback): + """Destroys the connection to LDAP""" + self.conn.unbind_s() + return False + + def get_user(self, uid): + attr = self.__find_object(self.__uid_to_dn(uid), + '(objectclass=novaUser)') + return self.__to_user(attr) + + def get_user_from_access_key(self, access): + query = '(accessKey=%s)' % access + dn = FLAGS.ldap_user_subtree + return self.__to_user(self.__find_object(dn, query)) + + def get_key_pair(self, uid, key_name): + dn = 'cn=%s,%s' % (key_name, + self.__uid_to_dn(uid)) + attr = self.__find_object(dn, '(objectclass=novaKeyPair)') + return self.__to_key_pair(uid, attr) + + def get_project(self, name): + dn = 'cn=%s,%s' % (name, + FLAGS.ldap_project_subtree) + attr = self.__find_object(dn, '(objectclass=novaProject)') + return self.__to_project(attr) + + def get_users(self): + attrs = self.__find_objects(FLAGS.ldap_user_subtree, + '(objectclass=novaUser)') + return [self.__to_user(attr) for attr in attrs] + + def get_key_pairs(self, uid): + attrs = self.__find_objects(self.__uid_to_dn(uid), + '(objectclass=novaKeyPair)') + return [self.__to_key_pair(uid, attr) for attr in attrs] + + def get_projects(self): + attrs = self.__find_objects(FLAGS.ldap_project_subtree, + '(objectclass=novaProject)') + return [self.__to_project(attr) for attr in attrs] + + def create_user(self, name, access_key, secret_key, is_admin): + if self.__user_exists(name): + raise exception.Duplicate("LDAP user %s already exists" % name) + attr = [ + ('objectclass', ['person', + 'organizationalPerson', + 'inetOrgPerson', + 'novaUser']), + ('ou', [FLAGS.ldap_user_unit]), + ('uid', [name]), + ('sn', [name]), + ('cn', [name]), + ('secretKey', [secret_key]), + ('accessKey', [access_key]), + ('isAdmin', [str(is_admin).upper()]), + ] + self.conn.add_s(self.__uid_to_dn(name), attr) + return self.__to_user(dict(attr)) + + def create_key_pair(self, uid, key_name, public_key, fingerprint): + """create's a public key in the directory underneath the user""" + # TODO(vish): possibly refactor this to store keys in their own ou + # and put dn reference in the user object + attr = [ + ('objectclass', ['novaKeyPair']), + ('cn', [key_name]), + ('sshPublicKey', [public_key]), + ('keyFingerprint', [fingerprint]), + ] + self.conn.add_s('cn=%s,%s' % (key_name, + self.__uid_to_dn(uid)), + attr) + return self.__to_key_pair(uid, dict(attr)) + + def create_project(self, name, manager_uid, + description=None, member_uids=None): + if self.__project_exists(name): + raise exception.Duplicate("Project can't be created because " + "project %s already exists" % name) + if not self.__user_exists(manager_uid): + raise exception.NotFound("Project can't be created because " + "manager %s doesn't exist" % manager_uid) + manager_dn = self.__uid_to_dn(manager_uid) + # description is a required attribute + if description is None: + description = name + members = [] + if member_uids != None: + for member_uid in member_uids: + if not self.__user_exists(member_uid): + raise exception.NotFound("Project can't be created " + "because user %s doesn't exist" % member_uid) + members.append(self.__uid_to_dn(member_uid)) + # always add the manager as a member because members is required + if not manager_dn in members: + members.append(manager_dn) + attr = [ + ('objectclass', ['novaProject']), + ('cn', [name]), + ('description', [description]), + ('projectManager', [manager_dn]), + ('member', members) + ] + self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) + return self.__to_project(dict(attr)) + + def add_to_project(self, uid, project_id): + dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + return self.__add_to_group(uid, dn) + + def remove_from_project(self, uid, project_id): + dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + return self.__remove_from_group(uid, dn) + + def is_in_project(self, uid, project_id): + dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + return self.__is_in_group(uid, dn) + + 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) + return self.__remove_from_group(uid, role_dn) + + def delete_user(self, uid): + if not self.__user_exists(uid): + raise exception.NotFound("User %s doesn't exist" % uid) + self.__delete_key_pairs(uid) + self.__remove_from_all(uid) + self.conn.delete_s('uid=%s,%s' % (uid, + FLAGS.ldap_user_subtree)) + + def delete_key_pair(self, uid, key_name): + if not self.__key_pair_exists(uid, key_name): + raise exception.NotFound("Key Pair %s doesn't exist for user %s" % + (key_name, uid)) + self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid, + FLAGS.ldap_user_subtree)) + + def delete_project(self, name): + project_dn = 'cn=%s,%s' % (name, FLAGS.ldap_project_subtree) + self.__delete_roles(project_dn) + self.__delete_group(project_dn) + + def __user_exists(self, name): + return self.get_user(name) != None + + def __key_pair_exists(self, uid, key_name): + return self.get_key_pair(uid, key_name) != None + + def __project_exists(self, name): + return self.get_project(name) != None + + def __find_object(self, dn, query = None): + objects = self.__find_objects(dn, query) + if len(objects) == 0: + return None + return objects[0] + + def __find_dns(self, dn, query=None): + try: + res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query) + except self.NO_SUCH_OBJECT: + return [] + # just return the DNs + return [dn for dn, attributes in res] + + def __find_objects(self, dn, query = None): + try: + res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query) + except self.NO_SUCH_OBJECT: + return [] + # just return the attributes + return [attributes for dn, attributes in res] + + def __find_role_dns(self, tree): + return self.__find_dns(tree, + '(&(objectclass=groupOfNames)(!(objectclass=novaProject)))') + + def __find_group_dns_with_member(self, tree, uid): + dns = self.__find_dns(tree, + '(&(objectclass=groupOfNames)(member=%s))' % + self.__uid_to_dn(uid)) + return dns + + def __group_exists(self, dn): + return self.__find_object(dn, '(objectclass=groupOfNames)') != None + + def __delete_key_pairs(self, uid): + keys = self.get_key_pairs(uid) + if keys != None: + for key in keys: + self.delete_key_pair(uid, key.name) + + 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.ldap_project_subtree) + + def __create_group(self, group_dn, name, uid, + description, member_uids = None): + if self.__group_exists(group_dn): + raise exception.Duplicate("Group can't be created because " + "group %s already exists" % name) + members = [] + if member_uids != None: + for member_uid in member_uids: + if not self.__user_exists(member_uid): + raise exception.NotFound("Group can't be created " + "because user %s doesn't exist" % member_uid) + members.append(self.__uid_to_dn(member_uid)) + dn = self.__uid_to_dn(uid) + if not dn in members: + members.append(dn) + attr = [ + ('objectclass', ['groupOfNames']), + ('cn', [name]), + ('description', [description]), + ('member', members) + ] + self.conn.add_s(group_dn, attr) + + 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,)) + if not self.__group_exists(group_dn): + return False + res = self.__find_object(group_dn, + '(member=%s)' % self.__uid_to_dn(uid)) + return res != None + + def __add_to_group(self, uid, group_dn): + if not self.__user_exists(uid): + raise exception.NotFound("User %s can't be added to the group " + "becuase the user doesn't exist" % (uid,)) + if not self.__group_exists(group_dn): + raise exception.NotFound("The group at dn %s doesn't exist" % + (group_dn,)) + if self.__is_in_group(uid, group_dn): + raise exception.Duplicate("User %s is already a member of " + "the group %s" % (uid, group_dn)) + attr = [ + (ldap.MOD_ADD, 'member', self.__uid_to_dn(uid)) + ] + self.conn.modify_s(group_dn, attr) + + def __remove_from_group(self, uid, group_dn): + if not self.__group_exists(group_dn): + raise exception.NotFound("The group at dn %s doesn't exist" % + (group_dn,)) + if not self.__user_exists(uid): + raise exception.NotFound("User %s can't be removed from the " + "group because the user doesn't exist" % (uid,)) + if not self.__is_in_group(uid, group_dn): + raise exception.NotFound("User %s is not a member of the group" % + (uid,)) + self.__safe_remove_from_group(group_dn, uid) + + def __safe_remove_from_group(self, group_dn, uid): + # FIXME(vish): what if deleted user is a project manager? + attr = [(ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))] + try: + self.conn.modify_s(group_dn, attr) + except self.OBJECT_CLASS_VIOLATION: + logging.debug("Attempted to remove the last member of a group. " + "Deleting the group at %s instead." % group_dn ) + self.__delete_group(group_dn) + + def __remove_from_all(self, uid): + if not self.__user_exists(uid): + raise exception.NotFound("User %s can't be removed from all " + "because the user doesn't exist" % (uid,)) + dn = self.__uid_to_dn(uid) + role_dns = self.__find_group_dns_with_member( + FLAGS.role_project_subtree, uid) + for role_dn in role_dns: + self.__safe_remove_from_group(role_dn, uid) + project_dns = self.__find_group_dns_with_member( + FLAGS.ldap_project_subtree, uid) + for project_dn in project_dns: + self.__safe_remove_from_group(project_dn, uid) + + def __delete_group(self, group_dn): + if not self.__group_exists(group_dn): + raise exception.NotFound("Group at dn %s doesn't exist" % group_dn) + self.conn.delete_s(group_dn) + + def __delete_roles(self, project_dn): + for role_dn in self.__find_role_dns(project_dn): + self.__delete_group(role_dn) + + def __to_user(self, attr): + if attr == None: + return None + return manager.User( + id = attr['uid'][0], + name = attr['cn'][0], + access = attr['accessKey'][0], + secret = attr['secretKey'][0], + admin = (attr['isAdmin'][0] == 'TRUE') + ) + + def __to_key_pair(self, owner, attr): + if attr == None: + return None + return manager.KeyPair( + id = attr['cn'][0], + owner_id = owner, + public_key = attr['sshPublicKey'][0], + fingerprint = attr['keyFingerprint'][0], + ) + + def __to_project(self, attr): + if attr == None: + return None + member_dns = attr.get('member', []) + return manager.Project( + id = attr['cn'][0], + project_manager_id = self.__dn_to_uid(attr['projectManager'][0]), + description = attr.get('description', [None])[0], + member_ids = [self.__dn_to_uid(x) for x in member_dns] + ) + + def __dn_to_uid(self, dn): + return dn.split(',')[0].split('=')[1] + + def __uid_to_dn(self, dn): + return 'uid=%s,%s' % (dn, FLAGS.ldap_user_subtree) + diff --git a/nova/auth/manager.py b/nova/auth/manager.py new file mode 100644 index 000000000..0b5039684 --- /dev/null +++ b/nova/auth/manager.py @@ -0,0 +1,741 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Nova authentication management +""" + +import logging +import os +import shutil +import string +import tempfile +import uuid +import zipfile + +from nova import crypto +from nova import datastore +from nova import exception +from nova import flags +from nova import objectstore # for flags +from nova import signer +from nova import utils +from nova.auth import ldapdriver +FLAGS = flags.FLAGS + +# NOTE(vish): 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') + +# NOTE(vish): 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', 'itsec'], + 'roles that apply to all projects') + +flags.DEFINE_string('credentials_template', + utils.abspath('auth/novarc.template'), + 'Template for creating users rc file') +flags.DEFINE_string('vpn_client_template', + utils.abspath('cloudpipe/client.ovpn.template'), + 'Template for creating users vpn file') +flags.DEFINE_string('credential_key_file', 'pk.pem', + 'Filename of private key in credentials zip') +flags.DEFINE_string('credential_cert_file', 'cert.pem', + 'Filename of certificate in credentials zip') +flags.DEFINE_string('credential_rc_file', 'novarc', + 'Filename of rc in credentials zip') + +flags.DEFINE_integer('vpn_start_port', 1000, + 'Start port for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_end_port', 2000, + 'End port for the cloudpipe VPN servers') + +flags.DEFINE_string('credential_cert_subject', + '/C=US/ST=California/L=MountainView/O=AnsoLabs/' + 'OU=NovaDev/CN=%s-%s', + 'Subject for certificate for users') + +flags.DEFINE_string('vpn_ip', '127.0.0.1', + 'Public IP for the cloudpipe VPN servers') + + +class AuthBase(object): + """Base class for objects relating to auth + + Objects derived from this class should be stupid data objects with + an id member. They may optionally contain methods that delegate to + AuthManager, but should not implement logic themselves. + """ + @classmethod + def safe_id(cls, obj): + """Safe get object id + + This method will return the id of the object if the object + is of this class, otherwise it will return the original object. + This allows methods to accept objects or ids as paramaters. + + """ + if isinstance(obj, cls): + return obj.id + else: + return obj + + +class User(AuthBase): + """Object representing a user""" + def __init__(self, id, name, access, secret, admin): + self.id = id + self.name = name + self.access = access + self.secret = secret + self.admin = admin + + def is_superuser(self): + return AuthManager().is_superuser(self) + + def is_admin(self): + return AuthManager().is_admin(self) + + def has_role(self, role): + return AuthManager().has_role(self, role) + + def add_role(self, role): + return AuthManager().add_role(self, role) + + def remove_role(self, role): + return AuthManager().remove_role(self, role) + + def is_project_member(self, project): + return AuthManager().is_project_member(self, project) + + def is_project_manager(self, project): + return AuthManager().is_project_manager(self, project) + + def generate_key_pair(self, name): + return AuthManager().generate_key_pair(self.id, name) + + def create_key_pair(self, name, public_key, fingerprint): + return AuthManager().create_key_pair(self.id, + name, + public_key, + fingerprint) + + def get_key_pair(self, name): + return AuthManager().get_key_pair(self.id, name) + + def delete_key_pair(self, name): + return AuthManager().delete_key_pair(self.id, name) + + def get_key_pairs(self): + return AuthManager().get_key_pairs(self.id) + + def __repr__(self): + return "User('%s', '%s', '%s', '%s', %s)" % (self.id, + self.name, + self.access, + self.secret, + self.admin) + + +class KeyPair(AuthBase): + """Represents an ssh key returned from the datastore + + Even though this object is named KeyPair, only the public key and + fingerprint is stored. The user's private key is not saved. + """ + def __init__(self, id, owner_id, public_key, fingerprint): + self.id = id + self.name = id + self.owner_id = owner_id + self.public_key = public_key + self.fingerprint = fingerprint + + def __repr__(self): + return "KeyPair('%s', '%s', '%s', '%s')" % (self.id, + self.owner_id, + self.public_key, + self.fingerprint) + + +class Project(AuthBase): + """Represents a Project returned from the datastore""" + def __init__(self, id, project_manager_id, description, member_ids): + self.project_manager_id = project_manager_id + self.id = id + self.name = id + self.description = description + self.member_ids = member_ids + + @property + def project_manager(self): + return AuthManager().get_user(self.project_manager_id) + + def has_manager(self, user): + return AuthManager().is_project_manager(user, self) + + def has_member(self, user): + return AuthManager().is_project_member(user, self) + + def add_role(self, user, role): + return AuthManager().add_role(user, role, self) + + def remove_role(self, user, role): + return AuthManager().remove_role(user, role, self) + + def has_role(self, user, role): + return AuthManager().has_role(user, role, self) + + def get_credentials(self, user): + return AuthManager().get_credentials(user, self) + + def __repr__(self): + return "Project('%s', '%s', '%s', %s)" % (self.id, + self.project_manager_id, + self.description, + self.member_ids) + + +class NoMorePorts(exception.Error): + pass + + +class Vpn(datastore.BasicModel): + """Manages vpn ips and ports for projects""" + def __init__(self, project_id): + self.project_id = project_id + super(Vpn, self).__init__() + + @property + def identifier(self): + return self.project_id + + @classmethod + def create(cls, project_id): + # TODO(vish): get list of vpn ips from redis + port = cls.find_free_port_for_ip(FLAGS.vpn_ip) + vpn = cls(project_id) + # save ip for project + vpn['project'] = project_id + vpn['ip'] = FLAGS.vpn_ip + vpn['port'] = port + vpn.save() + return vpn + + @classmethod + def find_free_port_for_ip(cls, ip): + # TODO(vish): these redis commands should be generalized and + # placed into a base class. Conceptually, it is + # similar to an association, but we are just + # storing a set of values instead of keys that + # should be turned into objects. + redis = datastore.Redis.instance() + key = 'ip:%s:ports' % ip + # TODO(vish): these ports should be allocated through an admin + # command instead of a flag + if (not redis.exists(key) and + not redis.exists(cls._redis_association_name('ip', ip))): + for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1): + redis.sadd(key, i) + + port = redis.spop(key) + if not port: + raise NoMorePorts() + return port + + @classmethod + def num_ports_for_ip(cls, ip): + return datastore.Redis.instance().scard('ip:%s:ports' % ip) + + @property + def ip(self): + return self['ip'] + + @property + def port(self): + return int(self['port']) + + def save(self): + self.associate_with('ip', self.ip) + super(Vpn, self).save() + + def destroy(self): + self.unassociate_with('ip', self.ip) + datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) + super(Vpn, self).destroy() + + +class AuthManager(object): + """Manager Singleton for dealing with Users, Projects, and Keypairs + + Methods accept objects or ids. + + AuthManager uses a driver object to make requests to the data backend. + See ldapdriver.LdapDriver for reference. + + AuthManager also manages associated data related to Auth objects that + need to be more accessible, such as vpn ips and ports. + """ + _instance=None + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(AuthManager, cls).__new__( + cls, *args, **kwargs) + return cls._instance + + def __init__(self, *args, **kwargs): + self.driver_class = kwargs.get('driver_class', ldapdriver.LdapDriver) + if FLAGS.fake_tests: + try: + self.create_user('fake', 'fake', 'fake') + except: pass + try: + self.create_user('user', 'user', 'user') + except: pass + try: + self.create_user('admin', 'admin', 'admin', True) + except: pass + + def authenticate(self, access, signature, params, verb='GET', + server_string='127.0.0.1:8773', path='/', + verify_signature=True): + """Authenticates AWS request using access key and signature + + If the project is not specified, attempts to authenticate to + a project with the same name as the user. This way, older tools + that have no project knowledge will still work. + + @type access: str + @param access: Access key for user in the form "access:project". + + @type signature: str + @param signature: Signature of the request. + + @type params: list of str + @param params: Web paramaters used for the signature. + + @type verb: str + @param verb: Web request verb ('GET' or 'POST'). + + @type server_string: str + @param server_string: Web request server string. + + @type path: str + @param path: Web request path. + + @type verify_signature: bool + @param verify_signature: Whether to verify the signature. + + @rtype: tuple (User, Project) + @return: User and project that the request represents. + """ + # TODO(vish): check for valid timestamp + (access_key, sep, project_name) = access.partition(':') + + user = self.get_user_from_access_key(access_key) + if user == None: + raise exception.NotFound('No user found for access key %s' % + access_key) + if project_name is '': + project_name = user.name + + project = self.get_project(project_name) + if project == None: + raise exception.NotFound('No project called %s could be found' % + project_name) + if not self.is_admin(user) and not self.is_project_member(user, + project): + raise exception.NotFound('User %s is not a member of project %s' % + (user.id, project.id)) + if verify_signature: + # NOTE(vish): hmac can't handle unicode, so encode ensures that + # secret isn't unicode + expected_signature = signer.Signer(user.secret.encode()).generate( + params, verb, server_string, path) + logging.debug('user.secret: %s', user.secret) + logging.debug('expected_signature: %s', expected_signature) + logging.debug('signature: %s', signature) + if signature != expected_signature: + raise exception.NotAuthorized('Signature does not match') + return (user, project) + + def is_superuser(self, user): + """Checks for superuser status, allowing user to bypass rbac + + @type user: User or uid + @param user: User to check. + + @rtype: bool + @return: True for superuser. + """ + if not isinstance(user, User): + user = self.get_user(user) + # NOTE(vish): admin flag on user represents superuser + if user.admin: + return True + for role in FLAGS.superuser_roles: + if self.has_role(user, role): + return True + + def is_admin(self, user): + """Checks for admin status, allowing user to access all projects + + @type user: User or uid + @param user: User to check. + + @rtype: bool + @return: True for admin. + """ + if not isinstance(user, User): + user = self.get_user(user) + if self.is_superuser(user): + return True + for role in FLAGS.global_roles: + if self.has_role(user, role): + return True + + def has_role(self, user, role, project=None): + """Checks existence of role for user + + If project is not specified, checks for a global role. If project + is specified, checks for the union of the global role and the + project role. + + Role 'projectmanager' only works for projects and simply checks to + see if the user is the project_manager of the specified project. It + is the same as calling is_project_manager(user, project). + + @type user: User or uid + @param user: User to check. + + @type role: str + @param role: Role to check. + + @type project: Project or project_id + @param project: Project in which to look for local role. + + @rtype: bool + @return: True if the user has the role. + """ + with self.driver_class() as drv: + if role == 'projectmanager': + if not project: + raise exception.Error("Must specify project") + return self.is_project_manager(user, project) + + global_role = drv.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 drv.has_role(User.safe_id(user), + role, + Project.safe_id(project)) + + def add_role(self, user, role, project=None): + """Adds role for user + + If project is not specified, adds a global role. If project + is specified, adds a local role. + + The 'projectmanager' role is special and can't be added or removed. + + @type user: User or uid + @param user: User to which to add role. + + @type role: str + @param role: Role to add. + + @type project: Project or project_id + @param project: Project in which to add local role. + """ + with self.driver_class() as drv: + drv.add_role(User.safe_id(user), role, Project.safe_id(project)) + + def remove_role(self, user, role, project=None): + """Removes role for user + + If project is not specified, removes a global role. If project + is specified, removes a local role. + + The 'projectmanager' role is special and can't be added or removed. + + @type user: User or uid + @param user: User from which to remove role. + + @type role: str + @param role: Role to remove. + + @type project: Project or project_id + @param project: Project in which to remove local role. + """ + with self.driver_class() as drv: + drv.remove_role(User.safe_id(user), role, Project.safe_id(project)) + + def create_project(self, name, manager_user, + description=None, member_users=None): + """Create a project + + @type name: str + @param name: Name of the project to create. The name will also be + used as the project id. + + @type manager_user: User or uid + @param manager_user: This user will be the project manager. + + @type description: str + @param project: Description of the project. If no description is + specified, the name of the project will be used. + + @type member_users: list of User or uid + @param: Initial project members. The project manager will always be + added as a member, even if he isn't specified in this list. + + @rtype: Project + @return: The new project. + """ + if member_users: + member_users = [User.safe_id(u) for u in member_users] + # NOTE(vish): try to associate a vpn ip and port first because + # if it throws an exception, we save having to + # create and destroy a project + Vpn.create(name) + with self.driver_class() as drv: + return drv.create_project(name, + User.safe_id(manager_user), + description, + member_users) + + def get_projects(self): + """Retrieves list of all projects""" + with self.driver_class() as drv: + return drv.get_projects() + + + def get_project(self, project): + """Get project object by id""" + with self.driver_class() as drv: + return drv.get_project(Project.safe_id(project)) + + def add_to_project(self, user, project): + """Add user to project""" + with self.driver_class() as drv: + return drv.add_to_project(User.safe_id(user), + Project.safe_id(project)) + + def is_project_manager(self, user, project): + """Checks if user is project manager""" + if not isinstance(project, Project): + project = self.get_project(project) + return User.safe_id(user) == project.project_manager_id + + def is_project_member(self, user, project): + """Checks to see if user is a member of project""" + if not isinstance(project, Project): + project = self.get_project(project) + return User.safe_id(user) in project.member_ids + + def remove_from_project(self, user, project): + """Removes a user from a project""" + with self.driver_class() as drv: + return drv.remove_from_project(User.safe_id(user), + Project.safe_id(project)) + + def delete_project(self, project): + """Deletes a project""" + with self.driver_class() as drv: + return drv.delete_project(Project.safe_id(project)) + + def get_user(self, uid): + """Retrieves a user by id""" + with self.driver_class() as drv: + return drv.get_user(uid) + + def get_user_from_access_key(self, access_key): + """Retrieves a user by access key""" + with self.driver_class() as drv: + return drv.get_user_from_access_key(access_key) + + def get_users(self): + """Retrieves a list of all users""" + with self.driver_class() as drv: + return drv.get_users() + + def create_user(self, user, access=None, secret=None, + admin=False, create_project=True): + """Creates a user + + @type user: str + @param name: Name of the user to create. The name will also be + used as the user id. + + @type access: str + @param access: Access Key (defaults to a random uuid) + + @type secret: str + @param secret: Secret Key (defaults to a random uuid) + + @type admin: bool + @param admin: Whether to set the admin flag. The admin flag gives + superuser status regardless of roles specifed for the user. + + @type create_project: bool + @param: Whether to create a project for the user with the same name. + + @rtype: User + @return: The new user. + """ + if access == None: access = str(uuid.uuid4()) + if secret == None: secret = str(uuid.uuid4()) + with self.driver_class() as drv: + user = User.safe_id(user) + result = drv.create_user(user, access, secret, admin) + if create_project: + # NOTE(vish): if the project creation fails, we delete + # the user and return an exception + try: + drv.create_project(user, user, user) + except Exception: + with self.driver_class() as drv: + drv.delete_user(user) + raise + return result + + def delete_user(self, user, delete_project=True): + """Deletes a user""" + with self.driver_class() as drv: + user = User.safe_id(user) + if delete_project: + try: + drv.delete_project(user) + except exception.NotFound: + pass + drv.delete_user(user) + + def generate_key_pair(self, user, key_name): + """Generates a key pair for a user + + Generates a public and private key, stores the public key using the + key_name, and returns the private key and fingerprint. + + @type user: User or uid + @param user: User for which to create key pair. + + @type key_name: str + @param key_name: Name to use for the generated KeyPair. + + @rtype: tuple (private_key, fingerprint) + @return: A tuple containing the private_key and fingerprint. + """ + # NOTE(vish): generating key pair is slow so check for legal + # creation before creating keypair + uid = User.safe_id(user) + with self.driver_class() as drv: + if not drv.get_user(uid): + raise exception.NotFound("User %s doesn't exist" % user) + if drv.get_key_pair(uid, key_name): + raise exception.Duplicate("The keypair %s already exists" + % key_name) + private_key, public_key, fingerprint = crypto.generate_key_pair() + self.create_key_pair(uid, key_name, public_key, fingerprint) + return private_key, fingerprint + + def create_key_pair(self, user, key_name, public_key, fingerprint): + """Creates a key pair for user""" + with self.driver_class() as drv: + return drv.create_key_pair(User.safe_id(user), key_name, + public_key, fingerprint) + + def get_key_pair(self, user, key_name): + """Retrieves a key pair for user""" + with self.driver_class() as drv: + return drv.get_key_pair(User.safe_id(user), key_name) + + def get_key_pairs(self, user): + """Retrieves all key pairs for user""" + with self.driver_class() as drv: + return drv.get_key_pairs(User.safe_id(user)) + + def delete_key_pair(self, user, key_name): + """Deletes a key pair for user""" + with self.driver_class() as drv: + drv.delete_key_pair(User.safe_id(user), key_name) + + def get_credentials(self, user, project=None): + """Get credential zip for user in project""" + if not isinstance(user, User): + user = self.get_user(user) + if project is None: + project = user.id + pid = Project.safe_id(project) + rc = self.__generate_rc(user.access, user.secret, pid) + private_key, signed_cert = self.__generate_x509_cert(user.id, pid) + + vpn = Vpn(pid) + 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=vpn.ip, + port=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(user.id)) + zippy.close() + with open(zf, 'rb') as f: + buffer = f.read() + + shutil.rmtree(tmpdir) + return buffer + + def __generate_rc(self, access, secret, pid): + """Generate rc file for user""" + rc = open(FLAGS.credentials_template).read() + rc = rc % { 'access': access, + 'project': pid, + 'secret': secret, + 'ec2': FLAGS.ec2_url, + 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port), + 'nova': FLAGS.ca_file, + 'cert': FLAGS.credential_cert_file, + 'key': FLAGS.credential_key_file, + } + return rc + + def __generate_x509_cert(self, uid, pid): + """Generate x509 cert for user""" + (private_key, csr) = crypto.generate_x509_cert( + self.__cert_subject(uid)) + # TODO(joshua): This should be async call back to the cloud controller + signed_cert = crypto.sign_csr(csr, pid) + return (private_key, signed_cert) + + def __cert_subject(self, uid): + """Helper to generate cert subject""" + return FLAGS.credential_cert_subject % (uid, utils.isotime()) diff --git a/nova/auth/rbac.py b/nova/auth/rbac.py index 9e2bb830c..7fab9419f 100644 --- a/nova/auth/rbac.py +++ b/nova/auth/rbac.py @@ -17,7 +17,7 @@ # under the License. from nova import exception -from nova.auth import users +from nova.auth import manager def allow(*roles): diff --git a/nova/auth/users.py b/nova/auth/users.py deleted file mode 100644 index fc08dc34d..000000000 --- a/nova/auth/users.py +++ /dev/null @@ -1,974 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Nova users and user management, including RBAC hooks. -""" - -import datetime -import logging -import os -import shutil -import signer -import string -import tempfile -import uuid -import zipfile - -try: - import ldap -except Exception, e: - import fakeldap as ldap - -import fakeldap - -# TODO(termie): clean up these imports -from nova import datastore -from nova import exception -from nova import flags -from nova import crypto -from nova import utils -from nova import objectstore # for flags - -FLAGS = flags.FLAGS - -flags.DEFINE_string('ldap_url', 'ldap://localhost', - 'Point this at your ldap server') -flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') -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') - -# NOTE(vish): 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') -flags.DEFINE_string('ldap_itsec', - 'cn=itsec,ou=Groups,dc=example,dc=com', 'cn for ItSec') -flags.DEFINE_string('ldap_sysadmin', - 'cn=sysadmins,ou=Groups,dc=example,dc=com', 'cn for Sysadmins') -flags.DEFINE_string('ldap_netadmin', - 'cn=netadmins,ou=Groups,dc=example,dc=com', 'cn for NetAdmins') -flags.DEFINE_string('ldap_developer', - 'cn=developers,ou=Groups,dc=example,dc=com', 'cn for Developers') - -# NOTE(vish): 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') - -# NOTE(vish): 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', 'itsec'], - 'roles that apply to all projects') - -flags.DEFINE_string('credentials_template', - utils.abspath('auth/novarc.template'), - 'Template for creating users rc file') -flags.DEFINE_string('vpn_client_template', - utils.abspath('cloudpipe/client.ovpn.template'), - 'Template for creating users vpn file') -flags.DEFINE_string('credential_key_file', 'pk.pem', - 'Filename of private key in credentials zip') -flags.DEFINE_string('credential_cert_file', 'cert.pem', - 'Filename of certificate in credentials zip') -flags.DEFINE_string('credential_rc_file', 'novarc', - 'Filename of rc in credentials zip') - -flags.DEFINE_integer('vpn_start_port', 1000, - 'Start port for the cloudpipe VPN servers') -flags.DEFINE_integer('vpn_end_port', 2000, - 'End port for the cloudpipe VPN servers') - -flags.DEFINE_string('credential_cert_subject', - '/C=US/ST=California/L=MountainView/O=AnsoLabs/' - 'OU=NovaDev/CN=%s-%s', - 'Subject for certificate for users') - -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): - """Safe get object id. - - This method will return the id of the object if the object - is of this class, otherwise it will return the original object. - This allows methods to accept objects or ids as paramaters. - - """ - if isinstance(obj, cls): - return obj.id - else: - return obj - - -class User(AuthBase): - """id and name are currently the same""" - def __init__(self, id, name, access, secret, admin): - self.id = id - self.name = name - self.access = access - self.secret = secret - self.admin = admin - - 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""" - 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) - - def is_project_manager(self, project): - return UserManager.instance().is_project_manager(self, project) - - def generate_rc(self, project=None): - if project is None: - project = self.id - rc = open(FLAGS.credentials_template).read() - rc = rc % { 'access': self.access, - 'project': project, - 'secret': self.secret, - 'ec2': FLAGS.ec2_url, - 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port), - 'nova': FLAGS.ca_file, - 'cert': FLAGS.credential_cert_file, - 'key': FLAGS.credential_key_file, - } - return rc - - def generate_key_pair(self, name): - return UserManager.instance().generate_key_pair(self.id, name) - - def create_key_pair(self, name, public_key, fingerprint): - return UserManager.instance().create_key_pair(self.id, - name, - public_key, - fingerprint) - - def get_key_pair(self, name): - return UserManager.instance().get_key_pair(self.id, name) - - def delete_key_pair(self, name): - return UserManager.instance().delete_key_pair(self.id, name) - - def get_key_pairs(self): - return UserManager.instance().get_key_pairs(self.id) - - def __repr__(self): - return "User('%s', '%s', '%s', '%s', %s)" % ( - self.id, self.name, self.access, self.secret, self.admin) - - -class KeyPair(AuthBase): - def __init__(self, id, owner_id, public_key, fingerprint): - self.id = id - self.name = id - self.owner_id = owner_id - self.public_key = public_key - self.fingerprint = fingerprint - - def delete(self): - return UserManager.instance().delete_key_pair(self.owner, self.name) - - def __repr__(self): - return "KeyPair('%s', '%s', '%s', '%s')" % ( - self.id, self.owner_id, self.public_key, self.fingerprint) - - -class Group(AuthBase): - """id and name are currently the same""" - def __init__(self, id, description = None, member_ids = None): - self.id = id - self.name = id - self.description = description - self.member_ids = member_ids - - def has_member(self, user): - return User.safe_id(user) in self.member_ids - - def __repr__(self): - return "Group('%s', '%s', %s)" % ( - self.id, self.description, self.member_ids) - - -class Project(Group): - def __init__(self, id, project_manager_id, description, member_ids): - self.project_manager_id = project_manager_id - super(Project, self).__init__(id, description, member_ids) - - @property - def project_manager(self): - return UserManager.instance().get_user(self.project_manager_id) - - 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_ip(self): - return Vpn(self.id).ip - - @property - def vpn_port(self): - return Vpn(self.id).port - - 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: - buffer = f.read() - - shutil.rmtree(tmpdir) - return buffer - - def generate_x509_cert(self, user): - return UserManager.instance().generate_x509_cert(user, self) - - def __repr__(self): - return "Project('%s', '%s', '%s', %s)" % ( - self.id, self.project_manager_id, - self.description, self.member_ids) - - -class NoMorePorts(exception.Error): - pass - - -class Vpn(datastore.BasicModel): - def __init__(self, project_id): - self.project_id = project_id - super(Vpn, self).__init__() - - @property - def identifier(self): - return self.project_id - - @classmethod - def create(cls, project_id): - # TODO(vish): get list of vpn ips from redis - port = cls.find_free_port_for_ip(FLAGS.vpn_ip) - vpn = cls(project_id) - # save ip for project - vpn['project'] = project_id - vpn['ip'] = FLAGS.vpn_ip - vpn['port'] = port - vpn.save() - return vpn - - @classmethod - def find_free_port_for_ip(cls, ip): - # TODO(vish): these redis commands should be generalized and - # placed into a base class. Conceptually, it is - # similar to an association, but we are just - # storing a set of values instead of keys that - # should be turned into objects. - redis = datastore.Redis.instance() - key = 'ip:%s:ports' % ip - # TODO(vish): these ports should be allocated through an admin - # command instead of a flag - if (not redis.exists(key) and - not redis.exists(cls._redis_association_name('ip', ip))): - for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1): - redis.sadd(key, i) - - port = redis.spop(key) - if not port: - raise NoMorePorts() - return port - - @classmethod - def num_ports_for_ip(cls, ip): - return datastore.Redis.instance().scard('ip:%s:ports' % ip) - - @property - def ip(self): - return self['ip'] - - @property - def port(self): - return int(self['port']) - - def save(self): - self.associate_with('ip', self.ip) - super(Vpn, self).save() - - def destroy(self): - self.unassociate_with('ip', self.ip) - datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) - super(Vpn, self).destroy() - - -class UserManager(object): - def __init__(self): - if hasattr(self.__class__, '_instance'): - raise Exception('Attempted to instantiate singleton') - - @classmethod - def instance(cls): - if not hasattr(cls, '_instance'): - inst = UserManager() - cls._instance = inst - if FLAGS.fake_users: - try: - inst.create_user('fake', 'fake', 'fake') - except: pass - try: - inst.create_user('user', 'user', 'user') - except: pass - try: - inst.create_user('admin', 'admin', 'admin', True) - except: pass - return cls._instance - - def authenticate(self, access, signature, params, verb='GET', - server_string='127.0.0.1:8773', path='/', - verify_signature=True): - # TODO: Check for valid timestamp - (access_key, sep, project_name) = access.partition(':') - - user = self.get_user_from_access_key(access_key) - if user == None: - raise exception.NotFound('No user found for access key %s' % - access_key) - if project_name is '': - project_name = user.name - - project = self.get_project(project_name) - if project == None: - raise exception.NotFound('No project called %s could be found' % - project_name) - if not user.is_admin() and not project.has_member(user): - raise exception.NotFound('User %s is not a member of project %s' % - (user.id, project.id)) - if verify_signature: - # NOTE(vish): hmac can't handle unicode, so encode ensures that - # secret isn't unicode - expected_signature = signer.Signer(user.secret.encode()).generate( - params, verb, server_string, path) - logging.debug('user.secret: %s', user.secret) - logging.debug('expected_signature: %s', expected_signature) - logging.debug('signature: %s', signature) - if signature != expected_signature: - raise exception.NotAuthorized('Signature does not match') - return (user, project) - - def has_role(self, user, role, project=None): - with LDAPWrapper() as conn: - if role == 'projectmanager': - if not project: - raise exception.Error("Must specify project") - 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] - # NOTE(vish): try to associate a vpn ip and port first because - # if it throws an exception, we save having to - # create and destroy a project - Vpn.create(name) - with LDAPWrapper() as conn: - return conn.create_project(name, - User.safe_id(manager_user), - description, - member_users) - - - def get_projects(self): - with LDAPWrapper() as conn: - return conn.find_projects() - - - def get_project(self, project): - with LDAPWrapper() as conn: - return conn.find_project(Project.safe_id(project)) - - def add_to_project(self, user, project): - with LDAPWrapper() as conn: - return conn.add_to_project(User.safe_id(user), - Project.safe_id(project)) - - def is_project_manager(self, user, project): - if not isinstance(project, Project): - project = self.get_project(project) - return project.has_manager(user) - - def is_project_member(self, user, project): - if isinstance(project, Project): - return project.has_member(user) - else: - with LDAPWrapper() as conn: - return conn.is_in_project(User.safe_id(user), project) - - def remove_from_project(self, user, project): - with LDAPWrapper() as conn: - return conn.remove_from_project(User.safe_id(user), - Project.safe_id(project)) - - def delete_project(self, project): - with LDAPWrapper() as conn: - return conn.delete_project(Project.safe_id(project)) - - def get_user(self, uid): - with LDAPWrapper() as conn: - return conn.find_user(uid) - - def get_user_from_access_key(self, access_key): - with LDAPWrapper() as conn: - return conn.find_user_by_access_key(access_key) - - def get_users(self): - with LDAPWrapper() as conn: - return conn.find_users() - - def create_user(self, user, access=None, secret=None, - admin=False, create_project=True): - if access == None: access = str(uuid.uuid4()) - if secret == None: secret = str(uuid.uuid4()) - with LDAPWrapper() as conn: - user = User.safe_id(user) - result = conn.create_user(user, access, secret, admin) - if create_project: - # NOTE(vish): if the project creation fails, we delete - # the user and return an exception - try: - conn.create_project(user, user, user) - except Exception: - with LDAPWrapper() as conn: - conn.delete_user(user) - raise - return result - - def delete_user(self, user, delete_project=True): - with LDAPWrapper() as conn: - user = User.safe_id(user) - if delete_project: - try: - conn.delete_project(user) - except exception.NotFound: - pass - conn.delete_user(user) - - def generate_key_pair(self, user, key_name): - # generating key pair is slow so delay generation - # until after check - user = User.safe_id(user) - with LDAPWrapper() as conn: - if not conn.user_exists(user): - raise exception.NotFound("User %s doesn't exist" % user) - if conn.key_pair_exists(user, key_name): - raise exception.Duplicate("The keypair %s already exists" - % key_name) - private_key, public_key, fingerprint = crypto.generate_key_pair() - self.create_key_pair(User.safe_id(user), key_name, - public_key, fingerprint) - return private_key, fingerprint - - def create_key_pair(self, user, key_name, public_key, fingerprint): - with LDAPWrapper() as conn: - return conn.create_key_pair(User.safe_id(user), key_name, - public_key, fingerprint) - - def get_key_pair(self, user, key_name): - with LDAPWrapper() as conn: - return conn.find_key_pair(User.safe_id(user), key_name) - - def get_key_pairs(self, user): - with LDAPWrapper() as conn: - return conn.find_key_pairs(User.safe_id(user)) - - def delete_key_pair(self, user, key_name): - with LDAPWrapper() as conn: - conn.delete_key_pair(User.safe_id(user), key_name) - - def generate_x509_cert(self, user, project): - (private_key, csr) = crypto.generate_x509_cert( - self.__cert_subject(User.safe_id(user))) - # TODO - This should be async call back to the cloud controller - signed_cert = crypto.sign_csr(csr, Project.safe_id(project)) - return (private_key, signed_cert) - - def __cert_subject(self, uid): - # FIXME(ja) - this should be pulled from a global configuration - return FLAGS.credential_cert_subject % (uid, utils.isotime()) - - -class LDAPWrapper(object): - def __init__(self): - self.user = FLAGS.user_dn - self.passwd = FLAGS.ldap_password - - def __enter__(self): - self.connect() - return self - - def __exit__(self, type, value, traceback): - self.conn.unbind_s() - return False - - def connect(self): - """ connect to ldap as admin user """ - if FLAGS.fake_users: - self.NO_SUCH_OBJECT = fakeldap.NO_SUCH_OBJECT - self.OBJECT_CLASS_VIOLATION = fakeldap.OBJECT_CLASS_VIOLATION - self.conn = fakeldap.initialize(FLAGS.ldap_url) - else: - self.NO_SUCH_OBJECT = ldap.NO_SUCH_OBJECT - self.OBJECT_CLASS_VIOLATION = ldap.OBJECT_CLASS_VIOLATION - self.conn = ldap.initialize(FLAGS.ldap_url) - self.conn.simple_bind_s(self.user, self.passwd) - - def find_object(self, dn, query = None): - objects = self.find_objects(dn, query) - if len(objects) == 0: - return None - return objects[0] - - def find_dns(self, dn, query=None): - try: - res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query) - except self.NO_SUCH_OBJECT: - return [] - # just return the DNs - return [dn for dn, attributes in res] - - def find_objects(self, dn, query = None): - try: - res = self.conn.search_s(dn, ldap.SCOPE_SUBTREE, query) - except self.NO_SUCH_OBJECT: - return [] - # just return the attributes - return [attributes for dn, attributes in res] - - def find_users(self): - attrs = self.find_objects(FLAGS.user_ldap_subtree, - '(objectclass=novaUser)') - return [self.__to_user(attr) for attr in attrs] - - def find_key_pairs(self, uid): - attrs = self.find_objects(self.__uid_to_dn(uid), - '(objectclass=novaKeyPair)') - return [self.__to_key_pair(uid, attr) for attr in attrs] - - def find_projects(self): - 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_group_dns_with_member(self, tree, uid): - dns = self.find_dns(tree, - '(&(objectclass=groupOfNames)(member=%s))' % - self.__uid_to_dn(uid)) - return dns - - def find_user(self, uid): - attr = self.find_object(self.__uid_to_dn(uid), - '(objectclass=novaUser)') - return self.__to_user(attr) - - def find_key_pair(self, uid, key_name): - dn = 'cn=%s,%s' % (key_name, - self.__uid_to_dn(uid)) - attr = self.find_object(dn, '(objectclass=novaKeyPair)') - return self.__to_key_pair(uid, attr) - - def find_group(self, dn): - """uses dn directly instead of custructing it from name""" - attr = self.find_object(dn, '(objectclass=groupOfNames)') - return self.__to_group(attr) - - def find_project(self, name): - dn = 'cn=%s,%s' % (name, - FLAGS.project_ldap_subtree) - attr = self.find_object(dn, '(objectclass=novaProject)') - return self.__to_project(attr) - - def user_exists(self, name): - return self.find_user(name) != None - - def key_pair_exists(self, uid, key_name): - return self.find_key_pair(uid, key_name) != None - - def project_exists(self, name): - return self.find_project(name) != None - - def group_exists(self, dn): - return self.find_group(dn) != None - - def delete_key_pairs(self, uid): - keys = self.find_key_pairs(uid) - if keys != None: - for key in keys: - self.delete_key_pair(uid, key.name) - - def create_user(self, name, access_key, secret_key, is_admin): - if self.user_exists(name): - raise exception.Duplicate("LDAP user %s already exists" % name) - attr = [ - ('objectclass', ['person', - 'organizationalPerson', - 'inetOrgPerson', - 'novaUser']), - ('ou', [FLAGS.user_unit]), - ('uid', [name]), - ('sn', [name]), - ('cn', [name]), - ('secretKey', [secret_key]), - ('accessKey', [access_key]), - ('isAdmin', [str(is_admin).upper()]), - ] - self.conn.add_s(self.__uid_to_dn(name), attr) - return self.__to_user(dict(attr)) - - def create_project(self, name, manager_uid, - description=None, member_uids=None): - if self.project_exists(name): - raise exception.Duplicate("Project can't be created because " - "project %s already exists" % name) - if not self.user_exists(manager_uid): - raise exception.NotFound("Project can't be created because " - "manager %s doesn't exist" % manager_uid) - manager_dn = self.__uid_to_dn(manager_uid) - # description is a required attribute - if description is None: - description = name - members = [] - if member_uids != None: - for member_uid in member_uids: - if not self.user_exists(member_uid): - raise exception.NotFound("Project can't be created " - "because user %s doesn't exist" % member_uid) - members.append(self.__uid_to_dn(member_uid)) - # always add the manager as a member because members is required - if not manager_dn in members: - members.append(manager_dn) - attr = [ - ('objectclass', ['novaProject']), - ('cn', [name]), - ('description', [description]), - ('projectManager', [manager_dn]), - ('member', members) - ] - self.conn.add_s('cn=%s,%s' % (name, FLAGS.project_ldap_subtree), attr) - return self.__to_project(dict(attr)) - - def add_to_project(self, uid, project_id): - dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree) - return self.add_to_group(uid, dn) - - def remove_from_project(self, uid, project_id): - dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree) - return self.remove_from_group(uid, dn) - - def is_in_project(self, uid, project_id): - 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(group_dn): - raise exception.Duplicate("Group can't be created because " - "group %s already exists" % name) - members = [] - if member_uids != None: - for member_uid in member_uids: - if not self.user_exists(member_uid): - raise exception.NotFound("Group can't be created " - "because user %s doesn't exist" % member_uid) - members.append(self.__uid_to_dn(member_uid)) - dn = self.__uid_to_dn(uid) - if not dn in members: - members.append(dn) - attr = [ - ('objectclass', ['groupOfNames']), - ('cn', [name]), - ('description', [description]), - ('member', members) - ] - 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) - return self.remove_from_group(uid, role_dn) - - 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,)) - if not self.group_exists(group_dn): - return False - res = self.find_object(group_dn, - '(member=%s)' % self.__uid_to_dn(uid)) - return res != None - - def add_to_group(self, uid, group_dn): - if not self.user_exists(uid): - raise exception.NotFound("User %s can't be added to the group " - "becuase the user doesn't exist" % (uid,)) - if not self.group_exists(group_dn): - raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) - if self.is_in_group(uid, group_dn): - raise exception.Duplicate("User %s is already a member of " - "the group %s" % (uid, group_dn)) - attr = [ - (ldap.MOD_ADD, 'member', self.__uid_to_dn(uid)) - ] - self.conn.modify_s(group_dn, attr) - - def remove_from_group(self, uid, group_dn): - if not self.group_exists(group_dn): - raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) - if not self.user_exists(uid): - raise exception.NotFound("User %s can't be removed from the " - "group because the user doesn't exist" % (uid,)) - if not self.is_in_group(uid, group_dn): - raise exception.NotFound("User %s is not a member of the group" % - (uid,)) - self._safe_remove_from_group(group_dn, uid) - - def _safe_remove_from_group(self, group_dn, uid): - # FIXME(vish): what if deleted user is a project manager? - attr = [(ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))] - try: - self.conn.modify_s(group_dn, attr) - except self.OBJECT_CLASS_VIOLATION: - logging.debug("Attempted to remove the last member of a group. " - "Deleting the group at %s instead." % group_dn ) - self.delete_group(group_dn) - - def remove_from_all(self, uid): - if not self.user_exists(uid): - raise exception.NotFound("User %s can't be removed from all " - "because the user doesn't exist" % (uid,)) - dn = self.__uid_to_dn(uid) - role_dns = self.find_group_dns_with_member( - FLAGS.role_ldap_subtree, uid) - for role_dn in role_dns: - self._safe_remove_from_group(role_dn, uid) - project_dns = self.find_group_dns_with_member( - FLAGS.project_ldap_subtree, uid) - for project_dn in project_dns: - self._safe_remove_from_group(project_dn, uid) - - def create_key_pair(self, uid, key_name, public_key, fingerprint): - """create's a public key in the directory underneath the user""" - # TODO(vish): possibly refactor this to store keys in their own ou - # and put dn reference in the user object - attr = [ - ('objectclass', ['novaKeyPair']), - ('cn', [key_name]), - ('sshPublicKey', [public_key]), - ('keyFingerprint', [fingerprint]), - ] - self.conn.add_s('cn=%s,%s' % (key_name, - self.__uid_to_dn(uid)), - attr) - return self.__to_key_pair(uid, dict(attr)) - - def find_user_by_access_key(self, access): - query = '(accessKey=%s)' % access - dn = FLAGS.user_ldap_subtree - return self.__to_user(self.find_object(dn, query)) - - def delete_user(self, uid): - if not self.user_exists(uid): - raise exception.NotFound("User %s doesn't exist" % uid) - self.delete_key_pairs(uid) - self.remove_from_all(uid) - self.conn.delete_s('uid=%s,%s' % (uid, - FLAGS.user_ldap_subtree)) - - def delete_key_pair(self, uid, key_name): - if not self.key_pair_exists(uid, key_name): - raise exception.NotFound("Key Pair %s doesn't exist for user %s" % - (key_name, uid)) - self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid, - FLAGS.user_ldap_subtree)) - - def delete_group(self, group_dn): - if not self.group_exists(group_dn): - 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) - 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): - if attr == None: - return None - return User( - id = attr['uid'][0], - name = attr['cn'][0], - access = attr['accessKey'][0], - secret = attr['secretKey'][0], - admin = (attr['isAdmin'][0] == 'TRUE') - ) - - def __to_key_pair(self, owner, attr): - if attr == None: - return None - return KeyPair( - id = attr['cn'][0], - owner_id = owner, - public_key = attr['sshPublicKey'][0], - fingerprint = attr['keyFingerprint'][0], - ) - - def __to_group(self, attr): - if attr == None: - return None - member_dns = attr.get('member', []) - return Group( - id = attr['cn'][0], - description = attr.get('description', [None])[0], - member_ids = [self.__dn_to_uid(x) for x in member_dns] - ) - - def __to_project(self, attr): - if attr == None: - return None - member_dns = attr.get('member', []) - return Project( - id = attr['cn'][0], - project_manager_id = self.__dn_to_uid(attr['projectManager'][0]), - description = attr.get('description', [None])[0], - member_ids = [self.__dn_to_uid(x) for x in member_dns] - ) - - def __dn_to_uid(self, dn): - return dn.split(',')[0].split('=')[1] - - def __uid_to_dn(self, dn): - return 'uid=%s,%s' % (dn, FLAGS.user_ldap_subtree) diff --git a/nova/cloudpipe/api.py b/nova/cloudpipe/api.py index a5f78a16d..0bffe9aa3 100644 --- a/nova/cloudpipe/api.py +++ b/nova/cloudpipe/api.py @@ -25,7 +25,7 @@ import tornado.web import urllib from nova import crypto -from nova.auth import users +from nova.auth import manager _log = logging.getLogger("api") diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 63f7ae222..5b0ed3471 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -31,7 +31,7 @@ import zipfile from nova import exception from nova import flags from nova import utils -from nova.auth import users +from nova.auth import manager from nova.endpoint import api @@ -44,7 +44,7 @@ flags.DEFINE_string('boot_script_template', class CloudPipe(object): def __init__(self, cloud_controller): self.controller = cloud_controller - self.manager = users.UserManager.instance() + self.manager = manager.AuthManager() def launch_vpn_instance(self, project_id): logging.debug( "Launching VPN for %s" % (project_id)) diff --git a/nova/compute/network.py b/nova/compute/network.py index 90d6b2dc6..370e2bf44 100644 --- a/nova/compute/network.py +++ b/nova/compute/network.py @@ -29,7 +29,7 @@ from nova import datastore from nova import exception from nova import flags from nova import utils -from nova.auth import users +from nova.auth import manager from nova.compute import exception as compute_exception from nova.compute import linux_net @@ -209,11 +209,11 @@ class BaseNetwork(datastore.BasicModel): @property def user(self): - return users.UserManager.instance().get_user(self['user_id']) + return manager.AuthManager().get_user(self['user_id']) @property def project(self): - return users.UserManager.instance().get_project(self['project_id']) + return manager.AuthManager().get_project(self['project_id']) @property def _hosts_key(self): @@ -511,7 +511,7 @@ def get_vlan_for_project(project_id): if not known_vlans.has_key(vstr): return Vlan.create(project_id, vnum) old_project_id = known_vlans[vstr] - if not users.UserManager.instance().get_project(old_project_id): + if not manager.AuthManager().get_project(old_project_id): vlan = Vlan.lookup(old_project_id) if vlan: # NOTE(todd): This doesn't check for vlan id match, because @@ -537,7 +537,7 @@ def get_network_by_interface(iface, security_group='default'): def get_network_by_address(address): logging.debug("Get Network By Address: %s" % address) - for project in users.UserManager.instance().get_projects(): + for project in manager.AuthManager().get_projects(): net = get_project_network(project.id) if address in net.assigned: logging.debug("Found %s in %s" % (address, project.id)) @@ -577,7 +577,7 @@ def get_project_network(project_id, security_group='default'): """ get a project's private network, allocating one if needed """ # TODO(todd): It looks goofy to get a project from a UserManager. # Refactor to still use the LDAP backend, but not User specific. - project = users.UserManager.instance().get_project(project_id) + project = manager.AuthManager().get_project(project_id) if not project: raise exception.Error("Project %s doesn't exist, uhoh." % project_id) @@ -587,5 +587,5 @@ def get_project_network(project_id, security_group='default'): def restart_nets(): """ Ensure the network for each user is enabled""" - for project in users.UserManager.instance().get_projects(): + for project in manager.AuthManager().get_projects(): get_project_network(project.id).express() diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py index b97a6727f..55a8e4238 100644 --- a/nova/endpoint/admin.py +++ b/nova/endpoint/admin.py @@ -22,7 +22,7 @@ Admin API controller, exposed through http via the api worker. import base64 -from nova.auth import users +from nova.auth import manager from nova.compute import model def user_dict(user, base64_file=None): @@ -69,18 +69,18 @@ class AdminController(object): @admin_only def describe_user(self, _context, name, **_kwargs): """Returns user data, including access and secret keys.""" - return user_dict(users.UserManager.instance().get_user(name)) + return user_dict(manager.AuthManager().get_user(name)) @admin_only def describe_users(self, _context, **_kwargs): """Returns all users - should be changed to deal with a list.""" return {'userSet': - [user_dict(u) for u in users.UserManager.instance().get_users()] } + [user_dict(u) for u in manager.AuthManager().get_users()] } @admin_only def register_user(self, _context, name, **_kwargs): """Creates a new user, and returns generated credentials.""" - return user_dict(users.UserManager.instance().create_user(name)) + return user_dict(manager.AuthManager().create_user(name)) @admin_only def deregister_user(self, _context, name, **_kwargs): @@ -88,7 +88,7 @@ class AdminController(object): Should throw an exception if the user has instances, volumes, or buckets remaining. """ - users.UserManager.instance().delete_user(name) + manager.AuthManager().delete_user(name) return True @@ -100,8 +100,8 @@ class AdminController(object): """ if project is None: project = name - project = users.UserManager.instance().get_project(project) - user = users.UserManager.instance().get_user(name) + project = manager.AuthManager().get_project(project) + user = manager.AuthManager().get_user(name) return user_dict(user, base64.b64encode(project.get_credentials(user))) @admin_only diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py index 79a2aaddb..78a18b9ea 100755 --- a/nova/endpoint/api.py +++ b/nova/endpoint/api.py @@ -35,7 +35,7 @@ from nova import crypto from nova import exception from nova import flags from nova import utils -from nova.auth import users +from nova.auth import manager import nova.cloudpipe.api from nova.endpoint import cloud @@ -266,7 +266,7 @@ class APIRequestHandler(tornado.web.RequestHandler): # Authenticate the request. try: - (user, project) = users.UserManager.instance().authenticate( + (user, project) = manager.AuthManager().authenticate( access, signature, auth_params, diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 3b7b4804b..8eac1ce4a 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -35,7 +35,7 @@ from nova import flags from nova import rpc from nova import utils from nova.auth import rbac -from nova.auth import users +from nova.auth import manager from nova.compute import model from nova.compute import network from nova.compute import node @@ -48,9 +48,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on') def _gen_key(user_id, key_name): - """ Tuck this into UserManager """ + """ Tuck this into AuthManager """ try: - manager = users.UserManager.instance() + manager = manager.AuthManager() private_key, fingerprint = manager.generate_key_pair(user_id, key_name) except Exception as ex: return {'exception': ex} diff --git a/nova/endpoint/rackspace.py b/nova/endpoint/rackspace.py index 9208ddab7..605f9b8e0 100644 --- a/nova/endpoint/rackspace.py +++ b/nova/endpoint/rackspace.py @@ -34,7 +34,7 @@ from nova import exception from nova import flags from nova import rpc from nova import utils -from nova.auth import users +from nova.auth import manager from nova.compute import model from nova.compute import network from nova.endpoint import images @@ -78,11 +78,11 @@ class Api(object): def build_context(self, env): rv = {} if env.has_key("HTTP_X_AUTH_TOKEN"): - rv['user'] = users.UserManager.instance().get_user_from_access_key( + rv['user'] = manager.AuthManager().get_user_from_access_key( env['HTTP_X_AUTH_TOKEN'] ) if rv['user']: - rv['project'] = users.UserManager.instance().get_project( + rv['project'] = manager.AuthManager().get_project( rv['user'].name ) return rv diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py index 8500dd0cb..832a4b279 100644 --- a/nova/tests/access_unittest.py +++ b/nova/tests/access_unittest.py @@ -22,7 +22,7 @@ import logging from nova import exception from nova import flags from nova import test -from nova.auth.users import UserManager +from nova.auth import manager from nova.auth import rbac @@ -35,7 +35,7 @@ class AccessTestCase(test.BaseTestCase): super(AccessTestCase, self).setUp() FLAGS.fake_libvirt = True FLAGS.fake_storage = True - um = UserManager.instance() + um = manager.AuthManager() # Make test users try: self.testadmin = um.create_user('testadmin') @@ -79,7 +79,7 @@ class AccessTestCase(test.BaseTestCase): #user is set in each test def tearDown(self): - um = UserManager.instance() + um = manager.AuthManager() # Delete the test project um.delete_project('testproj') # Delete the test user diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py index e5e2afe26..5c26192bd 100644 --- a/nova/tests/api_unittest.py +++ b/nova/tests/api_unittest.py @@ -26,7 +26,7 @@ from twisted.internet import defer from nova import flags from nova import test -from nova.auth import users +from nova.auth import manager from nova.endpoint import api from nova.endpoint import cloud @@ -150,7 +150,7 @@ class ApiEc2TestCase(test.BaseTestCase): def setUp(self): super(ApiEc2TestCase, self).setUp() - self.users = users.UserManager.instance() + self.users = manager.AuthManager() self.cloud = cloud.CloudController() self.host = '127.0.0.1' diff --git a/nova/tests/users_unittest.py b/nova/tests/auth_unittest.py index 301721075..000f6bf17 100644 --- a/nova/tests/users_unittest.py +++ b/nova/tests/auth_unittest.py @@ -25,19 +25,19 @@ import unittest from nova import crypto from nova import flags from nova import test -from nova.auth import users +from nova.auth import manager from nova.endpoint import cloud FLAGS = flags.FLAGS -class UserTestCase(test.BaseTestCase): +class AuthTestCase(test.BaseTestCase): flush_db = False def setUp(self): - super(UserTestCase, self).setUp() + super(AuthTestCase, self).setUp() self.flags(fake_libvirt=True, fake_storage=True) - self.users = users.UserManager.instance() + self.users = manager.AuthManager() def test_001_can_create_users(self): self.users.create_user('test1', 'access', 'secret') diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index b8614fdc8..3abef28a1 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -27,7 +27,7 @@ from xml.etree import ElementTree from nova import flags from nova import rpc from nova import test -from nova.auth import users +from nova.auth import manager from nova.compute import node from nova.endpoint import api from nova.endpoint import cloud @@ -61,15 +61,15 @@ class CloudTestCase(test.BaseTestCase): self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop)) try: - users.UserManager.instance().create_user('admin', 'admin', 'admin') + manager.AuthManager().create_user('admin', 'admin', 'admin') except: pass - admin = users.UserManager.instance().get_user('admin') - project = users.UserManager.instance().create_project('proj', 'admin', 'proj') + admin = manager.AuthManager().get_user('admin') + project = manager.AuthManager().create_project('proj', 'admin', 'proj') self.context = api.APIRequestContext(handler=None,project=project,user=admin) def tearDown(self): - users.UserManager.instance().delete_project('proj') - users.UserManager.instance().delete_user('admin') + manager.AuthManager().delete_project('proj') + manager.AuthManager().delete_user('admin') def test_console_output(self): if FLAGS.fake_libvirt: diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index a822cc1d9..fd0e64724 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -26,7 +26,7 @@ from nova import test from nova import exception from nova.compute.exception import NoMoreAddresses from nova.compute import network -from nova.auth import users +from nova.auth import manager from nova import utils @@ -38,7 +38,7 @@ class NetworkTestCase(test.TrialTestCase): fake_network=True, network_size=32) logging.getLogger().setLevel(logging.DEBUG) - self.manager = users.UserManager.instance() + self.manager = manager.AuthManager() self.dnsmasq = FakeDNSMasq() try: self.manager.create_user('netuser', 'netuser', 'netuser') diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index f47ca7f00..85bcd7c67 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -26,7 +26,7 @@ import tempfile from nova import flags from nova import objectstore from nova import test -from nova.auth import users +from nova.auth import manager FLAGS = flags.FLAGS @@ -57,7 +57,7 @@ class ObjectStoreTestCase(test.BaseTestCase): ca_path=os.path.join(os.path.dirname(__file__), 'CA')) logging.getLogger().setLevel(logging.DEBUG) - self.um = users.UserManager.instance() + self.um = manager.AuthManager() try: self.um.create_user('user1') except: pass @@ -177,7 +177,7 @@ class ObjectStoreTestCase(test.BaseTestCase): # FLAGS.images_path = os.path.join(tempdir, 'images') # FLAGS.ca_path = os.path.join(os.path.dirname(__file__), 'CA') # -# self.users = users.UserManager.instance() +# self.users = manager.AuthManager() # self.app = handler.Application(self.users) # # self.host = '127.0.0.1' diff --git a/run_tests.py b/run_tests.py index eb26459c5..f42d315e0 100644 --- a/run_tests.py +++ b/run_tests.py @@ -57,7 +57,7 @@ from nova.tests.node_unittest import * from nova.tests.objectstore_unittest import * from nova.tests.process_unittest import * from nova.tests.storage_unittest import * -from nova.tests.users_unittest import * +from nova.tests.auth_unittest import * from nova.tests.validator_unittest import * |