From 11b934f75ac4359b75f246fd9babfc3363a9a396 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 16 Sep 2010 14:41:51 -0500 Subject: Replaced the existing Rackspace Auth Mechanism with one that mirrors the implementation in the design document. --- nova/api/rackspace/__init__.py | 52 +++++++++++++++++++++++++++++++++--------- nova/api/rackspace/auth.py | 37 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 nova/api/rackspace/auth.py (limited to 'nova/api') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index b4d666d63..dbba97107 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 @@ -36,6 +38,10 @@ from nova.api.rackspace import sharedipgroups from nova.auth import manager +FLAGS = flags.FLAGS +flags.DEFINE_string('nova_api_auth', 'nova.api.rackspace.auth.FakeAuth', + 'The auth mechanism to use for the Rackspace API implemenation') + class API(wsgi.Middleware): """WSGI entry point for all Rackspace API requests.""" @@ -47,23 +53,47 @@ class API(wsgi.Middleware): 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.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 + def authenticate(self, req): + # Unless the request is explicitly made against // don't + # honor it + path_info = req.environ['wsgiorg.routing_args'][1]['path_info'] + if path_info: + return webob.exc.HTTPUnauthorized() + + if req.headers.has_key("X-Auth-User") and \ + req.headers.has_key("X-Auth-Key"): + username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key'] + token, user = self.auth_driver.authorize_user(username, key) + if user and token: + res = webob.Response() + res.headers['X-Auth-Token'] = token + res.headers['X-Server-Management-Url'] = \ + user['server_management_url'] + res.headers['X-Storage-Url'] = user['storage_url'] + res.headers['X-CDN-Management-Url'] = user['cdn_management_url'] + res.content_type = 'text/plain' + res.status = '204' + return res + else: + return webob.exc.HTTPUnauthorized() + return webob.exc.HTTPUnauthorized() class APIRouter(wsgi.Router): """ diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py new file mode 100644 index 000000000..d2b5193c3 --- /dev/null +++ b/nova/api/rackspace/auth.py @@ -0,0 +1,37 @@ +import json +from hashlib import sha1 +from nova import datastore + +class FakeAuth(object): + def __init__(self, store=datastore.Redis.instance): + self._store = store() + self.auth_hash = 'rs_fake_auth' + self._store.hsetnx(self.auth_hash, 'rs_last_id', 0) + + def authorize_token(self, token): + user = self._store.hget(self.auth_hash, token) + if user: + return json.loads(user) + return None + + def authorize_user(self, user, key): + token = sha1("%s_%s" % (user, key)).hexdigest() + user = self._store.hget(self.auth_hash, token) + if not user: + return None, None + else: + return token, json.loads(user) + + def add_user(self, user, key): + last_id = self._store.hget(self.auth_hash, 'rs_last_id') + token = sha1("%s_%s" % (user, key)).hexdigest() + user = { + 'id':last_id, + 'cdn_management_url':'cdn_management_url', + 'storage_url':'storage_url', + 'server_management_url':'server_management_url' + } + new_user = self._store.hsetnx(self.auth_hash, token, json.dumps(user)) + if new_user: + self._store.hincrby(self.auth_hash, 'rs_last_id') + -- cgit From 64dd3000c4a9b88719e86d1090097e35398d3838 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 20 Sep 2010 18:04:57 -0500 Subject: Refactored the auth branch based on review feedback --- nova/api/rackspace/__init__.py | 33 ++---------- nova/api/rackspace/auth.py | 117 +++++++++++++++++++++++++++++------------ 2 files changed, 88 insertions(+), 62 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index dbba97107..f62ddc1c7 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -39,7 +39,8 @@ from nova.auth import manager FLAGS = flags.FLAGS -flags.DEFINE_string('nova_api_auth', 'nova.api.rackspace.auth.FakeAuth', +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): @@ -49,7 +50,6 @@ class API(wsgi.Middleware): app = AuthMiddleware(APIRouter()) super(API, self).__init__(app) - class AuthMiddleware(wsgi.Middleware): """Authorize the rackspace API request or return an HTTP Forbidden.""" @@ -60,41 +60,16 @@ class AuthMiddleware(wsgi.Middleware): @webob.dec.wsgify def __call__(self, req): if not req.headers.has_key("X-Auth-Token"): - return self.authenticate(req) + 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} + context = {'user': user} req.environ['nova.context'] = context return self.application - def authenticate(self, req): - # Unless the request is explicitly made against // don't - # honor it - path_info = req.environ['wsgiorg.routing_args'][1]['path_info'] - if path_info: - return webob.exc.HTTPUnauthorized() - - if req.headers.has_key("X-Auth-User") and \ - req.headers.has_key("X-Auth-Key"): - username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key'] - token, user = self.auth_driver.authorize_user(username, key) - if user and token: - res = webob.Response() - res.headers['X-Auth-Token'] = token - res.headers['X-Server-Management-Url'] = \ - user['server_management_url'] - res.headers['X-Storage-Url'] = user['storage_url'] - res.headers['X-CDN-Management-Url'] = user['cdn_management_url'] - res.content_type = 'text/plain' - res.status = '204' - return res - else: - return webob.exc.HTTPUnauthorized() - return webob.exc.HTTPUnauthorized() - class APIRouter(wsgi.Router): """ Routes requests on the Rackspace API to the appropriate controller diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index d2b5193c3..b29596880 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -1,37 +1,88 @@ +import datetime import json -from hashlib import sha1 -from nova import datastore - -class FakeAuth(object): - def __init__(self, store=datastore.Redis.instance): - self._store = store() - self.auth_hash = 'rs_fake_auth' - self._store.hsetnx(self.auth_hash, 'rs_last_id', 0) - - def authorize_token(self, token): - user = self._store.hget(self.auth_hash, token) - if user: - return json.loads(user) - return None +import time +import webob.exc +import webob.dec +import hashlib + +from nova import auth +from nova import manager +from nova import db + +class Context(object): + pass + +class BasicApiAuthManager(manager.Manager): + """ Implements a somewhat rudimentary version of Rackspace Auth""" + + def __init__(self): + self.auth = auth.manager.AuthManager() + self.context = Context() + super(BasicApiAuthManager, self).__init__() + + def authenticate(self, req): + # Unless the request is explicitly made against // 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() - def authorize_user(self, user, key): - token = sha1("%s_%s" % (user, key)).hexdigest() - user = self._store.hget(self.auth_hash, token) - if not user: - return None, None + 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 token, json.loads(user) - - def add_user(self, user, key): - last_id = self._store.hget(self.auth_hash, 'rs_last_id') - token = sha1("%s_%s" % (user, key)).hexdigest() - user = { - 'id':last_id, - 'cdn_management_url':'cdn_management_url', - 'storage_url':'storage_url', - 'server_management_url':'server_management_url' - } - new_user = self._store.hsetnx(self.auth_hash, token, json.dumps(user)) - if new_user: - self._store.hincrby(self.auth_hash, 'rs_last_id') + 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(self.context, token['user_id']) + return { 'id':user['id'] } + 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'] = '' + self.db.auth_create_token(self.context, token, user['id']) + return token, user + return None, None + + def _get_server_mgmt_url(self): + return 'https://%s/v1.0/' % self.host -- cgit From 0880e49a4e9c9a246e8f4d7cc805d79947de095a Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 21 Sep 2010 10:07:59 -0500 Subject: Some more refactoring and another unit test --- nova/api/rackspace/auth.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index b29596880..1ef90c324 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -4,7 +4,7 @@ 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 @@ -12,10 +12,16 @@ from nova import db class Context(object): pass -class BasicApiAuthManager(manager.Manager): +class BasicApiAuthManager(object): """ Implements a somewhat rudimentary version of Rackspace Auth""" def __init__(self): + 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__() @@ -64,8 +70,8 @@ class BasicApiAuthManager(manager.Manager): if delta.days >= 2: self.db.auth_destroy_token(self.context, token) else: - user = self.auth.get_user(self.context, token['user_id']) - return { 'id':user['id'] } + user = self.auth.get_user(token['user_id']) + return { 'id':user['uid'] } return None def _authorize_user(self, username, key): @@ -79,7 +85,8 @@ class BasicApiAuthManager(manager.Manager): token['cdn_management_url'] = '' token['server_management_url'] = self._get_server_mgmt_url() token['storage_url'] = '' - self.db.auth_create_token(self.context, token, user['id']) + token['user_id'] = user['uid'] + self.db.auth_create_token(self.context, token) return token, user return None, None -- cgit From 84fbfe09e10b330a5668e99422247801f370d0f9 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 21 Sep 2010 16:57:08 -0400 Subject: Rewrite rbac tests to use Authorizer middleware --- nova/api/ec2/__init__.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova/api') 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: -- cgit From a8c5901faaa98b7f0c06db086a03a0d38a210986 Mon Sep 17 00:00:00 2001 From: mdietz Date: Wed, 22 Sep 2010 18:46:55 +0000 Subject: Added a primary_key to AuthToken, fixed some unbound variables, and now all unit tests pass --- nova/api/rackspace/auth.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index 1ef90c324..ce5a967eb 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -8,6 +8,9 @@ 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 @@ -15,7 +18,7 @@ class Context(object): class BasicApiAuthManager(object): """ Implements a somewhat rudimentary version of Rackspace Auth""" - def __init__(self): + def __init__(self, host=None, db_driver=None): if not host: host = FLAGS.host self.host = host -- cgit From 6f82d0f84c9474e72ef70c9ff568d68031191e0a Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 22 Sep 2010 17:35:02 -0400 Subject: Soren's patch to fix part of ec2 --- nova/api/ec2/apirequest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova/api') 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(): -- cgit From f188b5a02d34751e89fae60b4d3b1ef144f138d7 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 22 Sep 2010 19:11:04 -0400 Subject: Re-add root and metadata request handlers to EC2 API --- nova/api/__init__.py | 56 +++++++++++++++++++++++++-- nova/api/ec2/metadatarequesthandler.py | 71 ++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 nova/api/ec2/metadatarequesthandler.py (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 821f1deea..a0be05d86 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -23,9 +23,18 @@ Root WSGI middleware for all API controllers. import routes import webob.dec +from nova import flags from nova import wsgi 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 = flags.FLAGS class API(wsgi.Router): @@ -33,13 +42,33 @@ class API(wsgi.Router): def __init__(self): 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={'sub_domain': [FLAGS.rsapi_subdomain]}) + mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(), + conditions={'sub_domain': [FLAGS.rsapi_subdomain]}) + + mapper.connect("/", controller=self.ec2api_versions, + conditions={'sub_domain': [FLAGS.ec2api_subdomain]}) + mapper.connect("/services/{path_info:.*}", controller=ec2.API(), + conditions={'sub_domain': [FLAGS.ec2api_subdomain]}) + 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={'subdomain': FLAGS.ec2api_subdomain}) 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 +77,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/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) -- cgit From 54122c0a156d1562be76dfde41bd62006f9ed426 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 22 Sep 2010 17:54:57 -0700 Subject: Various loose ends for endpoint and tornado removal cleanup, including cloudpipe API addition, rpc.call() cleanup by removing tornado ioloop, and fixing bin/* programs. Tornado still exists as part of some test cases and those should be reworked to not require it. --- nova/api/__init__.py | 2 ++ nova/api/cloudpipe/__init__.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 nova/api/cloudpipe/__init__.py (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 821f1deea..ff9b94de9 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -24,6 +24,7 @@ import routes import webob.dec from nova import wsgi +from nova.api import cloudpipe from nova.api import ec2 from nova.api import rackspace @@ -36,6 +37,7 @@ class API(wsgi.Router): mapper.connect("/", controller=self.versions) mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API()) mapper.connect("/services/{path_info:.*}", controller=ec2.API()) + mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) super(API, self).__init__(mapper) @webob.dec.wsgify diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py new file mode 100644 index 000000000..642f8ef6c --- /dev/null +++ b/nova/api/cloudpipe/__init__.py @@ -0,0 +1,68 @@ +# 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): + 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) -- cgit From d98c663d3e521d45586ed3922d93e0ca612a5639 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Thu, 23 Sep 2010 09:06:45 -0400 Subject: Added FLAGS.FAKE_subdomain letting you manually set the subdomain for testing on localhost. --- nova/api/__init__.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index a0be05d86..8e4d844b2 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -34,6 +34,8 @@ 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 @@ -41,17 +43,26 @@ 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.sub_domains = True mapper.connect("/", controller=self.rsapi_versions, - conditions={'sub_domain': [FLAGS.rsapi_subdomain]}) + conditions=rsdomain) mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(), - conditions={'sub_domain': [FLAGS.rsapi_subdomain]}) + conditions=rsdomain) mapper.connect("/", controller=self.ec2api_versions, - conditions={'sub_domain': [FLAGS.ec2api_subdomain]}) + conditions=ec2domain) mapper.connect("/services/{path_info:.*}", controller=ec2.API(), - conditions={'sub_domain': [FLAGS.ec2api_subdomain]}) + conditions=ec2domain) mrh = metadatarequesthandler.MetadataRequestHandler() for s in ['/latest', '/2009-04-04', @@ -64,7 +75,7 @@ class API(wsgi.Router): '/2007-01-19', '/1.0']: mapper.connect('%s/{path_info:.*}' % s, controller=mrh, - conditions={'subdomain': FLAGS.ec2api_subdomain}) + conditions=ec2domain) super(API, self).__init__(mapper) @webob.dec.wsgify -- cgit From c9ac49b2425b932f60a87da80887d4556806ca60 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 23 Sep 2010 11:21:14 -0700 Subject: Fixed cloudpipe lib init. --- nova/api/cloudpipe/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py index 642f8ef6c..6d40990a8 100644 --- a/nova/api/cloudpipe/__init__.py +++ b/nova/api/cloudpipe/__init__.py @@ -51,6 +51,7 @@ class API(wsgi.Application): 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'] -- cgit From 2b30ffe2f3c79e3701487d18fe1d4eef671aa335 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 23 Sep 2010 13:18:40 -0700 Subject: Applied vish's fixes. --- nova/api/ec2/metadatarequesthandler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 229e5a78d..08a8040ca 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -18,6 +18,8 @@ """Metadata request handler.""" +import logging + import webob.dec import webob.exc @@ -63,9 +65,9 @@ class MetadataRequestHandler(object): 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) + logging.error('Failed to get metadata for ip: %s' % req.remote_addr) raise webob.exc.HTTPNotFound() - data = self.lookup(path, meta_data) + data = self.lookup(req.path_info, meta_data) if data is None: raise webob.exc.HTTPNotFound() return self.print_data(data) -- cgit From a70632890c610ece766bfd3c31eea4bc6eb4a316 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Thu, 23 Sep 2010 17:06:23 -0400 Subject: Apply vish's patch --- nova/api/ec2/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index b041787c2..f0aa57ee4 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -166,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. }, } @@ -177,8 +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.extend(FLAGS.superuser_roles) + allowed_roles = self.action_roles[controller_name].get(action, ['none']) if self._matches_any_role(context, allowed_roles): return self.application else: @@ -186,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: -- cgit