summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-09-28 12:29:43 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-09-28 12:29:43 -0700
commitbb1540d7bf2c323d43110738e43e9b99ef49b62c (patch)
tree5588175281a26e774fc580f97f047599455c1373 /nova/api
parentf3698b8da4bd63abfade32c9894ac2095672344e (diff)
parent669cf475d11700064aa16f959077d0512e6b1531 (diff)
merged floating-ips
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/__init__.py69
-rw-r--r--nova/api/cloudpipe/__init__.py69
-rw-r--r--nova/api/ec2/__init__.py10
-rw-r--r--nova/api/ec2/apirequest.py4
-rw-r--r--nova/api/ec2/metadatarequesthandler.py73
-rw-r--r--nova/api/rackspace/__init__.py31
-rw-r--r--nova/api/rackspace/auth.py98
7 files changed, 331 insertions, 23 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 821f1deea..744abd621 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -23,23 +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": [
@@ -48,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/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py
new file mode 100644
index 000000000..6d40990a8
--- /dev/null
+++ b/nova/api/cloudpipe/__init__.py
@@ -0,0 +1,69 @@
+# 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.
+
+"""
+REST API Request Handlers for CloudPipe
+"""
+
+import logging
+import urllib
+import webob
+import webob.dec
+import webob.exc
+
+from nova import crypto
+from nova import wsgi
+from nova.auth import manager
+from nova.api.ec2 import cloud
+
+
+_log = logging.getLogger("api")
+_log.setLevel(logging.DEBUG)
+
+
+class API(wsgi.Application):
+
+ def __init__(self):
+ self.controller = cloud.CloudController()
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ if req.method == 'POST':
+ return self.sign_csr(req)
+ _log.debug("Cloudpipe path is %s" % req.path_info)
+ if req.path_info.endswith("/getca/"):
+ return self.send_root_ca(req)
+ return webob.exc.HTTPNotFound()
+
+ def get_project_id_from_ip(self, ip):
+ # TODO(eday): This was removed with the ORM branch, fix!
+ instance = self.controller.get_instance_by_ip(ip)
+ return instance['project_id']
+
+ def send_root_ca(self, req):
+ _log.debug("Getting root ca")
+ project_id = self.get_project_id_from_ip(req.remote_addr)
+ res = webob.Response()
+ res.headers["Content-Type"] = "text/plain"
+ res.body = crypto.fetch_ca(project_id)
+ return res
+
+ def sign_csr(self, req):
+ project_id = self.get_project_id_from_ip(req.remote_addr)
+ cert = self.str_params['cert']
+ return crypto.sign_csr(urllib.unquote(cert), project_id)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index a7b10e428..f0aa57ee4 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)
@@ -164,8 +166,8 @@ class Authorizer(wsgi.Middleware):
'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
},
'AdminController': {
- # All actions have the same permission: [] (the default)
- # admins will be allowed to run them
+ # All actions have the same permission: ['none'] (the default)
+ # superusers will be allowed to run them
# all others will get HTTPUnauthorized.
},
}
@@ -175,7 +177,7 @@ class Authorizer(wsgi.Middleware):
context = req.environ['ec2.context']
controller_name = req.environ['ec2.controller'].__class__.__name__
action = req.environ['ec2.action']
- allowed_roles = self.action_roles[controller_name].get(action, [])
+ allowed_roles = self.action_roles[controller_name].get(action, ['none'])
if self._matches_any_role(context, allowed_roles):
return self.application
else:
@@ -183,6 +185,8 @@ class Authorizer(wsgi.Middleware):
def _matches_any_role(self, context, roles):
"""Return True if any role in roles is allowed in context."""
+ if context.user.is_superuser():
+ return True
if 'all' in roles:
return True
if 'none' in roles:
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..08a8040ca
--- /dev/null
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -0,0 +1,73 @@
+# 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 logging
+
+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:
+ logging.error('Failed to get metadata for ip: %s' % req.remote_addr)
+ raise webob.exc.HTTPNotFound()
+ data = self.lookup(req.path_info, meta_data)
+ if data is None:
+ raise webob.exc.HTTPNotFound()
+ return self.print_data(data)
diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py
index ac5365310..c24d08585 100644
--- a/nova/api/rackspace/__init__.py
+++ b/nova/api/rackspace/__init__.py
@@ -26,8 +26,10 @@ import time
import routes
import webob.dec
import webob.exc
+import webob
from nova import flags
+from nova import utils
from nova import wsgi
from nova.api.rackspace import flavors
from nova.api.rackspace import images
@@ -37,6 +39,11 @@ from nova.api.rackspace import sharedipgroups
from nova.auth import manager
+FLAGS = flags.FLAGS
+flags.DEFINE_string('nova_api_auth',
+ 'nova.api.rackspace.auth.BasicApiAuthManager',
+ 'The auth mechanism to use for the Rackspace API implemenation')
+
class API(wsgi.Middleware):
"""WSGI entry point for all Rackspace API requests."""
@@ -44,28 +51,26 @@ class API(wsgi.Middleware):
app = AuthMiddleware(RateLimitingMiddleware(APIRouter()))
super(API, self).__init__(app)
-
class AuthMiddleware(wsgi.Middleware):
"""Authorize the rackspace API request or return an HTTP Forbidden."""
- #TODO(gundlach): isn't this the old Nova API's auth? Should it be replaced
- #with correct RS API auth?
+ def __init__(self, application):
+ self.auth_driver = utils.import_class(FLAGS.nova_api_auth)()
+ super(AuthMiddleware, self).__init__(application)
@webob.dec.wsgify
def __call__(self, req):
- context = {}
- if "HTTP_X_AUTH_TOKEN" in req.environ:
- context['user'] = manager.AuthManager().get_user_from_access_key(
- req.environ['HTTP_X_AUTH_TOKEN'])
- if context['user']:
- context['project'] = manager.AuthManager().get_project(
- context['user'].name)
- if "user" not in context:
- return webob.exc.HTTPForbidden()
+ if not req.headers.has_key("X-Auth-Token"):
+ return self.auth_driver.authenticate(req)
+
+ user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"])
+
+ if not user:
+ return webob.exc.HTTPUnauthorized()
+ context = {'user': user}
req.environ['nova.context'] = context
return self.application
-
class RateLimitingMiddleware(wsgi.Middleware):
"""Rate limit incoming requests according to the OpenStack rate limits."""
diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py
new file mode 100644
index 000000000..ce5a967eb
--- /dev/null
+++ b/nova/api/rackspace/auth.py
@@ -0,0 +1,98 @@
+import datetime
+import json
+import time
+import webob.exc
+import webob.dec
+import hashlib
+from nova import flags
+from nova import auth
+from nova import manager
+from nova import db
+from nova import utils
+
+FLAGS = flags.FLAGS
+
+class Context(object):
+ pass
+
+class BasicApiAuthManager(object):
+ """ Implements a somewhat rudimentary version of Rackspace Auth"""
+
+ def __init__(self, host=None, db_driver=None):
+ if not host:
+ host = FLAGS.host
+ self.host = host
+ if not db_driver:
+ db_driver = FLAGS.db_driver
+ self.db = utils.import_object(db_driver)
+ self.auth = auth.manager.AuthManager()
+ self.context = Context()
+ super(BasicApiAuthManager, self).__init__()
+
+ def authenticate(self, req):
+ # Unless the request is explicitly made against /<version>/ don't
+ # honor it
+ path_info = req.path_info
+ if len(path_info) > 1:
+ return webob.exc.HTTPUnauthorized()
+
+ try:
+ username, key = req.headers['X-Auth-User'], \
+ req.headers['X-Auth-Key']
+ except KeyError:
+ return webob.exc.HTTPUnauthorized()
+
+ username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key']
+ token, user = self._authorize_user(username, key)
+ if user and token:
+ res = webob.Response()
+ res.headers['X-Auth-Token'] = token['token_hash']
+ res.headers['X-Server-Management-Url'] = \
+ token['server_management_url']
+ res.headers['X-Storage-Url'] = token['storage_url']
+ res.headers['X-CDN-Management-Url'] = token['cdn_management_url']
+ res.content_type = 'text/plain'
+ res.status = '204'
+ return res
+ else:
+ return webob.exc.HTTPUnauthorized()
+
+ def authorize_token(self, token_hash):
+ """ retrieves user information from the datastore given a token
+
+ If the token has expired, returns None
+ If the token is not found, returns None
+ Otherwise returns the token
+
+ This method will also remove the token if the timestamp is older than
+ 2 days ago.
+ """
+ token = self.db.auth_get_token(self.context, token_hash)
+ if token:
+ delta = datetime.datetime.now() - token['created_at']
+ if delta.days >= 2:
+ self.db.auth_destroy_token(self.context, token)
+ else:
+ user = self.auth.get_user(token['user_id'])
+ return { 'id':user['uid'] }
+ return None
+
+ def _authorize_user(self, username, key):
+ """ Generates a new token and assigns it to a user """
+ user = self.auth.get_user_from_access_key(key)
+ if user and user['name'] == username:
+ token_hash = hashlib.sha1('%s%s%f' % (username, key,
+ time.time())).hexdigest()
+ token = {}
+ token['token_hash'] = token_hash
+ token['cdn_management_url'] = ''
+ token['server_management_url'] = self._get_server_mgmt_url()
+ token['storage_url'] = ''
+ token['user_id'] = user['uid']
+ self.db.auth_create_token(self.context, token)
+ return token, user
+ return None, None
+
+ def _get_server_mgmt_url(self):
+ return 'https://%s/v1.0/' % self.host
+