diff --git a/devstack/lib/magnum b/devstack/lib/magnum index 94ae0a587c..c9eb2761c7 100644 --- a/devstack/lib/magnum +++ b/devstack/lib/magnum @@ -44,6 +44,7 @@ MAGNUM_AUTH_CACHE_DIR=${MAGNUM_AUTH_CACHE_DIR:-/var/cache/magnum} MAGNUM_CONF_DIR=/etc/magnum MAGNUM_CONF=$MAGNUM_CONF_DIR/magnum.conf MAGNUM_POLICY_JSON=$MAGNUM_CONF_DIR/policy.json +MAGNUM_API_PASTE=$MAGNUM_CONF_DIR/api-paste.ini if is_ssl_enabled_service "magnum" || is_service_enabled tls-proxy; then MAGNUM_SERVICE_PROTOCOL="https" @@ -95,6 +96,8 @@ function configure_magnum { # Rebuild the config file from scratch create_magnum_conf + create_api_paste_conf + update_heat_policy } @@ -206,6 +209,11 @@ function create_magnum_conf { iniset $MAGNUM_CONF cinder_client region_name $REGION_NAME } +function create_api_paste_conf { + # copy api_paste.ini + cp $MAGNUM_DIR/etc/magnum/api-paste.ini $MAGNUM_API_PASTE +} + function update_heat_policy { # enable stacks globel_index search so that magnum can use # list(global_tenant=True) diff --git a/etc/magnum/api-paste.ini b/etc/magnum/api-paste.ini new file mode 100644 index 0000000000..fe36640cde --- /dev/null +++ b/etc/magnum/api-paste.ini @@ -0,0 +1,19 @@ +[pipeline:main] +pipeline = cors request_id authtoken api_v1 + +[app:api_v1] +paste.app_factory = magnum.api.app:app_factory + +[filter:authtoken] +acl_public_routes = /, /v1 +paste.filter_factory = magnum.api.middleware.auth_token:AuthTokenMiddleware.factory + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = magnum +latent_allow_methods = GET, PUT, POST, DELETE, PATCH +latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/magnum/api/app.py b/magnum/api/app.py index ff2d561315..b7e5596fb4 100644 --- a/magnum/api/app.py +++ b/magnum/api/app.py @@ -9,14 +9,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os from oslo_config import cfg -from oslo_middleware import cors +from oslo_log import log +from paste import deploy import pecan -from magnum.api import auth from magnum.api import config as api_config from magnum.api import middleware +from magnum.i18n import _ # Register options for the service API_SERVICE_OPTS = [ @@ -29,7 +31,11 @@ API_SERVICE_OPTS = [ cfg.IntOpt('max_limit', default=1000, help='The maximum number of items returned in a single ' - 'response from a collection resource.') + 'response from a collection resource.'), + cfg.StrOpt('api_paste_config', + default="api-paste.ini", + help="Configuration file for WSGI definition of API." + ) ] CONF = cfg.CONF @@ -38,6 +44,8 @@ opt_group = cfg.OptGroup(name='api', CONF.register_group(opt_group) CONF.register_opts(API_SERVICE_OPTS, opt_group) +LOG = log.getLogger(__name__) + def get_pecan_config(): # Set up the pecan configuration @@ -58,17 +66,22 @@ def setup_app(config=None): **app_conf ) - app = auth.install(app, CONF, config.app.acl_public_routes) - - # CORS must be the last one. - app = cors.CORS(app, CONF) - app.set_latent( - allow_headers=['X-Auth-Token', 'X-Identity-Status', 'X-Roles', - 'X-Service-Catalog', 'X-User-Id', 'X-Tenant-Id', - 'X-OpenStack-Request-ID', 'X-Server-Management-Url'], - allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], - expose_headers=['X-Auth-Token', 'X-Subject-Token', 'X-Service-Token', - 'X-OpenStack-Request-ID', 'X-Server-Management-Url'] - ) - return app + + +def load_app(): + cfg_file = None + cfg_path = cfg.CONF.api.api_paste_config + if not os.path.isabs(cfg_path): + cfg_file = CONF.find_file(cfg_path) + elif os.path.exists(cfg_path): + cfg_file = cfg_path + + if not cfg_file: + raise cfg.ConfigFilesNotFoundError([cfg.CONF.api_paste_config]) + LOG.info(_("Full WSGI config used: %s") % cfg_file) + return deploy.loadapp("config:" + cfg_file) + + +def app_factory(global_config, **local_conf): + return setup_app() diff --git a/magnum/api/auth.py b/magnum/api/auth.py deleted file mode 100644 index ab36cb62cf..0000000000 --- a/magnum/api/auth.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2012 New Dream Network, LLC (DreamHost) -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Access Control Lists (ACL's) control access the API server.""" -from oslo_config import cfg - -from magnum.api.middleware import auth_token - - -AUTH_OPTS = [ - cfg.BoolOpt('enable_authentication', - default=True, - help='This option enables or disables user authentication ' - 'via Keystone. Default value is True.'), -] - -CONF = cfg.CONF -CONF.register_opts(AUTH_OPTS) - - -def install(app, conf, public_routes): - """Install ACL check on application. - - :param app: A WSGI application. - :param conf: Settings. Dict'ified and passed to keystone middleware - :param public_routes: The list of the routes which will be allowed to - access without authentication. - :return: The same WSGI application with ACL installed. - - """ - if not cfg.CONF.get('enable_authentication'): - return app - return auth_token.AuthTokenMiddleware(app, - conf=dict(conf), - public_api_routes=public_routes) diff --git a/magnum/api/middleware/auth_token.py b/magnum/api/middleware/auth_token.py index 40ec9abf91..788eb0b1b6 100644 --- a/magnum/api/middleware/auth_token.py +++ b/magnum/api/middleware/auth_token.py @@ -58,3 +58,12 @@ class AuthTokenMiddleware(auth_token.AuthProtocol): return self._app(env, start_response) return super(AuthTokenMiddleware, self).__call__(env, start_response) + + @classmethod + def factory(cls, global_config, **local_conf): + public_routes = local_conf.get('acl_public_routes', '') + public_api_routes = [path.strip() for path in public_routes.split(',')] + + def _factory(app): + return cls(app, global_config, public_api_routes=public_api_routes) + return _factory diff --git a/magnum/cmd/api.py b/magnum/cmd/api.py index 3155190b19..390d3a75a1 100644 --- a/magnum/cmd/api.py +++ b/magnum/cmd/api.py @@ -40,7 +40,7 @@ def main(): # Enable object backporting via the conductor base.MagnumObject.indirection_api = base.MagnumObjectIndirectionAPI() - app = api_app.setup_app() + app = api_app.load_app() # Create the WSGI server and start it host, port = cfg.CONF.api.host, cfg.CONF.api.port diff --git a/magnum/opts.py b/magnum/opts.py index aa2bbf7a82..782b1db49e 100644 --- a/magnum/opts.py +++ b/magnum/opts.py @@ -16,7 +16,6 @@ import itertools import magnum.api.app -import magnum.api.auth import magnum.api.validation import magnum.common.cert_manager from magnum.common.cert_manager import local_cert_manager @@ -35,8 +34,7 @@ import magnum.db def list_opts(): return [ ('DEFAULT', - itertools.chain(magnum.api.auth.AUTH_OPTS, - magnum.common.paths.PATH_OPTS, + itertools.chain(magnum.common.paths.PATH_OPTS, magnum.common.utils.UTILS_OPTS, magnum.common.rpc_service.periodic_opts, magnum.common.service.service_opts, diff --git a/magnum/tests/unit/api/base.py b/magnum/tests/unit/api/base.py index 0f72e8d298..6ac06f64d6 100644 --- a/magnum/tests/unit/api/base.py +++ b/magnum/tests/unit/api/base.py @@ -54,8 +54,6 @@ class FunctionalTest(base.DbTestCase): 'modules': ['magnum.api'], 'static_root': '%s/public' % root_dir, 'template_path': '%s/api/templates' % root_dir, - 'enable_acl': False, - 'acl_public_routes': ['/', '/v1'], 'hooks': [ hooks.ContextHook(), hooks.RPCHook(), @@ -83,12 +81,10 @@ class FunctionalTest(base.DbTestCase): for attr in attrs: verify_method(attr, response) - def _make_app(self, config=None, enable_acl=False): + def _make_app(self, config=None): if not config: config = self.config - config["app"]["enable_acl"] = enable_acl - return pecan.testing.load_test_app(config) def _request_json(self, path, params, expect_errors=False, headers=None, diff --git a/magnum/tests/unit/api/controllers/auth-paste.ini b/magnum/tests/unit/api/controllers/auth-paste.ini new file mode 100644 index 0000000000..498fb0b74d --- /dev/null +++ b/magnum/tests/unit/api/controllers/auth-paste.ini @@ -0,0 +1,18 @@ +[pipeline:main] +pipeline = cors request_id authtoken api_v1 + +[app:api_v1] +paste.app_factory = magnum.api.app:app_factory + +[filter:authtoken] +paste.filter_factory = magnum.api.middleware.auth_token:AuthTokenMiddleware.factory + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = magnum +latent_allow_methods = GET, PUT, POST, DELETE, PATCH +latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/magnum/tests/unit/api/controllers/auth-root-access.ini b/magnum/tests/unit/api/controllers/auth-root-access.ini new file mode 100644 index 0000000000..37971ec03b --- /dev/null +++ b/magnum/tests/unit/api/controllers/auth-root-access.ini @@ -0,0 +1,19 @@ +[pipeline:main] +pipeline = cors request_id authtoken api_v1 + +[app:api_v1] +paste.app_factory = magnum.api.app:app_factory + +[filter:authtoken] +acl_public_routes = / +paste.filter_factory = magnum.api.middleware.auth_token:AuthTokenMiddleware.factory + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = magnum +latent_allow_methods = GET, PUT, POST, DELETE, PATCH +latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/magnum/tests/unit/api/controllers/auth-v1-access.ini b/magnum/tests/unit/api/controllers/auth-v1-access.ini new file mode 100644 index 0000000000..4f88187934 --- /dev/null +++ b/magnum/tests/unit/api/controllers/auth-v1-access.ini @@ -0,0 +1,19 @@ +[pipeline:main] +pipeline = cors request_id authtoken api_v1 + +[app:api_v1] +paste.app_factory = magnum.api.app:app_factory + +[filter:authtoken] +acl_public_routes = /v1 +paste.filter_factory = magnum.api.middleware.auth_token:AuthTokenMiddleware.factory + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = magnum +latent_allow_methods = GET, PUT, POST, DELETE, PATCH +latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/magnum/tests/unit/api/controllers/noauth-paste.ini b/magnum/tests/unit/api/controllers/noauth-paste.ini new file mode 100644 index 0000000000..6c87f6464f --- /dev/null +++ b/magnum/tests/unit/api/controllers/noauth-paste.ini @@ -0,0 +1,19 @@ +[pipeline:main] +pipeline = cors request_id api_v1 + +[app:api_v1] +paste.app_factory = magnum.api.app:app_factory + +[filter:authtoken] +acl_public_routes = / +paste.filter_factory = magnum.api.middleware.auth_token:AuthTokenMiddleware.factory + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = magnum +latent_allow_methods = GET, PUT, POST, DELETE, PATCH +latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/magnum/tests/unit/api/controllers/test_root.py b/magnum/tests/unit/api/controllers/test_root.py index 9d4480745b..8f09c1951c 100644 --- a/magnum/tests/unit/api/controllers/test_root.py +++ b/magnum/tests/unit/api/controllers/test_root.py @@ -10,12 +10,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy - import mock from oslo_config import cfg from webob import exc as webob_exc +import webtest + +from magnum.api import app from magnum.api.controllers import v1 as v1_api from magnum.tests import base as test_base from magnum.tests.unit.api import base as api_base @@ -84,6 +85,11 @@ class TestRootController(api_base.FunctionalTest): {u'href': u'http://localhost/mservices/', u'rel': u'bookmark'}]} + def make_app(self, paste_file): + file_name = self.get_path(paste_file) + cfg.CONF.set_override("api_paste_config", file_name, group="api") + return webtest.TestApp(app.load_app()) + def test_version(self): response = self.app.get('/') self.assertEqual(self.root_expected, response.json) @@ -96,13 +102,10 @@ class TestRootController(api_base.FunctionalTest): response = self.app.get('/a/bogus/url', expect_errors=True) assert response.status_int == 404 - def test_acl_access_with_all(self): - cfg.CONF.set_override("enable_authentication", True) - - config = copy.deepcopy(self.config) - # Both / and /v1 and access without auth - config["app"]["acl_public_routes"] = ['/', '/v1'] - app = self._make_app(config=config) + def test_noauth(self): + # Don't need to auth + paste_file = "magnum/tests/unit/api/controllers/noauth-paste.ini" + app = self.make_app(paste_file) response = app.get('/') self.assertEqual(self.root_expected, response.json) @@ -110,13 +113,24 @@ class TestRootController(api_base.FunctionalTest): response = app.get('/v1/') self.assertEqual(self.v1_expected, response.json) - def test_acl_access_with_root(self): - cfg.CONF.set_override("enable_authentication", True) + response = app.get('/v1/baymodels') + self.assertEqual(200, response.status_int) - config = copy.deepcopy(self.config) + def test_auth_with_no_public_routes(self): + # All apis need auth when access + paste_file = "magnum/tests/unit/api/controllers/auth-paste.ini" + app = self.make_app(paste_file) + + response = app.get('/', expect_errors=True) + self.assertEqual(401, response.status_int) + + response = app.get('/v1/', expect_errors=True) + self.assertEqual(401, response.status_int) + + def test_auth_with_root_access(self): # Only / can access without auth - config["app"]["acl_public_routes"] = ['/'] - app = self._make_app(config=config) + paste_file = "magnum/tests/unit/api/controllers/auth-root-access.ini" + app = self.make_app(paste_file) response = app.get('/') self.assertEqual(self.root_expected, response.json) @@ -124,13 +138,13 @@ class TestRootController(api_base.FunctionalTest): response = app.get('/v1/', expect_errors=True) self.assertEqual(401, response.status_int) - def test_acl_access_with_v1(self): - cfg.CONF.set_override("enable_authentication", True) + response = app.get('/v1/baymodels', expect_errors=True) + self.assertEqual(401, response.status_int) - config = copy.deepcopy(self.config) + def test_auth_with_v1_access(self): # Only /v1 can access without auth - config["app"]["acl_public_routes"] = ['/v1'] - app = self._make_app(config=config) + paste_file = "magnum/tests/unit/api/controllers/auth-v1-access.ini" + app = self.make_app(paste_file) response = app.get('/', expect_errors=True) self.assertEqual(401, response.status_int) @@ -138,17 +152,7 @@ class TestRootController(api_base.FunctionalTest): response = app.get('/v1/') self.assertEqual(self.v1_expected, response.json) - def test_acl_with_neither(self): - cfg.CONF.set_override("enable_authentication", True) - - config = copy.deepcopy(self.config) - config["app"]["acl_public_routes"] = [] - app = self._make_app(config=config) - - response = app.get('/', expect_errors=True) - self.assertEqual(401, response.status_int) - - response = app.get('/v1/', expect_errors=True) + response = app.get('/v1/baymodels', expect_errors=True) self.assertEqual(401, response.status_int) diff --git a/magnum/tests/unit/api/test_auth.py b/magnum/tests/unit/api/test_auth.py deleted file mode 100644 index 168b091dbb..0000000000 --- a/magnum/tests/unit/api/test_auth.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2014 -# The Cloudscaling Group, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import mock -from oslo_config import fixture - -from magnum.api import auth -from magnum.tests import base -from magnum.tests import fakes - - -@mock.patch('magnum.api.middleware.auth_token.AuthTokenMiddleware', - new_callable=fakes.FakeAuthProtocol) -class TestAuth(base.BaseTestCase): - - def setUp(self): - super(TestAuth, self).setUp() - self.CONF = self.useFixture(fixture.Config()) - self.app = fakes.FakeApp() - - def test_check_auth_option_enabled(self, mock_auth): - self.CONF.config(enable_authentication=True) - result = auth.install(self.app, self.CONF.conf, ['/']) - self.assertIsInstance(result, fakes.FakeAuthProtocol) - - def test_check_auth_option_disabled(self, mock_auth): - self.CONF.config(enable_authentication=False) - result = auth.install(self.app, self.CONF.conf, ['/']) - self.assertIsInstance(result, fakes.FakeApp) diff --git a/magnum/tests/unit/db/base.py b/magnum/tests/unit/db/base.py index cd420bde3d..4abca0ce9d 100644 --- a/magnum/tests/unit/db/base.py +++ b/magnum/tests/unit/db/base.py @@ -31,8 +31,6 @@ from magnum.tests import base CONF = cfg.CONF -CONF.import_opt('enable_authentication', 'magnum.api.auth') - _DB_CACHE = None @@ -88,7 +86,6 @@ class Database(fixtures.Fixture): class DbTestCase(base.TestCase): def setUp(self): - cfg.CONF.set_override("enable_authentication", False) super(DbTestCase, self).setUp() self.dbapi = dbapi.get_instance()