Merged Vish's work on adding projects to nova

This commit is contained in:
Jesse Andrews
2010-05-30 15:21:34 -07:00
parent c43126ce51
commit c36ea2ce5d
20 changed files with 845 additions and 710 deletions

View File

@@ -1,69 +0,0 @@
# Copyright [2010] [Anso Labs, LLC]
#
# 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.
"""
Simple base set of RBAC rules which map API endpoints to LDAP groups.
For testing accounts, users will always have PM privileges.
"""
# This is logically a RuleSet or some such.
def allow_describe_images(user, project, target_object):
return True
def allow_describe_instances(user, project, target_object):
return True
def allow_describe_addresses(user, project, target_object):
return True
def allow_run_instances(user, project, target_object):
# target_object is a reservation, not an instance
# it needs to include count, type, image, etc.
# First, is the project allowed to use this image
# Second, is this user allowed to launch within this project
# Third, is the count or type within project quota
return True
def allow_terminate_instances(user, project, target_object):
# In a project, the PMs and Sysadmins can terminate
return True
def allow_get_console_output(user, project, target_object):
# If the user launched the instance,
# Or is a sysadmin in the project,
return True
def allow_allocate_address(user, project, target_object):
# There's no security concern in allocation,
# but it can get expensive. Limit to PM and NE.
return True
def allow_associate_address(user, project, target_object):
# project NE only
# In future, will perform a CloudAudit scan first
# (Pass / Fail gate)
return True
def allow_register(user, project, target_object):
return False
def is_allowed(action, user, project, target_object):
return globals()['allow_%s' % action](user, project, target_object)

View File

@@ -22,6 +22,12 @@ import logging
from nova import datastore from nova import datastore
SCOPE_SUBTREE = 1 SCOPE_SUBTREE = 1
MOD_ADD = 0
MOD_DELETE = 1
SUBS = {
'groupOfNames': ['novaProject']
}
class NO_SUCH_OBJECT(Exception): class NO_SUCH_OBJECT(Exception):
@@ -44,6 +50,46 @@ class FakeLDAP(object):
def unbind_s(self): def unbind_s(self):
pass pass
def _paren_groups(self, source):
count = 0
start = 0
result = []
for pos in xrange(len(source)):
if source[pos] == '(':
if count == 0:
start = pos
count += 1
if source[pos] == ')':
count -= 1
if count == 0:
result.append(source[start:pos+1])
def _match_query(self, query, attrs):
inner = query[1:-1]
if inner.startswith('&'):
l, r = self._paren_groups(inner[1:])
return self._match_query(l, attrs) and self._match_query(r, attrs)
if inner.startswith('|'):
l, r = self._paren_groups(inner[1:])
return self._match_query(l, attrs) or self._match_query(r, attrs)
if inner.startswith('!'):
return not self._match_query(query[2:-1], attrs)
(k, sep, v) = inner.partition('=')
return self._match(k, v, attrs)
def _subs(self, v):
if v in SUBS:
return [v] + SUBS[v]
return [v]
def _match(self, k, v, attrs):
if attrs.has_key(k):
for v in self._subs(v):
if (v in attrs[k]):
return True
return False
def search_s(self, dn, scope, query=None, fields=None): def search_s(self, dn, scope, query=None, fields=None):
logging.debug("searching for %s" % dn) logging.debug("searching for %s" % dn)
filtered = {} filtered = {}
@@ -51,12 +97,11 @@ class FakeLDAP(object):
for cn, attrs in d.iteritems(): for cn, attrs in d.iteritems():
if cn[-len(dn):] == dn: if cn[-len(dn):] == dn:
filtered[cn] = attrs filtered[cn] = attrs
objects = filtered
if query: if query:
k,v = query[1:-1].split('=')
objects = {} objects = {}
for cn, attrs in filtered.iteritems(): for cn, attrs in filtered.iteritems():
if attrs.has_key(k) and (v in attrs[k] or if self._match_query(query, attrs):
v == attrs[k]):
objects[cn] = attrs objects[cn] = attrs
if objects == {}: if objects == {}:
raise NO_SUCH_OBJECT() raise NO_SUCH_OBJECT()
@@ -75,7 +120,22 @@ class FakeLDAP(object):
self.keeper['objects'] = d self.keeper['objects'] = d
def delete_s(self, cn): def delete_s(self, cn):
logging.debug("creating for %s" % cn) logging.debug("deleting %s" % cn)
d = self.keeper['objects'] or {} d = self.keeper['objects']
del d[cn] del d[cn]
self.keeper['objects'] = d self.keeper['objects'] = d
def modify_s(self, cn, attr):
logging.debug("modifying %s" % cn)
d = self.keeper['objects']
for cmd, k, v in attr:
logging.debug("command %s" % cmd)
if cmd == MOD_ADD:
d[cn][k].append(v)
else:
d[cn][k].remove(v)
self.keeper['objects'] = d

View File

@@ -1,60 +0,0 @@
# Copyright [2010] [Anso Labs, LLC]
#
# 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.
# LDIF fragment to create group branch under root
#dn: ou=Groups,dc=example,dc=com
#objectclass:organizationalunit
#ou: groups
#description: generic groups branch
# create the itpeople entry
dn: cn=sysadmins,ou=Groups,dc=example,dc=com
objectclass: groupofnames
cn: itpeople
description: IT admin group
# add the group members all of which are
# assumed to exist under Users
#member: cn=micky mouse,ou=people,dc=example,dc=com
member: cn=admin,ou=Users,dc=example,dc=com
dn: cn=netadmins,ou=Groups,dc=example,dc=com
objectclass: groupofnames
cn: netadmins
description: Network admin group
member: cn=admin,ou=Users,dc=example,dc=com
dn: cn=cloudadmins,ou=Groups,dc=example,dc=com
objectclass: groupofnames
cn: cloudadmins
description: Cloud admin group
member: cn=admin,ou=Users,dc=example,dc=com
dn: cn=itsec,ou=Groups,dc=example,dc=com
objectclass: groupofnames
cn: itsec
description: IT security users group
member: cn=admin,ou=Users,dc=example,dc=com
# Example Project Group to demonstrate members
# and project members
dn: cn=myproject,ou=Groups,dc=example,dc=com
objectclass: groupofnames
objectclass: novaProject
cn: myproject
description: My Project Group
member: cn=admin,ou=Users,dc=example,dc=com
projectManager: cn=admin,ou=Users,dc=example,dc=com

View File

