Better TempAuth storage URL guessing
I know it's just TempAuth, but bug #959953 just caught my eye as something interesting to solve. This does a best guess on the storage URL to return for a given request. It allows $HOST to be used in the storage URL configuration, where $HOST will resolve to scheme://host:port. It bases the scheme on how the server is running or on storage_url_scheme if set. The host:port comes from the request's Host header if it exists, and falls back to the WSGI SERVER_NAME:SERVER_PORT otherwise. Fixes: bug #959953 DocImpact Change-Id: Ia494bcb99a04490911ee8d2cb8b12a94e77820c5
This commit is contained in:
parent
217bd202dd
commit
47ee1d7e17
@ -653,6 +653,15 @@ auth_prefix /auth/ The HTTP request path
|
|||||||
letter `v`.
|
letter `v`.
|
||||||
token_life 86400 The number of seconds a
|
token_life 86400 The number of seconds a
|
||||||
token is valid.
|
token is valid.
|
||||||
|
storage_url_scheme default Scheme to return with
|
||||||
|
storage urls: http,
|
||||||
|
https, or default
|
||||||
|
(chooses based on what
|
||||||
|
the server is running
|
||||||
|
as) This can be useful
|
||||||
|
with an SSL load
|
||||||
|
balancer in front of a
|
||||||
|
non-SSL server.
|
||||||
===================== =============================== =======================
|
===================== =============================== =======================
|
||||||
|
|
||||||
Additionally, you need to list all the accounts/users you want here. The format
|
Additionally, you need to list all the accounts/users you want here. The format
|
||||||
@ -677,12 +686,14 @@ that have been explicitly allowed for them by a .admin or .reseller_admin.
|
|||||||
The trailing optional storage_url allows you to specify an alternate url to
|
The trailing optional storage_url allows you to specify an alternate url to
|
||||||
hand back to the user upon authentication. If not specified, this defaults to::
|
hand back to the user upon authentication. If not specified, this defaults to::
|
||||||
|
|
||||||
http[s]://<ip>:<port>/v1/<reseller_prefix>_<account>
|
$HOST/v1/<reseller_prefix>_<account>
|
||||||
|
|
||||||
Where http or https depends on whether cert_file is specified in the [DEFAULT]
|
Where $HOST will do its best to resolve to what the requester would need to use
|
||||||
section, <ip> and <port> are based on the [DEFAULT] section's bind_ip and
|
to reach this host, <reseller_prefix> is from this section, and <account> is
|
||||||
bind_port (falling back to 127.0.0.1 and 8080), <reseller_prefix> is from this
|
from the user_<account>_<user> name. Note that $HOST cannot possibly handle
|
||||||
section, and <account> is from the user_<account>_<user> name.
|
when you have a load balancer in front of it that does https while TempAuth
|
||||||
|
itself runs with http; in such a case, you'll have to specify the
|
||||||
|
storage_url_scheme configuration value as an override.
|
||||||
|
|
||||||
Here are example entries, required for running the tests::
|
Here are example entries, required for running the tests::
|
||||||
|
|
||||||
|
@ -110,6 +110,10 @@ use = egg:swift#tempauth
|
|||||||
# you're not going to use such middleware and you want a bit of extra security,
|
# you're not going to use such middleware and you want a bit of extra security,
|
||||||
# you can set this to false.
|
# you can set this to false.
|
||||||
# allow_overrides = true
|
# allow_overrides = true
|
||||||
|
# This specifies what scheme to return with storage urls:
|
||||||
|
# http, https, or default (chooses based on what the server is running as)
|
||||||
|
# This can be useful with an SSL load balancer in front of a non-SSL server.
|
||||||
|
# storage_url_scheme = default
|
||||||
# Lastly, you need to list all the accounts/users you want here. The format is:
|
# Lastly, you need to list all the accounts/users you want here. The format is:
|
||||||
# user_<account>_<user> = <key> [group] [group] [...] [storage_url]
|
# user_<account>_<user> = <key> [group] [group] [...] [storage_url]
|
||||||
# or if you want underscores in <account> or <user>, you can base64 encode them
|
# or if you want underscores in <account> or <user>, you can base64 encode them
|
||||||
@ -122,11 +126,8 @@ use = egg:swift#tempauth
|
|||||||
# that have been explicitly allowed for them by a .admin or .reseller_admin.
|
# that have been explicitly allowed for them by a .admin or .reseller_admin.
|
||||||
# The trailing optional storage_url allows you to specify an alternate url to
|
# The trailing optional storage_url allows you to specify an alternate url to
|
||||||
# hand back to the user upon authentication. If not specified, this defaults to
|
# hand back to the user upon authentication. If not specified, this defaults to
|
||||||
# http[s]://<ip>:<port>/v1/<reseller_prefix>_<account> where http or https
|
# $HOST/v1/<reseller_prefix>_<account> where $HOST will do its best to resolve
|
||||||
# depends on whether cert_file is specified in the [DEFAULT] section, <ip> and
|
# to what the requester would need to use to reach this host.
|
||||||
# <port> are based on the [DEFAULT] section's bind_ip and bind_port (falling
|
|
||||||
# back to 127.0.0.1 and 8080), <reseller_prefix> is from this section, and
|
|
||||||
# <account> is from the user_<account>_<user> name.
|
|
||||||
# Here are example entries, required for running the tests:
|
# Here are example entries, required for running the tests:
|
||||||
user_admin_admin = admin .admin .reseller_admin
|
user_admin_admin = admin .admin .reseller_admin
|
||||||
user_test_tester = testing .admin
|
user_test_tester = testing .admin
|
||||||
|
@ -90,6 +90,7 @@ class TempAuth(object):
|
|||||||
if h.strip()]
|
if h.strip()]
|
||||||
self.allow_overrides = config_true_value(
|
self.allow_overrides = config_true_value(
|
||||||
conf.get('allow_overrides', 't'))
|
conf.get('allow_overrides', 't'))
|
||||||
|
self.storage_url_scheme = conf.get('storage_url_scheme', 'default')
|
||||||
self.users = {}
|
self.users = {}
|
||||||
for conf_key in conf:
|
for conf_key in conf:
|
||||||
if conf_key.startswith('user_') or conf_key.startswith('user64_'):
|
if conf_key.startswith('user_') or conf_key.startswith('user64_'):
|
||||||
@ -105,16 +106,10 @@ class TempAuth(object):
|
|||||||
if not values:
|
if not values:
|
||||||
raise ValueError('%s has no key set' % conf_key)
|
raise ValueError('%s has no key set' % conf_key)
|
||||||
key = values.pop(0)
|
key = values.pop(0)
|
||||||
if values and '://' in values[-1]:
|
if values and ('://' in values[-1] or '$HOST' in values[-1]):
|
||||||
url = values.pop()
|
url = values.pop()
|
||||||
else:
|
else:
|
||||||
url = 'https://' if 'cert_file' in conf else 'http://'
|
url = '$HOST/v1/%s%s' % (self.reseller_prefix, account)
|
||||||
ip = conf.get('bind_ip', '127.0.0.1')
|
|
||||||
if ip == '0.0.0.0':
|
|
||||||
ip = '127.0.0.1'
|
|
||||||
url += ip
|
|
||||||
url += ':' + conf.get('bind_port', '8080') + '/v1/' + \
|
|
||||||
self.reseller_prefix + account
|
|
||||||
self.users[account + ':' + username] = {
|
self.users[account + ':' + username] = {
|
||||||
'key': key, 'url': url, 'groups': values}
|
'key': key, 'url': url, 'groups': values}
|
||||||
|
|
||||||
@ -471,11 +466,13 @@ class TempAuth(object):
|
|||||||
'%s/user/%s' % (self.reseller_prefix, account_user)
|
'%s/user/%s' % (self.reseller_prefix, account_user)
|
||||||
memcache_client.set(memcache_user_key, token,
|
memcache_client.set(memcache_user_key, token,
|
||||||
timeout=float(expires - time()))
|
timeout=float(expires - time()))
|
||||||
return Response(request=req,
|
resp = Response(request=req, headers={
|
||||||
headers={
|
'x-auth-token': token, 'x-storage-token': token})
|
||||||
'x-auth-token': token,
|
url = self.users[account_user]['url'].replace('$HOST', resp.host_url())
|
||||||
'x-storage-token': token,
|
if self.storage_url_scheme != 'default':
|
||||||
'x-storage-url': self.users[account_user]['url']})
|
url = self.storage_url_scheme + ':' + url.split(':', 1)[1]
|
||||||
|
resp.headers['x-storage-url'] = url
|
||||||
|
return resp
|
||||||
|
|
||||||
def posthooklogger(self, env, req):
|
def posthooklogger(self, env, req):
|
||||||
if not req.path.startswith(self.auth_prefix):
|
if not req.path.startswith(self.auth_prefix):
|
||||||
|
@ -955,12 +955,11 @@ class Response(object):
|
|||||||
return [body]
|
return [body]
|
||||||
return ['']
|
return ['']
|
||||||
|
|
||||||
def absolute_location(self):
|
def host_url(self):
|
||||||
"""
|
"""
|
||||||
Attempt to construct an absolute location.
|
Returns the best guess that can be made for an absolute location up to
|
||||||
|
the path, for example: https://host.com:1234
|
||||||
"""
|
"""
|
||||||
if not self.location.startswith('/'):
|
|
||||||
return self.location
|
|
||||||
if 'HTTP_HOST' in self.environ:
|
if 'HTTP_HOST' in self.environ:
|
||||||
host = self.environ['HTTP_HOST']
|
host = self.environ['HTTP_HOST']
|
||||||
else:
|
else:
|
||||||
@ -971,7 +970,15 @@ class Response(object):
|
|||||||
host, port = host.rsplit(':', 1)
|
host, port = host.rsplit(':', 1)
|
||||||
elif scheme == 'https' and host.endswith(':443'):
|
elif scheme == 'https' and host.endswith(':443'):
|
||||||
host, port = host.rsplit(':', 1)
|
host, port = host.rsplit(':', 1)
|
||||||
return '%s://%s%s' % (scheme, host, self.location)
|
return '%s://%s' % (scheme, host)
|
||||||
|
|
||||||
|
def absolute_location(self):
|
||||||
|
"""
|
||||||
|
Attempt to construct an absolute location.
|
||||||
|
"""
|
||||||
|
if not self.location.startswith('/'):
|
||||||
|
return self.location
|
||||||
|
return self.host_url() + self.location
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
def __call__(self, env, start_response):
|
||||||
self.environ = env
|
self.environ = env
|
||||||
|
@ -429,6 +429,49 @@ class TestAuth(unittest.TestCase):
|
|||||||
headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth)
|
headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth)
|
||||||
self.assertEquals(resp.status_int, 401)
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
|
||||||
|
def test_storage_url_default(self):
|
||||||
|
self.test_auth = \
|
||||||
|
auth.filter_factory({'user_test_tester': 'testing'})(FakeApp())
|
||||||
|
req = self._make_request(
|
||||||
|
'/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:tester', 'X-Auth-Key': 'testing'})
|
||||||
|
del req.environ['HTTP_HOST']
|
||||||
|
req.environ['SERVER_NAME'] = 'bob'
|
||||||
|
req.environ['SERVER_PORT'] = '1234'
|
||||||
|
resp = req.get_response(self.test_auth)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertEquals(resp.headers['x-storage-url'],
|
||||||
|
'http://bob:1234/v1/AUTH_test')
|
||||||
|
|
||||||
|
def test_storage_url_based_on_host(self):
|
||||||
|
self.test_auth = \
|
||||||
|
auth.filter_factory({'user_test_tester': 'testing'})(FakeApp())
|
||||||
|
req = self._make_request(
|
||||||
|
'/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:tester', 'X-Auth-Key': 'testing'})
|
||||||
|
req.environ['HTTP_HOST'] = 'somehost:5678'
|
||||||
|
req.environ['SERVER_NAME'] = 'bob'
|
||||||
|
req.environ['SERVER_PORT'] = '1234'
|
||||||
|
resp = req.get_response(self.test_auth)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertEquals(resp.headers['x-storage-url'],
|
||||||
|
'http://somehost:5678/v1/AUTH_test')
|
||||||
|
|
||||||
|
def test_storage_url_overriden_scheme(self):
|
||||||
|
self.test_auth = \
|
||||||
|
auth.filter_factory({'user_test_tester': 'testing',
|
||||||
|
'storage_url_scheme': 'fake'})(FakeApp())
|
||||||
|
req = self._make_request(
|
||||||
|
'/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:tester', 'X-Auth-Key': 'testing'})
|
||||||
|
req.environ['HTTP_HOST'] = 'somehost:5678'
|
||||||
|
req.environ['SERVER_NAME'] = 'bob'
|
||||||
|
req.environ['SERVER_PORT'] = '1234'
|
||||||
|
resp = req.get_response(self.test_auth)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertEquals(resp.headers['x-storage-url'],
|
||||||
|
'fake://somehost:5678/v1/AUTH_test')
|
||||||
|
|
||||||
def test_allowed_sync_hosts(self):
|
def test_allowed_sync_hosts(self):
|
||||||
a = auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp())
|
a = auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp())
|
||||||
self.assertEquals(a.allowed_sync_hosts, ['127.0.0.1'])
|
self.assertEquals(a.allowed_sync_hosts, ['127.0.0.1'])
|
||||||
@ -598,18 +641,17 @@ class TestParseUserCreation(unittest.TestCase):
|
|||||||
def test_parse_user_creation(self):
|
def test_parse_user_creation(self):
|
||||||
auth_filter = auth.filter_factory({
|
auth_filter = auth.filter_factory({
|
||||||
'reseller_prefix': 'ABC',
|
'reseller_prefix': 'ABC',
|
||||||
'bind_ip': '1.2.3.4',
|
|
||||||
'user_test_tester3': 'testing',
|
'user_test_tester3': 'testing',
|
||||||
'user_has_url': 'urlly .admin http://a.b/v1/DEF_has',
|
'user_has_url': 'urlly .admin http://a.b/v1/DEF_has',
|
||||||
'user_admin_admin': 'admin .admin .reseller_admin',
|
'user_admin_admin': 'admin .admin .reseller_admin',
|
||||||
})(FakeApp())
|
})(FakeApp())
|
||||||
self.assertEquals(auth_filter.users, {
|
self.assertEquals(auth_filter.users, {
|
||||||
'admin:admin': {
|
'admin:admin': {
|
||||||
'url': 'http://1.2.3.4:8080/v1/ABC_admin',
|
'url': '$HOST/v1/ABC_admin',
|
||||||
'groups': ['.admin', '.reseller_admin'],
|
'groups': ['.admin', '.reseller_admin'],
|
||||||
'key': 'admin'
|
'key': 'admin'
|
||||||
}, 'test:tester3': {
|
}, 'test:tester3': {
|
||||||
'url': 'http://1.2.3.4:8080/v1/ABC_test',
|
'url': '$HOST/v1/ABC_test',
|
||||||
'groups': [],
|
'groups': [],
|
||||||
'key': 'testing'
|
'key': 'testing'
|
||||||
}, 'has:url': {
|
}, 'has:url': {
|
||||||
@ -622,7 +664,6 @@ class TestParseUserCreation(unittest.TestCase):
|
|||||||
def test_base64_encoding(self):
|
def test_base64_encoding(self):
|
||||||
auth_filter = auth.filter_factory({
|
auth_filter = auth.filter_factory({
|
||||||
'reseller_prefix': 'ABC',
|
'reseller_prefix': 'ABC',
|
||||||
'bind_ip': '1.2.3.4',
|
|
||||||
'user64_%s_%s' % (
|
'user64_%s_%s' % (
|
||||||
b64encode('test').rstrip('='),
|
b64encode('test').rstrip('='),
|
||||||
b64encode('tester3').rstrip('=')):
|
b64encode('tester3').rstrip('=')):
|
||||||
@ -634,7 +675,7 @@ class TestParseUserCreation(unittest.TestCase):
|
|||||||
})(FakeApp())
|
})(FakeApp())
|
||||||
self.assertEquals(auth_filter.users, {
|
self.assertEquals(auth_filter.users, {
|
||||||
'test:tester3': {
|
'test:tester3': {
|
||||||
'url': 'http://1.2.3.4:8080/v1/ABC_test',
|
'url': '$HOST/v1/ABC_test',
|
||||||
'groups': ['.reseller_admin'],
|
'groups': ['.reseller_admin'],
|
||||||
'key': 'testing'
|
'key': 'testing'
|
||||||
}, 'user_foo:ab': {
|
}, 'user_foo:ab': {
|
||||||
@ -644,20 +685,6 @@ class TestParseUserCreation(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_bind_ip_all_zeroes(self):
|
|
||||||
auth_filter = auth.filter_factory({
|
|
||||||
'reseller_prefix': 'ABC',
|
|
||||||
'bind_ip': '0.0.0.0',
|
|
||||||
'user_admin_admin': 'admin .admin .reseller_admin',
|
|
||||||
})(FakeApp())
|
|
||||||
self.assertEquals(auth_filter.users, {
|
|
||||||
'admin:admin': {
|
|
||||||
'url': 'http://127.0.0.1:8080/v1/ABC_admin',
|
|
||||||
'groups': ['.admin', '.reseller_admin'],
|
|
||||||
'key': 'admin',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_key_with_no_value(self):
|
def test_key_with_no_value(self):
|
||||||
self.assertRaises(ValueError, auth.filter_factory({
|
self.assertRaises(ValueError, auth.filter_factory({
|
||||||
'user_test_tester3': 'testing',
|
'user_test_tester3': 'testing',
|
||||||
|
@ -681,6 +681,69 @@ class TestResponse(unittest.TestCase):
|
|||||||
resp.etag = None
|
resp.etag = None
|
||||||
self.assert_('etag' not in resp.headers)
|
self.assert_('etag' not in resp.headers)
|
||||||
|
|
||||||
|
def test_host_url_default(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'http'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '1234'
|
||||||
|
del env['HTTP_HOST']
|
||||||
|
self.assertEquals(resp.host_url(), 'http://bob:1234')
|
||||||
|
|
||||||
|
def test_host_url_default_port_squelched(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'http'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '80'
|
||||||
|
del env['HTTP_HOST']
|
||||||
|
self.assertEquals(resp.host_url(), 'http://bob')
|
||||||
|
|
||||||
|
def test_host_url_https(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'https'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '1234'
|
||||||
|
del env['HTTP_HOST']
|
||||||
|
self.assertEquals(resp.host_url(), 'https://bob:1234')
|
||||||
|
|
||||||
|
def test_host_url_https_port_squelched(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'https'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '443'
|
||||||
|
del env['HTTP_HOST']
|
||||||
|
self.assertEquals(resp.host_url(), 'https://bob')
|
||||||
|
|
||||||
|
def test_host_url_host_override(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'http'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '1234'
|
||||||
|
env['HTTP_HOST'] = 'someother'
|
||||||
|
self.assertEquals(resp.host_url(), 'http://someother')
|
||||||
|
|
||||||
|
def test_host_url_host_port_override(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'http'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '1234'
|
||||||
|
env['HTTP_HOST'] = 'someother:5678'
|
||||||
|
self.assertEquals(resp.host_url(), 'http://someother:5678')
|
||||||
|
|
||||||
|
def test_host_url_host_https(self):
|
||||||
|
resp = self._get_response()
|
||||||
|
env = resp.environ
|
||||||
|
env['wsgi.url_scheme'] = 'https'
|
||||||
|
env['SERVER_NAME'] = 'bob'
|
||||||
|
env['SERVER_PORT'] = '1234'
|
||||||
|
env['HTTP_HOST'] = 'someother:5678'
|
||||||
|
self.assertEquals(resp.host_url(), 'https://someother:5678')
|
||||||
|
|
||||||
|
|
||||||
class TestUTC(unittest.TestCase):
|
class TestUTC(unittest.TestCase):
|
||||||
def test_tzname(self):
|
def test_tzname(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user