Merge "Allow to configure the nameservers in cname_lookup"
This commit is contained in:
commit
7bbe02b290
@ -564,6 +564,10 @@ The domain to be used by the middleware.
|
|||||||
.IP \fBlookup_depth\fR
|
.IP \fBlookup_depth\fR
|
||||||
How deep in the CNAME chain to look for something that matches the storage domain.
|
How deep in the CNAME chain to look for something that matches the storage domain.
|
||||||
The default is 1.
|
The default is 1.
|
||||||
|
.IP \fBnameservers\fR
|
||||||
|
Specify the nameservers to use to do the CNAME resolution. If unset, the system
|
||||||
|
configuration is used. Multiple nameservers can be specified separated by a comma.
|
||||||
|
Default is unset.
|
||||||
.RE
|
.RE
|
||||||
.PD
|
.PD
|
||||||
|
|
||||||
|
@ -565,6 +565,12 @@ use = egg:swift#cname_lookup
|
|||||||
# storage_domain = example.com
|
# storage_domain = example.com
|
||||||
#
|
#
|
||||||
# lookup_depth = 1
|
# lookup_depth = 1
|
||||||
|
#
|
||||||
|
# Specify the nameservers to use to do the CNAME resolution. If unset, the
|
||||||
|
# system configuration is used. Multiple nameservers can be specified
|
||||||
|
# separated by a comma. Default port 53 can be overriden. IPv6 is accepted.
|
||||||
|
# Example: 127.0.0.1, 127.0.0.2, 127.0.0.3:5353, [::1], [::1]:5353
|
||||||
|
# nameservers =
|
||||||
|
|
||||||
# Note: Put staticweb just after your auth filter(s) in the pipeline
|
# Note: Put staticweb just after your auth filter(s) in the pipeline
|
||||||
[filter:staticweb]
|
[filter:staticweb]
|
||||||
|
@ -29,7 +29,6 @@ rewritten and the request is passed further down the WSGI chain.
|
|||||||
|
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
|
|
||||||
import socket
|
|
||||||
from swift import gettext_ as _
|
from swift import gettext_ as _
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -43,19 +42,21 @@ else: # executed if the try block finishes with no errors
|
|||||||
|
|
||||||
from swift.common.middleware import RewriteContext
|
from swift.common.middleware import RewriteContext
|
||||||
from swift.common.swob import Request, HTTPBadRequest
|
from swift.common.swob import Request, HTTPBadRequest
|
||||||
from swift.common.utils import cache_from_env, get_logger, list_from_csv, \
|
from swift.common.utils import cache_from_env, get_logger, is_valid_ip, \
|
||||||
register_swift_info
|
list_from_csv, parse_socket_string, register_swift_info
|
||||||
|
|
||||||
|
|
||||||
def lookup_cname(domain): # pragma: no cover
|
def lookup_cname(domain, resolver): # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Given a domain, returns its DNS CNAME mapping and DNS ttl.
|
Given a domain, returns its DNS CNAME mapping and DNS ttl.
|
||||||
|
|
||||||
:param domain: domain to query on
|
:param domain: domain to query on
|
||||||
|
:param resolver: dns.resolver.Resolver() instance used for executing DNS
|
||||||
|
queries
|
||||||
:returns: (ttl, result)
|
:returns: (ttl, result)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
answer = dns.resolver.query(domain, 'CNAME').rrset
|
answer = resolver.query(domain, 'CNAME').rrset
|
||||||
ttl = answer.ttl
|
ttl = answer.ttl
|
||||||
result = answer.items[0].to_text()
|
result = answer.items[0].to_text()
|
||||||
result = result.rstrip('.')
|
result = result.rstrip('.')
|
||||||
@ -69,18 +70,6 @@ def lookup_cname(domain): # pragma: no cover
|
|||||||
return 0, None
|
return 0, None
|
||||||
|
|
||||||
|
|
||||||
def is_ip(domain):
|
|
||||||
try:
|
|
||||||
socket.inet_pton(socket.AF_INET, domain)
|
|
||||||
return True
|
|
||||||
except socket.error:
|
|
||||||
try:
|
|
||||||
socket.inet_pton(socket.AF_INET6, domain)
|
|
||||||
return True
|
|
||||||
except socket.error:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class _CnameLookupContext(RewriteContext):
|
class _CnameLookupContext(RewriteContext):
|
||||||
base_re = r'^(https?://)%s(/.*)?$'
|
base_re = r'^(https?://)%s(/.*)?$'
|
||||||
|
|
||||||
@ -108,6 +97,25 @@ class CNAMELookupMiddleware(object):
|
|||||||
self.storage_domain += [s for s in list_from_csv(storage_domain)
|
self.storage_domain += [s for s in list_from_csv(storage_domain)
|
||||||
if s.startswith('.')]
|
if s.startswith('.')]
|
||||||
self.lookup_depth = int(conf.get('lookup_depth', '1'))
|
self.lookup_depth = int(conf.get('lookup_depth', '1'))
|
||||||
|
nameservers = list_from_csv(conf.get('nameservers'))
|
||||||
|
try:
|
||||||
|
for i, server in enumerate(nameservers):
|
||||||
|
ip_or_host, maybe_port = nameservers[i] = \
|
||||||
|
parse_socket_string(server, None)
|
||||||
|
if not is_valid_ip(ip_or_host):
|
||||||
|
raise ValueError
|
||||||
|
if maybe_port is not None:
|
||||||
|
int(maybe_port)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError('Invalid cname_lookup/nameservers configuration '
|
||||||
|
'found. All nameservers must be valid IPv4 or '
|
||||||
|
'IPv6, followed by an optional :<integer> port.')
|
||||||
|
self.resolver = dns.resolver.Resolver()
|
||||||
|
if nameservers:
|
||||||
|
self.resolver.nameservers = [ip for (ip, port) in nameservers]
|
||||||
|
self.resolver.nameserver_ports = {
|
||||||
|
ip: int(port) for (ip, port) in nameservers
|
||||||
|
if port is not None}
|
||||||
self.memcache = None
|
self.memcache = None
|
||||||
self.logger = get_logger(conf, log_route='cname-lookup')
|
self.logger = get_logger(conf, log_route='cname-lookup')
|
||||||
|
|
||||||
@ -128,7 +136,7 @@ class CNAMELookupMiddleware(object):
|
|||||||
port = ''
|
port = ''
|
||||||
if ':' in given_domain:
|
if ':' in given_domain:
|
||||||
given_domain, port = given_domain.rsplit(':', 1)
|
given_domain, port = given_domain.rsplit(':', 1)
|
||||||
if is_ip(given_domain):
|
if is_valid_ip(given_domain):
|
||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
a_domain = given_domain
|
a_domain = given_domain
|
||||||
if not self._domain_endswith_in_storage_domain(a_domain):
|
if not self._domain_endswith_in_storage_domain(a_domain):
|
||||||
@ -141,7 +149,7 @@ class CNAMELookupMiddleware(object):
|
|||||||
memcache_key = ''.join(['cname-', a_domain])
|
memcache_key = ''.join(['cname-', a_domain])
|
||||||
found_domain = self.memcache.get(memcache_key)
|
found_domain = self.memcache.get(memcache_key)
|
||||||
if found_domain is None:
|
if found_domain is None:
|
||||||
ttl, found_domain = lookup_cname(a_domain)
|
ttl, found_domain = lookup_cname(a_domain, self.resolver)
|
||||||
if self.memcache and ttl > 0:
|
if self.memcache and ttl > 0:
|
||||||
memcache_key = ''.join(['cname-', given_domain])
|
memcache_key = ''.join(['cname-', given_domain])
|
||||||
self.memcache.set(memcache_key, found_domain,
|
self.memcache.set(memcache_key, found_domain,
|
||||||
|
@ -68,7 +68,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
self.assertEqual(resp, ['FAKE APP'])
|
self.assertEqual(resp, ['FAKE APP'])
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, d))
|
new=lambda d, r: (0, d))
|
||||||
def test_passthrough(self):
|
def test_passthrough(self):
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'foo.example.com'})
|
headers={'Host': 'foo.example.com'})
|
||||||
@ -85,7 +85,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
self.assertEqual(resp, ['FAKE APP'])
|
self.assertEqual(resp, ['FAKE APP'])
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, '%s.example.com' % d))
|
new=lambda d, r: (0, '%s.example.com' % d))
|
||||||
def test_good_lookup(self):
|
def test_good_lookup(self):
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'mysite.com'})
|
headers={'Host': 'mysite.com'})
|
||||||
@ -105,7 +105,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'mysite.com'})
|
headers={'Host': 'mysite.com'})
|
||||||
|
|
||||||
def my_lookup(d):
|
def my_lookup(d, r):
|
||||||
if d == 'mysite.com':
|
if d == 'mysite.com':
|
||||||
site = 'level1.foo.com'
|
site = 'level1.foo.com'
|
||||||
elif d == 'level1.foo.com':
|
elif d == 'level1.foo.com':
|
||||||
@ -120,7 +120,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
self.assertEqual(resp, ['CNAME lookup failed after 2 tries'])
|
self.assertEqual(resp, ['CNAME lookup failed after 2 tries'])
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, 'some.invalid.site.com'))
|
new=lambda d, r: (0, 'some.invalid.site.com'))
|
||||||
def test_lookup_chain_bad_target(self):
|
def test_lookup_chain_bad_target(self):
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'mysite.com'})
|
headers={'Host': 'mysite.com'})
|
||||||
@ -129,7 +129,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
['CNAME lookup failed to resolve to a valid domain'])
|
['CNAME lookup failed to resolve to a valid domain'])
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, None))
|
new=lambda d, r: (0, None))
|
||||||
def test_something_weird(self):
|
def test_something_weird(self):
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'mysite.com'})
|
headers={'Host': 'mysite.com'})
|
||||||
@ -138,7 +138,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
['CNAME lookup failed to resolve to a valid domain'])
|
['CNAME lookup failed to resolve to a valid domain'])
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, '%s.example.com' % d))
|
new=lambda d, r: (0, '%s.example.com' % d))
|
||||||
def test_with_memcache(self):
|
def test_with_memcache(self):
|
||||||
class memcache_stub(object):
|
class memcache_stub(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -175,7 +175,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
self.cache[key] = value
|
self.cache[key] = value
|
||||||
|
|
||||||
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
||||||
dns_module = 'dns.resolver.query'
|
dns_module = 'dns.resolver.Resolver.query'
|
||||||
memcache = memcache_stub()
|
memcache = memcache_stub()
|
||||||
|
|
||||||
with mock.patch(module) as m:
|
with mock.patch(module) as m:
|
||||||
@ -236,7 +236,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
self.assertFalse('cname-mysite5.com' in memcache.cache)
|
self.assertFalse('cname-mysite5.com' in memcache.cache)
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, 'c.aexample.com'))
|
new=lambda d, r: (0, 'c.aexample.com'))
|
||||||
def test_cname_matching_ending_not_domain(self):
|
def test_cname_matching_ending_not_domain(self):
|
||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'foo.com'})
|
headers={'Host': 'foo.com'})
|
||||||
@ -245,7 +245,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
['CNAME lookup failed to resolve to a valid domain'])
|
['CNAME lookup failed to resolve to a valid domain'])
|
||||||
|
|
||||||
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
@mock.patch('swift.common.middleware.cname_lookup.lookup_cname',
|
||||||
new=lambda d: (0, None))
|
new=lambda d, r: (0, None))
|
||||||
def test_cname_configured_with_empty_storage_domain(self):
|
def test_cname_configured_with_empty_storage_domain(self):
|
||||||
app = cname_lookup.CNAMELookupMiddleware(FakeApp(),
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(),
|
||||||
{'storage_domain': '',
|
{'storage_domain': '',
|
||||||
@ -285,7 +285,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'c.a.example.com'})
|
headers={'Host': 'c.a.example.com'})
|
||||||
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
||||||
with mock.patch(module, lambda x: (0, lookup_back)):
|
with mock.patch(module, lambda d, r: (0, lookup_back)):
|
||||||
return app(req.environ, start_response)
|
return app(req.environ, start_response)
|
||||||
|
|
||||||
resp = do_test('c.storage1.com')
|
resp = do_test('c.storage1.com')
|
||||||
@ -323,7 +323,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'mysite.com'})
|
headers={'Host': 'mysite.com'})
|
||||||
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
||||||
with mock.patch(module, lambda x: (0, 'example.com')):
|
with mock.patch(module, lambda d, r: (0, 'example.com')):
|
||||||
resp = app(req.environ, start_response)
|
resp = app(req.environ, start_response)
|
||||||
self.assertEqual(resp, ['FAKE APP'])
|
self.assertEqual(resp, ['FAKE APP'])
|
||||||
|
|
||||||
@ -331,7 +331,7 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
app = cname_lookup.CNAMELookupMiddleware(RedirectSlashApp(), {})
|
app = cname_lookup.CNAMELookupMiddleware(RedirectSlashApp(), {})
|
||||||
|
|
||||||
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
module = 'swift.common.middleware.cname_lookup.lookup_cname'
|
||||||
with mock.patch(module, lambda x: (0, 'cont.acct.example.com')):
|
with mock.patch(module, lambda d, r: (0, 'cont.acct.example.com')):
|
||||||
req = Request.blank('/test', environ={'REQUEST_METHOD': 'GET'},
|
req = Request.blank('/test', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Host': 'mysite.com'})
|
headers={'Host': 'mysite.com'})
|
||||||
resp = req.get_response(app)
|
resp = req.get_response(app)
|
||||||
@ -339,6 +339,89 @@ class TestCNAMELookup(unittest.TestCase):
|
|||||||
self.assertEqual(resp.headers.get('Location'),
|
self.assertEqual(resp.headers.get('Location'),
|
||||||
'http://mysite.com/test/')
|
'http://mysite.com/test/')
|
||||||
|
|
||||||
|
def test_configured_nameservers(self):
|
||||||
|
class MockedResolver(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.nameservers = None
|
||||||
|
self.nameserver_ports = None
|
||||||
|
|
||||||
|
def query(self, *args, **kwargs):
|
||||||
|
raise Exception('Stop processing')
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.nameservers = None
|
||||||
|
self.nameserver_ports = None
|
||||||
|
|
||||||
|
mocked_resolver = MockedResolver()
|
||||||
|
dns_module = 'dns.resolver.Resolver'
|
||||||
|
|
||||||
|
# If no nameservers provided in conf, resolver nameservers is unset
|
||||||
|
for conf in [{}, {'nameservers': ''}]:
|
||||||
|
mocked_resolver.reset()
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIs(app.resolver, mocked_resolver)
|
||||||
|
self.assertIsNone(mocked_resolver.nameservers)
|
||||||
|
|
||||||
|
# If invalid nameservers provided, resolver nameservers is unset
|
||||||
|
mocked_resolver.reset()
|
||||||
|
conf = {'nameservers': '127.0.0.1, 127.0.0.2, a.b.c.d'}
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
with self.assertRaises(ValueError) as exc_mgr:
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIn('Invalid cname_lookup/nameservers configuration',
|
||||||
|
str(exc_mgr.exception))
|
||||||
|
|
||||||
|
# If nameservers provided in conf, resolver nameservers is set
|
||||||
|
mocked_resolver.reset()
|
||||||
|
conf = {'nameservers': '127.0.0.1'}
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIs(app.resolver, mocked_resolver)
|
||||||
|
self.assertEqual(mocked_resolver.nameservers, ['127.0.0.1'])
|
||||||
|
self.assertEqual(mocked_resolver.nameserver_ports, {})
|
||||||
|
|
||||||
|
# IPv6 is OK
|
||||||
|
mocked_resolver.reset()
|
||||||
|
conf = {'nameservers': '[::1]'}
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIs(app.resolver, mocked_resolver)
|
||||||
|
self.assertEqual(mocked_resolver.nameservers, ['::1'])
|
||||||
|
self.assertEqual(mocked_resolver.nameserver_ports, {})
|
||||||
|
|
||||||
|
# As are port overrides
|
||||||
|
mocked_resolver.reset()
|
||||||
|
conf = {'nameservers': '127.0.0.1:5354'}
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIs(app.resolver, mocked_resolver)
|
||||||
|
self.assertEqual(mocked_resolver.nameservers, ['127.0.0.1'])
|
||||||
|
self.assertEqual(mocked_resolver.nameserver_ports, {'127.0.0.1': 5354})
|
||||||
|
|
||||||
|
# And IPv6 with port overrides
|
||||||
|
mocked_resolver.reset()
|
||||||
|
conf = {'nameservers': '[2001:db8::ff00:42:8329]:1234'}
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIs(app.resolver, mocked_resolver)
|
||||||
|
self.assertEqual(mocked_resolver.nameservers, [
|
||||||
|
'2001:db8::ff00:42:8329'])
|
||||||
|
self.assertEqual(mocked_resolver.nameserver_ports, {
|
||||||
|
'2001:db8::ff00:42:8329': 1234})
|
||||||
|
|
||||||
|
# Also accept lists, and bring it all together
|
||||||
|
mocked_resolver.reset()
|
||||||
|
conf = {'nameservers': '[::1], 127.0.0.1:5354, '
|
||||||
|
'[2001:db8::ff00:42:8329]:1234'}
|
||||||
|
with mock.patch(dns_module, return_value=mocked_resolver):
|
||||||
|
app = cname_lookup.CNAMELookupMiddleware(FakeApp(), conf)
|
||||||
|
self.assertIs(app.resolver, mocked_resolver)
|
||||||
|
self.assertEqual(mocked_resolver.nameservers, [
|
||||||
|
'::1', '127.0.0.1', '2001:db8::ff00:42:8329'])
|
||||||
|
self.assertEqual(mocked_resolver.nameserver_ports, {
|
||||||
|
'127.0.0.1': 5354, '2001:db8::ff00:42:8329': 1234})
|
||||||
|
|
||||||
|
|
||||||
class TestSwiftInfo(unittest.TestCase):
|
class TestSwiftInfo(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user