@@ -46,9 +46,6 @@ import urllib
import base64 import base64
from nova.exception import Error from nova.exception import Error
_log = logging.getLogger('signer')
logging.getLogger('signer').setLevel(logging.WARN)
class Signer(object): class Signer(object):
""" hacked up code from boto/connection.py """ """ hacked up code from boto/connection.py """
@@ -59,14 +56,13 @@ class Signer(object):
def generate(self, params, verb, server_string, path): def generate(self, params, verb, server_string, path):
if params['SignatureVersion'] == '0': if params['SignatureVersion'] == '0':
t = self._calc_signature_0(params) return self._calc_signature_0(params)
elif params['SignatureVersion'] == '1': if params['SignatureVersion'] == '1':
t = self._calc_signature_1(params) return self._calc_signature_1(params)
elif params['SignatureVersion'] == '2': if params['SignatureVersion'] == '2':
t = self._calc_signature_2(params, verb, server_string, path) return self._calc_signature_2(params, verb, server_string, path)
else: raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
return t
def _get_utf8_value(self, value): def _get_utf8_value(self, value):
if not isinstance(value, str) and not isinstance(value, unicode): if not isinstance(value, str) and not isinstance(value, unicode):
@@ -99,7 +95,7 @@ class Signer(object):
return base64.b64encode(self.hmac.digest()) return base64.b64encode(self.hmac.digest())
def _calc_signature_2(self, params, verb, server_string, path): def _calc_signature_2(self, params, verb, server_string, path):
_log.debug('using _calc_signature_2') logging.debug('using _calc_signature_2')
string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path) string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
if self.hmac_256: if self.hmac_256:
hmac = self.hmac_256 hmac = self.hmac_256
@@ -114,13 +110,13 @@ class Signer(object):
val = self._get_utf8_value(params[key]) val = self._get_utf8_value(params[key])
pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~')) pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
qs = '&'.join(pairs) qs = '&'.join(pairs)
_log.debug('query string: %s' % qs) logging.debug('query string: %s' % qs)
string_to_sign += qs string_to_sign += qs
_log.debug('string_to_sign: %s' % string_to_sign) logging.debug('string_to_sign: %s' % string_to_sign)
hmac.update(string_to_sign) hmac.update(string_to_sign)
b64 = base64.b64encode(hmac.digest()) b64 = base64.b64encode(hmac.digest())
_log.debug('len(b64)=%d' % len(b64)) logging.debug('len(b64)=%d' % len(b64))
_log.debug('base64 encoded digest: %s' % b64) logging.debug('base64 encoded digest: %s' % b64)
return b64 return b64
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -54,32 +54,32 @@ objectidentifier novaOCs novaSchema:4
attributetype ( attributetype (
novaAttrs:1 novaAttrs:1
NAME 'accessKey' NAME 'accessKey'
DESC 'Key for accessing data' DESC 'Key for accessing data'
EQUALITY caseIgnoreMatch EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE SINGLE-VALUE
) )
attributetype ( attributetype (
novaAttrs:2 novaAttrs:2
NAME 'secretKey' NAME 'secretKey'
DESC 'Secret key' DESC 'Secret key'
EQUALITY caseIgnoreMatch EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE SINGLE-VALUE
) )
attributetype ( attributetype (
novaAttrs:3 novaAttrs:3
NAME 'keyFingerprint' NAME 'keyFingerprint'
DESC 'Fingerprint of private key' DESC 'Fingerprint of private key'
EQUALITY caseIgnoreMatch EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE SINGLE-VALUE
) )
attributetype ( attributetype (
@@ -120,7 +120,7 @@ objectClass (
novaOCs:3 novaOCs:3
NAME 'novaProject' NAME 'novaProject'
DESC 'Container for project' DESC 'Container for project'
SUP groupofnames SUP groupOfNames
STRUCTURAL STRUCTURAL
MUST ( cn $ projectManager ) MUST ( cn $ projectManager )
) )

564
nova/auth/users.py Executable file → Normal file
View File

@@ -22,6 +22,7 @@ import datetime
import logging import logging
import os import os
import shutil import shutil
import string
import tempfile import tempfile
import uuid import uuid
import zipfile import zipfile
@@ -40,7 +41,6 @@ from nova import exception
from nova import flags from nova import flags
from nova import crypto from nova import crypto
from nova import utils from nova import utils
import access as simplerbac
from nova import objectstore # for flags from nova import objectstore # for flags
@@ -50,16 +50,8 @@ flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap ser
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') 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_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
flags.DEFINE_string('user_unit', 'Users', 'OID for Users') flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users') flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Projects')
flags.DEFINE_string('ldap_sysadmin',
'cn=sysadmins,ou=Groups,dc=example,dc=com', 'OU for Sysadmins')
flags.DEFINE_string('ldap_netadmin',
'cn=netadmins,ou=Groups,dc=example,dc=com', 'OU for NetAdmins')
flags.DEFINE_string('ldap_cloudadmin',
'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'OU for Cloud Admins')
flags.DEFINE_string('ldap_itsec',
'cn=itsec,ou=Groups,dc=example,dc=com', 'OU for ItSec')
flags.DEFINE_string('credentials_template', flags.DEFINE_string('credentials_template',
utils.abspath('auth/novarc.template'), utils.abspath('auth/novarc.template'),
@@ -71,46 +63,113 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
flags.DEFINE_string('credential_rc_file', 'novarc', flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip') 'Filename of rc in credentials zip')
_log = logging.getLogger('auth') class AuthBase(object):
_log.setLevel(logging.WARN) @classmethod
def safe_id(cls, obj):
"""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"""
class UserError(exception.ApiError):
pass
class InvalidKeyPair(exception.ApiError):
pass
class User(object):
def __init__(self, id, name, access, secret, admin): def __init__(self, id, name, access, secret, admin):
self.manager = UserManager.instance()
self.id = id self.id = id
self.name = name self.name = name
self.access = access self.access = access
self.secret = secret self.secret = secret
self.admin = admin self.admin = admin
self.keeper = datastore.Keeper(prefix="user")
def is_admin(self): def is_admin(self):
"""allows user to see objects from all projects"""
return self.admin return self.admin
def has_role(self, role_type): def is_project_member(self, project):
return self.manager.has_role(self.id, role_type) return UserManager.instance().is_project_member(self, project)
def is_authorized(self, owner_id, action=None): def is_project_manager(self, project):
if self.is_admin() or owner_id == self.id: return UserManager.instance().is_project_manager(self, project)
return True
if action == None:
return False
project = None #(Fixme)
target_object = None # (Fixme, should be passed in)
return simplerbac.is_allowed(action, self, project, target_object)
def get_credentials(self): def generate_rc(self):
rc = self.generate_rc() rc = open(FLAGS.credentials_template).read()
private_key, signed_cert = self.generate_x509_cert() rc = rc % { 'access': self.access,
'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)
self.keeper = datastore.Keeper(prefix="project-")
@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 get_credentials(self, user):
rc = user.generate_rc()
private_key, signed_cert = self.generate_x509_cert(user)
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
zf = os.path.join(tmpdir, "temp.zip") zf = os.path.join(tmpdir, "temp.zip")
@@ -126,50 +185,11 @@ class User(object):
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
return buffer return buffer
def generate_x509_cert(self, user):
return UserManager.instance().generate_x509_cert(user, self)
def generate_rc(self): def __repr__(self):
rc = open(FLAGS.credentials_template).read() return "Project('%s', '%s', '%s', %s)" % (self.id, self.project_manager_id, self.description, self.member_ids)
rc = rc % { 'access': self.access,
'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 self.manager.generate_key_pair(self.id, name)
def generate_x509_cert(self):
return self.manager.generate_x509_cert(self.id)
def create_key_pair(self, name, public_key, fingerprint):
return self.manager.create_key_pair(self.id,
name,
public_key,
fingerprint)
def get_key_pair(self, name):
return self.manager.get_key_pair(self.id, name)
def delete_key_pair(self, name):
return self.manager.delete_key_pair(self.id, name)
def get_key_pairs(self):
return self.manager.get_key_pairs(self.id)
class KeyPair(object):
def __init__(self, name, owner, public_key, fingerprint):
self.manager = UserManager.instance()
self.owner = owner
self.name = name
self.public_key = public_key
self.fingerprint = fingerprint
def delete(self):
return self.manager.delete_key_pair(self.owner, self.name)
class UserManager(object): class UserManager(object):
def __init__(self): def __init__(self):
@@ -193,31 +213,69 @@ class UserManager(object):
except: pass except: pass
return cls._instance return cls._instance
def authenticate(self, params, signature, verb='GET', server_string='127.0.0.1:8773', path='/'): def authenticate(self, access, signature, params, verb='GET', server_string='127.0.0.1:8773', path='/', verify_signature=True):
# TODO: Check for valid timestamp # TODO: Check for valid timestamp
access_key = params['AWSAccessKeyId'] (access_key, sep, project_name) = access.partition(':')
user = self.get_user_from_access_key(access_key) user = self.get_user_from_access_key(access_key)
if user == None: if user == None:
return None raise exception.NotFound('No user found for access key')
# hmac can't handle unicode, so encode ensures that secret isn't unicode if project_name is '':
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path) project_name = user.name
_log.debug('user.secret: %s', user.secret)
_log.debug('expected_signature: %s', expected_signature)
_log.debug('signature: %s', signature)
if signature == expected_signature:
return user
def has_role(self, user, role, project=None): project = self.get_project(project_name)
# Map role to ldap group if project == None:
group = FLAGS.__getitem__("ldap_%s" % role) raise exception.NotFound('No project called %s could be found' % project_name)
with LDAPWrapper() as conn: if not user.is_admin() and not project.has_member(user):
return conn.is_member_of(user, group) raise exception.NotFound('User %s is not a member of project %s' % (user.id, project.id))
if verify_signature:
# 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 add_role(self, user, role, project=None): def create_project(self, name, manager_user, description, member_users=None):
# TODO: Project-specific roles if member_users:
group = FLAGS.__getitem__("ldap_%s" % role) member_users = [User.safe_id(u) for u in member_users]
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.add_to_group(user, group) 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): def get_user(self, uid):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
@@ -231,56 +289,59 @@ class UserManager(object):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.find_users() return conn.find_users()
def create_user(self, uid, access=None, secret=None, admin=False): def create_user(self, user, access=None, secret=None, admin=False, create_project=True):
if access == None: access = str(uuid.uuid4()) if access == None: access = str(uuid.uuid4())
if secret == None: secret = str(uuid.uuid4()) if secret == None: secret = str(uuid.uuid4())
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
u = conn.create_user(uid, access, secret, admin) user = User.safe_id(user)
return u result = conn.create_user(user, access, secret, admin)
if create_project:
conn.create_project(user, user, user)
return result
def delete_user(self, uid): def delete_user(self, user, delete_project=True):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
conn.delete_user(uid) 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, uid, key_name): def generate_key_pair(self, user, key_name):
# generating key pair is slow so delay generation # generating key pair is slow so delay generation
# until after check # until after check
user = User.safe_id(user)
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
if not conn.user_exists(uid): if not conn.user_exists(user):
raise UserError("User " + uid + " doesn't exist") raise exception.NotFound("User %s doesn't exist" % user)
if conn.key_pair_exists(uid, key_name): if conn.key_pair_exists(user, key_name):
raise InvalidKeyPair("The keypair '" + raise exception.Duplicate("The keypair %s already exists" % key_name)
key_name +
"' already exists.",
"Duplicate")
private_key, public_key, fingerprint = crypto.generate_key_pair() private_key, public_key, fingerprint = crypto.generate_key_pair()
self.create_key_pair(uid, key_name, public_key, fingerprint) self.create_key_pair(User.safe_id(user), key_name, public_key, fingerprint)
return private_key, fingerprint return private_key, fingerprint
def create_key_pair(self, uid, key_name, public_key, fingerprint): def create_key_pair(self, user, key_name, public_key, fingerprint):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.create_key_pair(uid, key_name, public_key, fingerprint) return conn.create_key_pair(User.safe_id(user), key_name, public_key, fingerprint)
def get_key_pair(self, uid, key_name): def get_key_pair(self, user, key_name):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.find_key_pair(uid, key_name) return conn.find_key_pair(User.safe_id(user), key_name)
def get_key_pairs(self, uid): def get_key_pairs(self, user):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.find_key_pairs(uid) return conn.find_key_pairs(User.safe_id(user))
def delete_key_pair(self, uid, key_name): def delete_key_pair(self, user, key_name):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
conn.delete_key_pair(uid, key_name) conn.delete_key_pair(User.safe_id(user), key_name)
def get_signed_zip(self, uid): def generate_x509_cert(self, user, project):
user = self.get_user(uid) (private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(User.safe_id(user)))
return user.get_credentials()
def generate_x509_cert(self, uid):
(private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
# TODO - This should be async call back to the cloud controller # TODO - This should be async call back to the cloud controller
signed_cert = crypto.sign_csr(csr, uid) signed_cert = crypto.sign_csr(csr, Project.safe_id(project))
return (private_key, signed_cert) return (private_key, signed_cert)
def sign_cert(self, csr, uid): def sign_cert(self, csr, uid):
@@ -328,28 +389,53 @@ class LDAPWrapper(object):
return [x[1] for x in res] return [x[1] for x in res]
def find_users(self): def find_users(self):
attrs = self.find_objects(FLAGS.ldap_subtree, '(objectclass=novaUser)') attrs = self.find_objects(FLAGS.user_ldap_subtree, '(objectclass=novaUser)')
return [self.__to_user(attr) for attr in attrs] return [self.__to_user(attr) for attr in attrs]
def find_key_pairs(self, uid): def find_key_pairs(self, uid):
dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree) attrs = self.find_objects(self.__uid_to_dn(uid), '(objectclass=novaKeyPair)')
attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
return [self.__to_key_pair(uid, attr) for attr in attrs] return [self.__to_key_pair(uid, attr) for attr in attrs]
def find_user(self, name): def find_projects(self):
dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree) attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)')
attr = self.find_object(dn, '(objectclass=novaUser)') return [self.__to_project(attr) for attr in attrs]
def find_groups_with_member(self, tree, dn):
attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(member=%s))' % dn )
return [self.__to_group(attr) for attr in attrs]
def find_user(self, uid):
attr = self.find_object(self.__uid_to_dn(uid), '(objectclass=novaUser)')
return self.__to_user(attr) 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): def user_exists(self, name):
return self.find_user(name) != None return self.find_user(name) != None
def find_key_pair(self, uid, key_name): def key_pair_exists(self, uid, key_name):
dn = 'cn=%s,uid=%s,%s' % (key_name, return self.find_key_pair(uid, key_name) != None
uid,
FLAGS.ldap_subtree) def project_exists(self, name):
attr = self.find_object(dn, '(objectclass=novaKeyPair)') return self.find_project(name) != None
return self.__to_key_pair(uid, attr)
def group_exists(self, dn):
return self.find_group(dn) != None
def delete_key_pairs(self, uid): def delete_key_pairs(self, uid):
keys = self.find_key_pairs(uid) keys = self.find_key_pairs(uid)
@@ -357,12 +443,9 @@ class LDAPWrapper(object):
for key in keys: for key in keys:
self.delete_key_pair(uid, key.name) self.delete_key_pair(uid, key.name)
def key_pair_exists(self, uid, key_name):
return self.find_key_pair(uid, key_name) != None
def create_user(self, name, access_key, secret_key, is_admin): def create_user(self, name, access_key, secret_key, is_admin):
if self.user_exists(name): if self.user_exists(name):
raise UserError("LDAP user " + name + " already exists") raise exception.Duplicate("LDAP user %s already exists" % name)
attr = [ attr = [
('objectclass', ['person', ('objectclass', ['person',
'organizationalPerson', 'organizationalPerson',
@@ -376,22 +459,115 @@ class LDAPWrapper(object):
('accessKey', [access_key]), ('accessKey', [access_key]),
('isAdmin', [str(is_admin).upper()]), ('isAdmin', [str(is_admin).upper()]),
] ]
self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree), self.conn.add_s(self.__uid_to_dn(name), attr)
attr)
return self.__to_user(dict(attr)) return self.__to_user(dict(attr))
def create_project(self, name, project_manager): def create_project(self, name, manager_uid, description, member_uids = None):
# PM can be user object or string containing DN if self.project_exists(name):
pass 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)
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 is_member_of(self, name, group): def add_to_project(self, uid, project_id):
return True dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.add_to_group(uid, dn)
def add_to_group(self, name, group): def remove_from_project(self, uid, project_id):
pass dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.remove_from_group(uid, dn)
def remove_from_group(self, name, group): def is_in_project(self, uid, project_id):
pass dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.is_in_group(uid, dn)
def __create_group(self, group_dn, name, uid, description, member_uids = None):
if self.group_exists(name):
raise exception.Duplicate("Group can't be created because group %s already exists" % name)
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 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,))
attr = [
(ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))
]
try:
self.conn.modify_s(group_dn, attr)
except ldap.OBJECT_CLASS_VIOLATION:
logging.debug("Attempted to remove the last member of a group. Deleting the group instead.")
self.delete_group(group_dn)
def remove_from_all(self, uid):
# FIXME(vish): what if deleted user is a project manager?
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)
attr = [
(ldap.MOD_DELETE, 'member', dn)
]
projects = self.find_groups_with_member(FLAGS.project_ldap_subtree, dn)
for project in projects:
self.conn.modify_s('cn=%s,%s' % (project.id, FLAGS.project_ldap_subtree), attr)
def create_key_pair(self, uid, key_name, public_key, fingerprint): def create_key_pair(self, uid, key_name, public_key, fingerprint):
"""create's a public key in the directory underneath the user""" """create's a public key in the directory underneath the user"""
@@ -403,41 +579,46 @@ class LDAPWrapper(object):
('sshPublicKey', [public_key]), ('sshPublicKey', [public_key]),
('keyFingerprint', [fingerprint]), ('keyFingerprint', [fingerprint]),
] ]
self.conn.add_s('cn=%s,uid=%s,%s' % (key_name, self.conn.add_s('cn=%s,%s' % (key_name,
uid, self.__uid_to_dn(uid)),
FLAGS.ldap_subtree), attr)
attr)
return self.__to_key_pair(uid, dict(attr)) return self.__to_key_pair(uid, dict(attr))
def find_user_by_access_key(self, access): def find_user_by_access_key(self, access):
query = '(' + 'accessKey' + '=' + access + ')' query = '(accessKey=%s)' % access
dn = FLAGS.ldap_subtree dn = FLAGS.user_ldap_subtree
return self.__to_user(self.find_object(dn, query)) 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): def delete_key_pair(self, uid, key_name):
if not self.key_pair_exists(uid, key_name): if not self.key_pair_exists(uid, key_name):
raise UserError("Key Pair " + raise exception.NotFound("Key Pair %s doesn't exist for user %s" %
key_name + (key_name, uid))
" doesn't exist for user " +
uid)
self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid, self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid,
FLAGS.ldap_subtree)) FLAGS.user_ldap_subtree))
def delete_user(self, name): def delete_group(self, group_dn):
if not self.user_exists(name): if not self.group_exists(group_dn):
raise UserError("User " + raise exception.NotFound("Group at dn %s doesn't exist" % group_dn)
name + self.conn.delete_s(group_dn)
" doesn't exist")
self.delete_key_pairs(name) def delete_project(self, name):
self.conn.delete_s('uid=%s,%s' % (name, project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
FLAGS.ldap_subtree)) self.delete_group(project_dn)
def __to_user(self, attr): def __to_user(self, attr):
if attr == None: if attr == None:
return None return None
return User( return User(
id = attr['uid'][0], id = attr['uid'][0],
name = attr['uid'][0], name = attr['cn'][0],
access = attr['accessKey'][0], access = attr['accessKey'][0],
secret = attr['secretKey'][0], secret = attr['secretKey'][0],
admin = (attr['isAdmin'][0] == 'TRUE') admin = (attr['isAdmin'][0] == 'TRUE')
@@ -447,8 +628,35 @@ class LDAPWrapper(object):
if attr == None: if attr == None:
return None return None
return KeyPair( return KeyPair(
owner = owner, id = attr['cn'][0],
name = attr['cn'][0], owner_id = owner,
public_key = attr['sshPublicKey'][0], public_key = attr['sshPublicKey'][0],
fingerprint = attr['keyFingerprint'][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)

View File

@@ -264,6 +264,12 @@ class SqliteKeeper(object):
group.remove(value) group.remove(value)
self[item] = group self[item] = group
def set_members(self, item):
group = self[item]
if not group:
group = []
return group
def set_fetch(self, item): def set_fetch(self, item):
# TODO(termie): I don't really know what set_fetch is supposed to do # TODO(termie): I don't really know what set_fetch is supposed to do
group = self[item] group = self[item]
@@ -354,6 +360,10 @@ class RedisKeeper(object):
item = slugify(item, self.prefix) item = slugify(item, self.prefix)
return Redis.instance().srem(item, json.dumps(value)) return Redis.instance().srem(item, json.dumps(value))
def set_members(self, item):
item = slugify(item, self.prefix)
return [json.loads(v) for v in Redis.instance().smembers(item)]
def set_fetch(self, item): def set_fetch(self, item):
item = slugify(item, self.prefix) item = slugify(item, self.prefix)
for obj in Redis.instance().sinter([item]): for obj in Redis.instance().sinter([item]):

View File

@@ -61,7 +61,6 @@ class AdminController(object):
API Controller for users, node status, and worker mgmt. API Controller for users, node status, and worker mgmt.
Trivial admin_only wrapper will be replaced with RBAC, Trivial admin_only wrapper will be replaced with RBAC,
allowing project managers to administer project users. allowing project managers to administer project users.
""" """
def __init__(self, user_manager, node_manager=None): def __init__(self, user_manager, node_manager=None):
self.user_manager = user_manager self.user_manager = user_manager
@@ -87,9 +86,7 @@ class AdminController(object):
def register_user(self, _context, name, **_kwargs): def register_user(self, _context, name, **_kwargs):
""" Creates a new user, and returns generated credentials. """ Creates a new user, and returns generated credentials.
""" """
self.user_manager.create_user(name) return user_dict(self.user_manager.create_user(name))
return user_dict(self.user_manager.get_user(name))
@admin_only @admin_only
def deregister_user(self, _context, name, **_kwargs): def deregister_user(self, _context, name, **_kwargs):
@@ -102,13 +99,16 @@ class AdminController(object):
return True return True
@admin_only @admin_only
def generate_x509_for_user(self, _context, name, **_kwargs): def generate_x509_for_user(self, _context, name, project=None, **kwargs):
"""Generates and returns an x509 certificate for a single user. """Generates and returns an x509 certificate for a single user.
Is usually called from a client that will wrap this with Is usually called from a client that will wrap this with
access and secret key info, and return a zip file. access and secret key info, and return a zip file.
""" """
if project is None:
project = name
project = self.user_manager.get_project(project)
user = self.user_manager.get_user(name) user = self.user_manager.get_user(name)
return user_dict(user, base64.b64encode(user.get_credentials())) return user_dict(user, base64.b64encode(project.get_credentials(user)))
@admin_only @admin_only
def describe_nodes(self, _context, **_kwargs): def describe_nodes(self, _context, **_kwargs):
@@ -128,4 +128,3 @@ class AdminController(object):
"""Returns status info for single node. """Returns status info for single node.
""" """
return node_dict(self.node_manager.get_node(name)) return node_dict(self.node_manager.get_node(name))

View File

@@ -41,7 +41,6 @@ from nova.auth import users
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
_log = logging.getLogger("api") _log = logging.getLogger("api")
_log.setLevel(logging.DEBUG) _log.setLevel(logging.DEBUG)
@@ -63,9 +62,10 @@ def _underscore_to_xmlcase(str):
class APIRequestContext(object): class APIRequestContext(object):
def __init__(self, handler, user): def __init__(self, handler, user, project):
self.handler = handler self.handler = handler
self.user = user self.user = user
self.project = project
self.request_id = ''.join( self.request_id = ''.join(
[random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
for x in xrange(20)] for x in xrange(20)]
@@ -73,13 +73,11 @@ class APIRequestContext(object):
class APIRequest(object): class APIRequest(object):
def __init__(self, handler, controller, action): def __init__(self, controller, action):
self.handler = handler
self.controller = controller self.controller = controller
self.action = action self.action = action
def send(self, user, **kwargs): def send(self, context, **kwargs):
context = APIRequestContext(self.handler, user)
try: try:
method = getattr(self.controller, method = getattr(self.controller,
@@ -227,7 +225,6 @@ class MetadataRequestHandler(tornado.web.RequestHandler):
self.print_data(data) self.print_data(data)
self.finish() self.finish()
class APIRequestHandler(tornado.web.RequestHandler): class APIRequestHandler(tornado.web.RequestHandler):
def get(self, controller_name): def get(self, controller_name):
self.execute(controller_name) self.execute(controller_name)
@@ -257,7 +254,7 @@ class APIRequestHandler(tornado.web.RequestHandler):
# Get requested action and remove authentication args for final request. # Get requested action and remove authentication args for final request.
try: try:
action = args.pop('Action')[0] action = args.pop('Action')[0]
args.pop('AWSAccessKeyId') access = args.pop('AWSAccessKeyId')[0]
args.pop('SignatureMethod') args.pop('SignatureMethod')
args.pop('SignatureVersion') args.pop('SignatureVersion')
args.pop('Version') args.pop('Version')
@@ -266,15 +263,18 @@ class APIRequestHandler(tornado.web.RequestHandler):
raise tornado.web.HTTPError(400) raise tornado.web.HTTPError(400)
# Authenticate the request. # Authenticate the request.
user = self.application.user_manager.authenticate( try:
auth_params, (user, project) = users.UserManager.instance().authenticate(
signature, access,
self.request.method, signature,
self.request.host, auth_params,
self.request.path self.request.method,
) self.request.host,
self.request.path
)
if not user: except exception.Error, ex:
logging.debug("Authentication Failure: %s" % ex)
raise tornado.web.HTTPError(403) raise tornado.web.HTTPError(403)
_log.debug('action: %s' % action) _log.debug('action: %s' % action)
@@ -282,8 +282,9 @@ class APIRequestHandler(tornado.web.RequestHandler):
for key, value in args.items(): for key, value in args.items():
_log.debug('arg: %s\t\tval: %s' % (key, value)) _log.debug('arg: %s\t\tval: %s' % (key, value))
request = APIRequest(self, controller, action) request = APIRequest(controller, action)
d = request.send(user, **args) context = APIRequestContext(self, user, project)
d = request.send(context, **args)
# d.addCallback(utils.debug) # d.addCallback(utils.debug)
# TODO: Wrap response in AWS XML format # TODO: Wrap response in AWS XML format

View File

@@ -19,6 +19,7 @@ dispatched to other nodes via AMQP RPC. State is via distributed
datastore. datastore.
""" """
import base64
import json import json
import logging import logging
import os import os
@@ -58,7 +59,6 @@ class CloudController(object):
sent to the other nodes. sent to the other nodes.
""" """
def __init__(self): def __init__(self):
self._instances = datastore.Keeper(FLAGS.instances_prefix)
self.instdir = model.InstanceDirectory() self.instdir = model.InstanceDirectory()
self.network = network.NetworkController() self.network = network.NetworkController()
self.setup() self.setup()
@@ -97,7 +97,7 @@ class CloudController(object):
return self.instdir.by_ip(ip) return self.instdir.by_ip(ip)
def get_metadata(self, ip): def get_metadata(self, ip):
i = self.instdir.by_ip(ip) i = self.get_instance_by_ip(ip)
if i is None: if i is None:
return None return None
if i['key_name']: if i['key_name']:
@@ -145,7 +145,6 @@ class CloudController(object):
data['product-codes'] = i['product_codes'] data['product-codes'] = i['product_codes']
return data return data
def describe_availability_zones(self, context, **kwargs): def describe_availability_zones(self, context, **kwargs):
return {'availabilityZoneInfo': [{'zoneName': 'nova', return {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]} 'zoneState': 'available'}]}
@@ -207,11 +206,9 @@ class CloudController(object):
def get_console_output(self, context, instance_id, **kwargs): def get_console_output(self, context, instance_id, **kwargs):
# instance_id is passed in as a list of instances # instance_id is passed in as a list of instances
instance = self.instdir.get(instance_id[0]) instance = self._get_instance(context, instance_id[0])
if instance['state'] == 'pending': if instance['state'] == 'pending':
raise exception.ApiError('Cannot get output for pending instance') raise exception.ApiError('Cannot get output for pending instance')
if not context.user.is_authorized(instance.get('owner_id', None)):
raise exception.ApiError('Not authorized to view output')
return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']), return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "get_console_output", {"method": "get_console_output",
"args" : {"instance_id": instance_id[0]}}) "args" : {"instance_id": instance_id[0]}})
@@ -225,7 +222,7 @@ class CloudController(object):
def describe_volumes(self, context, **kwargs): def describe_volumes(self, context, **kwargs):
volumes = [] volumes = []
for volume in self.volumes: for volume in self.volumes:
if context.user.is_authorized(volume.get('user_id', None)): if context.user.is_admin() or volume['project_id'] == context.project.id:
v = self.format_volume(context, volume) v = self.format_volume(context, volume)
volumes.append(v) volumes.append(v)
return defer.succeed({'volumeSet': volumes}) return defer.succeed({'volumeSet': volumes})
@@ -252,36 +249,59 @@ class CloudController(object):
"args" : {"size": size, "args" : {"size": size,
"user_id": context.user.id}}) "user_id": context.user.id}})
def _format_result(result): def _format_result(result):
volume = self._get_volume(result['result']) volume = self._get_volume(context, result['result'])
return {'volumeSet': [self.format_volume(context, volume)]} return {'volumeSet': [self.format_volume(context, volume)]}
res.addCallback(_format_result) res.addCallback(_format_result)
return res return res
def _get_by_id(self, nodes, id): def _convert_address(self, network_address):
if nodes == {}: # FIXME(vish): this should go away when network.py stores info properly
raise exception.NotFound("%s not found" % id) address = {}
for node_name, node in nodes.iteritems(): address['public_ip'] == network_address[u'address']
if node.has_key(id): address['user_id'] == network_address[u'user_id']
return node_name, node[id] address['project_id'] == network_address.get(u'project_id', address['user_id'])
raise exception.NotFound("%s not found" % id) address['instance_id'] == network_address.get(u'instance_id', None)
return address
def _get_volume(self, volume_id): def _get_address(self, context, public_ip):
# right now all addresses are allocated locally
# FIXME(vish) this should move into network.py
for network_address in self.network.describe_addresses():
if network_address[u'address'] == public_ip:
address = self._convert_address(network_address)
if context.user.is_admin() or address['project_id'] == context.project.id:
return address
raise exception.NotFound("Address at ip %s not found" % public_ip)
def _get_image(self, context, image_id):
"""passes in context because
objectstore does its own authorization"""
result = images.list(context, [image_id])
if not result:
raise exception.NotFound('Image %s could not be found' % image_id)
image = result[0]
return image
def _get_instance(self, context, instance_id):
for instance in self.instances:
if instance['instance_id'] == instance_id:
if context.user.is_admin() or instance['project_id'] == context.project.id:
return instance
raise exception.NotFound('Instance %s could not be found' % instance_id)
def _get_volume(self, context, volume_id):
for volume in self.volumes: for volume in self.volumes:
if volume['volume_id'] == volume_id: if volume['volume_id'] == volume_id:
return volume if context.user.is_admin() or volume['project_id'] == context.project.id:
return volume
raise exception.NotFound('Volume %s could not be found' % volume_id)
def attach_volume(self, context, volume_id, instance_id, device, **kwargs): def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume = self._get_volume(volume_id) volume = self._get_volume(context, volume_id)
storage_node = volume['node_name'] storage_node = volume['node_name']
# TODO: (joshua) Fix volumes to store creator id # TODO: (joshua) Fix volumes to store creator id
if not context.user.is_authorized(volume.get('user_id', None)): instance = self._get_instance(context, instance_id)
raise exception.ApiError("%s not authorized for %s" %
(context.user.id, volume_id))
instance = self.instdir.get(instance_id)
compute_node = instance['node_name'] compute_node = instance['node_name']
if not context.user.is_authorized(instance.get('owner_id', None)):
raise exception.ApiError(message="%s not authorized for %s" %
(context.user.id, instance_id))
aoe_device = volume['aoe_device'] aoe_device = volume['aoe_device']
# Needs to get right node controller for attaching to # Needs to get right node controller for attaching to
# TODO: Maybe have another exchange that goes to everyone? # TODO: Maybe have another exchange that goes to everyone?
@@ -297,24 +317,17 @@ class CloudController(object):
"mountpoint" : device}}) "mountpoint" : device}})
return defer.succeed(True) return defer.succeed(True)
def detach_volume(self, context, volume_id, **kwargs): def detach_volume(self, context, volume_id, **kwargs):
# TODO(joshua): Make sure the updated state has been received first # TODO(joshua): Make sure the updated state has been received first
volume = self._get_volume(volume_id) volume = self._get_volume(context, volume_id)
storage_node = volume['node_name'] storage_node = volume['node_name']
if not context.user.is_authorized(volume.get('user_id', None)):
raise exception.ApiError("%s not authorized for %s" %
(context.user.id, volume_id))
if 'instance_id' in volume.keys(): if 'instance_id' in volume.keys():
instance_id = volume['instance_id'] instance_id = volume['instance_id']
try: try:
instance = self.instdir.get(instance_id) instance = self._get_instance(context, instance_id)
compute_node = instance['node_name'] compute_node = instance['node_name']
mountpoint = volume['mountpoint'] mountpoint = volume['mountpoint']
if not context.user.is_authorized(
instance.get('owner_id', None)):
raise exception.ApiError(
"%s not authorized for %s" %
(context.user.id, instance_id))
rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node), rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
{"method": "detach_volume", {"method": "detach_volume",
"args" : {"instance_id": instance_id, "args" : {"instance_id": instance_id,
@@ -332,16 +345,16 @@ class CloudController(object):
return [{str: x} for x in lst] return [{str: x} for x in lst]
def describe_instances(self, context, **kwargs): def describe_instances(self, context, **kwargs):
return defer.succeed(self.format_instances(context.user)) return defer.succeed(self._format_instances(context))
def format_instances(self, user, reservation_id = None): def _format_instances(self, context, reservation_id = None):
if self.instances == {}: if self.instances == {}:
return {'reservationSet': []} return {'reservationSet': []}
reservations = {} reservations = {}
for inst in self.instances: for inst in self.instances:
instance = inst.values()[0] instance = inst.values()[0]
res_id = instance.get('reservation_id', 'Unknown') res_id = instance.get('reservation_id', 'Unknown')
if (user.is_authorized(instance.get('owner_id', None)) if ((context.user.is_admin() or context.project.id == instance['project_id'])
and (reservation_id == None or reservation_id == res_id)): and (reservation_id == None or reservation_id == res_id)):
i = {} i = {}
i['instance_id'] = instance.get('instance_id', None) i['instance_id'] = instance.get('instance_id', None)
@@ -357,7 +370,7 @@ class CloudController(object):
i['public_dns_name'] = i['private_dns_name'] i['public_dns_name'] = i['private_dns_name']
i['dns_name'] = instance.get('dns_name', None) i['dns_name'] = instance.get('dns_name', None)
i['key_name'] = instance.get('key_name', None) i['key_name'] = instance.get('key_name', None)
if user.is_admin(): if context.user.is_admin():
i['key_name'] = '%s (%s, %s)' % (i['key_name'], i['key_name'] = '%s (%s, %s)' % (i['key_name'],
instance.get('owner_id', None), instance.get('node_name','')) instance.get('owner_id', None), instance.get('node_name',''))
i['product_codes_set'] = self._convert_to_set( i['product_codes_set'] = self._convert_to_set(
@@ -369,7 +382,7 @@ class CloudController(object):
if not reservations.has_key(res_id): if not reservations.has_key(res_id):
r = {} r = {}
r['reservation_id'] = res_id r['reservation_id'] = res_id
r['owner_id'] = instance.get('owner_id', None) r['owner_id'] = instance.get('project_id', None)
r['group_set'] = self._convert_to_set( r['group_set'] = self._convert_to_set(
instance.get('groups', None), 'group_id') instance.get('groups', None), 'group_id')
r['instances_set'] = [] r['instances_set'] = []
@@ -382,52 +395,52 @@ class CloudController(object):
def describe_addresses(self, context, **kwargs): def describe_addresses(self, context, **kwargs):
return self.format_addresses(context.user) return self.format_addresses(context.user)
def format_addresses(self, user): def format_addresses(self, context):
addresses = [] addresses = []
# TODO(vish): move authorization checking into network.py # TODO(vish): move authorization checking into network.py
for address_record in self.network.describe_addresses( for network_address in self.network.describe_addresses(type=network.PublicNetwork):
type=network.PublicNetwork):
#logging.debug(address_record) #logging.debug(address_record)
if user.is_authorized(address_record[u'user_id']): address = self._convert_address(network_address)
address = { address_rv = {
'public_ip': address_record[u'address'], 'public_ip': address['public_ip'],
'instance_id' : address_record.get(u'instance_id', 'free') 'instance_id' : address.get('instance_id', 'free')
} }
# FIXME: add another field for user id # FIXME: add another field for user id
if user.is_admin(): if context.user.is_admin():
address['instance_id'] = "%s (%s)" % ( address_rv['instance_id'] = "%s (%s, %s)" % (
address['instance_id'], address['instance_id'],
address_record[u'user_id'], address['user_id'],
) address['project_id'],
addresses.append(address) )
addresses.append(address_rv)
# logging.debug(addresses) # logging.debug(addresses)
return {'addressesSet': addresses} return {'addressesSet': addresses}
def allocate_address(self, context, **kwargs): def allocate_address(self, context, **kwargs):
# TODO: Verify user is valid?
kwargs['owner_id'] = context.user.id
(address,network_name) = self.network.allocate_address( (address,network_name) = self.network.allocate_address(
context.user.id, type=network.PublicNetwork) context.user.id, context.project_id, type=network.PublicNetwork)
return defer.succeed({'addressSet': [{'publicIp' : address}]}) return defer.succeed({'addressSet': [{'publicIp' : address}]})
def release_address(self, context, **kwargs): def release_address(self, context, public_ip, **kwargs):
self.network.deallocate_address(kwargs.get('public_ip', None)) address = self._get_address(public_ip)
return defer.succeed({'releaseResponse': ["Address released."]}) return defer.succeed({'releaseResponse': ["Address released."]})
def associate_address(self, context, instance_id, **kwargs): def associate_address(self, context, instance_id, **kwargs):
instance = self.instdir.get(instance_id) instance = self._get_instance(context, instance_id)
rv = self.network.associate_address( rv = self.network.associate_address(
kwargs['public_ip'], kwargs['public_ip'],
instance['private_dns_name'], instance['private_dns_name'],
instance_id) instance_id)
return defer.succeed({'associateResponse': ["Address associated."]}) return defer.succeed({'associateResponse': ["Address associated."]})
def disassociate_address(self, context, **kwargs): def disassociate_address(self, context, public_ip, **kwargs):
rv = self.network.disassociate_address(kwargs['public_ip']) address = self._get_address(public_ip)
rv = self.network.disassociate_address(public_ip)
# TODO - Strip the IP from the instance # TODO - Strip the IP from the instance
return rv return defer.succeed({'disassociateResponse': ["Address disassociated."]})
def run_instances(self, context, **kwargs): def run_instances(self, context, **kwargs):
image = self._get_image(context, kwargs['image_id'])
logging.debug("Going to run instances...") logging.debug("Going to run instances...")
reservation_id = utils.generate_uid('r') reservation_id = utils.generate_uid('r')
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
@@ -449,11 +462,14 @@ class CloudController(object):
inst['launch_time'] = launch_time inst['launch_time'] = launch_time
inst['key_data'] = key_data or '' inst['key_data'] = key_data or ''
inst['key_name'] = kwargs.get('key_name', '') inst['key_name'] = kwargs.get('key_name', '')
inst['owner_id'] = context.user.id inst['user_id'] = context.user.id
inst['project_id'] = context.project.id
inst['mac_address'] = utils.generate_mac() inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num inst['ami_launch_index'] = num
address, _netname = self.network.allocate_address( address, _netname = self.network.allocate_address(
inst['owner_id'], mac=inst['mac_address']) user_id=inst['user_id'],
project_id=inst['project_id'],
mac=inst['mac_address'])
network = self.network.get_users_network(str(context.user.id)) network = self.network.get_users_network(str(context.user.id))
inst['network_str'] = json.dumps(network.to_dict()) inst['network_str'] = json.dumps(network.to_dict())
inst['bridge_name'] = network.bridge_name inst['bridge_name'] = network.bridge_name
@@ -466,82 +482,77 @@ class CloudController(object):
logging.debug("Casting to node for %s's instance with IP of %s" % logging.debug("Casting to node for %s's instance with IP of %s" %
(context.user.name, inst['private_dns_name'])) (context.user.name, inst['private_dns_name']))
# TODO: Make the NetworkComputeNode figure out the network name from ip. # TODO: Make the NetworkComputeNode figure out the network name from ip.
return defer.succeed(self.format_instances( return defer.succeed(self._format_instances(
context.user, reservation_id)) context.user, reservation_id))
def terminate_instances(self, context, instance_id, **kwargs): def terminate_instances(self, context, instance_id, **kwargs):
logging.debug("Going to start terminating instances") logging.debug("Going to start terminating instances")
# TODO: return error if not authorized
for i in instance_id: for i in instance_id:
logging.debug("Going to try and terminate %s" % i) logging.debug("Going to try and terminate %s" % i)
instance = self.instdir.get(i) try:
#if instance['state'] == 'pending': instance = self._get_instance(context, i)
# raise exception.ApiError('Cannot terminate pending instance') except exception.NotFound:
if context.user.is_authorized(instance.get('owner_id', None)): logging.warning("Instance %s was not found during terminate" % i)
continue
try:
self.network.disassociate_address(
instance.get('public_dns_name', 'bork'))
except:
pass
if instance.get('private_dns_name', None):
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
try: try:
self.network.disassociate_address( self.network.deallocate_address(instance.get('private_dns_name', None))
instance.get('public_dns_name', 'bork')) except Exception, _err:
except:
pass pass
if instance.get('private_dns_name', None): if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None)) rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
try:
self.network.deallocate_address(instance.get('private_dns_name', None))
except Exception, _err:
pass
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "terminate_instance", {"method": "terminate_instance",
"args" : {"instance_id": i}}) "args" : {"instance_id": i}})
else: else:
instance.destroy() instance.destroy()
return defer.succeed(True) return defer.succeed(True)
def reboot_instances(self, context, instance_id, **kwargs): def reboot_instances(self, context, instance_id, **kwargs):
# TODO: return error if not authorized """instance_id is a list of instance ids"""
for i in instance_id: for i in instance_id:
instance = self.instdir.get(i) instance = self._get_instance(context, i)
if instance['state'] == 'pending': if instance['state'] == 'pending':
raise exception.ApiError('Cannot reboot pending instance') raise exception.ApiError('Cannot reboot pending instance')
if context.user.is_authorized(instance.get('owner_id', None)): rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
{"method": "reboot_instance", {"method": "reboot_instance",
"args" : {"instance_id": i}}) "args" : {"instance_id": i}})
return defer.succeed(True) return defer.succeed(True)
def delete_volume(self, context, volume_id, **kwargs): def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized # TODO: return error if not authorized
volume = self._get_volume(volume_id) volume = self._get_volume(context, volume_id)
storage_node = volume['node_name'] storage_node = volume['node_name']
if context.user.is_authorized(volume.get('user_id', None)): rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node), {"method": "delete_volume",
{"method": "delete_volume", "args" : {"volume_id": volume_id}})
"args" : {"volume_id": volume_id}})
return defer.succeed(True) return defer.succeed(True)
def describe_images(self, context, image_id=None, **kwargs): def describe_images(self, context, image_id=None, **kwargs):
imageSet = images.list(context.user) # The objectstore does its own authorization for describe
if not image_id is None: imageSet = images.list(context, image_id)
imageSet = [i for i in imageSet if i['imageId'] in image_id]
return defer.succeed({'imagesSet': imageSet}) return defer.succeed({'imagesSet': imageSet})
def deregister_image(self, context, image_id, **kwargs): def deregister_image(self, context, image_id, **kwargs):
images.deregister(context.user, image_id) # FIXME: should the objectstore be doing these authorization checks?
images.deregister(context, image_id)
return defer.succeed({'imageId': image_id}) return defer.succeed({'imageId': image_id})
def register_image(self, context, image_location=None, **kwargs): def register_image(self, context, image_location=None, **kwargs):
# FIXME: should the objectstore be doing these authorization checks?
if image_location is None and kwargs.has_key('name'): if image_location is None and kwargs.has_key('name'):
image_location = kwargs['name'] image_location = kwargs['name']
image_id = images.register(context, image_location)
image_id = images.register(context.user, image_location)
logging.debug("Registered %s as %s" % (image_location, image_id)) logging.debug("Registered %s as %s" % (image_location, image_id))
return defer.succeed({'imageId': image_id}) return defer.succeed({'imageId': image_id})
def modify_image_attribute(self, context, image_id, def modify_image_attribute(self, context, image_id, attribute, operation_type, **kwargs):
attribute, operation_type, **kwargs):
if attribute != 'launchPermission': if attribute != 'launchPermission':
raise exception.ApiError('only launchPermission is supported') raise exception.ApiError('only launchPermission is supported')
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':

