From 67498650bd213b42c18bdf12f842e056fb2b99d7 Mon Sep 17 00:00:00 2001 From: gordon chung Date: Thu, 23 Apr 2015 16:22:58 -0400 Subject: drop audit middleware the audit middleware is deprecated and is available via keystonemiddleware. pyCADF can now be returned to just a library that builds CADF events and validates them. Closes-Bug: #1448239 Change-Id: I1724a6637d9663a825055f56d17189e35d63f269 --- pycadf/audit/__init__.py | 0 pycadf/audit/api.py | 321 ------------------------------- pycadf/middleware/__init__.py | 0 pycadf/middleware/audit.py | 50 ----- pycadf/middleware/base.py | 55 ------ pycadf/middleware/notifier.py | 139 -------------- pycadf/tests/audit/__init__.py | 0 pycadf/tests/audit/test_api.py | 348 ---------------------------------- pycadf/tests/middleware/__init__.py | 0 pycadf/tests/middleware/test_audit.py | 158 --------------- 10 files changed, 1071 deletions(-) delete mode 100644 pycadf/audit/__init__.py delete mode 100644 pycadf/audit/api.py delete mode 100644 pycadf/middleware/__init__.py delete mode 100644 pycadf/middleware/audit.py delete mode 100644 pycadf/middleware/base.py delete mode 100644 pycadf/middleware/notifier.py delete mode 100644 pycadf/tests/audit/__init__.py delete mode 100644 pycadf/tests/audit/test_api.py delete mode 100644 pycadf/tests/middleware/__init__.py delete mode 100644 pycadf/tests/middleware/test_audit.py (limited to 'pycadf') diff --git a/pycadf/audit/__init__.py b/pycadf/audit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pycadf/audit/api.py b/pycadf/audit/api.py deleted file mode 100644 index e7fa2ca..0000000 --- a/pycadf/audit/api.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2013 IBM Corp. -# -# 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. - -import ast -import collections -import logging -import os -import re - -from oslo_config import cfg -from six.moves import configparser -from six.moves.urllib import parse as urlparse - -from pycadf._i18n import _LW -from pycadf import cadftaxonomy as taxonomy -from pycadf import cadftype -from pycadf import credential -from pycadf import endpoint -from pycadf import eventfactory as factory -from pycadf import host -from pycadf import identifier -from pycadf import reason -from pycadf import reporterstep -from pycadf import resource -from pycadf import tag -from pycadf import timestamp - -# NOTE(gordc): remove cfg once we move over to this middleware version -CONF = cfg.CONF -opts = [cfg.StrOpt('api_audit_map', - default='api_audit_map.conf', - help='File containing mapping for api paths and ' - 'service endpoints')] -CONF.register_opts(opts, group='audit') - -AuditMap = collections.namedtuple('AuditMap', - ['path_kw', - 'custom_actions', - 'service_endpoints', - 'default_target_endpoint_type']) - -LOG = logging.getLogger(__name__) - - -def _configure_audit_map(cfg_file): - """Configure to recognize and map known api paths.""" - - path_kw = {} - custom_actions = {} - service_endpoints = {} - default_target_endpoint_type = None - - if cfg_file: - try: - map_conf = configparser.SafeConfigParser() - map_conf.readfp(open(cfg_file)) - - try: - default_target_endpoint_type = \ - map_conf.get('DEFAULT', 'target_endpoint_type') - except configparser.NoOptionError: - pass - - try: - custom_actions = dict(map_conf.items('custom_actions')) - except configparser.Error: - pass - - try: - path_kw = dict(map_conf.items('path_keywords')) - except configparser.Error: - pass - - try: - service_endpoints = dict(map_conf.items('service_endpoints')) - except configparser.Error: - pass - except configparser.ParsingError as err: - raise PycadfAuditApiConfigError( - 'Error parsing audit map file: %s' % err) - return AuditMap(path_kw=path_kw, custom_actions=custom_actions, - service_endpoints=service_endpoints, - default_target_endpoint_type=default_target_endpoint_type) - - -class ClientResource(resource.Resource): - def __init__(self, project_id=None, **kwargs): - super(ClientResource, self).__init__(**kwargs) - if project_id is not None: - self.project_id = project_id - - -class KeystoneCredential(credential.Credential): - def __init__(self, identity_status=None, **kwargs): - super(KeystoneCredential, self).__init__(**kwargs) - if identity_status is not None: - self.identity_status = identity_status - - -class PycadfAuditApiConfigError(Exception): - """Error raised when pyCADF fails to configure correctly.""" - - -class OpenStackAuditApi(object): - - Service = collections.namedtuple('Service', - ['id', 'name', 'type', 'admin_endp', - 'public_endp', 'private_endp']) - - def __init__(self, map_file=None): - LOG.warning(_LW('pyCADF audit API is deprecated as of version 0.8.0,' - ' in favour of keystonemiddleware.audit.' - 'OpenStackAuditApi')) - if map_file is None: - map_file = CONF.audit.api_audit_map - if not os.path.exists(CONF.audit.api_audit_map): - map_file = cfg.CONF.find_file(CONF.audit.api_audit_map) - self._MAP = _configure_audit_map(map_file) - - @staticmethod - def _clean_path(value): - return value[:-5] if value.endswith('.json') else value - - def _get_action(self, req): - """Take a given Request, parse url path to calculate action type. - - Depending on req.method: - if POST: path ends with 'action', read the body and use as action; - path ends with known custom_action, take action from config; - request ends with known path, assume is create action; - request ends with unknown path, assume is update action. - if GET: request ends with known path, assume is list action; - request ends with unknown path, assume is read action. - if PUT, assume update action. - if DELETE, assume delete action. - if HEAD, assume read action. - - """ - path = req.path[:-1] if req.path.endswith('/') else req.path - url_ending = self._clean_path(path[path.rfind('/') + 1:]) - method = req.method - - if url_ending + '/' + method.lower() in self._MAP.custom_actions: - action = self._MAP.custom_actions[url_ending + '/' + - method.lower()] - elif url_ending in self._MAP.custom_actions: - action = self._MAP.custom_actions[url_ending] - elif method == 'POST': - if url_ending == 'action': - try: - if req.json: - body_action = list(req.json.keys())[0] - action = taxonomy.ACTION_UPDATE + '/' + body_action - else: - action = taxonomy.ACTION_CREATE - except ValueError: - action = taxonomy.ACTION_CREATE - elif url_ending not in self._MAP.path_kw: - action = taxonomy.ACTION_UPDATE - else: - action = taxonomy.ACTION_CREATE - elif method == 'GET': - if url_ending in self._MAP.path_kw: - action = taxonomy.ACTION_LIST - else: - action = taxonomy.ACTION_READ - elif method == 'PUT' or method == 'PATCH': - action = taxonomy.ACTION_UPDATE - elif method == 'DELETE': - action = taxonomy.ACTION_DELETE - elif method == 'HEAD': - action = taxonomy.ACTION_READ - else: - action = taxonomy.UNKNOWN - - return action - - def _get_service_info(self, endp): - # NOTE(stevemar): The catalog returned by X-Service-Catalog - # does not include IDs for endpoints, use the service name - # as a backup. - endpoint_id = endp['endpoints'][0].get('id', endp['name']) - service = self.Service( - type=self._MAP.service_endpoints.get( - endp['type'], - taxonomy.UNKNOWN), - name=endp['name'], - id=identifier.norm_ns(endpoint_id), - admin_endp=endpoint.Endpoint( - name='admin', - url=endp['endpoints'][0]['adminURL']), - private_endp=endpoint.Endpoint( - name='private', - url=endp['endpoints'][0]['internalURL']), - public_endp=endpoint.Endpoint( - name='public', - url=endp['endpoints'][0]['publicURL'])) - - return service - - def _build_typeURI(self, req, service_type): - type_uri = '' - prev_key = None - for key in re.split('/', req.path): - key = self._clean_path(key) - if key in self._MAP.path_kw: - type_uri += '/' + key - elif prev_key in self._MAP.path_kw: - type_uri += '/' + self._MAP.path_kw[prev_key] - prev_key = key - return service_type + type_uri - - def create_event(self, req, correlation_id): - action = self._get_action(req) - initiator_host = host.Host(address=req.client_addr, - agent=req.user_agent) - catalog = ast.literal_eval(req.environ['HTTP_X_SERVICE_CATALOG']) - service_info = self.Service(type=taxonomy.UNKNOWN, - name=taxonomy.UNKNOWN, - id=taxonomy.UNKNOWN, - admin_endp=None, - private_endp=None, - public_endp=None) - default_endpoint = None - for endp in catalog: - admin_urlparse = urlparse.urlparse( - endp['endpoints'][0]['adminURL']) - public_urlparse = urlparse.urlparse( - endp['endpoints'][0]['publicURL']) - req_url = urlparse.urlparse(req.host_url) - if (req_url.netloc == admin_urlparse.netloc - or req_url.netloc == public_urlparse.netloc): - service_info = self._get_service_info(endp) - break - elif (self._MAP.default_target_endpoint_type - and endp['type'] == self._MAP.default_target_endpoint_type): - default_endpoint = endp - else: - if default_endpoint: - service_info = self._get_service_info(default_endpoint) - - initiator = ClientResource( - typeURI=taxonomy.ACCOUNT_USER, - id=identifier.norm_ns(str(req.environ['HTTP_X_USER_ID'])), - name=req.environ['HTTP_X_USER_NAME'], - host=initiator_host, - credential=KeystoneCredential( - token=req.environ['HTTP_X_AUTH_TOKEN'], - identity_status=req.environ['HTTP_X_IDENTITY_STATUS']), - project_id=identifier.norm_ns(req.environ['HTTP_X_PROJECT_ID'])) - target_typeURI = (self._build_typeURI(req, service_info.type) - if service_info.type != taxonomy.UNKNOWN - else service_info.type) - target = resource.Resource(typeURI=target_typeURI, - id=service_info.id, - name=service_info.name) - if service_info.admin_endp: - target.add_address(service_info.admin_endp) - if service_info.private_endp: - target.add_address(service_info.private_endp) - if service_info.public_endp: - target.add_address(service_info.public_endp) - event = factory.EventFactory().new_event( - eventType=cadftype.EVENTTYPE_ACTIVITY, - outcome=taxonomy.OUTCOME_PENDING, - action=action, - initiator=initiator, - target=target, - observer=resource.Resource(id='target')) - event.requestPath = req.path_qs - event.add_tag(tag.generate_name_value_tag('correlation_id', - correlation_id)) - return event - - def append_audit_event(self, req): - """Append a CADF event to req.environ['CADF_EVENT'] - Also, stores model in request for future process and includes a - CADF correlation id. - """ - correlation_id = identifier.generate_uuid() - req.environ['CADF_EVENT_CORRELATION_ID'] = correlation_id - event = self.create_event(req, correlation_id) - setattr(req, 'cadf_model', event) - req.environ['CADF_EVENT'] = event.as_dict() - - def mod_audit_event(self, req, response): - """Modifies CADF event in request based on response. - If no event exists, a new event is created. - """ - if response: - if response.status_int >= 200 and response.status_int < 400: - result = taxonomy.OUTCOME_SUCCESS - else: - result = taxonomy.OUTCOME_FAILURE - else: - result = taxonomy.UNKNOWN - if hasattr(req, 'cadf_model'): - req.cadf_model.add_reporterstep( - reporterstep.Reporterstep( - role=cadftype.REPORTER_ROLE_MODIFIER, - reporter=resource.Resource(id='target'), - reporterTime=timestamp.get_utc_now())) - else: - self.append_audit_event(req) - req.cadf_model.outcome = result - if response: - req.cadf_model.reason = \ - reason.Reason(reasonType='HTTP', - reasonCode=str(response.status_int)) - req.environ['CADF_EVENT'] = req.cadf_model.as_dict() diff --git a/pycadf/middleware/__init__.py b/pycadf/middleware/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pycadf/middleware/audit.py b/pycadf/middleware/audit.py deleted file mode 100644 index 859dd1b..0000000 --- a/pycadf/middleware/audit.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# 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. - -""" -Attach open standard audit information to request.environ - -AuditMiddleware filter should be placed after Keystone's auth_token middleware -in the pipeline so that it can utilize the information Keystone provides. - -""" -import logging - -from pycadf._i18n import _LW -from pycadf.audit import api as cadf_api -from pycadf.middleware import notifier - - -class AuditMiddleware(notifier.RequestNotifier): - - def __init__(self, app, **conf): - super(AuditMiddleware, self).__init__(app, **conf) - LOG = logging.getLogger(conf.get('log_name', __name__)) - LOG.warning(_LW('pyCADF middleware is deprecated as of version 0.7.0,' - ' in favour of keystonemiddleware.audit.' - 'AuditMiddleware')) - map_file = conf.get('audit_map_file', None) - self.cadf_audit = cadf_api.OpenStackAuditApi(map_file) - - @notifier.log_and_ignore_error - def process_request(self, request): - self.cadf_audit.append_audit_event(request) - super(AuditMiddleware, self).process_request(request) - - @notifier.log_and_ignore_error - def process_response(self, request, response, - exception=None, traceback=None): - self.cadf_audit.mod_audit_event(request, response) - super(AuditMiddleware, self).process_response(request, response, - exception, traceback) diff --git a/pycadf/middleware/base.py b/pycadf/middleware/base.py deleted file mode 100644 index f627011..0000000 --- a/pycadf/middleware/base.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# -# 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. - -"""Base class(es) for WSGI Middleware.""" - -import webob.dec - - -class Middleware(object): - """Base WSGI middleware wrapper. - - These classes require an application to be initialized that will be called - next. By default the middleware will simply call its wrapped app, or you - can override __call__ to customize its behavior. - """ - - @classmethod - def factory(cls, global_conf, **local_conf): - """Factory method for paste.deploy.""" - return cls - - def __init__(self, application): - self.application = application - - def process_request(self, req): - """Called on each request. - - If this returns None, the next application down the stack will be - executed. If it returns a response then that response will be returned - and execution will stop here. - """ - return None - - def process_response(self, response): - """Do whatever you'd like to the response.""" - return response - - @webob.dec.wsgify - def __call__(self, req): - response = self.process_request(req) - if response: - return response - response = req.get_response(self.application) - return self.process_response(response) diff --git a/pycadf/middleware/notifier.py b/pycadf/middleware/notifier.py deleted file mode 100644 index c63dadc..0000000 --- a/pycadf/middleware/notifier.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright (c) 2013 eNovance -# -# 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. - -"""Send notifications on request""" - -import os.path -import sys -import traceback as tb - -from oslo_config import cfg -from oslo_context import context -import oslo_messaging -import six -import webob.dec - -from pycadf._i18n import _ -from pycadf.middleware import base - -LOG = None - - -def log_and_ignore_error(fn): - def wrapped(*args, **kwargs): - try: - return fn(*args, **kwargs) - except Exception as e: - if LOG: - LOG.exception(_('An exception occurred processing ' - 'the API call: %s ') % e) - return wrapped - - -class RequestNotifier(base.Middleware): - """Send notification on request.""" - - @classmethod - def factory(cls, global_conf, **local_conf): - """Factory method for paste.deploy.""" - conf = global_conf.copy() - conf.update(local_conf) - - def _factory(app): - return cls(app, **conf) - return _factory - - def __init__(self, app, **conf): - global LOG - - proj = cfg.CONF.project - TRANSPORT_ALIASES = {} - if proj: - log_mod = '%s.openstack.common.log' % proj - if log_mod in sys.modules: - LOG = sys.modules[log_mod].getLogger(__name__) - # Aliases to support backward compatibility - TRANSPORT_ALIASES = { - '%s.openstack.common.rpc.impl_kombu' % proj: 'rabbit', - '%s.openstack.common.rpc.impl_qpid' % proj: 'qpid', - '%s.openstack.common.rpc.impl_zmq' % proj: 'zmq', - '%s.rpc.impl_kombu' % proj: 'rabbit', - '%s.rpc.impl_qpid' % proj: 'qpid', - '%s.rpc.impl_zmq' % proj: 'zmq', - } - - self.service_name = conf.get('service_name') - self.ignore_req_list = [x.upper().strip() for x in - conf.get('ignore_req_list', '').split(',')] - self.notifier = oslo_messaging.Notifier( - oslo_messaging.get_transport(cfg.CONF, aliases=TRANSPORT_ALIASES), - os.path.basename(sys.argv[0])) - super(RequestNotifier, self).__init__(app) - - @staticmethod - def environ_to_dict(environ): - """Following PEP 333, server variables are lower case, so don't - include them. - """ - return dict((k, v) for k, v in six.iteritems(environ) - if k.isupper() and k != 'HTTP_X_AUTH_TOKEN') - - @log_and_ignore_error - def process_request(self, request): - request.environ['HTTP_X_SERVICE_NAME'] = \ - self.service_name or request.host - payload = { - 'request': self.environ_to_dict(request.environ), - } - - self.notifier.info(context.get_admin_context().to_dict(), - 'http.request', payload) - - @log_and_ignore_error - def process_response(self, request, response, - exception=None, traceback=None): - payload = { - 'request': self.environ_to_dict(request.environ), - } - - if response: - payload['response'] = { - 'status': response.status, - 'headers': response.headers, - } - - if exception: - payload['exception'] = { - 'value': repr(exception), - 'traceback': tb.format_tb(traceback) - } - - self.notifier.info(context.get_admin_context().to_dict(), - 'http.response', payload) - - @webob.dec.wsgify - def __call__(self, req): - if req.method in self.ignore_req_list: - return req.get_response(self.application) - else: - self.process_request(req) - try: - response = req.get_response(self.application) - except Exception: - exc_type, value, traceback = sys.exc_info() - self.process_response(req, None, value, traceback) - raise - else: - self.process_response(req, response) - return response diff --git a/pycadf/tests/audit/__init__.py b/pycadf/tests/audit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pycadf/tests/audit/test_api.py b/pycadf/tests/audit/test_api.py deleted file mode 100644 index 50c21ce..0000000 --- a/pycadf/tests/audit/test_api.py +++ /dev/null @@ -1,348 +0,0 @@ -# Copyright 2013 IBM Corp. -# -# 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. - -import uuid - -from oslo_config import cfg -import webob - -from pycadf.audit import api -from pycadf.tests import base - - -class TestAuditApi(base.TestCase): - ENV_HEADERS = {'HTTP_X_SERVICE_CATALOG': - '''[{"endpoints_links": [], - "endpoints": [{"adminURL": - "http://admin_host:8774", - "region": "RegionOne", - "publicURL": - "http://public_host:8775", - "internalURL": - "http://internal_host:8776", - "id": "resource_id"}], - "type": "compute", - "name": "nova"},]''', - 'HTTP_X_USER_ID': 'user_id', - 'HTTP_X_USER_NAME': 'user_name', - 'HTTP_X_AUTH_TOKEN': 'token', - 'HTTP_X_PROJECT_ID': 'tenant_id', - 'HTTP_X_IDENTITY_STATUS': 'Confirmed'} - - ENV_HEADERS_NO_ID = {'HTTP_X_SERVICE_CATALOG': - '''[{"endpoints_links": [], - "endpoints": [{"adminURL": - "http://admin_host:8774", - "region": "RegionOne", - "publicURL": - "http://public_host:8775", - "internalURL": - "http://internal_host:8776"}], - "type": "compute", - "name": "nova"}]''', - 'HTTP_X_USER_ID': 'user_id', - 'HTTP_X_USER_NAME': 'user_name', - 'HTTP_X_AUTH_TOKEN': 'token', - 'HTTP_X_PROJECT_ID': 'tenant_id', - 'HTTP_X_IDENTITY_STATUS': 'Confirmed'} - - def setUp(self): - super(TestAuditApi, self).setUp() - self.audit_api = api.OpenStackAuditApi( - 'etc/pycadf/nova_api_audit_map.conf') - - def api_request(self, method, url): - self.ENV_HEADERS['REQUEST_METHOD'] = method - req = webob.Request.blank(url, environ=self.ENV_HEADERS, - remote_addr='192.168.0.1') - self.audit_api.append_audit_event(req) - self.assertIn('CADF_EVENT_CORRELATION_ID', req.environ) - return req - - def api_request_missing_id(self, method, url): - self.ENV_HEADERS_NO_ID['REQUEST_METHOD'] = method - req = webob.Request.blank(url, environ=self.ENV_HEADERS_NO_ID, - remote_addr='192.168.0.1') - self.audit_api.append_audit_event(req) - self.assertIn('CADF_EVENT_CORRELATION_ID', req.environ) - return req - - def test_get_list_with_cfg(self): - cfg.CONF.set_override( - 'api_audit_map', - self.path_get('etc/pycadf/nova_api_audit_map.conf'), - group='audit') - self.audit_api = api.OpenStackAuditApi() - req = self.api_request('GET', - 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers/') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['action'], 'read/list') - - def test_get_list(self): - req = self.api_request('GET', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['action'], 'read/list') - self.assertEqual(payload['typeURI'], - 'http://schemas.dmtf.org/cloud/audit/1.0/event') - self.assertEqual(payload['outcome'], 'pending') - self.assertEqual(payload['eventType'], 'activity') - self.assertEqual(payload['target']['name'], 'nova') - self.assertEqual(payload['target']['id'], 'openstack:resource_id') - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - self.assertEqual(len(payload['target']['addresses']), 3) - self.assertEqual(payload['target']['addresses'][0]['name'], 'admin') - self.assertEqual(payload['target']['addresses'][0]['url'], - 'http://admin_host:8774') - self.assertEqual(payload['initiator']['id'], 'openstack:user_id') - self.assertEqual(payload['initiator']['name'], 'user_name') - self.assertEqual(payload['initiator']['project_id'], - 'openstack:tenant_id') - self.assertEqual(payload['initiator']['host']['address'], - '192.168.0.1') - self.assertEqual(payload['initiator']['typeURI'], - 'service/security/account/user') - self.assertNotEqual(payload['initiator']['credential']['token'], - 'token') - self.assertEqual(payload['initiator']['credential']['identity_status'], - 'Confirmed') - self.assertNotIn('reason', payload) - self.assertNotIn('reporterchain', payload) - self.assertEqual(payload['observer']['id'], 'target') - self.assertEqual(req.path, payload['requestPath']) - - def test_get_read(self): - req = self.api_request('GET', - 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers/' - + str(uuid.uuid4())) - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers/server') - self.assertEqual(payload['action'], 'read') - self.assertEqual(payload['outcome'], 'pending') - - def test_get_unknown_endpoint(self): - req = self.api_request('GET', - 'http://unknown:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['action'], 'read/list') - self.assertEqual(payload['outcome'], 'pending') - self.assertEqual(payload['target']['name'], 'unknown') - self.assertEqual(payload['target']['id'], 'unknown') - self.assertEqual(payload['target']['typeURI'], 'unknown') - - def test_templated_catalog(self): - url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers' - req = self.api_request_missing_id('GET', url) - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['id'], 'openstack:nova') - self.assertEqual(payload['target']['name'], 'nova') - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - - def test_get_unknown_endpoint_default_set(self): - tmpfile = self.temp_config_file_path() - with open(tmpfile, "w") as f: - f.write("[DEFAULT]\n") - f.write("target_endpoint_type = compute \n") - f.write("[path_keywords]\n") - f.write("servers = server\n\n") - f.write("[service_endpoints]\n") - f.write("compute = service/compute") - self.audit_api = api.OpenStackAuditApi(tmpfile) - - req = self.api_request('GET', - 'http://unknown:8774/v2/' - + str(uuid.uuid4()) + '/servers') - self.assertEqual(self.audit_api._MAP.default_target_endpoint_type, - 'compute') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['action'], 'read/list') - self.assertEqual(payload['outcome'], 'pending') - self.assertEqual(payload['target']['name'], 'nova') - self.assertEqual(payload['target']['id'], 'openstack:resource_id') - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - - def test_put(self): - req = self.api_request('PUT', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - self.assertEqual(payload['action'], 'update') - self.assertEqual(payload['outcome'], 'pending') - - def test_delete(self): - req = self.api_request('DELETE', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - self.assertEqual(payload['action'], 'delete') - self.assertEqual(payload['outcome'], 'pending') - - def test_head(self): - req = self.api_request('HEAD', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - self.assertEqual(payload['action'], 'read') - self.assertEqual(payload['outcome'], 'pending') - - def test_post_update(self): - req = self.api_request('POST', - 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers/' - + str(uuid.uuid4())) - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers/server') - self.assertEqual(payload['action'], 'update') - self.assertEqual(payload['outcome'], 'pending') - - def test_post_create(self): - req = self.api_request('POST', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers') - self.assertEqual(payload['action'], 'create') - self.assertEqual(payload['outcome'], 'pending') - - def test_post_action(self): - self.ENV_HEADERS['REQUEST_METHOD'] = 'POST' - req = webob.Request.blank('http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers/action', - environ=self.ENV_HEADERS) - req.body = b'{"createImage" : {"name" : "new-image","metadata": ' \ - b'{"ImageType": "Gold","ImageVersion": "2.0"}}}' - self.audit_api.append_audit_event(req) - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers/action') - self.assertEqual(payload['action'], 'update/createImage') - self.assertEqual(payload['outcome'], 'pending') - - def test_post_empty_body_action(self): - self.ENV_HEADERS['REQUEST_METHOD'] = 'POST' - req = webob.Request.blank('http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers/action', - environ=self.ENV_HEADERS) - self.audit_api.append_audit_event(req) - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/servers/action') - self.assertEqual(payload['action'], 'create') - self.assertEqual(payload['outcome'], 'pending') - - def test_custom_action(self): - req = self.api_request('GET', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/os-hosts/' - + str(uuid.uuid4()) + '/reboot') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/os-hosts/host/reboot') - self.assertEqual(payload['action'], 'start/reboot') - self.assertEqual(payload['outcome'], 'pending') - - def test_custom_action_complex(self): - req = self.api_request('GET', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/os-migrations') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/os-migrations') - self.assertEqual(payload['action'], 'read') - req = self.api_request('POST', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/os-migrations') - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['target']['typeURI'], - 'service/compute/os-migrations') - self.assertEqual(payload['action'], 'create') - - def test_response_mod_msg(self): - req = self.api_request('GET', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.audit_api.mod_audit_event(req, webob.Response()) - payload2 = req.environ['CADF_EVENT'] - self.assertEqual(payload['id'], payload2['id']) - self.assertEqual(payload['tags'], payload2['tags']) - self.assertEqual(payload2['outcome'], 'success') - self.assertEqual(payload2['reason']['reasonType'], 'HTTP') - self.assertEqual(payload2['reason']['reasonCode'], '200') - self.assertEqual(len(payload2['reporterchain']), 1) - self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier') - self.assertEqual(payload2['reporterchain'][0]['reporter']['id'], - 'target') - - def test_no_response(self): - req = self.api_request('GET', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers') - payload = req.environ['CADF_EVENT'] - self.audit_api.mod_audit_event(req, None) - payload2 = req.environ['CADF_EVENT'] - self.assertEqual(payload['id'], payload2['id']) - self.assertEqual(payload['tags'], payload2['tags']) - self.assertEqual(payload2['outcome'], 'unknown') - self.assertNotIn('reason', payload2) - self.assertEqual(len(payload2['reporterchain']), 1) - self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier') - self.assertEqual(payload2['reporterchain'][0]['reporter']['id'], - 'target') - - def test_missing_req(self): - self.ENV_HEADERS['REQUEST_METHOD'] = 'GET' - req = webob.Request.blank('http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/servers', - environ=self.ENV_HEADERS) - self.assertNotIn('CADF_EVENT', req.environ) - self.audit_api.mod_audit_event(req, webob.Response()) - self.assertIn('CADF_EVENT', req.environ) - self.assertIn('CADF_EVENT_CORRELATION_ID', req.environ) - payload = req.environ['CADF_EVENT'] - self.assertEqual(payload['outcome'], 'success') - self.assertEqual(payload['reason']['reasonType'], 'HTTP') - self.assertEqual(payload['reason']['reasonCode'], '200') - self.assertEqual(payload['observer']['id'], 'target') - self.assertNotIn('reporterchain', payload) - - def test_missing_tag(self): - req = self.api_request('GET', 'http://admin_host:8774/v2/' - + str(uuid.uuid4()) + '/os-migrations') - tmpfile = self.temp_config_file_path() - with open(tmpfile, "w") as f: - f.write("[DEFAULT]\n") - f.write("api_paths = servers\n\n") - f.write("[service_endpoints]\n") - f.write("compute = service/compute") - audit_api = api.OpenStackAuditApi(tmpfile) - self.assertRaises(ValueError, audit_api.create_event, req, None) - - -class TestAuditApiConf(base.TestCase): - def test_missing_default_option(self): - tmpfile = self.temp_config_file_path() - # NOTE(gordc): ensure target_endpoint_type is not in conf file - with open(tmpfile, "w") as f: - f.write("[DEFAULT]\n") - f.write("api_paths = servers\n\n") - f.write("[service_endpoints]\n") - f.write("compute = service/compute") - self.audit_api = api.OpenStackAuditApi(tmpfile) diff --git a/pycadf/tests/middleware/__init__.py b/pycadf/tests/middleware/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pycadf/tests/middleware/test_audit.py b/pycadf/tests/middleware/test_audit.py deleted file mode 100644 index aeb63d7..0000000 --- a/pycadf/tests/middleware/test_audit.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (c) 2014 OpenStack Foundation -# -# 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. - -import mock -import webob - -from pycadf.audit import api as cadf_api -from pycadf.middleware import audit -from pycadf.tests import base - - -class FakeApp(object): - def __call__(self, env, start_response): - body = 'Some response' - start_response('200 OK', [ - ('Content-Type', 'text/plain'), - ('Content-Length', str(sum(map(len, body)))) - ]) - return [body] - - -class FakeFailingApp(object): - def __call__(self, env, start_response): - raise Exception("It happens!") - - -@mock.patch('oslo_messaging.get_transport', mock.MagicMock()) -class AuditMiddlewareTest(base.TestCase): - ENV_HEADERS = {'HTTP_X_SERVICE_CATALOG': - '''[{"endpoints_links": [], - "endpoints": [{"adminURL": - "http://host:8774/v2/admin", - "region": "RegionOne", - "publicURL": - "http://host:8774/v2/public", - "internalURL": - "http://host:8774/v2/internal", - "id": "resource_id"}], - "type": "compute", - "name": "nova"},]''', - 'HTTP_X_USER_ID': 'user_id', - 'HTTP_X_USER_NAME': 'user_name', - 'HTTP_X_AUTH_TOKEN': 'token', - 'HTTP_X_PROJECT_ID': 'tenant_id', - 'HTTP_X_IDENTITY_STATUS': 'Confirmed'} - - def setUp(self): - super(AuditMiddlewareTest, self).setUp() - self.map_file = 'etc/pycadf/nova_api_audit_map.conf' - - def test_api_request(self): - middleware = audit.AuditMiddleware( - FakeApp(), - audit_map_file='etc/pycadf/nova_api_audit_map.conf', - service_name='pycadf') - self.ENV_HEADERS['REQUEST_METHOD'] = 'GET' - req = webob.Request.blank('/foo/bar', - environ=self.ENV_HEADERS) - with mock.patch('oslo_messaging.Notifier.info') as notify: - middleware(req) - # Check first notification with only 'request' - call_args = notify.call_args_list[0][0] - self.assertEqual(call_args[1], 'http.request') - self.assertEqual(set(call_args[2].keys()), - set(['request'])) - - request = call_args[2]['request'] - self.assertEqual(request['PATH_INFO'], '/foo/bar') - self.assertEqual(request['REQUEST_METHOD'], 'GET') - self.assertIn('CADF_EVENT', request) - self.assertEqual(request['CADF_EVENT']['outcome'], 'pending') - - # Check second notification with request + response - call_args = notify.call_args_list[1][0] - self.assertEqual(call_args[1], 'http.response') - self.assertEqual(set(call_args[2].keys()), - set(['request', 'response'])) - - request = call_args[2]['request'] - self.assertEqual(request['PATH_INFO'], '/foo/bar') - self.assertEqual(request['REQUEST_METHOD'], 'GET') - self.assertIn('CADF_EVENT', request) - self.assertEqual(request['CADF_EVENT']['outcome'], 'success') - - def test_api_request_failure(self): - middleware = audit.AuditMiddleware( - FakeFailingApp(), - audit_map_file='etc/pycadf/nova_api_audit_map.conf', - service_name='pycadf') - self.ENV_HEADERS['REQUEST_METHOD'] = 'GET' - req = webob.Request.blank('/foo/bar', - environ=self.ENV_HEADERS) - with mock.patch('oslo_messaging.Notifier.info') as notify: - try: - middleware(req) - self.fail("Application exception has not been re-raised") - except Exception: - pass - # Check first notification with only 'request' - call_args = notify.call_args_list[0][0] - self.assertEqual(call_args[1], 'http.request') - self.assertEqual(set(call_args[2].keys()), - set(['request'])) - - request = call_args[2]['request'] - self.assertEqual(request['PATH_INFO'], '/foo/bar') - self.assertEqual(request['REQUEST_METHOD'], 'GET') - self.assertIn('CADF_EVENT', request) - self.assertEqual(request['CADF_EVENT']['outcome'], 'pending') - - # Check second notification with request + response - call_args = notify.call_args_list[1][0] - self.assertEqual(call_args[1], 'http.response') - self.assertEqual(set(call_args[2].keys()), - set(['request', 'exception'])) - - request = call_args[2]['request'] - self.assertEqual(request['PATH_INFO'], '/foo/bar') - self.assertEqual(request['REQUEST_METHOD'], 'GET') - self.assertIn('CADF_EVENT', request) - self.assertEqual(request['CADF_EVENT']['outcome'], 'unknown') - - def test_process_request_fail(self): - def func_error(self, req): - raise Exception('error') - self.stubs.Set(cadf_api.OpenStackAuditApi, 'append_audit_event', - func_error) - middleware = audit.AuditMiddleware( - FakeApp(), - audit_map_file='etc/pycadf/nova_api_audit_map.conf', - service_name='pycadf') - req = webob.Request.blank('/foo/bar', - environ={'REQUEST_METHOD': 'GET'}) - middleware.process_request(req) - - def test_process_response_fail(self): - def func_error(self, req, res): - raise Exception('error') - self.stubs.Set(cadf_api.OpenStackAuditApi, 'mod_audit_event', - func_error) - middleware = audit.AuditMiddleware( - FakeApp(), - audit_map_file='etc/pycadf/nova_api_audit_map.conf', - service_name='pycadf') - req = webob.Request.blank('/foo/bar', - environ={'REQUEST_METHOD': 'GET'}) - middleware.process_response(req, webob.response.Response()) -- cgit v1.2.1