Add simple Maintenance Mode WSGI middleware.

Additionally, this commit moves the auth middware to a common middleware
module.

Change-Id: Ieddc8ebca25b99483232cb4b0203871454789ad4
This commit is contained in:
Kiall Mac Innes 2013-06-26 15:23:07 +01:00
parent 09fdf2a250
commit 9a107daf07
4 changed files with 136 additions and 20 deletions

View File

@ -13,6 +13,7 @@
# 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 designate.openstack.common import cfg
from designate.openstack.common import local
from designate.openstack.common import log as logging
@ -22,8 +23,38 @@ from designate.context import DesignateContext
LOG = logging.getLogger(__name__)
cfg.CONF.register_opts([
cfg.BoolOpt('maintenance-mode', default=False,
help='Enable API Maintenance Mode'),
cfg.StrOpt('maintenance-mode-role', default='admin',
help='Role allowed to bypass maintaince mode'),
], group='service:api')
def pipeline_factory(loader, global_conf, **local_conf):
class MaintenanceMiddleware(wsgi.Middleware):
def __init__(self, application):
super(MaintenanceMiddleware, self).__init__(application)
self.enabled = cfg.CONF['service:api'].maintenance_mode
self.role = cfg.CONF['service:api'].maintenance_mode_role
def process_request(self, request):
# If maintaince mode is not enabled, pass the request on as soon as
# possible
if not self.enabled:
return None
# If the caller has the bypass role, let them through
if ('context' in request.environ
and self.role in request.environ['context'].roles):
LOG.warning('Request authorized to bypass maintenance mode')
return None
# Otherwise, reject the request with a 503 Service Unavailable
return flask.Response(status=503, headers={'Retry-After': 60})
def auth_pipeline_factory(loader, global_conf, **local_conf):
"""
A paste pipeline replica that keys off of auth_strategy.

View File

@ -1,4 +1,5 @@
# Copyright 2012 Managed I.T.
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
@ -14,20 +15,102 @@
# License for the specific language governing permissions and limitations
# under the License.
from designate.tests.test_api import ApiTestCase
from designate.api import auth
from designate.api import middleware
class FakeContext(object):
def __init__(self, roles=[]):
self.roles = roles
class FakeRequest(object):
headers = {}
environ = {}
def __init__(self):
self.headers = {}
self.environ = {}
def get_response(self, app):
return "FakeResponse"
class MaintenanceMiddlewareTest(ApiTestCase):
__test__ = True
def test_process_request_disabled(self):
self.config(maintenance_mode=False, group='service:api')
request = FakeRequest()
app = middleware.MaintenanceMiddleware({})
# Process the request
response = app(request)
# Ensure request was not blocked
self.assertEqual(response, 'FakeResponse')
def test_process_request_enabled_reject(self):
self.config(maintenance_mode=True, maintenance_mode_role='admin',
group='service:api')
request = FakeRequest()
request.environ['context'] = FakeContext(roles=['user'])
app = middleware.MaintenanceMiddleware({})
# Process the request
response = app(request)
# Ensure request was blocked
self.assertEqual(response.status_code, 503)
def test_process_request_enabled_reject_no_roles(self):
self.config(maintenance_mode=True, maintenance_mode_role='admin',
group='service:api')
request = FakeRequest()
request.environ['context'] = FakeContext(roles=[])
app = middleware.MaintenanceMiddleware({})
# Process the request
response = app(request)
# Ensure request was blocked
self.assertEqual(response.status_code, 503)
def test_process_request_enabled_reject_no_context(self):
self.config(maintenance_mode=True, maintenance_mode_role='admin',
group='service:api')
request = FakeRequest()
app = middleware.MaintenanceMiddleware({})
# Process the request
response = app(request)
# Ensure request was blocked
self.assertEqual(response.status_code, 503)
def test_process_request_enabled_bypass(self):
self.config(maintenance_mode=True, maintenance_mode_role='admin',
group='service:api')
request = FakeRequest()
request.environ['context'] = FakeContext(roles=['admin'])
app = middleware.MaintenanceMiddleware({})
# Process the request
response = app(request)
# Ensure request was not blocked
self.assertEqual(response, 'FakeResponse')
class KeystoneContextMiddlewareTest(ApiTestCase):
__test__ = True
def test_process_request(self):
app = {}
middleware = auth.KeystoneContextMiddleware(app)
app = middleware.KeystoneContextMiddleware({})
request = FakeRequest()
@ -39,7 +122,7 @@ class KeystoneContextMiddlewareTest(ApiTestCase):
}
# Process the request
middleware.process_request(request)
app.process_request(request)
self.assertIn('context', request.environ)
@ -55,8 +138,7 @@ class KeystoneContextMiddlewareTest(ApiTestCase):
# Set the policy to accept the authz
self.policy({'use_sudo': '@'})
app = {}
middleware = auth.KeystoneContextMiddleware(app)
app = middleware.KeystoneContextMiddleware({})
request = FakeRequest()
@ -70,7 +152,7 @@ class KeystoneContextMiddlewareTest(ApiTestCase):
}
# Process the request
middleware.process_request(request)
app.process_request(request)
self.assertIn('context', request.environ)
@ -89,13 +171,12 @@ class NoAuthContextMiddlewareTest(ApiTestCase):
__test__ = True
def test_process_request(self):
app = {}
middleware = auth.NoAuthContextMiddleware(app)
app = middleware.NoAuthContextMiddleware({})
request = FakeRequest()
# Process the request
middleware.process_request(request)
app.process_request(request)
self.assertIn('context', request.environ)

View File

@ -15,7 +15,7 @@
# under the License.
from designate.openstack.common import log as logging
from designate.api import v1 as api_v1
from designate.api import auth
from designate.api import middleware
from designate.tests.test_api import ApiTestCase
@ -35,7 +35,8 @@ class ApiV1Test(ApiTestCase):
self.app.wsgi_app = api_v1.FaultWrapperMiddleware(self.app.wsgi_app)
# Inject the NoAuth middleware
self.app.wsgi_app = auth.NoAuthContextMiddleware(self.app.wsgi_app)
self.app.wsgi_app = middleware.NoAuthContextMiddleware(
self.app.wsgi_app)
# Obtain a test client
self.client = self.app.test_client()

View File

@ -7,9 +7,9 @@ use = egg:Paste#urlmap
paste.app_factory = designate.api.versions:factory
[composite:osapi_dns_v1]
use = call:designate.api.auth:pipeline_factory
noauth = noauthcontext faultwrapper_v1 osapi_dns_app_v1
keystone = authtoken keystonecontext faultwrapper_v1 osapi_dns_app_v1
use = call:designate.api.middleware:auth_pipeline_factory
noauth = noauthcontext maintenance faultwrapper_v1 osapi_dns_app_v1
keystone = authtoken keystonecontext maintenance faultwrapper_v1 osapi_dns_app_v1
[app:osapi_dns_app_v1]
paste.app_factory = designate.api.v1:factory
@ -17,11 +17,14 @@ paste.app_factory = designate.api.v1:factory
[filter:faultwrapper_v1]
paste.filter_factory = designate.api.v1:FaultWrapperMiddleware.factory
[filter:maintenance]
paste.filter_factory = designate.api.middleware:MaintenanceMiddleware.factory
[filter:noauthcontext]
paste.filter_factory = designate.api.auth:NoAuthContextMiddleware.factory
paste.filter_factory = designate.api.middleware:NoAuthContextMiddleware.factory
[filter:keystonecontext]
paste.filter_factory = designate.api.auth:KeystoneContextMiddleware.factory
paste.filter_factory = designate.api.middleware:KeystoneContextMiddleware.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory