summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Day <eday@oddments.org>2010-09-23 11:24:26 -0700
committerEric Day <eday@oddments.org>2010-09-23 11:24:26 -0700
commit6460a2e9f808c2200274fc41777b6ceaed182d82 (patch)
tree4ec9abff5810e5049c6d85dab6c896fdfac885b1
parentc9ac49b2425b932f60a87da80887d4556806ca60 (diff)
parentc29c68f0fa27fa81f22ca42958bbd564f719f3ae (diff)
downloadnova-6460a2e9f808c2200274fc41777b6ceaed182d82.tar.gz
nova-6460a2e9f808c2200274fc41777b6ceaed182d82.tar.xz
nova-6460a2e9f808c2200274fc41777b6ceaed182d82.zip
Merged gundlach's branch.
-rwxr-xr-xbin/nova-api10
-rw-r--r--doc/source/auth.rst8
-rw-r--r--nova/api/__init__.py67
-rw-r--r--nova/api/ec2/__init__.py3
-rw-r--r--nova/api/ec2/apirequest.py4
-rw-r--r--nova/api/ec2/metadatarequesthandler.py71
-rw-r--r--nova/auth/manager.py4
-rw-r--r--nova/tests/access_unittest.py111
-rw-r--r--nova/tests/api/__init__.py30
-rw-r--r--nova/tests/api_unittest.py5
-rw-r--r--run_tests.py3
11 files changed, 224 insertions, 92 deletions
diff --git a/bin/nova-api b/bin/nova-api
index 8625c487f..a5027700b 100755
--- a/bin/nova-api
+++ b/bin/nova-api
@@ -32,14 +32,18 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
-from nova import api
from nova import flags
from nova import utils
-from nova import wsgi
+from nova import server
FLAGS = flags.FLAGS
flags.DEFINE_integer('api_port', 8773, 'API port')
+def main(_args):
+ from nova import api
+ from nova import wsgi
+ wsgi.run_server(api.API(), FLAGS.api_port)
+
if __name__ == '__main__':
utils.default_flagfile()
- wsgi.run_server(api.API(), FLAGS.api_port)
+ server.serve('nova-api', main)
diff --git a/doc/source/auth.rst b/doc/source/auth.rst
index 70aca704a..3fcb309cd 100644
--- a/doc/source/auth.rst
+++ b/doc/source/auth.rst
@@ -172,14 +172,6 @@ Further Challenges
-The :mod:`rbac` Module
---------------------------
-
-.. automodule:: nova.auth.rbac
- :members:
- :undoc-members:
- :show-inheritance:
-
The :mod:`signer` Module
------------------------
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index ff9b94de9..744abd621 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -23,25 +23,65 @@ Root WSGI middleware for all API controllers.
import routes
import webob.dec
+from nova import flags
from nova import wsgi
from nova.api import cloudpipe
from nova.api import ec2
from nova.api import rackspace
+from nova.api.ec2 import metadatarequesthandler
+
+
+flags.DEFINE_string('rsapi_subdomain', 'rs',
+ 'subdomain running the RS API')
+flags.DEFINE_string('ec2api_subdomain', 'ec2',
+ 'subdomain running the EC2 API')
+flags.DEFINE_string('FAKE_subdomain', None,
+ 'set to rs or ec2 to fake the subdomain of the host for testing')
+FLAGS = flags.FLAGS
class API(wsgi.Router):
"""Routes top-level requests to the appropriate controller."""
def __init__(self):
+ rsdomain = {'sub_domain': [FLAGS.rsapi_subdomain]}
+ ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]}
+ # If someone wants to pretend they're hitting the RS subdomain
+ # on their local box, they can set FAKE_subdomain to 'rs', which
+ # removes subdomain restrictions from the RS routes below.
+ if FLAGS.FAKE_subdomain == 'rs':
+ rsdomain = {}
+ elif FLAGS.FAKE_subdomain == 'ec2':
+ ec2domain = {}
mapper = routes.Mapper()
- mapper.connect("/", controller=self.versions)
- mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API())
- mapper.connect("/services/{path_info:.*}", controller=ec2.API())
+ mapper.sub_domains = True
+ mapper.connect("/", controller=self.rsapi_versions,
+ conditions=rsdomain)
+ mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(),
+ conditions=rsdomain)
+
+ mapper.connect("/", controller=self.ec2api_versions,
+ conditions=ec2domain)
+ mapper.connect("/services/{path_info:.*}", controller=ec2.API(),
+ conditions=ec2domain)
mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API())
+ mrh = metadatarequesthandler.MetadataRequestHandler()
+ for s in ['/latest',
+ '/2009-04-04',
+ '/2008-09-01',
+ '/2008-02-01',
+ '/2007-12-15',
+ '/2007-10-10',
+ '/2007-08-29',
+ '/2007-03-01',
+ '/2007-01-19',
+ '/1.0']:
+ mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
+ conditions=ec2domain)
super(API, self).__init__(mapper)
@webob.dec.wsgify
- def versions(self, req):
+ def rsapi_versions(self, req):
"""Respond to a request for all OpenStack API versions."""
response = {
"versions": [
@@ -50,3 +90,22 @@ class API(wsgi.Router):
"application/xml": {
"attributes": dict(version=["status", "id"])}}
return wsgi.Serializer(req.environ, metadata).to_content_type(response)
+
+ @webob.dec.wsgify
+ def ec2api_versions(self, req):
+ """Respond to a request for all EC2 versions."""
+ # available api versions
+ versions = [
+ '1.0',
+ '2007-01-19',
+ '2007-03-01',
+ '2007-08-29',
+ '2007-10-10',
+ '2007-12-15',
+ '2008-02-01',
+ '2008-09-01',
+ '2009-04-04',
+ ]
+ return ''.join('%s\n' % v for v in versions)
+
+
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index a7b10e428..b041787c2 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -25,6 +25,7 @@ import webob.dec
import webob.exc
from nova import exception
+from nova import flags
from nova import wsgi
from nova.api.ec2 import apirequest
from nova.api.ec2 import context
@@ -33,6 +34,7 @@ from nova.api.ec2 import cloud
from nova.auth import manager
+FLAGS = flags.FLAGS
_log = logging.getLogger("api")
_log.setLevel(logging.DEBUG)
@@ -176,6 +178,7 @@ class Authorizer(wsgi.Middleware):
controller_name = req.environ['ec2.controller'].__class__.__name__
action = req.environ['ec2.action']
allowed_roles = self.action_roles[controller_name].get(action, [])
+ allowed_roles.extend(FLAGS.superuser_roles)
if self._matches_any_role(context, allowed_roles):
return self.application
else:
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index a3b20118f..a87c21fb3 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -68,10 +68,8 @@ class APIRequest(object):
key = _camelcase_to_underscore(parts[0])
if len(parts) > 1:
d = args.get(key, {})
- d[parts[1]] = value[0]
+ d[parts[1]] = value
value = d
- else:
- value = value[0]
args[key] = value
for key in args.keys():
diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py
new file mode 100644
index 000000000..229e5a78d
--- /dev/null
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -0,0 +1,71 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Metadata request handler."""
+
+import webob.dec
+import webob.exc
+
+from nova.api.ec2 import cloud
+
+
+class MetadataRequestHandler(object):
+
+ """Serve metadata from the EC2 API."""
+
+ def print_data(self, data):
+ if isinstance(data, dict):
+ output = ''
+ for key in data:
+ if key == '_name':
+ continue
+ output += key
+ if isinstance(data[key], dict):
+ if '_name' in data[key]:
+ output += '=' + str(data[key]['_name'])
+ else:
+ output += '/'
+ output += '\n'
+ return output[:-1] # cut off last \n
+ elif isinstance(data, list):
+ return '\n'.join(data)
+ else:
+ return str(data)
+
+ def lookup(self, path, data):
+ items = path.split('/')
+ for item in items:
+ if item:
+ if not isinstance(data, dict):
+ return data
+ if not item in data:
+ return None
+ data = data[item]
+ return data
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ cc = cloud.CloudController()
+ meta_data = cc.get_metadata(req.remote_addr)
+ if meta_data is None:
+ _log.error('Failed to get metadata for ip: %s' % req.remote_addr)
+ raise webob.exc.HTTPNotFound()
+ data = self.lookup(path, meta_data)
+ if data is None:
+ raise webob.exc.HTTPNotFound()
+ return self.print_data(data)
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index bc3a8a12e..928e0fd69 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -44,7 +44,7 @@ flags.DEFINE_list('allowed_roles',
# NOTE(vish): a user with one of these roles will be a superuser and
# have access to all api commands
flags.DEFINE_list('superuser_roles', ['cloudadmin'],
- 'Roles that ignore rbac checking completely')
+ 'Roles that ignore authorization checking completely')
# NOTE(vish): a user with one of these roles will have it for every
# project, even if he or she is not a member of the project
@@ -304,7 +304,7 @@ class AuthManager(object):
return "%s:%s" % (user.access, Project.safe_id(project))
def is_superuser(self, user):
- """Checks for superuser status, allowing user to bypass rbac
+ """Checks for superuser status, allowing user to bypass authorization
@type user: User or uid
@param user: User to check.
diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py
index 59e1683db..c8a49d2ca 100644
--- a/nova/tests/access_unittest.py
+++ b/nova/tests/access_unittest.py
@@ -18,12 +18,13 @@
import unittest
import logging
+import webob
from nova import exception
from nova import flags
from nova import test
+from nova.api import ec2
from nova.auth import manager
-from nova.auth import rbac
FLAGS = flags.FLAGS
@@ -72,9 +73,17 @@ class AccessTestCase(test.BaseTestCase):
try:
self.project.add_role(self.testsys, 'sysadmin')
except: pass
- self.context = Context()
- self.context.project = self.project
#user is set in each test
+ def noopWSGIApp(environ, start_response):
+ start_response('200 OK', [])
+ return ['']
+ self.mw = ec2.Authorizer(noopWSGIApp)
+ self.mw.action_roles = {'str': {
+ '_allow_all': ['all'],
+ '_allow_none': [],
+ '_allow_project_manager': ['projectmanager'],
+ '_allow_sys_and_net': ['sysadmin', 'netadmin'],
+ '_allow_sysadmin': ['sysadmin']}}
def tearDown(self):
um = manager.AuthManager()
@@ -87,76 +96,46 @@ class AccessTestCase(test.BaseTestCase):
um.delete_user('testsys')
super(AccessTestCase, self).tearDown()
+ def response_status(self, user, methodName):
+ context = Context()
+ context.project = self.project
+ context.user = user
+ environ = {'ec2.context' : context,
+ 'ec2.controller': 'some string',
+ 'ec2.action': methodName}
+ req = webob.Request.blank('/', environ)
+ resp = req.get_response(self.mw)
+ return resp.status_int
+
+ def shouldAllow(self, user, methodName):
+ self.assertEqual(200, self.response_status(user, methodName))
+
+ def shouldDeny(self, user, methodName):
+ self.assertEqual(401, self.response_status(user, methodName))
+
def test_001_allow_all(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_all(self.context))
- self.context.user = self.testpmsys
- self.assertTrue(self._allow_all(self.context))
- self.context.user = self.testnet
- self.assertTrue(self._allow_all(self.context))
- self.context.user = self.testsys
- self.assertTrue(self._allow_all(self.context))
+ users = [self.testadmin, self.testpmsys, self.testnet, self.testsys]
+ for user in users:
+ self.shouldAllow(user, '_allow_all')
def test_002_allow_none(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_none(self.context))
- self.context.user = self.testpmsys
- self.assertRaises(exception.NotAuthorized, self._allow_none, self.context)
- self.context.user = self.testnet
- self.assertRaises(exception.NotAuthorized, self._allow_none, self.context)
- self.context.user = self.testsys
- self.assertRaises(exception.NotAuthorized, self._allow_none, self.context)
+ self.shouldAllow(self.testadmin, '_allow_none')
+ users = [self.testpmsys, self.testnet, self.testsys]
+ for user in users:
+ self.shouldDeny(user, '_allow_none')
def test_003_allow_project_manager(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_project_manager(self.context))
- self.context.user = self.testpmsys
- self.assertTrue(self._allow_project_manager(self.context))
- self.context.user = self.testnet
- self.assertRaises(exception.NotAuthorized, self._allow_project_manager, self.context)
- self.context.user = self.testsys
- self.assertRaises(exception.NotAuthorized, self._allow_project_manager, self.context)
+ for user in [self.testadmin, self.testpmsys]:
+ self.shouldAllow(user, '_allow_project_manager')
+ for user in [self.testnet, self.testsys]:
+ self.shouldDeny(user, '_allow_project_manager')
def test_004_allow_sys_and_net(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_sys_and_net(self.context))
- self.context.user = self.testpmsys # doesn't have the per project sysadmin
- self.assertRaises(exception.NotAuthorized, self._allow_sys_and_net, self.context)
- self.context.user = self.testnet
- self.assertTrue(self._allow_sys_and_net(self.context))
- self.context.user = self.testsys
- self.assertTrue(self._allow_sys_and_net(self.context))
-
- def test_005_allow_sys_no_pm(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_sys_no_pm(self.context))
- self.context.user = self.testpmsys
- self.assertRaises(exception.NotAuthorized, self._allow_sys_no_pm, self.context)
- self.context.user = self.testnet
- self.assertRaises(exception.NotAuthorized, self._allow_sys_no_pm, self.context)
- self.context.user = self.testsys
- self.assertTrue(self._allow_sys_no_pm(self.context))
-
- @rbac.allow('all')
- def _allow_all(self, context):
- return True
-
- @rbac.allow('none')
- def _allow_none(self, context):
- return True
-
- @rbac.allow('projectmanager')
- def _allow_project_manager(self, context):
- return True
-
- @rbac.allow('sysadmin', 'netadmin')
- def _allow_sys_and_net(self, context):
- return True
-
- @rbac.allow('sysadmin')
- @rbac.deny('projectmanager')
- def _allow_sys_no_pm(self, context):
- return True
+ for user in [self.testadmin, self.testnet, self.testsys]:
+ self.shouldAllow(user, '_allow_sys_and_net')
+ # denied because it doesn't have the per project sysadmin
+ for user in [self.testpmsys]:
+ self.shouldDeny(user, '_allow_sys_and_net')
if __name__ == "__main__":
# TODO: Implement use_fake as an option
diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py
index 4682c094e..fc1ab9ae2 100644
--- a/nova/tests/api/__init__.py
+++ b/nova/tests/api/__init__.py
@@ -25,6 +25,7 @@ import stubout
import webob
import webob.dec
+import nova.exception
from nova import api
from nova.tests.api.test_helper import *
@@ -36,25 +37,46 @@ class Test(unittest.TestCase):
def tearDown(self): # pylint: disable-msg=C0103
self.stubs.UnsetAll()
+ def _request(self, url, subdomain, **kwargs):
+ environ_keys = {'HTTP_HOST': '%s.example.com' % subdomain}
+ environ_keys.update(kwargs)
+ req = webob.Request.blank(url, environ_keys)
+ return req.get_response(api.API())
+
def test_rackspace(self):
self.stubs.Set(api.rackspace, 'API', APIStub)
- result = webob.Request.blank('/v1.0/cloud').get_response(api.API())
+ result = self._request('/v1.0/cloud', 'rs')
self.assertEqual(result.body, "/cloud")
def test_ec2(self):
self.stubs.Set(api.ec2, 'API', APIStub)
- result = webob.Request.blank('/ec2/cloud').get_response(api.API())
+ result = self._request('/services/cloud', 'ec2')
self.assertEqual(result.body, "/cloud")
def test_not_found(self):
self.stubs.Set(api.ec2, 'API', APIStub)
self.stubs.Set(api.rackspace, 'API', APIStub)
- result = webob.Request.blank('/test/cloud').get_response(api.API())
+ result = self._request('/test/cloud', 'ec2')
self.assertNotEqual(result.body, "/cloud")
def test_query_api_versions(self):
- result = webob.Request.blank('/').get_response(api.API())
+ result = self._request('/', 'rs')
self.assertTrue('CURRENT' in result.body)
+ def test_metadata(self):
+ def go(url):
+ result = self._request(url, 'ec2',
+ REMOTE_ADDR='128.192.151.2')
+ # Each should get to the ORM layer and fail to find the IP
+ self.assertRaises(nova.exception.NotFound, go, '/latest/')
+ self.assertRaises(nova.exception.NotFound, go, '/2009-04-04/')
+ self.assertRaises(nova.exception.NotFound, go, '/1.0/')
+
+ def test_ec2_root(self):
+ result = self._request('/', 'ec2')
+ self.assertTrue('2007-12-15\n' in result.body)
+
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py
index 0732c39bb..c040cdad3 100644
--- a/nova/tests/api_unittest.py
+++ b/nova/tests/api_unittest.py
@@ -25,12 +25,17 @@ import random
import StringIO
import webob
+from nova import flags
from nova import test
from nova import api
from nova.api.ec2 import cloud
from nova.auth import manager
+FLAGS = flags.FLAGS
+FLAGS.FAKE_subdomain = 'ec2'
+
+
class FakeHttplibSocket(object):
"""a fake socket implementation for httplib.HTTPResponse, trivial"""
def __init__(self, response_string):
diff --git a/run_tests.py b/run_tests.py
index bea97c0b3..4121f4c06 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -49,8 +49,7 @@ from nova import datastore
from nova import flags
from nova import twistd
-#TODO(gundlach): rewrite and readd this after merge
-#from nova.tests.access_unittest import *
+from nova.tests.access_unittest import *
from nova.tests.auth_unittest import *
from nova.tests.api_unittest import *
from nova.tests.cloud_unittest import *