def setUp(self): super(BaseAuditMiddlewareTest, self).setUp() self.fd, self.audit_map = tempfile.mkstemp() with open(self.audit_map, "w") as f: f.write("[custom_actions]\n") f.write("reboot = start/reboot\n") f.write("os-migrations/get = read\n\n") f.write("[path_keywords]\n") f.write("action = None\n") f.write("os-hosts = host\n") f.write("os-migrations = None\n") f.write("reboot = None\n") f.write("servers = server\n\n") f.write("[service_endpoints]\n") f.write("compute = service/compute") cfg.CONF([], project='keystonemiddleware') self.middleware = audit.AuditMiddleware(FakeApp(), audit_map_file=self.audit_map, service_name='pycadf') self.addCleanup(lambda: os.close(self.fd)) self.addCleanup(cfg.CONF.reset)
def _wrap_app(app): """Wraps wsgi app with additional middlewares.""" app = request_id.RequestId(app) if CONF.audit.enabled: try: app = audit_middleware.AuditMiddleware( app, audit_map_file=CONF.audit.audit_map_file, ignore_req_list=CONF.audit.ignore_req_list) except (EnvironmentError, OSError, audit_middleware.PycadfAuditApiConfigError) as e: raise exceptions.InputFileError( file_name=CONF.audit.audit_map_file, reason=e) if cfg.CONF.api_settings.auth_strategy == constants.KEYSTONE: app = keystone.SkippingAuthProtocol(app, {}) # This should be the last middleware in the list (which results in # it being the first in the middleware chain). This is to ensure # that any errors thrown by other middleware, such as an auth # middleware - are annotated with CORS headers, and thus accessible # by the browser. app = cors.CORS(app, cfg.CONF) cors.set_defaults( allow_headers=['X-Auth-Token', 'X-Openstack-Request-Id'], allow_methods=['GET', 'PUT', 'POST', 'DELETE'], expose_headers=['X-Auth-Token', 'X-Openstack-Request-Id']) return app
def test_ignore_req_opt(self): self.middleware = audit.AuditMiddleware(FakeApp(), audit_map_file=self.audit_map, ignore_req_list='get, PUT') req = webob.Request.blank('/skip/foo', environ=self.get_environ_header('GET')) req1 = webob.Request.blank('/skip/foo', environ=self.get_environ_header('PUT')) req2 = webob.Request.blank('/accept/foo', environ=self.get_environ_header('POST')) with mock.patch('oslo.messaging.Notifier.info') as notify: # Check GET/PUT request does not send notification self.middleware(req) self.middleware(req1) self.assertEqual([], notify.call_args_list) # Check non-GET/PUT request does send notification self.middleware(req2) self.assertThat(notify.call_args_list, matchers.HasLength(2)) call_args = notify.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]) self.assertEqual('/accept/foo', call_args[2]['requestPath']) call_args = notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/accept/foo', call_args[2]['requestPath'])
def setUp(self): super(BaseAuditMiddlewareTest, self).setUp() self.fd, self.audit_map = tempfile.mkstemp() with open(self.audit_map, "w") as f: f.write("[custom_actions]\n") f.write("reboot = start/reboot\n") f.write("os-migrations/get = read\n\n") f.write("[path_keywords]\n") f.write("action = None\n") f.write("os-hosts = host\n") f.write("os-migrations = None\n") f.write("reboot = None\n") f.write("servers = server\n\n") f.write("[service_endpoints]\n") f.write("compute = service/compute") cfg.CONF([], project='keystonemiddleware') self.middleware = audit.AuditMiddleware(FakeApp(), audit_map_file=self.audit_map, service_name='pycadf') # NOTE(stevemar): For this test suite and for the stable liberty branch # only, we will ignore deprecated calls that keystonemiddleware makes. warnings.filterwarnings('ignore', category=DeprecationWarning, module='^keystonemiddleware\\.') self.addCleanup(lambda: os.close(self.fd)) self.addCleanup(cfg.CONF.reset)
def test_api_request_failure(self): self.middleware = audit.AuditMiddleware(FakeFailingApp(), audit_map_file=self.audit_map, service_name='pycadf') req = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) with mock.patch('oslo.messaging.Notifier.info') as notify: try: self.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('audit.http.request', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('pending', call_args[2]['outcome']) self.assertNotIn('reporterchain', call_args[2]) # Check second notification with request + response call_args = notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('unknown', call_args[2]['outcome']) self.assertIn('reporterchain', call_args[2])
def setup_app(pecan_config=None, extra_hooks=None): app_hooks = [ hooks.ConfigHook(), hooks.DBHook(), hooks.ContextHook(pecan_config.app.acl_public_routes), hooks.RPCHook(), hooks.NoExceptionTracebackHook(), hooks.PublicUrlHook() ] if extra_hooks: app_hooks.extend(extra_hooks) if not pecan_config: pecan_config = get_pecan_config() pecan.configuration.set_config(dict(pecan_config), overwrite=True) app = pecan.make_app( pecan_config.app.root, debug=CONF.pecan_debug, static_root=pecan_config.app.static_root if CONF.pecan_debug else None, force_canonical=getattr(pecan_config.app, 'force_canonical', True), hooks=app_hooks, wrap_app=middleware.ParsableErrorMiddleware, ) if CONF.audit.enabled: try: app = audit_middleware.AuditMiddleware( app, audit_map_file=CONF.audit.audit_map_file, ignore_req_list=CONF.audit.ignore_req_list) except (EnvironmentError, OSError, audit_middleware.PycadfAuditApiConfigError) as e: raise exception.InputFileError(file_name=CONF.audit.audit_map_file, reason=e) if CONF.auth_strategy == "keystone": app = auth_token.AuthTokenMiddleware( app, dict(cfg.CONF), public_api_routes=pecan_config.app.acl_public_routes) # Create a CORS wrapper, and attach ironic-specific defaults that must be # included in all CORS responses. app = cors_middleware.CORS(app, CONF) cors_middleware.set_defaults( allow_headers=[ base.Version.max_string, base.Version.min_string, base.Version.string ], allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], expose_headers=[ base.Version.max_string, base.Version.min_string, base.Version.string ]) return app
def create_middleware(self, cb, **kwargs): @webob.dec.wsgify def _do_cb(req): return cb(req) kwargs.setdefault('audit_map_file', self.audit_map) kwargs.setdefault('service_name', 'pycadf') return audit.AuditMiddleware(_do_cb, **kwargs)
def test_process_response_fail(self): 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')) with mock.patch('oslo.messaging.Notifier.info', side_effect=Exception('error')) as notify: middleware._process_response(req, webob.response.Response()) self.assertTrue(notify.called)
def test_cadf_event_scoped_to_request(self): 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')) with mock.patch('oslo.messaging.Notifier.info') as notify: middleware(req) self.assertIsNotNone(req.environ.get('cadf_event')) # ensure exact same event is used between request and response self.assertEqual(notify.call_args_list[0][0][2]['id'], notify.call_args_list[1][0][2]['id'])
def test_cadf_event_scoped_to_request_on_error(self): 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')) with mock.patch('oslo.messaging.Notifier.info', side_effect=Exception('error')) as notify: middleware._process_request(req) self.assertTrue(notify.called) req2 = webob.Request.blank('/foo/bar', environ=self.get_environ_header('GET')) with mock.patch('oslo.messaging.Notifier.info') as notify: middleware._process_response(req2, webob.response.Response()) self.assertTrue(notify.called) # ensure event is not the same across requests self.assertNotEqual(req.environ['cadf_event'].id, notify.call_args_list[0][0][2]['id'])
def test_api_request_no_messaging(self): 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')) with mock.patch('keystonemiddleware.audit.messaging', None): with mock.patch('keystonemiddleware.audit._LOG.info') as log: middleware(req) # Check first notification with only 'request' call_args = log.call_args_list[0][0] self.assertEqual('audit.http.request', call_args[1]['event_type']) # Check second notification with request + response call_args = log.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]['event_type'])
def test_get_unknown_endpoint_default_set(self): with open(self.audit_map, "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.middleware = audit.AuditMiddleware(FakeApp(), audit_map_file=self.audit_map, service_name='pycadf') req = self.api_request( 'GET', 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers') payload = req.environ['cadf_event'].as_dict() 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_api_request(self): 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')) 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('audit.http.request', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('pending', call_args[2]['outcome']) self.assertNotIn('reason', call_args[2]) self.assertNotIn('reporterchain', call_args[2]) # Check second notification with request + response call_args = notify.call_args_list[1][0] self.assertEqual('audit.http.response', call_args[1]) self.assertEqual('/foo/bar', call_args[2]['requestPath']) self.assertEqual('success', call_args[2]['outcome']) self.assertIn('reason', call_args[2]) self.assertIn('reporterchain', call_args[2])
def setup_app(pecan_config=None, extra_hooks=None): app_hooks = [ hooks.ConfigHook(), hooks.DBHook(), hooks.ContextHook(pecan_config.app.acl_public_routes), hooks.RPCHook(), hooks.NoExceptionTracebackHook(), hooks.PublicUrlHook() ] if extra_hooks: app_hooks.extend(extra_hooks) if not pecan_config: pecan_config = get_pecan_config() pecan.configuration.set_config(dict(pecan_config), overwrite=True) app = pecan.make_app( pecan_config.app.root, debug=CONF.pecan_debug, static_root=pecan_config.app.static_root if CONF.pecan_debug else None, force_canonical=getattr(pecan_config.app, 'force_canonical', True), hooks=app_hooks, wrap_app=middleware.ParsableErrorMiddleware, # NOTE(dtantsur): enabling this causes weird issues with nodes named # as if they had a known mime extension, e.g. "mynode.1". We do # simulate the same behaviour for .json extensions for backward # compatibility through JsonExtensionMiddleware. guess_content_type_from_ext=False, ) if CONF.audit.enabled: try: app = audit_middleware.AuditMiddleware( app, audit_map_file=CONF.audit.audit_map_file, ignore_req_list=CONF.audit.ignore_req_list) except (EnvironmentError, OSError, audit_middleware.PycadfAuditApiConfigError) as e: raise exception.InputFileError(file_name=CONF.audit.audit_map_file, reason=e) if CONF.auth_strategy == "keystone": app = auth_token.AuthTokenMiddleware( app, {"oslo_config_config": cfg.CONF}, public_api_routes=pecan_config.app.acl_public_routes) if CONF.profiler.enabled: app = osprofiler_web.WsgiMiddleware(app) # Create a CORS wrapper, and attach ironic-specific defaults that must be # included in all CORS responses. app = IronicCORS(app, CONF) cors_middleware.set_defaults( allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], expose_headers=[ base.Version.max_string, base.Version.min_string, base.Version.string ]) app = json_ext.JsonExtensionMiddleware(app) return app
def setup_app(pecan_config=None, extra_hooks=None): app_hooks = [ hooks.ConfigHook(), hooks.DBHook(), hooks.ContextHook(pecan_config.app.acl_public_routes), hooks.RPCHook(), hooks.NoExceptionTracebackHook(), hooks.PublicUrlHook() ] if extra_hooks: app_hooks.extend(extra_hooks) if not pecan_config: pecan_config = get_pecan_config() pecan.configuration.set_config(dict(pecan_config), overwrite=True) app = pecan.make_app( pecan_config.app.root, debug=CONF.pecan_debug, static_root=pecan_config.app.static_root if CONF.pecan_debug else None, force_canonical=getattr(pecan_config.app, 'force_canonical', True), hooks=app_hooks, wrap_app=middleware.ParsableErrorMiddleware, # NOTE(dtantsur): enabling this causes weird issues with nodes named # as if they had a known mime extension, e.g. "mynode.1". We do # simulate the same behaviour for .json extensions for backward # compatibility through JsonExtensionMiddleware. guess_content_type_from_ext=False, ) if CONF.audit.enabled: try: app = audit_middleware.AuditMiddleware( app, audit_map_file=CONF.audit.audit_map_file, ignore_req_list=CONF.audit.ignore_req_list) except (EnvironmentError, OSError, audit_middleware.PycadfAuditApiConfigError) as e: raise exception.InputFileError(file_name=CONF.audit.audit_map_file, reason=e) auth_middleware = None if CONF.auth_strategy == "keystone": auth_middleware = auth_token.AuthProtocol( app, {"oslo_config_config": cfg.CONF}) elif CONF.auth_strategy == "http_basic": auth_middleware = auth_basic.BasicAuthMiddleware( app, cfg.CONF.http_basic_auth_user_file) if auth_middleware: app = auth_public_routes.AuthPublicRoutes( app, auth=auth_middleware, public_api_routes=pecan_config.app.acl_public_routes) if CONF.profiler.enabled: app = osprofiler_web.WsgiMiddleware(app) # NOTE(pas-ha) this registers oslo_middleware.enable_proxy_headers_parsing # option, when disabled (default) this is noop middleware app = http_proxy_to_wsgi.HTTPProxyToWSGI(app, CONF) # add in the healthcheck middleware if enabled # NOTE(jroll) this is after the auth token middleware as we don't want auth # in front of this, and WSGI works from the outside in. Requests to # /healthcheck will be handled and returned before the auth middleware # is reached. if CONF.healthcheck.enabled: app = healthcheck.Healthcheck(app, CONF) # Create a CORS wrapper, and attach ironic-specific defaults that must be # included in all CORS responses. app = IronicCORS(app, CONF) cors_middleware.set_defaults( allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], expose_headers=[ base.Version.max_string, base.Version.min_string, base.Version.string ]) app = json_ext.JsonExtensionMiddleware(app) return app