summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2010-07-19 13:19:26 -0500
committerVishvananda Ishaya <vishvananda@gmail.com>2010-07-19 13:19:26 -0500
commita3ca587654095ffd4b97103302fb0744e505e332 (patch)
tree5e7ef9e1e7fc7f35ca7c216ebfffea958c9d4d58
parentb09e69c5579526fcc1a08c7e3c3a3c880fa09297 (diff)
downloadnova-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-xbin/nova-api2
-rwxr-xr-xbin/nova-manage10
-rwxr-xr-xbin/nova-objectstore6
-rwxr-xr-xbin/nova-rsapi12
-rw-r--r--nova/auth/ldapdriver.py428
-rw-r--r--nova/auth/manager.py741
-rw-r--r--nova/auth/rbac.py2
-rw-r--r--nova/auth/users.py974
-rw-r--r--nova/cloudpipe/api.py2
-rw-r--r--nova/cloudpipe/pipelib.py4
-rw-r--r--nova/compute/network.py14
-rw-r--r--nova/endpoint/admin.py14
-rwxr-xr-xnova/endpoint/api.py4
-rw-r--r--nova/endpoint/cloud.py6
-rw-r--r--nova/endpoint/rackspace.py6
-rw-r--r--nova/tests/access_unittest.py6
-rw-r--r--nova/tests/api_unittest.py4
-rw-r--r--nova/tests/auth_unittest.py (renamed from nova/tests/users_unittest.py)8
-rw-r--r--nova/tests/cloud_unittest.py12
-rw-r--r--nova/tests/network_unittest.py4
-rw-r--r--nova/tests/objectstore_unittest.py6
-rw-r--r--run_tests.py2
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 *