dns_[a|aaaa] filter; use host for lookup

After adding iptables configuration to allow bridge.o.o to send stats
to graphite.o.o in I299c0ab5dc3dea4841e560d8fb95b8f3e7df89f2, I
encountered the weird failure that ipv6 rules seemed to be applied on
graphite.o.o, but not the ipv4 ones.

Eventually I realised that the dns_a filter as written is using
socket.getaddrinfo() on bridge.o.o and querying for itself.  It thus
gets matches the loopback entry in /etc/hosts and passes along a rule
for 127.0.1.1 or similar.  The ipv6 hostname is not in /etc/hosts so
this works there.

What we really want the dns_<a|aaaa> filters to do is lookup the
address in DNS, rather than the local resolver.  Without wanting to
get involved in new libraries, etc. the simplest option seems to be to
use the well-known 'host' tool.  We can easily parse the output of
this to ensure we're getting the actual DNS addresses for hostnames.

An ipv6 match is added to the existing test.  This is effectively
tested by the existing usage of the iptables role which sets up rules
for cacti.o.o access.

Change-Id: Ia7988626e9b1fba998fee796d4016fc66332ec03
This commit is contained in:
Ian Wienand 2018-09-13 13:58:52 +10:00
parent 93d62f8d71
commit 11343cc75d
3 changed files with 26 additions and 9 deletions

View File

@ -13,26 +13,36 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import socket import subprocess
class FilterModule(object): class FilterModule(object):
def dns(self, value, family): def dns(self, value, family):
ret = set() ret = set()
if family == '4':
match = 'has address'
elif family == '6':
match = 'has IPv6 address'
try: try:
addr_info = socket.getaddrinfo(value, None, family) # Note we use 'host' rather than something like
except socket.gaierror: # getaddrinfo so we actually query DNS and don't get any
# local-only results from /etc/hosts
output = subprocess.check_output(
['/usr/bin/host', value], universal_newlines=True)
for line in output.split('\n'):
if match in line:
address = line.split()[-1]
ret.add(address)
except Exception as e:
return ret return ret
for addr in addr_info:
ret.add(addr[4][0])
return sorted(ret) return sorted(ret)
def dns_a(self, value): def dns_a(self, value):
return self.dns(value, socket.AF_INET) return self.dns(value, '4')
def dns_aaaa(self, value): def dns_aaaa(self, value):
return self.dns(value, socket.AF_INET6) return self.dns(value, '6')
def filters(self): def filters(self):
return { return {

View File

@ -23,7 +23,7 @@
{% endfor -%} {% endfor -%}
{% for host in iptables_allowed_hosts -%} {% for host in iptables_allowed_hosts -%}
{% for addr in host.hostname | dns_aaaa -%} {% for addr in host.hostname | dns_aaaa -%}
-A openstack-INPUT {% if host.protocol == 'tcp' %}-m state --state NEW {% endif %} -m {{ host.protocol }} -p {{ host.protocol }} -s {{ addr }} --dport {{ host.port }} -j ACCEPT -A openstack-INPUT {% if host.protocol == 'tcp' %}-m state --state NEW {% endif %}-m {{ host.protocol }} -p {{ host.protocol }} -s {{ addr }} --dport {{ host.port }} -j ACCEPT
{% endfor -%} {% endfor -%}
{% endfor -%} {% endfor -%}
-A openstack-INPUT -j REJECT --reject-with icmp6-adm-prohibited -A openstack-INPUT -j REJECT --reject-with icmp6-adm-prohibited

View File

@ -83,12 +83,19 @@ def test_iptables(host):
' -m tcp --dport 19885 -j ACCEPT') ' -m tcp --dport 19885 -j ACCEPT')
assert zuul in rules assert zuul in rules
# Ensure all IPv4 addresses for cacti are allowed # Ensure all IPv4+6 addresses for cacti are allowed
for ip in get_ips('cacti.openstack.org', socket.AF_INET): for ip in get_ips('cacti.openstack.org', socket.AF_INET):
snmp = ('-A openstack-INPUT -s %s/32 -p udp -m udp' snmp = ('-A openstack-INPUT -s %s/32 -p udp -m udp'
' --dport 161 -j ACCEPT' % ip) ' --dport 161 -j ACCEPT' % ip)
assert snmp in rules assert snmp in rules
# TODO(ianw) add ip6tables support to testinfra iptables module
ip6rules = host.check_output('ip6tables -S')
for ip in get_ips('cacti.openstack.org', socket.AF_INET6):
snmp = ('-A openstack-INPUT -s %s/128 -p udp -m udp'
' --dport 161 -j ACCEPT' % ip)
assert snmp in ip6rules
def test_ntp(host): def test_ntp(host):
package = host.package("ntp") package = host.package("ntp")