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_headers = false
|
||||
# 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
|
||||
[filter:tempurl]
|
||||
|
@ -128,7 +128,7 @@ import json
|
||||
import time
|
||||
|
||||
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.http import is_success, is_redirection, HTTP_NOT_FOUND
|
||||
from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound, \
|
||||
@ -154,6 +154,8 @@ class _StaticWebContext(WSGIContext):
|
||||
self.container = container
|
||||
self.obj = obj
|
||||
self.app = staticweb.app
|
||||
self.url_scheme = staticweb.url_scheme
|
||||
self.url_host = staticweb.url_host
|
||||
self.agent = '%(orig)s StaticWeb'
|
||||
# Results from the last call to self._get_container_info.
|
||||
self._index = self._error = self._listings = self._listings_css = \
|
||||
@ -353,6 +355,16 @@ class _StaticWebContext(WSGIContext):
|
||||
css_path = '../' * prefix.count('/') + quote(self._listings_css)
|
||||
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):
|
||||
"""
|
||||
Handles a possible static web request for a container.
|
||||
@ -374,9 +386,7 @@ class _StaticWebContext(WSGIContext):
|
||||
return HTTPNotFound()(env, start_response)
|
||||
return self.app(env, start_response)
|
||||
if not env['PATH_INFO'].endswith('/'):
|
||||
resp = HTTPMovedPermanently(
|
||||
location=(env['PATH_INFO'] + '/'))
|
||||
return resp(env, start_response)
|
||||
return self._redirect_with_slash(env, start_response)
|
||||
if not self._index:
|
||||
return self._listing(env, start_response)
|
||||
tmp_env = dict(env)
|
||||
@ -445,9 +455,7 @@ class _StaticWebContext(WSGIContext):
|
||||
status_int = self._get_status_int()
|
||||
if is_success(status_int) or is_redirection(status_int):
|
||||
if not env['PATH_INFO'].endswith('/'):
|
||||
resp = HTTPMovedPermanently(
|
||||
location=env['PATH_INFO'] + '/')
|
||||
return resp(env, start_response)
|
||||
return self._redirect_with_slash(env, start_response)
|
||||
start_response(self._response_status, self._response_headers,
|
||||
self._response_exc_info)
|
||||
return resp
|
||||
@ -465,8 +473,7 @@ class _StaticWebContext(WSGIContext):
|
||||
not json.loads(body):
|
||||
resp = HTTPNotFound()(env, self._start_response)
|
||||
return self._error_response(resp, env, start_response)
|
||||
resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/')
|
||||
return resp(env, start_response)
|
||||
return self._redirect_with_slash(env, start_response)
|
||||
return self._listing(env, start_response, self.obj)
|
||||
|
||||
|
||||
@ -485,10 +492,20 @@ class StaticWeb(object):
|
||||
def __init__(self, app, conf):
|
||||
#: The next WSGI application/filter in the paste.deploy pipeline.
|
||||
self.app = app
|
||||
#: The filter configuration dict.
|
||||
#: The filter configuration dict. Only used in tests.
|
||||
self.conf = conf
|
||||
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):
|
||||
"""
|
||||
Main hook into the WSGI paste.deploy filter/app pipeline.
|
||||
|
@ -17,6 +17,8 @@ import json
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from swift.common.swob import Request, Response, HTTPUnauthorized
|
||||
from swift.common.middleware import staticweb
|
||||
|
||||
@ -841,5 +843,56 @@ class TestStaticWeb(unittest.TestCase):
|
||||
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__':
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user