summaryrefslogtreecommitdiff
path: root/pycadf
diff options
context:
space:
mode:
authorgordon chung <gord@live.ca>2015-04-23 16:22:58 -0400
committergordon chung <gord@live.ca>2015-04-24 13:37:56 -0400
commit67498650bd213b42c18bdf12f842e056fb2b99d7 (patch)
tree5ffdc4e1ad2fc658c7c8482621847257d5c86ce8 /pycadf
parent52893135b53c5ad54ad539ceb134f17ad0aced57 (diff)
downloadpycadf-67498650bd213b42c18bdf12f842e056fb2b99d7.tar.gz
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
Diffstat (limited to 'pycadf')
-rw-r--r--pycadf/audit/__init__.py0
-rw-r--r--pycadf/audit/api.py321
-rw-r--r--pycadf/middleware/__init__.py0
-rw-r--r--pycadf/middleware/audit.py50
-rw-r--r--pycadf/middleware/base.py55
-rw-r--r--pycadf/middleware/notifier.py139
-rw-r--r--pycadf/tests/audit/__init__.py0
-rw-r--r--pycadf/tests/audit/test_api.py348
-rw-r--r--pycadf/tests/middleware/__init__.py0
-rw-r--r--pycadf/tests/middleware/test_audit.py158
10 files changed, 0 insertions, 1071 deletions
diff --git a/pycadf/audit/__init__.py b/pycadf/audit/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pycadf/audit/__init__.py
+++ /dev/null
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
--- a/pycadf/middleware/__init__.py
+++ /dev/null
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
--- a/pycadf/tests/audit/__init__.py
+++ /dev/null
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
--- a/pycadf/tests/middleware/__init__.py
+++ /dev/null
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())