diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index 496f4f5d2c9..fe11b1d41f6 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -39,7 +39,7 @@ pipeline = ec2faultwrap logrequest ec2noauth cloudrequest authorizer validator e # NOTE(vish): use the following pipeline for deprecated auth # pipeline = ec2faultwrap logrequest authenticate cloudrequest authorizer validator ec2executor # NOTE(vish): use the following pipeline for keystone auth -# pipeline = ec2faultwrap logrequest totoken authtoken keystonecontext cloudrequest authorizer validator ec2executor +# pipeline = ec2faultwrap logrequest ec2keystoneauth cloudrequest authorizer validator ec2executor [filter:ec2faultwrap] paste.filter_factory = nova.api.ec2:FaultWrapper.factory @@ -53,6 +53,9 @@ paste.filter_factory = nova.api.ec2:Lockout.factory [filter:totoken] paste.filter_factory = nova.api.ec2:EC2Token.factory +[filter:ec2keystoneauth] +paste.filter_factory = nova.api.ec2:EC2KeystoneAuth.factory + [filter:ec2noauth] paste.filter_factory = nova.api.ec2:NoAuth.factory diff --git a/nova/api/auth.py b/nova/api/auth.py index 316a8f72f66..586944ff59b 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -75,7 +75,6 @@ def __call__(self, req): 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 bcd7b239e67..199759d13e1 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -64,6 +64,21 @@ 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('\n' + '%s' + '%s' + '%s' % + (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 @@ def __call__(self, req): 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 @@ def __call__(self, req): 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 @@ def __call__(self, req): 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 @@ def __call__(self, req): 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 @@ def __call__(self, req): 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('\n' - '%s' - '%s' - '%s' % - (utils.utf8(code), utils.utf8(message), - utils.utf8(context.request_id))) - return resp diff --git a/nova/context.py b/nova/context.py index b0c0603a8b1..dbeb18652e0 100644 --- a/nova/context.py +++ b/nova/context.py @@ -20,12 +20,15 @@ """RequestContext: context for requests that persist through all of nova.""" import copy -import uuid from nova import local from nova import utils +def generate_request_id(): + return 'req-' + str(utils.gen_uuid()) + + class RequestContext(object): """Security context and request information. @@ -59,7 +62,7 @@ def __init__(self, user_id, project_id, is_admin=None, read_deleted="no", timestamp = utils.parse_strtime(timestamp) self.timestamp = timestamp if not request_id: - request_id = 'req-' + str(utils.gen_uuid()) + request_id = generate_request_id() self.request_id = request_id self.auth_token = auth_token self.strategy = strategy