View File

@@ -31,9 +31,8 @@ from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
def modify(context, image_id, operation):
def modify(user, image_id, operation): conn(context).make_request(
conn(user).make_request(
method='POST', method='POST',
bucket='_images', bucket='_images',
query_args=qs({'image_id': image_id, 'operation': operation})) query_args=qs({'image_id': image_id, 'operation': operation}))
@@ -41,11 +40,11 @@ def modify(user, image_id, operation):
return True return True
def register(user, image_location): def register(context, image_location):
""" rpc call to register a new image based from a manifest """ """ rpc call to register a new image based from a manifest """
image_id = utils.generate_uid('ami') image_id = utils.generate_uid('ami')
conn(user).make_request( conn(context).make_request(
method='PUT', method='PUT',
bucket='_images', bucket='_images',
query_args=qs({'image_location': image_location, query_args=qs({'image_location': image_location,
@@ -53,32 +52,32 @@ def register(user, image_location):
return image_id return image_id
def list(context, filter_list=[]):
def list(user, filter_list=[]):
""" return a list of all images that a user can see """ return a list of all images that a user can see
optionally filtered by a list of image_id """ optionally filtered by a list of image_id """
# FIXME: send along the list of only_images to check for # FIXME: send along the list of only_images to check for
response = conn(user).make_request( response = conn(context).make_request(
method='GET', method='GET',
bucket='_images') bucket='_images')
return json.loads(response.read()) result = json.loads(response.read())
if not filter_list is None:
return [i for i in result if i['imageId'] in filter_list]
return result
def deregister(context, image_id):
def deregister(user, image_id):
""" unregister an image """ """ unregister an image """
conn(user).make_request( conn(context).make_request(
method='DELETE', method='DELETE',
bucket='_images', bucket='_images',
query_args=qs({'image_id': image_id})) query_args=qs({'image_id': image_id}))
def conn(context):
def conn(user):
return boto.s3.connection.S3Connection ( return boto.s3.connection.S3Connection (
aws_access_key_id=user.access, aws_access_key_id='%s:%s' % (context.user.access, context.project.name),
aws_secret_access_key=user.secret, aws_secret_access_key=context.user.secret,
is_secure=False, is_secure=False,
calling_format=boto.s3.connection.OrdinaryCallingFormat(), calling_format=boto.s3.connection.OrdinaryCallingFormat(),
port=FLAGS.s3_port, port=FLAGS.s3_port,

View File

@@ -92,7 +92,7 @@ class Backend(object):
routing_key) routing_key)
def get(self, queue, no_ack=False): def get(self, queue, no_ack=False):
if not self._queues[queue].size(): if not queue in self._queues or not self._queues[queue].size():
return None return None
(message_data, content_type, content_encoding) = \ (message_data, content_type, content_encoding) = \
self._queues[queue].pop() self._queues[queue].pop()

View File

@@ -1,60 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
# 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.
import logging
import os
import unittest
from nova import flags
from nova import test
from nova.auth import users
from nova.endpoint import cloud
FLAGS = flags.FLAGS
class AccessTestCase(test.BaseTestCase):
def setUp(self):
FLAGS.fake_libvirt = True
FLAGS.fake_storage = True
self.users = users.UserManager.instance()
super(AccessTestCase, self).setUp()
# Make a test project
# Make a test user
self.users.create_user('test1', 'access', 'secret')
# Make the test user a member of the project
def tearDown(self):
# Delete the test user
# Delete the test project
self.users.delete_user('test1')
pass
def test_001_basic_user_access(self):
user = self.users.get_user('test1')
# instance-foo, should be using object and not owner_id
instance_id = "i-12345678"
self.assertTrue(user.is_authorized(instance_id, action="describe_instances"))
def test_002_sysadmin_access(self):
user = self.users.get_user('test1')
bucket = "foo/bar/image"
self.assertFalse(user.is_authorized(bucket, action="register"))
self.users.add_role(user, "sysadmin")
if __name__ == "__main__":
# TODO: Implement use_fake as an option
unittest.main()

View File

@@ -38,7 +38,6 @@ class APIIntegrationTests(unittest.TestCase):
def test_001_get_all_images(self): def test_001_get_all_images(self):
conn = get_connection() conn = get_connection()
res = conn.get_all_images() res = conn.get_all_images()
print res
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -20,7 +20,6 @@ import unittest
from xml.etree import ElementTree from xml.etree import ElementTree
from nova import vendor from nova import vendor
import mox
from tornado import ioloop from tornado import ioloop
from twisted.internet import defer from twisted.internet import defer
@@ -61,10 +60,16 @@ class CloudTestCase(test.BaseTestCase):
proxy=self.node) proxy=self.node)
self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop)) self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
user_mocker = mox.Mox() try:
self.admin = user_mocker.CreateMock(users.User) users.UserManager.instance().create_user('admin', 'admin', 'admin')
self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True) except: pass
self.context = api.APIRequestContext(handler=None,user=self.admin) admin = users.UserManager.instance().get_user('admin')
project = users.UserManager.instance().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')
def test_console_output(self): def test_console_output(self):
if FLAGS.fake_libvirt: if FLAGS.fake_libvirt:
@@ -128,9 +133,7 @@ class CloudTestCase(test.BaseTestCase):
'state': 0x01, 'state': 0x01,
'user_data': '' 'user_data': ''
} }
rv = self.cloud._format_instances(self.context)
rv = self.cloud.format_instances(self.admin)
print rv
self.assert_(len(rv['reservationSet']) == 0) self.assert_(len(rv['reservationSet']) == 0)
# simulate launch of 5 instances # simulate launch of 5 instances
@@ -139,10 +142,9 @@ class CloudTestCase(test.BaseTestCase):
# inst = instance(i) # inst = instance(i)
# self.cloud.instances['pending'][inst['instance_id']] = inst # self.cloud.instances['pending'][inst['instance_id']] = inst
#rv = self.cloud.format_instances(self.admin) #rv = self.cloud._format_instances(self.admin)
#self.assert_(len(rv['reservationSet']) == 1) #self.assert_(len(rv['reservationSet']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5) #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
# report 4 nodes each having 1 of the instances # report 4 nodes each having 1 of the instances
#for i in xrange(4): #for i in xrange(4):
# self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}}) # self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}})
@@ -151,7 +153,7 @@ class CloudTestCase(test.BaseTestCase):
#self.assert_(len(self.cloud.instances['pending'].keys()) == 1) #self.assert_(len(self.cloud.instances['pending'].keys()) == 1)
# check that the reservations collapse # check that the reservations collapse
#rv = self.cloud.format_instances(self.admin) #rv = self.cloud._format_instances(self.admin)
#self.assert_(len(rv['reservationSet']) == 1) #self.assert_(len(rv['reservationSet']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5) #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)

