Unverified Commit 45939be0 authored by Kali Kaneko's avatar Kali Kaneko
Browse files

[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
parent 1410e4ce
riseup-vpn (0.21.2.5) groovy; urgency=medium
riseup-vpn (0.21.2.6) groovy; urgency=medium
* Initial Release.
......
......@@ -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\ " +
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment