From c813c35214b44ebd135d89ea43bf55d3d327a11c Mon Sep 17 00:00:00 2001 From: Arun Kant Date: Fri, 12 Feb 2016 16:41:19 -0800 Subject: [PATCH] Adding audit middleware specific notification driver conf Now oslo messaging notifier can use driver information from audit middleware specific conf section. This allows audit to have different driver and transport usage from existing standard oslo messaging configuration. If audit middleware section is not defined, then existing logic is used which identifies driver from shared common oslo messaging notification conf section. Adjusted code and tests to recent oslo messaging notifier topic to topics arg change. And recent request.context change. Change-Id: Ia9ce654d3903efd0fd7893347e44ee27a765c745 Closes-Bug: 1544840 --- doc/source/audit.rst | 25 ++++- keystonemiddleware/audit.py | 32 +++++- .../tests/unit/test_audit_middleware.py | 98 +++++++++++++++++++ .../notes/bug-1544840-a534127f8663e40f.yaml | 6 ++ 4 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1544840-a534127f8663e40f.yaml diff --git a/doc/source/audit.rst b/doc/source/audit.rst index d23f8168..bd7f94fd 100644 --- a/doc/source/audit.rst +++ b/doc/source/audit.rst @@ -15,9 +15,9 @@ .. _middleware: -================= - Audit middleware -================= +================ +Audit middleware +================ The Keystone middleware library provides an optional WSGI middleware filter which allows the ability to audit API requests for each component of OpenStack. @@ -78,4 +78,23 @@ Additional options can be set:: service_name = test # opt to set HTTP_X_SERVICE_NAME environ variable ignore_req_list = GET,POST # opt to ignore specific requests +Audit middleware can be configured to use its own exclusive notification driver +and topic(s) value. This can be useful when the service is already using oslo +messaging notifications and wants to use a different driver for auditing e.g. +service has existing notifications sent to queue via 'messagingv2' and wants to +send audit notifications to a log file via 'log' driver. Example shown below:: + + [audit_middleware_notifications] + driver = log + +When audit events are sent via 'messagingv2' or 'messaging', middleware can +specify a transport URL if its transport URL needs to be different from the +service's own messaging transport setting. Other Transport related settings are +read from oslo messaging sections defined in service configuration e.g. +'oslo_messaging_rabbit'. Example shown below:: + + [audit_middleware_notifications] + driver = messaging + transport_url = rabbit://user2:passwd@host:5672/another_virtual_host + .. _pyCADF library: https://github.com/openstack/pycadf/tree/master/etc/pycadf diff --git a/keystonemiddleware/audit.py b/keystonemiddleware/audit.py index cecadc94..6028f357 100644 --- a/keystonemiddleware/audit.py +++ b/keystonemiddleware/audit.py @@ -56,6 +56,27 @@ from keystonemiddleware.i18n import _LE, _LI _LOG = None +_AUDIT_OPTS = [ + cfg.StrOpt('driver', + default=None, + help='The Driver to handle sending notifications. Possible ' + 'values are messaging, messagingv2, routing, log, test, ' + 'noop. If not specified, then value from ' + 'oslo_messaging_notifications conf section is used.'), + cfg.ListOpt('topics', + default=None, + help='List of AMQP topics used for OpenStack notifications. If' + ' not specified, then value from ' + ' oslo_messaging_notifications conf section is used.'), + cfg.StrOpt('transport_url', + default=None, + secret=True, + help='A URL representing messaging driver to use for ' + 'notification. If not specified, we fall back to the same ' + 'configuration used for RPC.'), +] +cfg.CONF.register_opts(_AUDIT_OPTS, group="audit_middleware_notifications") + def _log_and_ignore_error(fn): @functools.wraps(fn) @@ -345,10 +366,15 @@ class AuditMiddleware(object): transport_aliases = self._get_aliases(cfg.CONF.project) if messaging: + transport = oslo_messaging.get_transport( + cfg.CONF, + url=cfg.CONF.audit_middleware_notifications.transport_url, + aliases=transport_aliases) self._notifier = oslo_messaging.Notifier( - oslo_messaging.get_transport(cfg.CONF, - aliases=transport_aliases), - os.path.basename(sys.argv[0])) + transport, + os.path.basename(sys.argv[0]), + driver=cfg.CONF.audit_middleware_notifications.driver, + topics=cfg.CONF.audit_middleware_notifications.topics) def _emit_audit(self, context, event_type, payload): """Emit audit notification. diff --git a/keystonemiddleware/tests/unit/test_audit_middleware.py b/keystonemiddleware/tests/unit/test_audit_middleware.py index c19ea375..fb39229c 100644 --- a/keystonemiddleware/tests/unit/test_audit_middleware.py +++ b/keystonemiddleware/tests/unit/test_audit_middleware.py @@ -258,6 +258,104 @@ class AuditMiddlewareTest(BaseAuditMiddlewareTest): notify.call_args_list[0][0][2]['id']) +def _get_transport(conf, aliases=None, url=None): + transport = mock.MagicMock() + transport.conf = conf + conf.register_opts = mock.MagicMock() + return transport + + +@mock.patch('oslo_messaging.get_transport', side_effect=_get_transport) +class AuditNotifierConfigTest(BaseAuditMiddlewareTest): + + def test_conf_middleware_log_and_default_as_messaging(self, t): + cfg.CONF.notification_driver = ['messaging'] # MultiOptStr value + cfg.CONF.audit_middleware_notifications.driver = 'log' + middleware = audit.AuditMiddleware( + FakeApp(), + audit_map_file=self.audit_map, + service_name='pycadf') + req = webob.Request.blank('/foo/bar', + environ=self.get_environ_header('GET')) + req.context = {} + with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify', + side_effect=Exception('error')) as driver: + middleware._process_request(req) + # audit middleware conf has 'log' make sure that driver is invoked + # and not the one specified in DEFAULT section + self.assertTrue(driver.called) + + def test_conf_middleware_log_and_oslo_msg_as_messaging(self, t): + cfg.CONF.notification_driver = None + cfg.CONF.oslo_messaging_notifications.driver = ['messaging'] + cfg.CONF.audit_middleware_notifications.driver = 'log' + middleware = audit.AuditMiddleware( + FakeApp(), + audit_map_file=self.audit_map, + service_name='pycadf') + req = webob.Request.blank('/foo/bar', + environ=self.get_environ_header('GET')) + req.context = {} + with mock.patch('oslo_messaging.notify._impl_log.LogDriver.notify', + side_effect=Exception('error')) as driver: + middleware._process_request(req) + # audit middleware conf has 'log' make sure that driver is invoked + # and not the one specified in oslo_messaging_notifications section + self.assertTrue(driver.called) + + def test_conf_middleware_messaging_and_oslo_msg_as_log(self, t): + cfg.CONF.notification_driver = None + cfg.CONF.oslo_messaging_notifications.driver = ['log'] + cfg.CONF.audit_middleware_notifications.driver = 'messaging' + middleware = audit.AuditMiddleware( + FakeApp(), + audit_map_file=self.audit_map, + service_name='pycadf') + req = webob.Request.blank('/foo/bar', + environ=self.get_environ_header('GET')) + req.context = {} + with mock.patch('oslo_messaging.notify.messaging.MessagingDriver' + '.notify', + side_effect=Exception('error')) as driver: + # audit middleware has 'messaging' make sure that driver is invoked + # and not the one specified in oslo_messaging_notifications section + middleware._process_request(req) + self.assertTrue(driver.called) + + def test_with_no_middleware_notification_conf(self, t): + cfg.CONF.notification_driver = None + cfg.CONF.oslo_messaging_notifications.driver = ['messaging'] + cfg.CONF.audit_middleware_notifications.driver = None + middleware = audit.AuditMiddleware( + FakeApp(), + audit_map_file=self.audit_map, + service_name='pycadf') + req = webob.Request.blank('/foo/bar', + environ=self.get_environ_header('GET')) + req.context = {} + with mock.patch('oslo_messaging.notify.messaging.MessagingDriver' + '.notify', + side_effect=Exception('error')) as driver: + # audit middleware section is not set. So driver needs to be + # invoked from oslo_messaging_notifications section. + middleware._process_request(req) + self.assertTrue(driver.called) + + def test_conf_middleware_messaging_and_transport_set(self, mock_transport): + transport_url = 'rabbit://me:passwd@host:5672/virtual_host' + cfg.CONF.audit_middleware_notifications.driver = 'messaging' + cfg.CONF.audit_middleware_notifications.transport_url = transport_url + + audit.AuditMiddleware( + FakeApp(), + audit_map_file=self.audit_map, + service_name='pycadf') + self.assertTrue(mock_transport.called) + # make sure first call kwarg 'url' is same as provided transport_url + self.assertEqual(transport_url, + mock_transport.call_args_list[0][1]['url']) + + @mock.patch('oslo_messaging.rpc', mock.MagicMock()) class AuditApiLogicTest(BaseAuditMiddlewareTest): diff --git a/releasenotes/notes/bug-1544840-a534127f8663e40f.yaml b/releasenotes/notes/bug-1544840-a534127f8663e40f.yaml new file mode 100644 index 00000000..69752074 --- /dev/null +++ b/releasenotes/notes/bug-1544840-a534127f8663e40f.yaml @@ -0,0 +1,6 @@ +--- +features: + - > + [`bug 1544840 `_] + Adding audit middleware specific notification related configuration + to allow a different notification driver and transport for audit if needed.