View File

@@ -30,84 +30,86 @@ class NetworkTestCase(test.TrialTestCase):
super(NetworkTestCase, self).setUp() super(NetworkTestCase, self).setUp()
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
self.manager = users.UserManager.instance() self.manager = users.UserManager.instance()
try:
self.manager.create_user('netuser', 'netuser', 'netuser')
except: pass
for i in range(0, 6): for i in range(0, 6):
name = 'user%s' % i name = 'project%s' % i
if not self.manager.get_user(name): if not self.manager.get_project(name):
self.manager.create_user(name, name, name) self.manager.create_project(name, 'netuser', name)
self.network = network.NetworkController(netsize=16) self.network = network.NetworkController(netsize=16)
def tearDown(self): def tearDown(self):
super(NetworkTestCase, self).tearDown() super(NetworkTestCase, self).tearDown()
for i in range(0, 6): for i in range(0, 6):
name = 'user%s' % i name = 'project%s' % i
self.manager.delete_user(name) self.manager.delete_project(name)
self.manager.delete_user('netuser')
def test_network_serialization(self): def test_network_serialization(self):
net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None) net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None)
address = net1.allocate_ip("user0", "01:24:55:36:f2:a0") address = net1.allocate_ip("netuser", "project0", "01:24:55:36:f2:a0")
net_json = str(net1) net_json = str(net1)
net2 = network.Network.from_json(net_json) net2 = network.Network.from_json(net_json)
self.assertEqual(net_json, str(net2)) self.assertEqual(net_json, str(net2))
self.assertTrue(IPy.IP(address) in net2.network) self.assertTrue(IPy.IP(address) in net2.network)
def test_allocate_deallocate_address(self): def test_allocate_deallocate_address(self):
for flag in flags.FLAGS: (address, net_name) = self.network.allocate_address("netuser",
print "%s=%s" % (flag, flags.FLAGS.get(flag, None)) "project0", "01:24:55:36:f2:a0")
(address, net_name) = self.network.allocate_address(
"user0", "01:24:55:36:f2:a0")
logging.debug("Was allocated %s" % (address)) logging.debug("Was allocated %s" % (address))
self.assertEqual(True, address in self._get_user_addresses("user0")) self.assertEqual(True, address in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(address) rv = self.network.deallocate_address(address)
self.assertEqual(False, address in self._get_user_addresses("user0")) self.assertEqual(False, address in self._get_project_addresses("project0"))
def test_range_allocation(self): def test_range_allocation(self):
(address, net_name) = self.network.allocate_address( (address, net_name) = self.network.allocate_address("netuser",
"user0", "01:24:55:36:f2:a0") "project0", "01:24:55:36:f2:a0")
(secondaddress, net_name) = self.network.allocate_address( (secondaddress, net_name) = self.network.allocate_address("netuser",
"user1", "01:24:55:36:f2:a0") "project1", "01:24:55:36:f2:a0")
self.assertEqual(True, address in self._get_user_addresses("user0")) self.assertEqual(True, address in self._get_project_addresses("project0"))
self.assertEqual(True, self.assertEqual(True,
secondaddress in self._get_user_addresses("user1")) secondaddress in self._get_project_addresses("project1"))
self.assertEqual(False, address in self._get_user_addresses("user1")) self.assertEqual(False, address in self._get_project_addresses("project1"))
rv = self.network.deallocate_address(address) rv = self.network.deallocate_address(address)
self.assertEqual(False, address in self._get_user_addresses("user0")) self.assertEqual(False, address in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(secondaddress) rv = self.network.deallocate_address(secondaddress)
self.assertEqual(False, self.assertEqual(False,
secondaddress in self._get_user_addresses("user1")) secondaddress in self._get_project_addresses("project1"))
def test_subnet_edge(self): def test_subnet_edge(self):
(secondaddress, net_name) = self.network.allocate_address("user0") (secondaddress, net_name) = self.network.allocate_address("netuser", "project0")
for user in range(1,5): for project in range(1,5):
user_id = "user%s" % (user) project_id = "project%s" % (project)
(address, net_name) = self.network.allocate_address( (address, net_name) = self.network.allocate_address("netuser",
user_id, "01:24:55:36:f2:a0") project_id, "01:24:55:36:f2:a0")
(address2, net_name) = self.network.allocate_address( (address2, net_name) = self.network.allocate_address("netuser",
user_id, "01:24:55:36:f2:a0") project_id, "01:24:55:36:f2:a0")
(address3, net_name) = self.network.allocate_address( (address3, net_name) = self.network.allocate_address("netuser",
user_id, "01:24:55:36:f2:a0") project_id, "01:24:55:36:f2:a0")
self.assertEqual(False, self.assertEqual(False,
address in self._get_user_addresses("user0")) address in self._get_project_addresses("project0"))
self.assertEqual(False, self.assertEqual(False,
address2 in self._get_user_addresses("user0")) address2 in self._get_project_addresses("project0"))
self.assertEqual(False, self.assertEqual(False,
address3 in self._get_user_addresses("user0")) address3 in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(address) rv = self.network.deallocate_address(address)
rv = self.network.deallocate_address(address2) rv = self.network.deallocate_address(address2)
rv = self.network.deallocate_address(address3) rv = self.network.deallocate_address(address3)
rv = self.network.deallocate_address(secondaddress) rv = self.network.deallocate_address(secondaddress)
def test_too_many_users(self): def test_too_many_projects(self):
for i in range(0, 30): for i in range(0, 30):
name = 'toomany-user%s' % i name = 'toomany-project%s' % i
self.manager.create_user(name, name, name) self.manager.create_project(name, 'netuser', name)
(address, net_name) = self.network.allocate_address( (address, net_name) = self.network.allocate_address("netuser",
name, "01:24:55:36:f2:a0") name, "01:24:55:36:f2:a0")
self.manager.delete_user(name) self.manager.delete_project(name)
def _get_user_addresses(self, user_id): def _get_project_addresses(self, project_id):
rv = self.network.describe_addresses() rv = self.network.describe_addresses()
user_addresses = [] project_addresses = []
for item in rv: for item in rv:
if item['user_id'] == user_id: if item['project_id'] == project_id:
user_addresses.append(item['address']) project_addresses.append(item['address'])
return user_addresses return project_addresses

View File

@@ -14,14 +14,9 @@
# limitations under the License. # limitations under the License.
import logging import logging
import StringIO
import time
import unittest
from xml.etree import ElementTree from xml.etree import ElementTree
from nova import vendor from nova import vendor
import mox
from tornado import ioloop
from twisted.internet import defer from twisted.internet import defer
from nova import exception from nova import exception
@@ -71,7 +66,9 @@ class NodeConnectionTestCase(test.TrialTestCase):
# TODO(ja): add ami, ari, aki, user_data # TODO(ja): add ami, ari, aki, user_data
inst['reservation_id'] = 'r-fakeres' inst['reservation_id'] = 'r-fakeres'
inst['launch_time'] = '10' inst['launch_time'] = '10'
inst['owner_id'] = 'fake' inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
inst['instance_type'] = 'm1.tiny'
inst['node_name'] = FLAGS.node_name inst['node_name'] = FLAGS.node_name
inst['mac_address'] = utils.generate_mac() inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0 inst['ami_launch_index'] = 0
@@ -98,7 +95,6 @@ class NodeConnectionTestCase(test.TrialTestCase):
rv = yield self.node.run_instance(instance_id) rv = yield self.node.run_instance(instance_id)
rv = yield self.node.describe_instances() rv = yield self.node.describe_instances()
logging.debug("describe_instances returns %s" % (rv))
self.assertEqual(rv[instance_id].name, instance_id) self.assertEqual(rv[instance_id].name, instance_id)
yield self.node.reboot_instance(instance_id) yield self.node.reboot_instance(instance_id)

View File

@@ -56,8 +56,6 @@ class ObjectStoreTestCase(test.BaseTestCase):
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
self.um = users.UserManager.instance() self.um = users.UserManager.instance()
def test_buckets(self):
try: try:
self.um.create_user('user1') self.um.create_user('user1')
except: pass except: pass
@@ -67,18 +65,41 @@ class ObjectStoreTestCase(test.BaseTestCase):
try: try:
self.um.create_user('admin_user', admin=True) self.um.create_user('admin_user', admin=True)
except: pass except: pass
try:
self.um.create_project('proj1', 'user1', 'a proj', ['user1'])
except: pass
try:
self.um.create_project('proj2', 'user2', 'a proj', ['user2'])
except: pass
class Context(object): pass
self.context = Context()
objectstore.bucket.Bucket.create('new_bucket', self.um.get_user('user1')) def tearDown(self):
self.um.delete_project('proj1')
self.um.delete_project('proj2')
self.um.delete_user('user1')
self.um.delete_user('user2')
self.um.delete_user('admin_user')
super(ObjectStoreTestCase, self).tearDown()
def test_buckets(self):
self.context.user = self.um.get_user('user1')
self.context.project = self.um.get_project('proj1')
objectstore.bucket.Bucket.create('new_bucket', self.context)
bucket = objectstore.bucket.Bucket('new_bucket') bucket = objectstore.bucket.Bucket('new_bucket')
# creator is authorized to use bucket # creator is authorized to use bucket
self.assert_(bucket.is_authorized(self.um.get_user('user1'))) self.assert_(bucket.is_authorized(self.context))
# another user is not authorized # another user is not authorized
self.assert_(bucket.is_authorized(self.um.get_user('user2')) == False) self.context.user = self.um.get_user('user2')
self.context.project = self.um.get_project('proj2')
self.assert_(bucket.is_authorized(self.context) == False)
# admin is authorized to use bucket # admin is authorized to use bucket
self.assert_(bucket.is_authorized(self.um.get_user('admin_user'))) self.context.user = self.um.get_user('admin_user')
self.context.project = None
self.assert_(bucket.is_authorized(self.context))
# new buckets are empty # new buckets are empty
self.assert_(bucket.list_keys()['Contents'] == []) self.assert_(bucket.list_keys()['Contents'] == [])
@@ -116,18 +137,13 @@ class ObjectStoreTestCase(test.BaseTestCase):
exception = True exception = True
self.assert_(exception) self.assert_(exception)
self.um.delete_user('user1')
self.um.delete_user('user2')
self.um.delete_user('admin_user')
def test_images(self): def test_images(self):
try: self.context.user = self.um.get_user('user1')
self.um.create_user('image_creator') self.context.project = self.um.get_project('proj1')
except: pass
image_user = self.um.get_user('image_creator')
# create a bucket for our bundle # create a bucket for our bundle
objectstore.bucket.Bucket.create('image_bucket', image_user) objectstore.bucket.Bucket.create('image_bucket', self.context)
bucket = objectstore.bucket.Bucket('image_bucket') bucket = objectstore.bucket.Bucket('image_bucket')
# upload an image manifest/parts # upload an image manifest/parts
@@ -136,7 +152,7 @@ class ObjectStoreTestCase(test.BaseTestCase):
bucket[os.path.basename(path)] = open(path, 'rb').read() bucket[os.path.basename(path)] = open(path, 'rb').read()
# register an image # register an image
objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', image_user) objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', self.context)
# verify image # verify image
my_img = objectstore.image.Image('i-testing') my_img = objectstore.image.Image('i-testing')
@@ -147,14 +163,9 @@ class ObjectStoreTestCase(test.BaseTestCase):
self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3') self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
# verify image permissions # verify image permissions
try: self.context.user = self.um.get_user('user2')
self.um.create_user('new_user') self.context.project = self.um.get_project('proj2')
except: pass self.assert_(my_img.is_authorized(self.context) == False)
new_user = self.um.get_user('new_user')
self.assert_(my_img.is_authorized(new_user) == False)
self.um.delete_user('new_user')
self.um.delete_user('image_creator')
# class ApiObjectStoreTestCase(test.BaseTestCase): # class ApiObjectStoreTestCase(test.BaseTestCase):
# def setUp(self): # def setUp(self):

View File

@@ -24,11 +24,9 @@ from M2Crypto import X509
from nova import crypto from nova import crypto
from nova import flags from nova import flags
from nova import test from nova import test
from nova import utils
from nova.auth import users from nova.auth import users
from nova.endpoint import cloud from nova.endpoint import cloud
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -40,8 +38,9 @@ class UserTestCase(test.BaseTestCase):
redis_db=8) redis_db=8)
self.users = users.UserManager.instance() self.users = users.UserManager.instance()
def test_001_can_create_user(self): def test_001_can_create_users(self):
self.users.create_user('test1', 'access', 'secret') self.users.create_user('test1', 'access', 'secret')
self.users.create_user('test2')
def test_002_can_get_user(self): def test_002_can_get_user(self):
user = self.users.get_user('test1') user = self.users.get_user('test1')
@@ -83,7 +82,6 @@ class UserTestCase(test.BaseTestCase):
key.save_pub_key_bio(bio) key.save_pub_key_bio(bio)
converted = crypto.ssl_pub_to_ssh_pub(bio.read()) converted = crypto.ssl_pub_to_ssh_pub(bio.read())
# assert key fields are equal # assert key fields are equal
print converted
self.assertEqual(public_key.split(" ")[1].strip(), self.assertEqual(public_key.split(" ")[1].strip(),
converted.split(" ")[1].strip()) converted.split(" ")[1].strip())
@@ -101,16 +99,44 @@ class UserTestCase(test.BaseTestCase):
users = self.users.get_users() users = self.users.get_users()
self.assertTrue(filter(lambda u: u.id == 'test1', users)) self.assertTrue(filter(lambda u: u.id == 'test1', users))
def test_011_can_generate_x509(self): def test_201_can_create_project(self):
project = self.users.create_project('testproj', 'test1', 'A test project', ['test1'])
self.assertTrue(filter(lambda p: p.name == 'testproj', self.users.get_projects()))
self.assertEqual(project.name, 'testproj')
self.assertEqual(project.description, 'A test project')
self.assertEqual(project.project_manager_id, 'test1')
self.assertTrue(project.has_member('test1'))
def test_202_user1_is_project_member(self):
self.assertTrue(self.users.get_user('test1').is_project_member('testproj'))
def test_203_user2_is_not_project_member(self):
self.assertFalse(self.users.get_user('test2').is_project_member('testproj'))
def test_204_user1_is_project_manager(self):
self.assertTrue(self.users.get_user('test1').is_project_manager('testproj'))
def test_205_user2_is_not_project_manager(self):
self.assertFalse(self.users.get_user('test2').is_project_manager('testproj'))
def test_206_can_add_user_to_project(self):
self.users.add_to_project('test2', 'testproj')
self.assertTrue(self.users.get_project('testproj').has_member('test2'))
def test_208_can_remove_user_from_project(self):
self.users.remove_from_project('test2', 'testproj')
self.assertFalse(self.users.get_project('testproj').has_member('test2'))
def test_209_can_generate_x509(self):
# MUST HAVE RUN CLOUD SETUP BY NOW # MUST HAVE RUN CLOUD SETUP BY NOW
self.cloud = cloud.CloudController() self.cloud = cloud.CloudController()
self.cloud.setup() self.cloud.setup()
private_key, signed_cert_string = self.users.get_user('test1').generate_x509_cert() private_key, signed_cert_string = self.users.get_project('testproj').generate_x509_cert('test1')
logging.debug(signed_cert_string) logging.debug(signed_cert_string)
# Need to verify that it's signed by the right intermediate CA # Need to verify that it's signed by the right intermediate CA
full_chain = crypto.fetch_ca(username='test1', chain=True) full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
int_cert = crypto.fetch_ca(username='test1', chain=False) int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
cloud_cert = crypto.fetch_ca() cloud_cert = crypto.fetch_ca()
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
signed_cert = X509.load_cert_string(signed_cert_string) signed_cert = X509.load_cert_string(signed_cert_string)
@@ -125,11 +151,16 @@ class UserTestCase(test.BaseTestCase):
else: else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
def test_012_can_delete_user(self): def test_299_can_delete_project(self):
self.users.delete_project('testproj')
self.assertFalse(filter(lambda p: p.name == 'testproj', self.users.get_projects()))
def test_999_can_delete_users(self):
self.users.delete_user('test1') self.users.delete_user('test1')
users = self.users.get_users() users = self.users.get_users()
if users != None: self.assertFalse(filter(lambda u: u.id == 'test1', users))
self.assertFalse(filter(lambda u: u.id == 'test1', users)) self.users.delete_user('test2')
self.assertEqual(self.users.get_user('test2'), None)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -44,7 +44,6 @@ from twisted.scripts import trial as trial_script
from nova import flags from nova import flags
from nova import twistd from nova import twistd
from nova.tests.access_unittest import *
from nova.tests.api_unittest import * from nova.tests.api_unittest import *
from nova.tests.cloud_unittest import * from nova.tests.cloud_unittest import *
from nova.tests.keeper_unittest import * from nova.tests.keeper_unittest import *