This commit is contained in:
Manuel Amador (Rudd-O) 2020-09-25 16:53:14 +00:00
parent a057e31578
commit b92f096dcc

View File

@ -1,54 +1,58 @@
#!/usr/bin/python2 #!/usr/bin/python2
''' """
This program reads the /qubes-firewall/{ip}/qubes-routing-method file This program reads the /qubes-firewall/{ip}/qubes-routing-method file
for any firewall configuration, then configures the network to obey for any firewall configuration, then configures the network to obey
the routing method for the VM. If the routing method is "masquerade", the routing method for the VM. If the routing method is "masquerade",
then nothing happens. If, however, the routing method is "forward", then nothing happens. If, however, the routing method is "forward",
then VM-specific rules are enacted in the VM's attached NetVM to allow then VM-specific rules are enacted in the VM's attached NetVM to allow
traffic coming from other VMs and the outside world to reach this VM. traffic coming from other VMs and the outside world to reach this VM.
''' """
import glob import glob
import logging import logging
import qubesdb
import os import os
import subprocess
import socket import socket
import subprocess
import qubesdb
FORWARD_ROUTING_METHOD = 'forward'
FORWARD_ROUTING_METHOD = "forward"
class AdjunctWorker(object): class AdjunctWorker(object):
def __init__(self): def __init__(self):
self.qdb = qubesdb.QubesDB() self.qdb = qubesdb.QubesDB()
@staticmethod @staticmethod
def is_ip6(addr): def is_ip6(addr):
return addr.count(':') > 0 return addr.count(":") > 0
def setup_plain_forwarding_for_address(self, source, enable, family): def setup_plain_forwarding_for_address(self, source, enable, family):
def find_pos_of_first_rule(table, startswith): def find_pos_of_first_rule(table, startswith):
rules = [n for n, l in enumerate(out) if l.startswith(startswith)] rules = [n for n, l in enumerate(out) if l.startswith(startswith)]
if rules: if rules:
return rules[0] return rules[0]
return None return None
cmd = 'ip6tables' if family == 6 else 'iptables' cmd = "ip6tables" if family == 6 else "iptables"
mask = '/128' if family == 6 else '/32' mask = "/128" if family == 6 else "/32"
def run_ipt(*args):
return subprocess.check_call([cmd, '-w'] + list(args))
out = subprocess.check_output([cmd + '-save'], universal_newlines=True).splitlines() def run_ipt(*args):
return subprocess.check_call([cmd, "-w"] + list(args))
out = subprocess.check_output(
[cmd + "-save"], universal_newlines=True
).splitlines()
if enable: if enable:
# Create necessary prerouting chain. # Create necessary prerouting chain.
if not find_pos_of_first_rule(out, ':PR-PLAIN-FORWARDING - '): if not find_pos_of_first_rule(out, ":PR-PLAIN-FORWARDING - "):
run_ipt('-t', 'nat', '-N', 'PR-PLAIN-FORWARDING') run_ipt("-t", "nat", "-N", "PR-PLAIN-FORWARDING")
# Route prerouting traffic to necessary chain. # Route prerouting traffic to necessary chain.
if not find_pos_of_first_rule(out, "-A POSTROUTING -j PR-PLAIN-FORWARDING"): if not find_pos_of_first_rule(out, "-A POSTROUTING -j PR-PLAIN-FORWARDING"):
rule_num = find_pos_of_first_rule(out, "-A POSTROUTING -j MASQUERADE") rule_num = find_pos_of_first_rule(out, "-A POSTROUTING -j MASQUERADE")
@ -59,15 +63,25 @@ class AdjunctWorker(object):
first_rule_num = find_pos_of_first_rule(out, "-A POSTROUTING") first_rule_num = find_pos_of_first_rule(out, "-A POSTROUTING")
pos = rule_num - first_rule_num + 1 pos = rule_num - first_rule_num + 1
logging.info("Adding POSTROUTING chain PR-PLAIN-FORWARDING.") logging.info("Adding POSTROUTING chain PR-PLAIN-FORWARDING.")
run_ipt('-t', 'nat', '-I', 'POSTROUTING', str(pos), '-j', 'PR-PLAIN-FORWARDING') run_ipt(
"-t",
"nat",
"-I",
"POSTROUTING",
str(pos),
"-j",
"PR-PLAIN-FORWARDING",
)
# Create necessary forward chain. # Create necessary forward chain.
if not find_pos_of_first_rule(out, ':PLAIN-FORWARDING - '): if not find_pos_of_first_rule(out, ":PLAIN-FORWARDING - "):
run_ipt('-t', 'filter', '-N', 'PLAIN-FORWARDING') run_ipt("-t", "filter", "-N", "PLAIN-FORWARDING")
# Route forward traffic to necessary chain. # Route forward traffic to necessary chain.
if not find_pos_of_first_rule(out, "-A FORWARD -j PLAIN-FORWARDING"): if not find_pos_of_first_rule(out, "-A FORWARD -j PLAIN-FORWARDING"):
rule_num = find_pos_of_first_rule(out, "-A FORWARD -i vif+ -o vif+ -j DROP") rule_num = find_pos_of_first_rule(
out, "-A FORWARD -i vif+ -o vif+ -j DROP"
)
if not rule_num: if not rule_num:
# This table does not contain the masquerading rule. # This table does not contain the masquerading rule.
# Accordingly, we will not do anything. # Accordingly, we will not do anything.
@ -75,34 +89,43 @@ class AdjunctWorker(object):
first_rule_num = find_pos_of_first_rule(out, "-A FORWARD") first_rule_num = find_pos_of_first_rule(out, "-A FORWARD")
pos = rule_num - first_rule_num + 1 pos = rule_num - first_rule_num + 1
logging.info("Adding FORWARD chain PLAIN-FORWARDING.") logging.info("Adding FORWARD chain PLAIN-FORWARDING.")
run_ipt('-t', 'filter', '-I', 'FORWARD', str(pos), '-j', 'PLAIN-FORWARDING') run_ipt(
"-t", "filter", "-I", "FORWARD", str(pos), "-j", "PLAIN-FORWARDING"
)
rule = find_pos_of_first_rule( rule = find_pos_of_first_rule(
out, out, "-A PR-PLAIN-FORWARDING -s {}{} -j ACCEPT".format(source, mask)
'-A PR-PLAIN-FORWARDING -s {}{} -j ACCEPT'.format(source, mask)
) )
if enable: if enable:
if rule: if rule:
pass pass
else: else:
logging.info("Adding POSTROUTING rule to forward traffic from %s.", source) logging.info(
"Adding POSTROUTING rule to forward traffic from %s.", source
)
run_ipt( run_ipt(
'-t', 'nat', '-A', "-t",
'PR-PLAIN-FORWARDING', '-s', '{}{}'.format(source, mask), "nat",
'-j', 'ACCEPT' "-A",
"PR-PLAIN-FORWARDING",
"-s",
"{}{}".format(source, mask),
"-j",
"ACCEPT",
) )
else: else:
if rule: if rule:
first_rule = find_pos_of_first_rule(out, '-A PR-PLAIN-FORWARDING') first_rule = find_pos_of_first_rule(out, "-A PR-PLAIN-FORWARDING")
pos = rule - first_rule + 1 pos = rule - first_rule + 1
logging.info("Removing POSTROUTING rule forwarding traffic from %s.", source) logging.info(
run_ipt('-t', 'nat', '-D', 'PR-PLAIN-FORWARDING', str(pos)) "Removing POSTROUTING rule forwarding traffic from %s.", source
)
run_ipt("-t", "nat", "-D", "PR-PLAIN-FORWARDING", str(pos))
else: else:
pass pass
rule = find_pos_of_first_rule( rule = find_pos_of_first_rule(
out, out, "-A PLAIN-FORWARDING -d {}{} -o vif+ -j ACCEPT".format(source, mask)
'-A PLAIN-FORWARDING -d {}{} -o vif+ -j ACCEPT'.format(source, mask)
) )
if enable: if enable:
if rule: if rule:
@ -110,16 +133,23 @@ class AdjunctWorker(object):
else: else:
logging.info("Adding FORWARD rule to allow traffic to %s.", source) logging.info("Adding FORWARD rule to allow traffic to %s.", source)
run_ipt( run_ipt(
'-t', 'filter', '-A', "-t",
'PLAIN-FORWARDING', '-d', '{}{}'.format(source, mask), "filter",
'-o', 'vif+', '-j', 'ACCEPT' "-A",
"PLAIN-FORWARDING",
"-d",
"{}{}".format(source, mask),
"-o",
"vif+",
"-j",
"ACCEPT",
) )
else: else:
if rule: if rule:
logging.info("Removing FORWARD rule allowing traffic to %s.", source) logging.info("Removing FORWARD rule allowing traffic to %s.", source)
first_rule = find_pos_of_first_rule(out, '-A PLAIN-FORWARDING') first_rule = find_pos_of_first_rule(out, "-A PLAIN-FORWARDING")
pos = rule - first_rule + 1 pos = rule - first_rule + 1
run_ipt('-t', 'filter', '-D', 'PLAIN-FORWARDING', str(pos)) run_ipt("-t", "filter", "-D", "PLAIN-FORWARDING", str(pos))
else: else:
pass pass
@ -131,32 +161,32 @@ class AdjunctWorker(object):
# specific IP addresses in question, but it is not clear to me how # specific IP addresses in question, but it is not clear to me how
# to cause this outcome to take place. # to cause this outcome to take place.
if family == 6: if family == 6:
globber = '/proc/sys/net/ipv6/conf/*/proxy_ndp' globber = "/proc/sys/net/ipv6/conf/*/proxy_ndp"
name = 'proxy NDP' name = "proxy NDP"
elif family == 4: elif family == 4:
globber = '/proc/sys/net/ipv4/conf/*/proxy_arp' globber = "/proc/sys/net/ipv4/conf/*/proxy_arp"
name = 'proxy ARP' name = "proxy ARP"
else: else:
return return
if enabled: if enabled:
action = 'Enabling' action = "Enabling"
val = '1\n' val = "1\n"
else: else:
action = 'Disabling' action = "Disabling"
val = '0\n' val = "0\n"
matches = glob.glob(globber) matches = glob.glob(globber)
for m in matches: for m in matches:
iface = m.split('/')[6] iface = m.split("/")[6]
if iface in ('all', 'lo') or iface.startswith('vif'): if iface in ("all", "lo") or iface.startswith("vif"):
# No need to enable it for "all", or VIFs, or loopback. # No need to enable it for "all", or VIFs, or loopback.
continue continue
with open(m, 'w+') as f: with open(m, "w+") as f:
oldval = f.read() oldval = f.read()
f.seek(0)
if oldval != val: if oldval != val:
logging.info('%s %s on interface %s.', action, name, iface) logging.info("%s %s on interface %s.", action, name, iface)
f.seek(0)
f.write(val) f.write(val)
def handle_addr(self, addr): def handle_addr(self, addr):
@ -165,7 +195,7 @@ class AdjunctWorker(object):
self.setup_plain_forwarding_for_address( self.setup_plain_forwarding_for_address(
addr, addr,
routing_method == FORWARD_ROUTING_METHOD, routing_method == FORWARD_ROUTING_METHOD,
6 if self.is_ip6(addr) else 4 6 if self.is_ip6(addr) else 4,
) )
# Manipulate proxy ARP for all known addresses. # Manipulate proxy ARP for all known addresses.
@ -178,21 +208,20 @@ class AdjunctWorker(object):
} }
for family, methods in mmethods.items(): for family, methods in mmethods.items():
self.setup_proxy_arp_ndp( self.setup_proxy_arp_ndp(
FORWARD_ROUTING_METHOD in methods, FORWARD_ROUTING_METHOD in methods, family,
family,
) )
def list_targets(self): def list_targets(self):
return set(t.split('/')[2] for t in self.qdb.list('/qubes-routing-method/')) return set(t.split('/')[2] for t in self.qdb.list('/qubes-routing-method/'))
def sd_notify(self, state): def sd_notify(self, state):
'''Send notification to systemd, if available''' """Send notification to systemd, if available"""
# based on sdnotify python module # based on sdnotify python module
if not 'NOTIFY_SOCKET' in os.environ: if not "NOTIFY_SOCKET" in os.environ:
return return
addr = os.environ['NOTIFY_SOCKET'] addr = os.environ["NOTIFY_SOCKET"]
if addr[0] == '@': if addr[0] == "@":
addr = '\0' + addr[1:] addr = "\0" + addr[1:]
try: try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.connect(addr) sock.connect(addr)
@ -203,17 +232,17 @@ class AdjunctWorker(object):
def main(self): def main(self):
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
self.qdb.watch('/qubes-routing-method/') self.qdb.watch("/qubes-routing-method/")
for source_addr in self.list_targets(): for source_addr in self.list_targets():
self.handle_addr(source_addr) self.handle_addr(source_addr)
self.sd_notify('READY=1') self.sd_notify("READY=1")
try: try:
for watch_path in iter(self.qdb.read_watch, None): for watch_path in iter(self.qdb.read_watch, None):
# ignore writing rules itself - wait for final write at # ignore writing rules itself - wait for final write at
# source_addr level empty write (/qubes-firewall/SOURCE_ADDR) # source_addr level empty write (/qubes-firewall/SOURCE_ADDR)
if watch_path.count('/') != 2: if watch_path.count('/') != 2:
continue continue
source_addr = watch_path.split('/')[2] source_addr = watch_path.split("/")[2]
self.handle_addr(source_addr) self.handle_addr(source_addr)
except OSError: # EINTR except OSError: # EINTR
# signal received, don't continue the loop # signal received, don't continue the loop