Add a configurable URL base to staticweb
This came to light because someone ran Tempest against a standard installation of RDO, which helpfuly terminates SSL for Swift in a pre-configured load-balancer. In such a case, staticweb has no way to know what scheme to use and guesses wrong, causing Tempest to fail. Related upstream bug: https://bugs.launchpad.net/mos/+bug/1537071 Change-Id: Ie15cf2aff4f7e6bcf68b67ae733c77bb9353587a Closes-Bug: 1572011
This commit is contained in:
parent
32bc272634
commit
f62df7b80c
@ -550,6 +550,18 @@ use = egg:swift#staticweb
|
|||||||
# set log_level = INFO
|
# set log_level = INFO
|
||||||
# set log_headers = false
|
# set log_headers = false
|
||||||
# set log_address = /dev/log
|
# set log_address = /dev/log
|
||||||
|
#
|
||||||
|
# At times when it's impossible for staticweb to guess the outside
|
||||||
|
# endpoint correctly, the url_base may be used to supply the URL
|
||||||
|
# scheme and/or the host name (and port number) in order to generate
|
||||||
|
# redirects.
|
||||||
|
# Example values:
|
||||||
|
# http://www.example.com - redirect to www.example.com
|
||||||
|
# https: - changes the schema only
|
||||||
|
# https:// - same, changes the schema only
|
||||||
|
# //www.example.com:8080 - redirect www.example.com on port 8080
|
||||||
|
# (schema unchanged)
|
||||||
|
# url_base =
|
||||||
|
|
||||||
# Note: Put tempurl before dlo, slo and your auth filter(s) in the pipeline
|
# Note: Put tempurl before dlo, slo and your auth filter(s) in the pipeline
|
||||||
[filter:tempurl]
|
[filter:tempurl]
|
||||||
|
@ -128,7 +128,7 @@ import json
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from swift.common.utils import human_readable, split_path, config_true_value, \
|
from swift.common.utils import human_readable, split_path, config_true_value, \
|
||||||
quote, register_swift_info, get_logger
|
quote, register_swift_info, get_logger, urlparse
|
||||||
from swift.common.wsgi import make_env, WSGIContext
|
from swift.common.wsgi import make_env, WSGIContext
|
||||||
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
|
from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
|
||||||
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound, \
|
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound, \
|
||||||
@ -154,6 +154,8 @@ class _StaticWebContext(WSGIContext):
|
|||||||
self.container = container
|
self.container = container
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.app = staticweb.app
|
self.app = staticweb.app
|
||||||
|
self.url_scheme = staticweb.url_scheme
|
||||||
|
self.url_host = staticweb.url_host
|
||||||
self.agent = '%(orig)s StaticWeb'
|
self.agent = '%(orig)s StaticWeb'
|
||||||
# Results from the last call to self._get_container_info.
|
# Results from the last call to self._get_container_info.
|
||||||
self._index = self._error = self._listings = self._listings_css = \
|
self._index = self._error = self._listings = self._listings_css = \
|
||||||
@ -353,6 +355,16 @@ class _StaticWebContext(WSGIContext):
|
|||||||
css_path = '../' * prefix.count('/') + quote(self._listings_css)
|
css_path = '../' * prefix.count('/') + quote(self._listings_css)
|
||||||
return css_path
|
return css_path
|
||||||
|
|
||||||
|
def _redirect_with_slash(self, env_, start_response):
|
||||||
|
env = {}
|
||||||
|
env.update(env_)
|
||||||
|
if self.url_scheme:
|
||||||
|
env['wsgi.url_scheme'] = self.url_scheme
|
||||||
|
if self.url_host:
|
||||||
|
env['HTTP_HOST'] = self.url_host
|
||||||
|
resp = HTTPMovedPermanently(location=(env['PATH_INFO'] + '/'))
|
||||||
|
return resp(env, start_response)
|
||||||
|
|
||||||
def handle_container(self, env, start_response):
|
def handle_container(self, env, start_response):
|
||||||
"""
|
"""
|
||||||
Handles a possible static web request for a container.
|
Handles a possible static web request for a container.
|
||||||
@ -374,9 +386,7 @@ class _StaticWebContext(WSGIContext):
|
|||||||
return HTTPNotFound()(env, start_response)
|
return HTTPNotFound()(env, start_response)
|
||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
if not env['PATH_INFO'].endswith('/'):
|
if not env['PATH_INFO'].endswith('/'):
|
||||||
resp = HTTPMovedPermanently(
|
return self._redirect_with_slash(env, start_response)
|
||||||
location=(env['PATH_INFO'] + '/'))
|
|
||||||
return resp(env, start_response)
|
|
||||||
if not self._index:
|
if not self._index:
|
||||||
return self._listing(env, start_response)
|
return self._listing(env, start_response)
|
||||||
tmp_env = dict(env)
|
tmp_env = dict(env)
|
||||||
@ -445,9 +455,7 @@ class _StaticWebContext(WSGIContext):
|
|||||||
status_int = self._get_status_int()
|
status_int = self._get_status_int()
|
||||||
if is_success(status_int) or is_redirection(status_int):
|
if is_success(status_int) or is_redirection(status_int):
|
||||||
if not env['PATH_INFO'].endswith('/'):
|
if not env['PATH_INFO'].endswith('/'):
|
||||||
resp = HTTPMovedPermanently(
|
return self._redirect_with_slash(env, start_response)
|
||||||
location=env['PATH_INFO'] + '/')
|
|
||||||
return resp(env, start_response)
|
|
||||||
start_response(self._response_status, self._response_headers,
|
start_response(self._response_status, self._response_headers,
|
||||||
self._response_exc_info)
|
self._response_exc_info)
|
||||||
return resp
|
return resp
|
||||||
@ -465,8 +473,7 @@ class _StaticWebContext(WSGIContext):
|
|||||||
not json.loads(body):
|
not json.loads(body):
|
||||||
resp = HTTPNotFound()(env, self._start_response)
|
resp = HTTPNotFound()(env, self._start_response)
|
||||||
return self._error_response(resp, env, start_response)
|
return self._error_response(resp, env, start_response)
|
||||||
resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/')
|
return self._redirect_with_slash(env, start_response)
|
||||||
return resp(env, start_response)
|
|
||||||
return self._listing(env, start_response, self.obj)
|
return self._listing(env, start_response, self.obj)
|
||||||
|
|
||||||
|
|
||||||
@ -485,10 +492,20 @@ class StaticWeb(object):
|
|||||||
def __init__(self, app, conf):
|
def __init__(self, app, conf):
|
||||||
#: The next WSGI application/filter in the paste.deploy pipeline.
|
#: The next WSGI application/filter in the paste.deploy pipeline.
|
||||||
self.app = app
|
self.app = app
|
||||||
#: The filter configuration dict.
|
#: The filter configuration dict. Only used in tests.
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.logger = get_logger(conf, log_route='staticweb')
|
self.logger = get_logger(conf, log_route='staticweb')
|
||||||
|
|
||||||
|
# We expose a more general "url_base" parameter in case we want
|
||||||
|
# to incorporate the path prefix later. Currently it is discarded.
|
||||||
|
url_base = conf.get('url_base', None)
|
||||||
|
self.url_scheme = None
|
||||||
|
self.url_host = None
|
||||||
|
if url_base:
|
||||||
|
parsed = urlparse(url_base)
|
||||||
|
self.url_scheme = parsed.scheme
|
||||||
|
self.url_host = parsed.netloc
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
def __call__(self, env, start_response):
|
||||||
"""
|
"""
|
||||||
Main hook into the WSGI paste.deploy filter/app pipeline.
|
Main hook into the WSGI paste.deploy filter/app pipeline.
|
||||||
|
@ -17,6 +17,8 @@ import json
|
|||||||
import unittest
|
import unittest
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from six.moves.urllib.parse import urlparse
|
||||||
|
|
||||||
from swift.common.swob import Request, Response, HTTPUnauthorized
|
from swift.common.swob import Request, Response, HTTPUnauthorized
|
||||||
from swift.common.middleware import staticweb
|
from swift.common.middleware import staticweb
|
||||||
|
|
||||||
@ -841,5 +843,56 @@ class TestStaticWeb(unittest.TestCase):
|
|||||||
self.assertEqual(resp.status_int, 503) # sanity
|
self.assertEqual(resp.status_int, 503) # sanity
|
||||||
|
|
||||||
|
|
||||||
|
class TestStaticWebUrlBase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.app = FakeApp()
|
||||||
|
self._orig_get_container_info = staticweb.get_container_info
|
||||||
|
staticweb.get_container_info = mock_get_container_info
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
staticweb.get_container_info = self._orig_get_container_info
|
||||||
|
|
||||||
|
def test_container3subdirz_scheme(self):
|
||||||
|
path = '/v1/a/c3/subdirz'
|
||||||
|
scheme = 'https'
|
||||||
|
test_staticweb = FakeAuthFilter(
|
||||||
|
staticweb.filter_factory({'url_base': 'https://'})(self.app))
|
||||||
|
resp = Request.blank(path).get_response(test_staticweb)
|
||||||
|
self.assertEqual(resp.status_int, 301)
|
||||||
|
parsed = urlparse(resp.location)
|
||||||
|
self.assertEqual(parsed.scheme, scheme)
|
||||||
|
# We omit comparing netloc here, because swob is free to add port.
|
||||||
|
self.assertEqual(parsed.path, path + '/')
|
||||||
|
|
||||||
|
def test_container3subdirz_host(self):
|
||||||
|
path = '/v1/a/c3/subdirz'
|
||||||
|
netloc = 'example.com'
|
||||||
|
test_staticweb = FakeAuthFilter(
|
||||||
|
staticweb.filter_factory({
|
||||||
|
'url_base': '//%s' % (netloc,)})(self.app))
|
||||||
|
resp = Request.blank(path).get_response(test_staticweb)
|
||||||
|
self.assertEqual(resp.status_int, 301)
|
||||||
|
parsed = urlparse(resp.location)
|
||||||
|
# We compare scheme with the default. This may change, but unlikely.
|
||||||
|
self.assertEqual(parsed.scheme, 'http')
|
||||||
|
self.assertEqual(parsed.netloc, netloc)
|
||||||
|
self.assertEqual(parsed.path, path + '/')
|
||||||
|
|
||||||
|
def test_container3subdirz_both(self):
|
||||||
|
path = '/v1/a/c3/subdirz'
|
||||||
|
scheme = 'http'
|
||||||
|
netloc = 'example.com'
|
||||||
|
test_staticweb = FakeAuthFilter(
|
||||||
|
staticweb.filter_factory({
|
||||||
|
'url_base': 'http://example.com'})(self.app))
|
||||||
|
resp = Request.blank(path).get_response(test_staticweb)
|
||||||
|
self.assertEqual(resp.status_int, 301)
|
||||||
|
parsed = urlparse(resp.location)
|
||||||
|
self.assertEqual(parsed.scheme, scheme)
|
||||||
|
self.assertEqual(parsed.netloc, netloc)
|
||||||
|
self.assertEqual(parsed.path, path + '/')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user