Use api-paste.ini for loading middleware
Changes: * use api-paste.ini config for loading middleware * refactoring middlewares to support loading via pastedeploy * use debug middleware from oslo_middleware library instead log_exchange middleware Closes-bug: #1503983 Closes-bug: #1361360 Change-Id: I444c1799ef53dbb19a601e51dd95cd8509fb1c0c
This commit is contained in:
parent
f8e6907299
commit
64583e4b83
|
@ -10,6 +10,8 @@ include sahara/db/migration/alembic_migrations/versions/*.py
|
||||||
include sahara/db/migration/alembic_migrations/versions/README
|
include sahara/db/migration/alembic_migrations/versions/README
|
||||||
include sahara/db/templates/README.rst
|
include sahara/db/templates/README.rst
|
||||||
|
|
||||||
|
include etc/sahara/api-paste.ini
|
||||||
|
|
||||||
recursive-include sahara/locale *
|
recursive-include sahara/locale *
|
||||||
recursive-include sahara/db/migration/alembic_migrations *
|
recursive-include sahara/db/migration/alembic_migrations *
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,8 @@ function configure_sahara {
|
||||||
cp -p $SAHARA_DIR/etc/sahara/policy.json $SAHARA_CONF_DIR
|
cp -p $SAHARA_DIR/etc/sahara/policy.json $SAHARA_CONF_DIR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cp -p $SAHARA_DIR/etc/sahara/api-paste.ini $SAHARA_CONF_DIR
|
||||||
|
|
||||||
# Create auth cache dir
|
# Create auth cache dir
|
||||||
sudo install -d -o $STACK_USER -m 700 $SAHARA_AUTH_CACHE_DIR
|
sudo install -d -o $STACK_USER -m 700 $SAHARA_AUTH_CACHE_DIR
|
||||||
rm -rf $SAHARA_AUTH_CACHE_DIR/*
|
rm -rf $SAHARA_AUTH_CACHE_DIR/*
|
||||||
|
|
|
@ -250,3 +250,12 @@ Example 2. Disallow image registry manipulations to non-admin users.
|
||||||
"data-processing:images:add_tags": "role:admin",
|
"data-processing:images:add_tags": "role:admin",
|
||||||
"data-processing:images:remove_tags": "role:admin"
|
"data-processing:images:remove_tags": "role:admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
API configuration
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Sahara uses the ``api-paste.ini`` file to configure the data processing API
|
||||||
|
service. For middleware injection sahara uses pastedeploy library. The location
|
||||||
|
of the api-paste file is controlled by the ``api_paste_config`` parameter in
|
||||||
|
the ``[default]`` section. By default sahara will search for a
|
||||||
|
``api-paste.ini`` file in the same directory as the configuration file.
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
[pipeline:sahara]
|
||||||
|
pipeline = request_id cors acl auth_validator sahara_api
|
||||||
|
|
||||||
|
[composite:sahara_api]
|
||||||
|
use = egg:Paste#urlmap
|
||||||
|
/: sahara_apiv11
|
||||||
|
|
||||||
|
[app:sahara_apiv11]
|
||||||
|
paste.app_factory = sahara.api.middleware.sahara_middleware:Router.factory
|
||||||
|
|
||||||
|
[filter:cors]
|
||||||
|
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||||
|
allow_headers=X-Auth-Token,X-Server-Management-Url
|
||||||
|
allow_methods=GET,PUT,POST,DELETE,PATCH
|
||||||
|
allowed_origin=
|
||||||
|
expose_headers=X-Auth-Token,X-Server-Management-Url
|
||||||
|
|
||||||
|
[filter:request_id]
|
||||||
|
paste.filter_factory = oslo_middleware.request_id:RequestId.factory
|
||||||
|
|
||||||
|
[filter:acl]
|
||||||
|
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||||
|
|
||||||
|
[filter:auth_validator]
|
||||||
|
paste.filter_factory = sahara.api.middleware.auth_valid:AuthValidator.factory
|
||||||
|
|
||||||
|
[filter:debug]
|
||||||
|
paste.filter_factory = oslo_middleware.debug:Debug.factory
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from keystonemiddleware import auth_token
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_policy import policy
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
@ -45,8 +44,3 @@ def enforce(rule):
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def wrap(app):
|
|
||||||
"""Wrap wsgi application with ACL check."""
|
|
||||||
return auth_token.AuthProtocol(app, {})
|
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_middleware import base
|
||||||
|
import webob
|
||||||
import webob.exc as ex
|
import webob.exc as ex
|
||||||
|
|
||||||
from sahara.i18n import _
|
from sahara.i18n import _
|
||||||
|
@ -24,13 +26,12 @@ import sahara.openstack.commons as commons
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AuthValidator(object):
|
class AuthValidator(base.Middleware):
|
||||||
|
|
||||||
"""Handles token auth results and tenants."""
|
"""Handles token auth results and tenants."""
|
||||||
|
|
||||||
def __init__(self, app):
|
@webob.dec.wsgify
|
||||||
self.app = app
|
def __call__(self, req):
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
|
||||||
"""Ensures that tenants in url and token are equal.
|
"""Ensures that tenants in url and token are equal.
|
||||||
|
|
||||||
Handle incoming request by checking tenant info prom the headers and
|
Handle incoming request by checking tenant info prom the headers and
|
||||||
|
@ -40,30 +41,20 @@ class AuthValidator(object):
|
||||||
Reject request if tenant_id from headers not equals to tenant_id from
|
Reject request if tenant_id from headers not equals to tenant_id from
|
||||||
url.
|
url.
|
||||||
"""
|
"""
|
||||||
token_tenant = env['HTTP_X_TENANT_ID']
|
token_tenant = req.environ.get("HTTP_X_TENANT_ID")
|
||||||
if not token_tenant:
|
if not token_tenant:
|
||||||
LOG.warning(_LW("Can't get tenant_id from env"))
|
LOG.warning(_LW("Can't get tenant_id from env"))
|
||||||
resp = ex.HTTPServiceUnavailable()
|
raise ex.HTTPServiceUnavailable()
|
||||||
return resp(env, start_response)
|
|
||||||
|
|
||||||
path = env['PATH_INFO']
|
path = req.environ['PATH_INFO']
|
||||||
if path != '/':
|
if path != '/':
|
||||||
version, url_tenant, rest = commons.split_path(path, 3, 3, True)
|
version, url_tenant, rest = commons.split_path(path, 3, 3, True)
|
||||||
if not version or not url_tenant or not rest:
|
if not version or not url_tenant or not rest:
|
||||||
LOG.warning(_LW("Incorrect path: {path}").format(path=path))
|
LOG.warning(_LW("Incorrect path: {path}").format(path=path))
|
||||||
resp = ex.HTTPNotFound(_("Incorrect path"))
|
raise ex.HTTPNotFound(_("Incorrect path"))
|
||||||
return resp(env, start_response)
|
|
||||||
|
|
||||||
if token_tenant != url_tenant:
|
if token_tenant != url_tenant:
|
||||||
LOG.debug("Unauthorized: token tenant != requested tenant")
|
LOG.debug("Unauthorized: token tenant != requested tenant")
|
||||||
resp = ex.HTTPUnauthorized(
|
raise ex.HTTPUnauthorized(
|
||||||
_('Token tenant != requested tenant'))
|
_('Token tenant != requested tenant'))
|
||||||
return resp(env, start_response)
|
return self.application
|
||||||
|
|
||||||
return self.app(env, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
def wrap(app):
|
|
||||||
"""Wrap wsgi application with auth validator check."""
|
|
||||||
|
|
||||||
return AuthValidator(app)
|
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
# Copyright 2011 OpenStack Foundation.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# It's based on debug middleware from oslo-incubator
|
|
||||||
|
|
||||||
"""Debug middleware"""
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslo_middleware import base
|
|
||||||
import six
|
|
||||||
import webob.dec
|
|
||||||
|
|
||||||
|
|
||||||
class LogExchange(base.Middleware):
|
|
||||||
"""Helper class that returns debug information.
|
|
||||||
|
|
||||||
Can be inserted into any WSGI application chain to get information about
|
|
||||||
the request and response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@webob.dec.wsgify
|
|
||||||
def __call__(self, req):
|
|
||||||
print(("*" * 40) + " REQUEST ENVIRON")
|
|
||||||
for key, value in req.environ.items():
|
|
||||||
print(key, "=", value)
|
|
||||||
if req.is_body_readable:
|
|
||||||
print(('*' * 40) + " REQUEST BODY")
|
|
||||||
if req.content_type == 'application/json':
|
|
||||||
print(req.json)
|
|
||||||
else:
|
|
||||||
print(req.body)
|
|
||||||
print()
|
|
||||||
|
|
||||||
resp = req.get_response(self.application)
|
|
||||||
|
|
||||||
print(("*" * 40) + " RESPONSE HEADERS")
|
|
||||||
for (key, value) in six.iteritems(resp.headers):
|
|
||||||
print(key, "=", value)
|
|
||||||
print()
|
|
||||||
|
|
||||||
resp.app_iter = self.print_generator(resp.app_iter)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def print_generator(app_iter):
|
|
||||||
"""Prints the contents of a wrapper string iterator when iterated."""
|
|
||||||
print(("*" * 40) + " RESPONSE BODY")
|
|
||||||
for part in app_iter:
|
|
||||||
sys.stdout.write(part)
|
|
||||||
sys.stdout.flush()
|
|
||||||
yield part
|
|
||||||
print()
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Copyright (c) 2015 Mirantis 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 flask
|
||||||
|
from oslo_config import cfg
|
||||||
|
import six
|
||||||
|
from werkzeug import exceptions as werkzeug_exceptions
|
||||||
|
|
||||||
|
from sahara.api import v10 as api_v10
|
||||||
|
from sahara.api import v11 as api_v11
|
||||||
|
from sahara import context
|
||||||
|
from sahara.utils import api as api_utils
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
def build_app():
|
||||||
|
"""App builder (wsgi).
|
||||||
|
|
||||||
|
Entry point for Sahara REST API server
|
||||||
|
"""
|
||||||
|
app = flask.Flask('sahara.api')
|
||||||
|
|
||||||
|
@app.route('/', methods=['GET'])
|
||||||
|
def version_list():
|
||||||
|
context.set_ctx(None)
|
||||||
|
return api_utils.render({
|
||||||
|
"versions": [
|
||||||
|
{"id": "v1.0", "status": "SUPPORTED"},
|
||||||
|
{"id": "v1.1", "status": "CURRENT"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.teardown_request
|
||||||
|
def teardown_request(_ex=None):
|
||||||
|
context.set_ctx(None)
|
||||||
|
|
||||||
|
app.register_blueprint(api_v10.rest, url_prefix='/v1.0')
|
||||||
|
app.register_blueprint(api_v10.rest, url_prefix='/v1.1')
|
||||||
|
app.register_blueprint(api_v11.rest, url_prefix='/v1.1')
|
||||||
|
|
||||||
|
def make_json_error(ex):
|
||||||
|
status_code = (ex.code
|
||||||
|
if isinstance(ex, werkzeug_exceptions.HTTPException)
|
||||||
|
else 500)
|
||||||
|
description = (ex.description
|
||||||
|
if isinstance(ex, werkzeug_exceptions.HTTPException)
|
||||||
|
else str(ex))
|
||||||
|
return api_utils.render({'error': status_code,
|
||||||
|
'error_message': description},
|
||||||
|
status=status_code)
|
||||||
|
|
||||||
|
for code in six.iterkeys(werkzeug_exceptions.default_exceptions):
|
||||||
|
app.error_handler_spec[None][code] = make_json_error
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
class Router(object):
|
||||||
|
def __call__(self, environ, response):
|
||||||
|
return self.app(environ, response)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(cls, global_config, **local_config):
|
||||||
|
cls.app = build_app()
|
||||||
|
return cls(**local_config)
|
|
@ -41,7 +41,6 @@ if os.path.exists(os.path.join(possible_topdir,
|
||||||
oslo_i18n.enable_lazy()
|
oslo_i18n.enable_lazy()
|
||||||
|
|
||||||
|
|
||||||
from sahara.api import acl
|
|
||||||
import sahara.main as server
|
import sahara.main as server
|
||||||
from sahara.service import ops
|
from sahara.service import ops
|
||||||
|
|
||||||
|
@ -49,11 +48,6 @@ from sahara.service import ops
|
||||||
def main():
|
def main():
|
||||||
server.setup_common(possible_topdir, 'engine')
|
server.setup_common(possible_topdir, 'engine')
|
||||||
|
|
||||||
# NOTE(apavlov): acl.wrap is called here to set up auth_uri value
|
|
||||||
# in context by using keystone functionality (mostly to avoid
|
|
||||||
# code duplication).
|
|
||||||
acl.wrap(None)
|
|
||||||
|
|
||||||
server.setup_sahara_engine()
|
server.setup_sahara_engine()
|
||||||
|
|
||||||
ops_server = ops.OpsServer()
|
ops_server = ops.OpsServer()
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
# loading keystonemiddleware opts because sahara uses these options in code
|
||||||
|
from keystonemiddleware import opts # noqa
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import types
|
from oslo_config import types
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
|
@ -15,23 +15,14 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import flask
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_middleware.cors as cors_middleware
|
|
||||||
from oslo_middleware import request_id
|
|
||||||
from oslo_service import systemd
|
from oslo_service import systemd
|
||||||
import six
|
from oslo_service import wsgi as oslo_wsgi
|
||||||
import stevedore
|
import stevedore
|
||||||
from werkzeug import exceptions as werkzeug_exceptions
|
|
||||||
|
|
||||||
from sahara.api import acl
|
from sahara.api import acl
|
||||||
from sahara.api.middleware import auth_valid
|
|
||||||
from sahara.api.middleware import log_exchange
|
|
||||||
from sahara.api import v10 as api_v10
|
|
||||||
from sahara.api import v11 as api_v11
|
|
||||||
from sahara import config
|
from sahara import config
|
||||||
from sahara import context
|
|
||||||
from sahara.i18n import _LI
|
from sahara.i18n import _LI
|
||||||
from sahara.i18n import _LW
|
from sahara.i18n import _LW
|
||||||
from sahara.plugins import base as plugins_base
|
from sahara.plugins import base as plugins_base
|
||||||
|
@ -39,7 +30,6 @@ from sahara.service import api as service_api
|
||||||
from sahara.service.edp import api as edp_api
|
from sahara.service.edp import api as edp_api
|
||||||
from sahara.service import ops as service_ops
|
from sahara.service import ops as service_ops
|
||||||
from sahara.service import periodic
|
from sahara.service import periodic
|
||||||
from sahara.utils import api as api_utils
|
|
||||||
from sahara.utils.openstack import cinder
|
from sahara.utils.openstack import cinder
|
||||||
from sahara.utils import remote
|
from sahara.utils import remote
|
||||||
from sahara.utils import rpc as messaging
|
from sahara.utils import rpc as messaging
|
||||||
|
@ -113,65 +103,8 @@ def setup_auth_policy():
|
||||||
|
|
||||||
|
|
||||||
def make_app():
|
def make_app():
|
||||||
"""App builder (wsgi)
|
app_loader = oslo_wsgi.Loader(CONF)
|
||||||
|
return app_loader.load_app("sahara")
|
||||||
Entry point for Sahara REST API server
|
|
||||||
"""
|
|
||||||
app = flask.Flask('sahara.api')
|
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
|
||||||
def version_list():
|
|
||||||
context.set_ctx(None)
|
|
||||||
return api_utils.render({
|
|
||||||
"versions": [
|
|
||||||
{"id": "v1.0", "status": "SUPPORTED"},
|
|
||||||
{"id": "v1.1", "status": "CURRENT"}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
@app.teardown_request
|
|
||||||
def teardown_request(_ex=None):
|
|
||||||
context.set_ctx(None)
|
|
||||||
|
|
||||||
app.register_blueprint(api_v10.rest, url_prefix='/v1.0')
|
|
||||||
app.register_blueprint(api_v10.rest, url_prefix='/v1.1')
|
|
||||||
app.register_blueprint(api_v11.rest, url_prefix='/v1.1')
|
|
||||||
|
|
||||||
def make_json_error(ex):
|
|
||||||
status_code = (ex.code
|
|
||||||
if isinstance(ex, werkzeug_exceptions.HTTPException)
|
|
||||||
else 500)
|
|
||||||
description = (ex.description
|
|
||||||
if isinstance(ex, werkzeug_exceptions.HTTPException)
|
|
||||||
else str(ex))
|
|
||||||
return api_utils.render({'error': status_code,
|
|
||||||
'error_message': description},
|
|
||||||
status=status_code)
|
|
||||||
|
|
||||||
for code in six.iterkeys(werkzeug_exceptions.default_exceptions):
|
|
||||||
app.error_handler_spec[None][code] = make_json_error
|
|
||||||
|
|
||||||
if CONF.debug and not CONF.log_exchange:
|
|
||||||
LOG.debug('Logging of request/response exchange could be enabled using'
|
|
||||||
' flag --log-exchange')
|
|
||||||
|
|
||||||
# Create a CORS wrapper, and attach sahara-specific defaults that must be
|
|
||||||
# included in all CORS responses.
|
|
||||||
app.wsgi_app = cors_middleware.CORS(app.wsgi_app, CONF)
|
|
||||||
app.wsgi_app.set_latent(
|
|
||||||
allow_headers=['X-Auth-Token', 'X-Server-Management-Url'],
|
|
||||||
allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'],
|
|
||||||
expose_headers=['X-Auth-Token', 'X-Server-Management-Url']
|
|
||||||
)
|
|
||||||
|
|
||||||
if CONF.log_exchange:
|
|
||||||
app.wsgi_app = log_exchange.LogExchange.factory(CONF)(app.wsgi_app)
|
|
||||||
|
|
||||||
app.wsgi_app = auth_valid.wrap(app.wsgi_app)
|
|
||||||
app.wsgi_app = acl.wrap(app.wsgi_app)
|
|
||||||
app.wsgi_app = request_id.RequestId(app.wsgi_app)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
def _load_driver(namespace, name):
|
def _load_driver(namespace, name):
|
||||||
|
|
Loading…
Reference in New Issue