def __call__(self, req): context = req.environ['ec2api.context'] api_request = req.environ['ec2.request'] try: result = api_request.invoke(context) except botocore.exceptions.ClientError as ex: error = ex.response.get('Error', {}) code = ex.response.get('Code', error.get('Code')) message = ex.response.get('Message', error.get('Message')) # the early versions of botocore didn't provide HTTPStatusCode # for 400 errors status = ex.response.get('ResponseMetadata', {}).get( 'HTTPStatusCode', 400) if status < 400 or status > 499: LOG.exception("Exception from remote server") return faults.ec2_error_response( context.request_id, code, message, status=status) except Exception as ex: return ec2_error_ex( ex, req, unexpected=not isinstance(ex, exception.EC2Exception)) else: resp = webob.Response() resp.status = 200 resp.headers['Content-Type'] = 'text/xml' resp.body = six.binary_type(result) return resp
def __call__(self, req): context = req.environ['ec2api.context'] api_request = req.environ['ec2.request'] try: result = api_request.invoke(context) except botocore.exceptions.ClientError as ex: error = ex.response.get('Error', {}) code = ex.response.get('Code', error.get('Code')) message = ex.response.get('Message', error.get('Message')) # the early versions of botocore didn't provide HTTPStatusCode # for 400 errors status = ex.response.get('ResponseMetadata', {}).get('HTTPStatusCode', 400) if status < 400 or status > 499: LOG.exception("Exception from remote server") return faults.ec2_error_response(context.request_id, code, message, status=status) except Exception as ex: return ec2_error_ex( ex, req, unexpected=not isinstance(ex, exception.EC2Exception)) else: resp = webob.Response() resp.status = 200 resp.headers['Content-Type'] = 'text/xml' resp.body = six.binary_type(result) return resp
def ec2_error_ex(ex, req, unexpected=False): """Return an EC2 error response. Return an EC2 error response based on passed exception and log the exception on an appropriate log level: * DEBUG: expected errors * ERROR: unexpected errors All expected errors are treated as client errors and 4xx HTTP status codes are always returned for them. Unexpected 5xx errors may contain sensitive information, suppress their messages for security. """ code = exception_to_ec2code(ex) for status_name in ('code', 'status', 'status_code', 'http_status'): status = getattr(ex, status_name, None) if isinstance(status, int): break else: status = 500 if unexpected: log_fun = LOG.error log_msg = _("Unexpected %(ex_name)s raised: %(ex_str)s") exc_info = sys.exc_info() else: log_fun = LOG.debug log_msg = _("%(ex_name)s raised: %(ex_str)s") exc_info = None context = req.environ['ec2api.context'] request_id = context.request_id log_msg_args = { 'ex_name': type(ex).__name__, 'ex_str': ex } log_fun(log_msg % log_msg_args, context=context, exc_info=exc_info) if unexpected and status >= 500: message = _('Unknown error occurred.') elif getattr(ex, 'message', None): message = six.text_type(ex.message) elif ex.args and any(arg for arg in ex.args): message = " ".join(map(six.text_type, ex.args)) else: message = six.text_type(ex) if unexpected: # Log filtered environment for unexpected errors. env = req.environ.copy() for k in list(env.keys()): if not isinstance(env[k], six.string_types): env.pop(k) log_fun(_('Environment: %s') % jsonutils.dumps(env)) return faults.ec2_error_response(request_id, code, message, status=status)
def _get_ec2_credentials(context): # ec2_creds = clients.keystone(context).ec2.list(context.user_id) url = CONF.keystone_url + '/users/%s/credentials/OS-EC2' % context.user_id headers = { 'Content-Type': 'application/json', 'X-Auth-Token': context.auth_token } verify = CONF.ssl_ca_file or not CONF.ssl_insecure response = requests.request('GET', url, headers=headers, verify=verify) status_code = response.status_code if status_code != 200: msg = response.reason return faults.ec2_error_response(context.request_id, "AuthFailure", msg, status=status_code) result = response.json() return result['credentials']
def __call__(self, req): request_id = common_context.generate_request_id() # NOTE(alevine) We need to calculate the hash here because # subsequent access to request modifies the req.body so the hash # calculation will yield invalid results. body_hash = hashlib.sha256(req.body).hexdigest() signature = self._get_signature(req) if not signature: msg = _("Signature not provided") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) access = self._get_access(req) if not access: msg = _("Access key not provided") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) if 'X-Amz-Signature' in req.params or 'Authorization' in req.headers: params = {} else: # Make a copy of args for authentication and signature verification params = dict(req.params) # Not part of authentication args params.pop('Signature', None) cred_dict = { 'access': access, 'signature': signature, 'host': req.host, 'verb': req.method, 'path': req.path, 'params': params, # python3 takes only keys for json from headers object 'headers': {k: req.headers[k] for k in req.headers}, 'body_hash': body_hash } token_url = CONF.keystone_ec2_tokens_url if "ec2" in token_url: creds = {'ec2Credentials': cred_dict} else: creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}} creds_json = jsonutils.dumps(creds) headers = {'Content-Type': 'application/json'} params = {'data': creds_json, 'headers': headers} clients.update_request_params_with_ssl(params) response = requests.request('POST', token_url, **params) status_code = response.status_code if status_code != 200: msg = response.reason return faults.ec2_error_response(request_id, "AuthFailure", msg, status=status_code) try: auth_ref = keystone_access.AccessInfo.factory(resp=response, body=response.json()) except (NotImplementedError, KeyError): LOG.exception("Keystone failure") msg = _("Failure communicating with keystone") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) auth = keystone_identity_access.AccessInfoPlugin(auth_ref) params = {'auth': auth} clients.update_request_params_with_ssl(params) session = keystone_session.Session(**params) remote_address = req.remote_addr if CONF.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) ctxt = context.RequestContext(auth_ref.user_id, auth_ref.project_id, request_id=request_id, user_name=auth_ref.username, project_name=auth_ref.project_name, remote_address=remote_address, session=session, api_version=req.params.get('Version')) req.environ['ec2api.context'] = ctxt return self.application
def __call__(self, req): metric_logger.startTime() request_id = context.generate_request_id() # NOTE(alevine) We need to calculate the hash here because # subsequent access to request modifies the req.body so the hash # calculation will yield invalid results. body_hash = hashlib.sha256(req.body).hexdigest() # Verify the version is as expected from config file req_version = req.params.get('Version') if not req_version or req_version not in CONF.supported_api_versions: _msg = ("Unsupported Version used in the request.") return faults.ec2_error_response(request_id, 'BadRequest', _msg, status=400) if CONF.enable_policy_engine: try: rsrc_action_list = self.policy_engine.handle_params( dict(req.params)) except Exception as e: exc_type, exc_obj, exc_tb = sys.exc_info() LOG.exception(str(e)) if isinstance(exc_obj, webob.exc.HTTPUnauthorized): return faults.ec2_error_response(request_id, 'AuthFailure', str(e), status=403) elif isinstance(exc_obj, webob.exc.HTTPInternalServerError): return faults.ec2_error_response(request_id, 'InternalError', str(e), status=500) else: return faults.ec2_error_response(request_id, 'BadRequest', str(e), status=400) keystone_validation_url = "" data = {} headers = {'Content-Type': 'application/json'} auth_token = self._get_auth_token(req) if auth_token: headers['X-Auth-Token'] = auth_token data['action_resource_list'] = rsrc_action_list data = jsonutils.dumps(data) keystone_validation_url = CONF.keystone_token_url else: signature = self._get_signature(req) if not signature: msg = _("Signature not provided") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) access = self._get_access(req) if not access: msg = _("Access key not provided") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) if 'X-Amz-Signature' in req.params or 'Authorization' in req.headers: params = {} else: # Make a copy of args for authentication and signature verification params = dict(req.params) # Not part of authentication args params.pop('Signature', None) cred_dict = { 'access': access, 'signature': signature, 'host': req.host, 'verb': req.method, 'path': req.path, 'params': params, 'headers': req.headers, 'body_hash': body_hash } if CONF.enable_policy_engine: cred_dict['action_resource_list'] = rsrc_action_list keystone_validation_url = CONF.keystone_sig_url if "ec2" in keystone_validation_url: creds = {'ec2Credentials': cred_dict} else: creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}} data = jsonutils.dumps(creds) verify = CONF.ssl_ca_file or not CONF.ssl_insecure response = requests.request('POST', keystone_validation_url, verify=verify, data=data, headers=headers) status_code = response.status_code if status_code != 200: msg = response.text return faults.ec2_error_response(request_id, "AuthFailure", msg, status=status_code) result = response.json() try: user_id = result.get('user_id') project_id = result.get('account_id') if auth_token: token_id = auth_token else: token_id = result.get('token_id') if not user_id or not project_id: raise KeyError except (AttributeError, KeyError): LOG.exception(_("Keystone failure")) msg = _("Failure communicating with keystone") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) remote_address = req.remote_addr if CONF.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) # Fill in default values user_name = project_name = 'default' roles = catalog = [] ctxt = context.RequestContext(user_id, project_id, user_name=user_name, project_name=project_name, roles=roles, auth_token=token_id, remote_address=remote_address, service_catalog=catalog, api_version=req.params.get('Version')) req.environ['ec2api.context'] = ctxt compute_request_id = ctxt.request_id appendRequestDict = {'requestid': compute_request_id} actionName = ec2utils.camelcase_to_underscore(req.params.get('Action')) actionName = actionName + "-auth" metric_logger.reportTime(actionName, addOnInfoPairs=appendRequestDict) return self.application
def __call__(self, req): request_id = context.generate_request_id() # NOTE(alevine) We need to calculate the hash here because # subsequent access to request modifies the req.body so the hash # calculation will yield invalid results. body_hash = hashlib.sha256(req.body).hexdigest() signature = self._get_signature(req) if not signature: msg = _("Signature not provided") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) access = self._get_access(req) if not access: msg = _("Access key not provided") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) if 'X-Amz-Signature' in req.params or 'Authorization' in req.headers: params = {} else: # Make a copy of args for authentication and signature verification params = dict(req.params) # Not part of authentication args params.pop('Signature', None) cred_dict = { 'access': access, 'signature': signature, 'host': req.host, 'verb': req.method, 'path': req.path, 'params': params, 'headers': req.headers, 'body_hash': body_hash } token_url = CONF.keystone_ec2_tokens_url if "ec2" in token_url: creds = {'ec2Credentials': cred_dict} else: creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}} creds_json = jsonutils.dumps(creds) headers = {'Content-Type': 'application/json'} response = requests.request('POST', token_url, data=creds_json, headers=headers) status_code = response.status_code if status_code != 200: msg = response.reason return faults.ec2_error_response(request_id, "AuthFailure", msg, status=status_code) result = response.json() try: if 'token' in result: # NOTE(andrey-mp): response from keystone v3 token_id = response.headers['x-subject-token'] user_id = result['token']['user']['id'] project_id = result['token']['project']['id'] user_name = result['token']['user'].get('name') project_name = result['token']['project'].get('name') catalog = result['token']['catalog'] else: token_id = result['access']['token']['id'] user_id = result['access']['user']['id'] project_id = result['access']['token']['tenant']['id'] user_name = result['access']['user'].get('name') project_name = result['access']['token']['tenant'].get('name') catalog = result['access']['serviceCatalog'] except (AttributeError, KeyError): LOG.exception(_("Keystone failure")) msg = _("Failure communicating with keystone") return faults.ec2_error_response(request_id, "AuthFailure", msg, status=400) remote_address = req.remote_addr if CONF.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) ctxt = context.RequestContext(user_id, project_id, request_id=request_id, user_name=user_name, project_name=project_name, auth_token=token_id, remote_address=remote_address, service_catalog=catalog, api_version=req.params.get('Version')) req.environ['ec2api.context'] = ctxt return self.application