Merge "Use api-paste.ini for loading middleware"
This commit is contained in:
commit
8910d7d826
@ -10,6 +10,8 @@ include sahara/db/migration/alembic_migrations/versions/*.py
|
||||
include sahara/db/migration/alembic_migrations/versions/README
|
||||
include sahara/db/templates/README.rst
|
||||
|
||||
include etc/sahara/api-paste.ini
|
||||
|
||||
recursive-include sahara/locale *
|
||||
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
|
||||
fi
|
||||
|
||||
cp -p $SAHARA_DIR/etc/sahara/api-paste.ini $SAHARA_CONF_DIR
|
||||
|
||||
# Create auth cache dir
|
||||
sudo install -d -o $STACK_USER -m 700 $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: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.
|
||||
|
28
etc/sahara/api-paste.ini
Normal file
28
etc/sahara/api-paste.ini
Normal 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
|
||||
|
||||
from keystonemiddleware import auth_token
|
||||
from oslo_config import cfg
|
||||
from oslo_policy import policy
|
||||
|
||||
@ -45,8 +44,3 @@ def enforce(rule):
|
||||
return handler
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def wrap(app):
|
||||
"""Wrap wsgi application with ACL check."""
|
||||
return auth_token.AuthProtocol(app, {})
|
||||
|
@ -14,6 +14,8 @@
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_middleware import base
|
||||
import webob
|
||||
import webob.exc as ex
|
||||
|
||||
from sahara.i18n import _
|
||||
@ -24,13 +26,12 @@ import sahara.openstack.commons as commons
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthValidator(object):
|
||||
class AuthValidator(base.Middleware):
|
||||
|
||||
"""Handles token auth results and tenants."""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, req):
|
||||
"""Ensures that tenants in url and token are equal.
|
||||
|
||||
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
|
||||
url.
|
||||
"""
|
||||
token_tenant = env['HTTP_X_TENANT_ID']
|
||||
token_tenant = req.environ.get("HTTP_X_TENANT_ID")
|
||||
if not token_tenant:
|
||||
LOG.warning(_LW("Can't get tenant_id from env"))
|
||||
resp = ex.HTTPServiceUnavailable()
|
||||
return resp(env, start_response)
|
||||
raise ex.HTTPServiceUnavailable()
|
||||
|
||||
path = env['PATH_INFO']
|
||||
path = req.environ['PATH_INFO']
|
||||
if path != '/':
|
||||
version, url_tenant, rest = commons.split_path(path, 3, 3, True)
|
||||
if not version or not url_tenant or not rest:
|
||||
LOG.warning(_LW("Incorrect path: {path}").format(path=path))
|
||||
resp = ex.HTTPNotFound(_("Incorrect path"))
|
||||
return resp(env, start_response)
|
||||
raise ex.HTTPNotFound(_("Incorrect path"))
|
||||
|
||||
if token_tenant != url_tenant:
|
||||
LOG.debug("Unauthorized: token tenant != requested tenant")
|
||||
resp = ex.HTTPUnauthorized(
|
||||
raise ex.HTTPUnauthorized(
|
||||
_('Token tenant != requested tenant'))
|
||||
return resp(env, start_response)
|
||||
|
||||
return self.app(env, start_response)
|
||||
|
||||
|
||||
def wrap(app):
|
||||
"""Wrap wsgi application with auth validator check."""
|
||||
|
||||
return AuthValidator(app)
|
||||
return self.application
|
||||
|
@ -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()
|
79
sahara/api/middleware/sahara_middleware.py
Normal file
79
sahara/api/middleware/sahara_middleware.py
Normal file
@ -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()
|
||||
|
||||
|
||||
from sahara.api import acl
|
||||
import sahara.main as server
|
||||
from sahara.service import ops
|
||||
|
||||
@ -49,11 +48,6 @@ from sahara.service import ops
|
||||
def main():
|
||||
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()
|
||||
|
||||
ops_server = ops.OpsServer()
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
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 types
|
||||
from oslo_log import log
|
||||
|
@ -15,23 +15,14 @@
|
||||
|
||||
import os
|
||||
|
||||
import flask
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import oslo_middleware.cors as cors_middleware
|
||||
from oslo_middleware import request_id
|
||||
from oslo_service import systemd
|
||||
import six
|
||||
from oslo_service import wsgi as oslo_wsgi
|
||||
import stevedore
|
||||
from werkzeug import exceptions as werkzeug_exceptions
|
||||
|
||||
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 context
|
||||
from sahara.i18n import _LI
|
||||
from sahara.i18n import _LW
|
||||
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 import ops as service_ops
|
||||
from sahara.service import periodic
|
||||
from sahara.utils import api as api_utils
|
||||
from sahara.utils.openstack import cinder
|
||||
from sahara.utils import remote
|
||||
from sahara.utils import rpc as messaging
|
||||
@ -113,65 +103,8 @@ def setup_auth_policy():
|
||||
|
||||
|
||||
def make_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
|
||||
|
||||
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
|
||||
app_loader = oslo_wsgi.Loader(CONF)
|
||||
return app_loader.load_app("sahara")
|
||||
|
||||
|
||||
def _load_driver(namespace, name):
|
||||
|
Loading…
Reference in New Issue
Block a user