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
This commit is contained in:
Sean Dague 2015-02-26 09:20:26 -05:00
parent a5b8f6d344
commit df9181e564
8 changed files with 69 additions and 9 deletions

View File

@ -22,6 +22,7 @@ use = egg:Paste#urlmap
[composite:ec2cloud] [composite:ec2cloud]
use = call:nova.api.auth:pipeline_factory use = call:nova.api.auth:pipeline_factory
noauth = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor noauth = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor
noauth2 = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor
keystone = ec2faultwrap logrequest ec2keystoneauth cloudrequest validator ec2executor keystone = ec2faultwrap logrequest ec2keystoneauth cloudrequest validator ec2executor
[filter:ec2faultwrap] [filter:ec2faultwrap]
@ -67,17 +68,20 @@ use = call:nova.api.openstack.urlmap:urlmap_factory
[composite:openstack_compute_api_v2] [composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory use = call:nova.api.auth:pipeline_factory
noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 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 = 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 keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[composite:openstack_compute_api_v21] [composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21 use = call:nova.api.auth:pipeline_factory_v21
noauth = compute_req_id faultwrap sizelimit noauth osapi_compute_app_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 keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21
[composite:openstack_compute_api_v3] [composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v21 use = call:nova.api.auth:pipeline_factory_v21
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3 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 keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
[filter:request_id] [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 paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:noauth] [filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareOld.factory
[filter:noauth2]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:noauth_v3] [filter:noauth_v3]

View File

@ -25,6 +25,7 @@ import webob.exc
from nova import context from nova import context
from nova.i18n import _ from nova.i18n import _
from nova.openstack.common import versionutils
from nova import wsgi from nova import wsgi
@ -36,7 +37,13 @@ auth_opts = [
'is removed from v3 api.'), 'is removed from v3 api.'),
cfg.StrOpt('auth_strategy', cfg.StrOpt('auth_strategy',
default='keystone', 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', cfg.BoolOpt('use_forwarded_for',
default=False, default=False,
help='Treat X-Forwarded-For as the canonical remote address. ' 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): def pipeline_factory(loader, global_conf, **local_conf):
"""A paste pipeline replica that keys off of auth_strategy.""" """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] pipeline = local_conf[CONF.auth_strategy]
if not CONF.api_rate_limit: if not CONF.api_rate_limit:
limit_name = CONF.auth_strategy + '_nolimit' limit_name = CONF.auth_strategy + '_nolimit'

View File

@ -29,7 +29,7 @@ CONF.import_opt('use_forwarded_for', 'nova.api.auth')
class NoAuthMiddlewareBase(base_wsgi.Middleware): class NoAuthMiddlewareBase(base_wsgi.Middleware):
"""Return a fake token if one isn't specified.""" """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: if 'X-Auth-Token' not in req.headers:
user_id = req.headers.get('X-Auth-User', 'admin') user_id = req.headers.get('X-Auth-User', 'admin')
project_id = req.headers.get('X-Auth-Project-Id', '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') remote_address = getattr(req, 'remote_address', '127.0.0.1')
if CONF.use_forwarded_for: if CONF.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address) remote_address = req.headers.get('X-Forwarded-For', remote_address)
is_admin = always_admin or (user_id == 'admin')
ctx = context.RequestContext(user_id, ctx = context.RequestContext(user_id,
project_id, project_id,
is_admin=True, is_admin=is_admin,
remote_address=remote_address) remote_address=remote_address)
req.environ['nova.context'] = ctx req.environ['nova.context'] = ctx
@ -63,7 +64,27 @@ class NoAuthMiddlewareBase(base_wsgi.Middleware):
class NoAuthMiddleware(NoAuthMiddlewareBase): 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) @webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req): def __call__(self, req):
return self.base_call(req, True) return self.base_call(req, True)

View File

@ -331,6 +331,8 @@ class OSAPIFixture(fixtures.Fixture):
'host': osapi.host, 'port': osapi.port, 'host': osapi.host, 'port': osapi.port,
'api_version': self.api_version}) 'api_version': self.api_version})
self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url) self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url)
self.admin_api = client.TestOpenStackClient(
'admin', 'admin', self.auth_url)
class PoisonFunctions(fixtures.Fixture): class PoisonFunctions(fixtures.Fixture):

View File

@ -88,6 +88,7 @@ class _IntegratedTestBase(test.TestCase):
self.api_fixture = self.useFixture( self.api_fixture = self.useFixture(
nova_fixtures.OSAPIFixture(self._api_version)) nova_fixtures.OSAPIFixture(self._api_version))
self.api = self.api_fixture.api self.api = self.api_fixture.api
self.admin_api = self.api_fixture.admin_api
self.useFixture(cast_as_call.CastAsCall(self.stubs)) self.useFixture(cast_as_call.CastAsCall(self.stubs))

View File

@ -70,7 +70,7 @@ class FlavorManageFullstack(test.TestCase):
""" """
def setUp(self): def setUp(self):
super(FlavorManageFullstack, self).setUp() 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): def assertFlavorDbEqual(self, flav, flavdb):
# a mapping of the REST params to the db fields # a mapping of the REST params to the db fields

View File

@ -152,18 +152,34 @@ class TestPipeLineFactory(test.NoDBTestCase):
self.assertEqual(app.name, pipeline.split()[-1]) self.assertEqual(app.name, pipeline.split()[-1])
self.assertIsInstance(app, TestPipeLineFactory.FakeApp) self.assertIsInstance(app, TestPipeLineFactory.FakeApp)
def test_pipeline_factory(self): def test_pipeline_factory_noauthold(self):
fake_pipeline = 'test1 test2 test3' fake_pipeline = 'test1 test2 test3'
CONF.set_override('auth_strategy', 'noauth')
app = nova.api.auth.pipeline_factory( app = nova.api.auth.pipeline_factory(
TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline) TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline)
self._test_pipeline(fake_pipeline, app) self._test_pipeline(fake_pipeline, app)
def test_pipeline_factory_v21(self): def test_pipeline_factory_v21_noauthold(self):
fake_pipeline = 'test1 test2 test3' fake_pipeline = 'test1 test2 test3'
CONF.set_override('auth_strategy', 'noauth')
app = nova.api.auth.pipeline_factory_v21( app = nova.api.auth.pipeline_factory_v21(
TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline) TestPipeLineFactory.FakeLoader(), None, noauth=fake_pipeline)
self._test_pipeline(fake_pipeline, app) 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): def test_pipeline_factory_with_rate_limits(self):
CONF.set_override('api_rate_limit', True) CONF.set_override('api_rate_limit', True)
CONF.set_override('auth_strategy', 'keystone') CONF.set_override('auth_strategy', 'keystone')

View File

@ -531,6 +531,6 @@ class Loader(object):
LOG.debug("Loading app %(name)s from %(path)s", LOG.debug("Loading app %(name)s from %(path)s",
{'name': name, 'path': self.config_path}) {'name': name, 'path': self.config_path})
return deploy.loadapp("config:%s" % self.config_path, name=name) return deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError as err: except LookupError:
LOG.error(err) LOG.exception(_LE("Couldn't lookup app: %s"), name)
raise exception.PasteAppNotFound(name=name, path=self.config_path) raise exception.PasteAppNotFound(name=name, path=self.config_path)