summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2012-02-06 16:29:30 -0800
committerVishvananda Ishaya <vishvananda@gmail.com>2012-02-07 17:14:56 -0800
commit13b82dbbcfe280eda15fd9248a494cb8ce4e5056 (patch)
tree736df371cc415bd2f80706a3c3ca7a78d12cfa29 /nova/api
parentb0a708f67407256a449414a000b070752e51dba2 (diff)
downloadnova-13b82dbbcfe280eda15fd9248a494cb8ce4e5056.tar.gz
nova-13b82dbbcfe280eda15fd9248a494cb8ce4e5056.tar.xz
nova-13b82dbbcfe280eda15fd9248a494cb8ce4e5056.zip
Optimizes ec2 keystone usage and handles errors
* breaks out gen_request_id so we can return it in error msg * breaks out ec2_error so we can use it in multiple middlewares * adds new middleware (remove old after devstack change) * skips extra call to keystone for second authentication * fixes bug 922373 Change-Id: If765d149289255b0bf0e0c1b647ebb547ce5759b
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/auth.py1
-rw-r--r--nova/api/ec2/__init__.py140
2 files changed, 110 insertions, 31 deletions
diff --git a/nova/api/auth.py b/nova/api/auth.py
index 316a8f72f..586944ff5 100644
--- a/nova/api/auth.py
+++ b/nova/api/auth.py
@@ -75,7 +75,6 @@ class NovaKeystoneContext(wsgi.Middleware):
req.headers.get('X_STORAGE_TOKEN'))
# Build a context, including the auth_token...
- remote_address = getattr(req, 'remote_address', '127.0.0.1')
remote_address = req.remote_addr
if FLAGS.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index bcd7b239e..199759d13 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -64,6 +64,21 @@ FLAGS.add_options(ec2_opts)
flags.DECLARE('use_forwarded_for', 'nova.api.auth')
+def ec2_error(req, request_id, code, message):
+ """Helper to send an ec2_compatible error"""
+ LOG.error(_('%(code)s: %(message)s') % locals())
+ resp = webob.Response()
+ resp.status = 400
+ resp.headers['Content-Type'] = 'text/xml'
+ resp.body = str('<?xml version="1.0"?>\n'
+ '<Response><Errors><Error><Code>%s</Code>'
+ '<Message>%s</Message></Error></Errors>'
+ '<RequestID>%s</RequestID></Response>' %
+ (utils.utf8(code), utils.utf8(message),
+ utils.utf8(request_id)))
+ return resp
+
+
## Fault Wrapper around all EC2 requests ##
class FaultWrapper(wsgi.Middleware):
"""Calls the middleware stack, captures any exceptions into faults."""
@@ -168,7 +183,7 @@ class Lockout(wsgi.Middleware):
class EC2Token(wsgi.Middleware):
- """Authenticate an EC2 request with keystone and convert to token."""
+ """Deprecated, only here to make merging easier."""
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
@@ -237,6 +252,85 @@ class EC2Token(wsgi.Middleware):
return self.application
+class EC2KeystoneAuth(wsgi.Middleware):
+ """Authenticate an EC2 request with keystone and convert to context."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ request_id = context.generate_request_id()
+ signature = req.params.get('Signature')
+ if not signature:
+ msg = _("Signature not provided")
+ return ec2_error(req, request_id, "Unauthorized", msg)
+ access = req.params.get('AWSAccessKeyId')
+ if not access:
+ msg = _("Access key not provided")
+ return ec2_error(req, request_id, "Unauthorized", msg)
+
+ # Make a copy of args for authentication and signature verification.
+ auth_params = dict(req.params)
+ # Not part of authentication args
+ auth_params.pop('Signature')
+
+ cred_dict = {
+ 'access': access,
+ 'signature': signature,
+ 'host': req.host,
+ 'verb': req.method,
+ 'path': req.path,
+ 'params': auth_params,
+ }
+ if "ec2" in FLAGS.keystone_ec2_url:
+ creds = {'ec2Credentials': cred_dict}
+ else:
+ creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}}
+ creds_json = utils.dumps(creds)
+ headers = {'Content-Type': 'application/json'}
+
+ o = urlparse.urlparse(FLAGS.keystone_ec2_url)
+ if o.scheme == "http":
+ conn = httplib.HTTPConnection(o.netloc)
+ else:
+ conn = httplib.HTTPSConnection(o.netloc)
+ conn.request('POST', o.path, body=creds_json, headers=headers)
+ response = conn.getresponse()
+ data = response.read()
+ if response.status != 200:
+ if response.status == 401:
+ msg = response.reason
+ else:
+ msg = _("Failure communicating with keystone")
+ return ec2_error(req, request_id, "Unauthorized", msg)
+ result = utils.loads(data)
+ conn.close()
+
+ try:
+ token_id = result['access']['token']['id']
+ user_id = result['access']['user']['id']
+ project_id = result['access']['token']['tenant']
+ roles = [role['name'] for role
+ in result['access']['user']['roles']]
+ except (AttributeError, KeyError), e:
+ LOG.exception("Keystone failure: %s" % e)
+ msg = _("Failure communicating with keystone")
+ return ec2_error(req, request_id, "Unauthorized", msg)
+
+ remote_address = req.remote_addr
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For',
+ remote_address)
+ ctxt = context.RequestContext(user_id,
+ project_id,
+ roles=roles,
+ auth_token=token_id,
+ strategy='keystone',
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctxt
+
+ return self.application
+
+
class NoAuth(wsgi.Middleware):
"""Add user:project as 'nova.context' to WSGI environ."""
@@ -246,7 +340,7 @@ class NoAuth(wsgi.Middleware):
raise webob.exc.HTTPBadRequest()
user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
project_id = project_id or user_id
- remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ remote_address = req.remote_addr
if FLAGS.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address)
ctx = context.RequestContext(user_id,
@@ -478,6 +572,7 @@ class Executor(wsgi.Application):
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
context = req.environ['nova.context']
+ request_id = context.request_id
api_request = req.environ['ec2.request']
result = None
try:
@@ -487,58 +582,56 @@ class Executor(wsgi.Application):
context=context)
ec2_id = ec2utils.id_to_ec2_id(ex.kwargs['instance_id'])
message = ex.message % {'instance_id': ec2_id}
- return self._error(req, context, type(ex).__name__, message)
+ return ec2_error(req, request_id, type(ex).__name__, message)
except exception.VolumeNotFound as ex:
LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
context=context)
ec2_id = ec2utils.id_to_ec2_vol_id(ex.kwargs['volume_id'])
message = ex.message % {'volume_id': ec2_id}
- return self._error(req, context, type(ex).__name__, message)
+ return ec2_error(req, request_id, type(ex).__name__, message)
except exception.SnapshotNotFound as ex:
LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex),
context=context)
ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id'])
message = ex.message % {'snapshot_id': ec2_id}
- return self._error(req, context, type(ex).__name__, message)
+ return ec2_error(req, request_id, type(ex).__name__, message)
except exception.NotFound as ex:
LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)
- return self._error(req, context, type(ex).__name__, unicode(ex))
+ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
except exception.ApiError as ex:
LOG.exception(_('ApiError raised: %s'), unicode(ex),
context=context)
if ex.code:
- return self._error(req, context, ex.code, unicode(ex))
+ return ec2_error(req, request_id, ex.code, unicode(ex))
else:
- return self._error(req, context, type(ex).__name__,
+ return ec2_error(req, request_id, type(ex).__name__,
unicode(ex))
except exception.KeyPairExists as ex:
LOG.debug(_('KeyPairExists raised: %s'), unicode(ex),
context=context)
- return self._error(req, context, type(ex).__name__, unicode(ex))
+ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
except exception.InvalidParameterValue as ex:
LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex),
context=context)
- return self._error(req, context, type(ex).__name__, unicode(ex))
+ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
except exception.InvalidPortRange as ex:
LOG.debug(_('InvalidPortRange raised: %s'), unicode(ex),
context=context)
- return self._error(req, context, type(ex).__name__, unicode(ex))
+ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
except exception.NotAuthorized as ex:
LOG.info(_('NotAuthorized raised: %s'), unicode(ex),
context=context)
- return self._error(req, context, type(ex).__name__, unicode(ex))
+ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
except exception.InvalidRequest as ex:
LOG.debug(_('InvalidRequest raised: %s'), unicode(ex),
context=context)
- return self._error(req, context, type(ex).__name__, unicode(ex))
+ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
except Exception as ex:
extra = {'environment': req.environ}
LOG.exception(_('Unexpected error raised: %s'), unicode(ex),
extra=extra, context=context)
- return self._error(req,
- context,
- 'UnknownError',
- _('An unknown error has occurred. '
+ return ec2_error(req, request_id, 'UnknownError',
+ _('An unknown error has occurred. '
'Please try your request again.'))
else:
resp = webob.Response()
@@ -546,16 +639,3 @@ class Executor(wsgi.Application):
resp.headers['Content-Type'] = 'text/xml'
resp.body = str(result)
return resp
-
- def _error(self, req, context, code, message):
- LOG.error(_('%(code)s: %(message)s') % locals())
- resp = webob.Response()
- resp.status = 400
- resp.headers['Content-Type'] = 'text/xml'
- resp.body = str('<?xml version="1.0"?>\n'
- '<Response><Errors><Error><Code>%s</Code>'
- '<Message>%s</Message></Error></Errors>'
- '<RequestID>%s</RequestID></Response>' %
- (utils.utf8(code), utils.utf8(message),
- utils.utf8(context.request_id)))
- return resp