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.