From df9181e5646f8cbf55a1f9357b51170c2025c18f Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Thu, 26 Feb 2015 09:20:26 -0500 Subject: [PATCH] create noauth2 This creates a noauth2 auth_strategy which is similar to noauth, except it only gives you an admin context if the username passed in is 'admin'. This allows testing of non admin activities. noauth is deprecated as of this commit. While we expect that it would only be used in testing, it is exposed as a conf option, so could be used behind a different auth proxy. Also make the error path for pipeline loading contain a full LOG.exception. This is a fatal condition for nova, and the current error was often quite opaque. The full stack trace during this fatal error makes addressing paste.ini issues much more straight forward. DocImpact Change-Id: I7cb5ab3e43a1e3bd7ccba0480053361743f859b2 --- etc/nova/api-paste.ini | 7 +++++ nova/api/auth.py | 15 ++++++++++- nova/api/openstack/auth.py | 27 ++++++++++++++++--- nova/tests/fixtures.py | 2 ++ nova/tests/functional/integrated_helpers.py | 1 + .../functional/wsgi/test_flavor_manage.py | 2 +- nova/tests/unit/api/test_auth.py | 20 ++++++++++++-- nova/wsgi.py | 4 +-- 8 files changed, 69 insertions(+), 9 deletions(-) diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index be00f3873535..1a87f0c5a346 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -22,6 +22,7 @@ use = egg:Paste#urlmap [composite:ec2cloud] use = call:nova.api.auth:pipeline_factory noauth = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor +noauth2 = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor keystone = ec2faultwrap logrequest ec2keystoneauth cloudrequest validator ec2executor [filter:ec2faultwrap] @@ -67,17 +68,20 @@ use = call:nova.api.openstack.urlmap:urlmap_factory [composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 +noauth2 = compute_req_id faultwrap sizelimit noauth2 ratelimit osapi_compute_app_v2 keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2 [composite:openstack_compute_api_v21] use = call:nova.api.auth:pipeline_factory_v21 noauth = compute_req_id faultwrap sizelimit noauth osapi_compute_app_v21 +noauth2 = compute_req_id faultwrap sizelimit noauth2 osapi_compute_app_v21 keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21 [composite:openstack_compute_api_v3] use = call:nova.api.auth:pipeline_factory_v21 noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3 +noauth2 = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3 keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 [filter:request_id] @@ -90,6 +94,9 @@ paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory paste.filter_factory = nova.api.openstack:FaultWrapper.factory [filter:noauth] +paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareOld.factory + +[filter:noauth2] paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory [filter:noauth_v3] diff --git a/nova/api/auth.py b/nova/api/auth.py index 33613e107b4e..d0c4b1ed799f 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -25,6 +25,7 @@ import webob.exc from nova import context from nova.i18n import _ +from nova.openstack.common import versionutils from nova import wsgi @@ -36,7 +37,13 @@ auth_opts = [ 'is removed from v3 api.'), cfg.StrOpt('auth_strategy', default='keystone', - help='The strategy to use for auth: noauth or keystone.'), + help=''' +The strategy to use for auth: keystone, noauth (deprecated), or +noauth2. Both noauth and noauth2 are designed for testing only, as +they do no actual credential checking. noauth provides administrative +credentials regardless of the passed in user, noauth2 only does if +'admin' is specified as the username. +'''), cfg.BoolOpt('use_forwarded_for', default=False, help='Treat X-Forwarded-For as the canonical remote address. ' @@ -60,6 +67,12 @@ def _load_pipeline(loader, pipeline): def pipeline_factory(loader, global_conf, **local_conf): """A paste pipeline replica that keys off of auth_strategy.""" + # TODO(sdague): remove deprecated noauth in Liberty + if CONF.auth_strategy == 'noauth': + versionutils.report_deprecated_feature( + LOG, + ('The noauth middleware will be removed in Liberty.' + ' noauth2 should be used instead.')) pipeline = local_conf[CONF.auth_strategy] if not CONF.api_rate_limit: limit_name = CONF.auth_strategy + '_nolimit' diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 469bf2181deb..6f53a9d26145 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -29,7 +29,7 @@ CONF.import_opt('use_forwarded_for', 'nova.api.auth') class NoAuthMiddlewareBase(base_wsgi.Middleware): """Return a fake token if one isn't specified.""" - def base_call(self, req, project_id_in_path): + def base_call(self, req, project_id_in_path, always_admin=True): if 'X-Auth-Token' not in req.headers: user_id = req.headers.get('X-Auth-User', 'admin') project_id = req.headers.get('X-Auth-Project-Id', 'admin') @@ -53,9 +53,10 @@ class NoAuthMiddlewareBase(base_wsgi.Middleware): remote_address = getattr(req, 'remote_address', '127.0.0.1') if CONF.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) + is_admin = always_admin or (user_id == 'admin') ctx = context.RequestContext(user_id, project_id, - is_admin=True, + is_admin=is_admin, remote_address=remote_address) req.environ['nova.context'] = ctx @@ -63,7 +64,27 @@ class NoAuthMiddlewareBase(base_wsgi.Middleware): class NoAuthMiddleware(NoAuthMiddlewareBase): - """Return a fake token if one isn't specified.""" + """Return a fake token if one isn't specified. + + noauth2 is a variation on noauth that only provides admin privs if + 'admin' is provided as the user id. We will deprecate the + NoAuthMiddlewareOld for future removal so we don't need to + maintain both code paths. + + """ + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + return self.base_call(req, True, always_admin=False) + + +# TODO(sdague): remove in Liberty +class NoAuthMiddlewareOld(NoAuthMiddlewareBase): + """Return a fake token if one isn't specified. + + This is the Deprecated version of noauth, and should be removed in + the Liberty cycle. + + """ @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): return self.base_call(req, True) diff --git a/nova/tests/fixtures.py b/nova/tests/fixtures.py index f1ae8ea32a2c..f1941ec35bc5 100644 --- a/nova/tests/fixtures.py +++ b/nova/tests/fixtures.py @@ -331,6 +331,8 @@ class OSAPIFixture(fixtures.Fixture): 'host': osapi.host, 'port': osapi.port, 'api_version': self.api_version}) self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url) + self.admin_api = client.TestOpenStackClient( + 'admin', 'admin', self.auth_url) class PoisonFunctions(fixtures.Fixture): diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py index e74d5888ec09..f9e8f8087487 100644 --- a/nova/tests/functional/integrated_helpers.py +++ b/nova/tests/functional/integrated_helpers.py @@ -88,6 +88,7 @@ class _IntegratedTestBase(test.TestCase): self.api_fixture = self.useFixture( nova_fixtures.OSAPIFixture(self._api_version)) self.api = self.api_fixture.api + self.admin_api = self.api_fixture.admin_api self.useFixture(cast_as_call.CastAsCall(self.stubs)) diff --git a/nova/tests/functional/wsgi/test_flavor_manage.py b/nova/tests/functional/wsgi/test_flavor_manage.py index b5c76af437ea..8f13872c1ff7 100644 --- a/nova/tests/functional/wsgi/test_flavor_manage.py +++ b/nova/tests/functional/wsgi/test_flavor_manage.py @@ -70,7 +70,7 @@ class FlavorManageFullstack(test.TestCase): """ def setUp(self): super(FlavorManageFullstack, self).setUp() - self.api = self.useFixture(nova_fixtures.OSAPIFixture()).api + self.api = self.useFixture(nova_fixtures.OSAPIFixture()).admin_api def assertFlavorDbEqual(self, flav, flavdb): # a mapping of the REST params to the db fields diff --git a/nova/tests/unit/api/test_auth.py b/nova/tests/unit/api/test_auth.py index 19215d18fdf9..76ef16d3e91e 100644 --- a/nova/tests/unit/api/test_auth.py +++ b/nova/tests/unit/api/test_auth.py @@ -152,18 +152,34 @@ class TestPipeLineFactory(test.NoDBTestCase): self.assertEqual(app.name, pipeline.split()[-1]) self.assertIsInstance(app, TestPipeLineFactory.FakeApp) - def test_pipeline_factory(self): + def test_pipeline_factory_noauthold(self): fake_pipeline = 'test1 test2 test3' + CONF.set_override('auth_strategy', 'noauth') app = nova.api.auth.pipeline_factory( TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline) self._test_pipeline(fake_pipeline, app) - def test_pipeline_factory_v21(self): + def test_pipeline_factory_v21_noauthold(self): fake_pipeline = 'test1 test2 test3' + CONF.set_override('auth_strategy', 'noauth') app = nova.api.auth.pipeline_factory_v21( TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline) self._test_pipeline(fake_pipeline, app) + def test_pipeline_factory(self): + fake_pipeline = 'test1 test2 test3' + CONF.set_override('auth_strategy', 'noauth2') + app = nova.api.auth.pipeline_factory( + TestPipeLineFactory.FakeLoader(), None, noauth2=fake_pipeline) + self._test_pipeline(fake_pipeline, app) + + def test_pipeline_factory_v21(self): + fake_pipeline = 'test1 test2 test3' + CONF.set_override('auth_strategy', 'noauth2') + app = nova.api.auth.pipeline_factory_v21( + TestPipeLineFactory.FakeLoader(), None, noauth2=fake_pipeline) + self._test_pipeline(fake_pipeline, app) + def test_pipeline_factory_with_rate_limits(self): CONF.set_override('api_rate_limit', True) CONF.set_override('auth_strategy', 'keystone') diff --git a/nova/wsgi.py b/nova/wsgi.py index ba3f0c939f80..1b8a649b08e0 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -531,6 +531,6 @@ class Loader(object): LOG.debug("Loading app %(name)s from %(path)s", {'name': name, 'path': self.config_path}) return deploy.loadapp("config:%s" % self.config_path, name=name) - except LookupError as err: - LOG.error(err) + except LookupError: + LOG.exception(_LE("Couldn't lookup app: %s"), name) raise exception.PasteAppNotFound(name=name, path=self.config_path)