keystonemiddleware/keystonemiddleware/audit.py

170 lines
5.9 KiB
Python

#
# 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.
"""
Build open standard audit information based on incoming requests
AuditMiddleware filter should be placed after keystonemiddleware.auth_token
in the pipeline so that it can utilise the information the Identity server
provides.
"""
import functools
import logging
import os.path
import sys
from oslo.config import cfg
try:
import oslo.messaging
messaging = True
except ImportError:
messaging = False
import pycadf
from pycadf.audit import api
import webob.dec
from keystonemiddleware.i18n import _LE, _LI
from keystonemiddleware.openstack.common import context
_LOG = None
def _log_and_ignore_error(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as e:
_LOG.exception(_LE('An exception occurred processing '
'the API call: %s '), e)
return wrapper
class AuditMiddleware(object):
"""Create an audit event based on request/response.
The audit middleware takes in various configuration options such as the
ability to skip audit of certain requests. The full list of options can
be discovered here:
http://docs.openstack.org/developer/keystonemiddleware/audit.html
"""
@staticmethod
def _get_aliases(proj):
aliases = {}
if proj:
# Aliases to support backward compatibility
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',
}
return aliases
def __init__(self, app, **conf):
self._application = app
global _LOG
_LOG = logging.getLogger(conf.get('log_name', __name__))
self._service_name = conf.get('service_name')
self._ignore_req_list = [x.upper().strip() for x in
conf.get('ignore_req_list', '').split(',')]
self._cadf_audit = api.OpenStackAuditApi(
conf.get('audit_map_file'))
transport_aliases = self._get_aliases(cfg.CONF.project)
if messaging:
self._notifier = oslo.messaging.Notifier(
oslo.messaging.get_transport(cfg.CONF,
aliases=transport_aliases),
os.path.basename(sys.argv[0]))
def _emit_audit(self, context, event_type, payload):
"""Emit audit notification
if oslo.messaging enabled, send notification. if not, log event.
"""
if messaging:
self._notifier.info(context, event_type, payload)
else:
_LOG.info(_LI('Event type: %(event_type)s, Context: %(context)s, '
'Payload: %(payload)s'), {'context': context,
'event_type': event_type,
'payload': payload})
@_log_and_ignore_error
def _process_request(self, request):
correlation_id = pycadf.identifier.generate_uuid()
self._event = self._cadf_audit.create_event(request, correlation_id)
self._emit_audit(context.get_admin_context().to_dict(),
'audit.http.request', self._event.as_dict())
@_log_and_ignore_error
def _process_response(self, request, response=None):
if not hasattr(self, 'event'):
# NOTE(gordc): handle case where error processing request
correlation_id = pycadf.identifier.generate_uuid()
self._event = self._cadf_audit.create_event(request,
correlation_id)
if response:
if response.status_int >= 200 and response.status_int < 400:
result = pycadf.cadftaxonomy.OUTCOME_SUCCESS
else:
result = pycadf.cadftaxonomy.OUTCOME_FAILURE
self._event.reason = pycadf.reason.Reason(
reasonType='HTTP', reasonCode=str(response.status_int))
else:
result = pycadf.cadftaxonomy.UNKNOWN
self._event.outcome = result
self._event.add_reporterstep(
pycadf.reporterstep.Reporterstep(
role=pycadf.cadftype.REPORTER_ROLE_MODIFIER,
reporter=pycadf.resource.Resource(id='target'),
reporterTime=pycadf.timestamp.get_utc_now()))
self._emit_audit(context.get_admin_context().to_dict(),
'audit.http.response', self._event.as_dict())
@webob.dec.wsgify
def __call__(self, req):
if req.method in self._ignore_req_list:
return req.get_response(self._application)
self._process_request(req)
try:
response = req.get_response(self._application)
except Exception:
self._process_response(req)
raise
else:
self._process_response(req, response)
return response
def filter_factory(global_conf, **local_conf):
"""Returns a WSGI filter app for use with paste.deploy."""
conf = global_conf.copy()
conf.update(local_conf)
def audit_filter(app):
return AuditMiddleware(app, **conf)
return audit_filter