summaryrefslogtreecommitdiff
path: root/helpers
diff options
context:
space:
mode:
authorkali kaneko (leap communications) <kali@leap.se>2021-06-14 21:45:48 +0200
committerkali kaneko (leap communications) <kali@leap.se>2021-06-14 21:45:58 +0200
commit45939be0800f8cb77dcac854706ed1c7ac757931 (patch)
tree314b32733c5126c4b7f53349f2818f9fcf865b88 /helpers
parent1410e4cecccb860fc1dd29d1d4021ff72b5dc1ab (diff)
[feat] allow to define explicitely allowed private address
By default, bitmask-root allows traffic to devices in local networks. However, this behavior depends on it correctly identifying the local network of the default route, and it can fail on more complex network setups (one common failure mode is when one of the ifaces gets a link-local ip). This commit introduces an explicit mechanism, by parsing lines in /etc/bitmask/ipv4.allow /etc/bitmask/ipv6.allow If valid private ips are defined in either of the files, the behavior will change to fail close for local devices, and allow traffic (both tcp and udp) to the defined ips, on all ports. - Resolves: #503
Diffstat (limited to 'helpers')
-rw-r--r--helpers/bitmask-root76
1 files changed, 68 insertions, 8 deletions
diff --git a/helpers/bitmask-root b/helpers/bitmask-root
index 6615d3b..f105bfc 100644
--- a/helpers/bitmask-root
+++ b/helpers/bitmask-root
@@ -43,6 +43,7 @@ The `openvpn start` action is special: it calls exec on openvpn and replaces
the current process. If the `restart` parameter is passed, the firewall will
not be teared down in the case of an error during launch.
"""
+import ipaddress
import os
import re
import signal
@@ -83,7 +84,7 @@ def get_no_group_name():
def tostr(s):
return s.decode('utf-8')
-VERSION = "12"
+VERSION = "13"
SCRIPT = "bitmask-root"
NAMESERVER_TCP = "10.41.0.1"
NAMESERVER_UDP = "10.42.0.1"
@@ -275,6 +276,29 @@ def get_process_list():
return filter(None, res)
+def getIPv4AllowAddresses():
+ lines = []
+ try:
+ with open("/etc/bitmask/ipv4.allow", 'r') as f:
+ lines = [l.strip() for l in f.readlines()]
+ except FileNotFoundError:
+ return lines
+
+ lines = filter(lambda x: ipaddress.ip_address(x).version == 4, lines)
+ return list(filter(lambda x: ipaddress.ip_address(x).is_private, lines))
+
+def getIPv6AllowAddresses():
+ lines = []
+ try:
+ with open("/etc/bitmask/ipv6.allow", 'r') as f:
+ lines = [l.strip() for l in f.readlines()]
+ except FileNotFoundError:
+ return lines
+
+ lines = filter(lambda x: ipaddress.ip_address(x).version == 6, lines)
+ return list(filter(lambda x: ipaddress.ip_address(x).is_private, lines))
+
+
def run(command, *args, **options):
"""
Run an external command.
@@ -655,6 +679,16 @@ def firewall_start(args):
local_network_ipv6 = get_local_network_ipv6(default_device)
gateways = get_gateways(args)
+ # allow local address in listed exception list
+ # this will allow all ports and both tcp and udp.
+ def allow4(ip):
+ ip4tables("--append", BITMASK_CHAIN, "--destination", ip,
+ "-o", default_device, "--jump", "ACCEPT")
+
+ def allow6(ip):
+ ip6tables("--append", BITMASK_CHAIN, "--destination", ip,
+ "-o", default_device, "--jump", "ACCEPT")
+
# add custom chain "bitmask" to front of OUTPUT chain for both
# the 'filter' and the 'nat' tables.
if not ipv4_chain_exists(BITMASK_CHAIN):
@@ -707,11 +741,14 @@ def firewall_start(args):
"--protocol", "tcp", "--dport", "53", "--jump", "MASQUERADE")
# allow local network traffic
+
+ ipv4_exceptions = getIPv4AllowAddresses()
if local_network_ipv4:
- # allow local network destinations
- ip4tables("--append", BITMASK_CHAIN,
- "--destination", local_network_ipv4, "-o", default_device,
- "--jump", "ACCEPT")
+ if len(ipv4_exceptions) == 0:
+ # allow all local network destinations if no explicit allow rules defined
+ ip4tables("--append", BITMASK_CHAIN,
+ "--destination", local_network_ipv4, "-o", default_device,
+ "--jump", "ACCEPT")
# allow local network sources for DNS
# (required to allow local network DNS that gets rewritten by NAT
# to get passed through so that MASQUERADE can set correct source IP)
@@ -731,10 +768,15 @@ def firewall_start(args):
"--protocol", "udp",
"--destination", "224.0.0.251", "--dport", "5353",
"-o", default_device, "--jump", "RETURN")
+
+
+ ipv6_exceptions = getIPv6AllowAddresses()
if local_network_ipv6:
- ip6tables("--append", BITMASK_CHAIN,
- "--destination", local_network_ipv6, "-o", default_device,
- "--jump", "ACCEPT")
+ if len(ipv6_exceptions) == 0:
+ # allow all local network destinations if no explicit allow rules defined
+ ip6tables("--append", BITMASK_CHAIN,
+ "--destination", local_network_ipv6, "-o", default_device,
+ "--jump", "ACCEPT")
# allow multicast Simple Service Discovery Protocol
ip6tables("--append", BITMASK_CHAIN,
"--protocol", "udp",
@@ -751,12 +793,29 @@ def firewall_start(args):
ip4tables("--append", BITMASK_CHAIN, "--destination", gateway,
"-o", default_device, "--jump", "ACCEPT")
+ # TODO allow ipv6 traffic to gws too
+
# log rejected packets to syslog
if DEBUG:
iptables("--append", BITMASK_CHAIN, "-o", default_device,
"--jump", "LOG", "--log-prefix", "iptables denied: ",
"--log-level", "7")
+ # allow explicit private exceptions
+ if len(ipv4_exceptions) != 0:
+ for ip in ipv4_exceptions:
+ allow4(ip)
+ ip4tables("--append", BITMASK_CHAIN,
+ "--destination", local_network_ipv4, "-o", default_device,
+ "--jump", "REJECT")
+
+ if len(ipv6_exceptions) != 0:
+ for ip in ipv6_exceptions:
+ allow6(ip)
+ ip6tables("--append", BITMASK_CHAIN,
+ "--destination", local_network_ipv6, "-o", default_device,
+ "--jump", "REJECT")
+
# for now, ensure all other ipv6 packets get rejected (regardless of
# device). not sure why, but "-p any" doesn't work.
ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT")
@@ -766,6 +825,7 @@ def firewall_start(args):
ip4tables("--append", BITMASK_CHAIN, "-o",
default_device, "--jump", "REJECT")
+
# On Qubes OS, add anti-leak rules for proxyVM qubes-firewall.service
# Must stay on 'top' of chain!
if QUBES_PROXY and QUBES_VER >= 3 and run("grep", "installed\ by\ " +