Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace iptables with nftables #71

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
54 changes: 30 additions & 24 deletions files-main/proxy-firewall-restrict
Expand Up @@ -14,31 +14,38 @@ groupadd -rf qvpn && sync
# Set firewall restriction policy

# Stop all leaks between downstream (vif+) and upstream (Internet eth0):
iptables -P FORWARD DROP
iptables -I FORWARD -o eth0 -j DROP
iptables -I FORWARD -i eth0 -j DROP
nft chain ip qubes forward '{ policy drop; }'
nft insert rule ip qubes custom-forward oifgroup 1 drop
nft insert rule ip qubes custom-forward iifgroup 1 drop

ip6tables -P FORWARD DROP
ip6tables -I FORWARD -o eth0 -j DROP
ip6tables -I FORWARD -i eth0 -j DROP
nft chain ip6 qubes forward '{ policy drop; }'
nft insert rule ip6 qubes custom-forward oifgroup 1 drop
nft insert rule ip6 qubes custom-forward iifgroup 1 drop

# Accept forward traffic between dowstream (vif+, group 2) and VPN interface (group 9)
nft insert rule ip qubes custom-forward iifgroup 2 oifgroup 9 accept
nft insert rule ip qubes custom-forward iifgroup 9 oifgroup 2 accept
nft insert rule ip6 qubes custom-forward iifgroup 2 oifgroup 9 accept
nft insert rule ip6 qubes custom-forward iifgroup 9 oifgroup 2 accept

# Block INPUT from tunnel(s):
iptables -P INPUT DROP
ip6tables -P INPUT DROP
nft chain ip qubes input '{ policy drop; }'
nft chain ip6 qubes input '{ policy drop; }'

# Allow established v6 traffic (v4 rule already present):
#iptables -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
# Create output chain since it's not created by Qubes by default
nft add chain ip qubes output '{ type filter hook output priority 0; policy accept; }'
nft add chain ip6 qubes output '{ type filter hook output priority 0; policy accept; }'

# Disable icmp packets
if [ -e /var/run/qubes-service/vpn-handler-no-icmp ]; then
iptables -D INPUT -p icmp -j ACCEPT || true
ip6tables -D INPUT -p icmp -j ACCEPT || true
iptables -I OUTPUT -p icmp -o eth0 -j DROP
ip6tables -I OUTPUT -p icmp -o eth0 -j DROP
nft insert rule ip qubes custom-input meta l4proto icmp drop
nft insert rule ip6 qubes custom-input meta l4proto icmp drop

nft insert rule ip qubes output oifgroup 1 meta l4proto icmp drop
nft insert rule ip6 qubes output oifgroup 1 meta l4proto icmp drop
else
iptables -I OUTPUT -p icmp -m owner --gid-owner qvpn -j ACCEPT
ip6tables -I OUTPUT -p icmp -m owner --gid-owner qvpn -j ACCEPT
nft insert rule ip qubes output meta l4proto icmp skgid qvpn accept
nft insert rule ip6 qubes output meta l4proto icmp skgid qvpn accept
fi

###-------------------------------------------------------------------###
Expand All @@ -50,11 +57,10 @@ fi
# Extra restriction prevents accidental communications from within VPN VM to net;
# The gid-owner rule requires net programs be run with group ID 'qvpn'
# to allow outbound traffic.
iptables -P OUTPUT DROP
iptables -I OUTPUT -o lo -j ACCEPT
iptables -I OUTPUT -p all -o eth0 -m owner --gid-owner qvpn -j ACCEPT

ip6tables -P OUTPUT DROP
ip6tables -I OUTPUT -o lo -j ACCEPT
ip6tables -I OUTPUT -p all -o eth0 -m owner --gid-owner qvpn -j ACCEPT
nft chain ip qubes output '{ policy drop; }'
nft insert rule ip qubes output oif "lo" accept
nft insert rule ip qubes output oifgroup 1 skgid qvpn accept

nft chain ip6 qubes output '{ policy drop; }'
nft insert rule ip6 qubes output oif "lo" accept
nft insert rule ip6 qubes output oifgroup 1 skgid qvpn accept
2 changes: 1 addition & 1 deletion files-main/qubes-vpn-handler.service
Expand Up @@ -22,7 +22,7 @@ Environment="client_cmd=/usr/sbin/openvpn"
Environment="client_opt1=--cd /rw/config/vpn/ --config /tmp/vpn-client.conf --verb 3"
Environment="client_opt2=--mlock --ping 10 --ping-restart 42 --connect-retry 5 30"
Environment="client_opt3=--connect-retry-max 7 --resolv-retry 15 --group qvpn"
Environment='client_opt4=--script-security 2 --up "/usr/lib/qubes/qubes-vpn-ns up" --down "/usr/lib/qubes/qubes-vpn-ns down"'
Environment='client_opt4=--script-security 2 --up "/usr/lib/qubes/qubes-vpn-openvpn-script" --down "/usr/lib/qubes/qubes-vpn-openvpn-script"'
Environment="client_opt5="
Environment="userpassword_opt=--auth-user-pass /tmp/userpassword.txt"

Expand Down
6 changes: 4 additions & 2 deletions files-main/qubes-vpn-handler.service.d/10_wg.conf.example
Expand Up @@ -21,10 +21,12 @@ Environment="userpassword_opt="

# Override wg-quick DNS functions:
ExecStartPre=/bin/cp -a /usr/bin/wg-quick /tmp
ExecStartPre=/bin/sed -i "/~~ function override insertion point/a set_dns() { export vpn_dns=\$DNS; /usr/lib/qubes/qubes-vpn-ns up; HAVE_SET_DNS=1; }" /tmp/wg-quick
ExecStartPre=/bin/sed -i "/~~ function override insertion point/a set_dns() { export vpn_dns=\$\{DNS[*]\}; /usr/lib/qubes/qubes-vpn-ns up; HAVE_SET_DNS=1; }" /tmp/wg-quick
ExecStartPre=/bin/sed -i "/~~ function override insertion point/a unset_dns() { /usr/lib/qubes/qubes-vpn-ns down; }" /tmp/wg-quick

# Workaround: Allow wg access to net
ExecStartPre=/sbin/iptables -P OUTPUT ACCEPT
ExecStartPre=/sbin/nft chain ip qubes output '{ policy accept; }'
ExecStartPre=/sbin/nft chain ip6 qubes output '{ policy accept; }'
ExecStartPost=/sbin/ip link set dev "vpn-client" group 9

ExecStop=/tmp/wg-quick down /tmp/vpn-client.conf
56 changes: 50 additions & 6 deletions files-main/qubes-vpn-ns
Expand Up @@ -38,18 +38,61 @@ up|test-up)

;;&
up)
# Flush redirects to Qubes OS virtual DNS servers
# from qubes connected to this qube
nft flush chain ip qubes dnat-dns
# ip6 qubes dnat-dns chain is missing upstream so fix it here for now
nft add chain ip6 qubes dnat-dns '{ type nat hook prerouting priority dstnat; policy accept; }'
nft flush chain ip6 qubes dnat-dns
if [[ -n "$vpn_dns" ]] ; then
# Set DNS address translation in firewall:
echo "$vpn_dns " >$nspath
echo "Using DNS servers $vpn_dns"
iptables -t nat -F PR-QBS
. /var/run/qubes/qubes-ns
q_addr=""

vpn_dns_ip4=()
vpn_dns_ip6=()
for DNS in $vpn_dns; do
iptables -t nat -I PR-QBS $q_addr -i vif+ -p tcp --dport 53 -j DNAT --to $DNS
iptables -t nat -I PR-QBS $q_addr -i vif+ -p udp --dport 53 -j DNAT --to $DNS
q_addr="-d $NS1"
if [[ $DNS =~ .*\..* ]] ; then
vpn_dns_ip4+=($DNS)
elif [[ $DNS =~ .*:.* ]] ; then
vpn_dns_ip6+=($DNS)
fi
done

qubes_dns_ip4=()
qubes_dns_ip6=()
for NS in $NS1 $NS2; do
if [[ $NS =~ .*\..* ]] ; then
qubes_dns_ip4+=($NS)
elif [[ $NS =~ .*:.* ]] ; then
qubes_dns_ip6+=($NS)
fi
done

if [[ ${#vpn_dns_ip4[@]} != 0 ]] && [[ ${#qubes_dns_ip4[@]} != 0 ]]; then
for i in $(seq 0 $((${#qubes_dns_ip4[@]} - 1))); do
if [[ $i < ${#vpn_dns_ip4[@]} ]] ; then
nft add rule ip qubes dnat-dns iifgroup 2 ip daddr ${qubes_dns_ip4[$i]} tcp dport 53 dnat to ${vpn_dns_ip4[$i]}
nft add rule ip qubes dnat-dns iifgroup 2 ip daddr ${qubes_dns_ip4[$i]} udp dport 53 dnat to ${vpn_dns_ip4[$i]}
else
nft add rule ip qubes dnat-dns iifgroup 2 ip daddr ${qubes_dns_ip4[$i]} tcp dport 53 dnat to ${vpn_dns_ip4[0]}
nft add rule ip qubes dnat-dns iifgroup 2 ip daddr ${qubes_dns_ip4[$i]} udp dport 53 dnat to ${vpn_dns_ip4[0]}
fi
done
fi

if [[ ${#vpn_dns_ip6[@]} != 0 ]] && [[ ${#qubes_dns_ip6[@]} != 0 ]]; then
for i in $(seq 0 $((${#qubes_dns_ip6[@]} - 1))); do
if [[ $i < ${#vpn_dns_ip6[@]} ]] ; then
nft add rule ip6 qubes dnat-dns iifgroup 2 ip6 daddr ${qubes_dns_ip6[$i]} tcp dport 53 dnat to ${qubes_dns_ip6[$i]}
nft add rule ip6 qubes dnat-dns iifgroup 2 ip6 daddr ${qubes_dns_ip6[$i]} udp dport 53 dnat to ${qubes_dns_ip6[$i]}
else
nft add rule ip6 qubes dnat-dns iifgroup 2 ip6 daddr ${qubes_dns_ip6[$i]} tcp dport 53 dnat to ${qubes_dns_ip6[0]}
nft add rule ip6 qubes dnat-dns iifgroup 2 ip6 daddr ${qubes_dns_ip6[$i]} udp dport 53 dnat to ${qubes_dns_ip6[0]}
fi
done
fi
do_notify "LINK IS UP." "network-idle"
else
do_notify "LINK UP, NO DNS!" "dialog-error"
Expand All @@ -71,7 +114,8 @@ test-up)

;;
down)
iptables -t nat -F PR-QBS
nft flush chain ip qubes dnat-dns
nft flush chain ip6 qubes dnat-dns
do_notify "LINK IS DOWN !" "dialog-error"

;;
Expand Down
9 changes: 9 additions & 0 deletions files-main/qubes-vpn-openvpn-script
@@ -0,0 +1,9 @@
#!/bin/bash

/usr/lib/qubes/qubes-vpn-ns $script_type

case $script_type in
up)
/usr/sbin/ip link set dev "$dev" group 9
;;
esac
68 changes: 58 additions & 10 deletions files-main/qubes-vpn-setup
Expand Up @@ -53,10 +53,53 @@ firewall_link() {
case "$1" in
--check-firewall)
for i in 1 2 3; do
if iptables -C FORWARD -o eth0 -j DROP \
&& iptables -C FORWARD -i eth0 -j DROP \
&& ip6tables -C FORWARD -o eth0 -j DROP \
&& ip6tables -C FORWARD -i eth0 -j DROP ; then
if (nft -j -s list chain ip qubes custom-forward && nft -j -s list chain ip qubes custom-forward) | python3 -c "
import sys, json;
def main():
rule_out_ipv4_exists = False
rule_in_ipv4_exists = False
rule_out_ipv6_exists = False
rule_in_ipv6_exists = False
nft_rules = sys.stdin.readlines()
nft_rules_ip = json.loads(nft_rules[0])['nftables']
nft_rules_ip6 = json.loads(nft_rules[1])['nftables']
for nft_rule in nft_rules_ip:
for nft_rule_item in nft_rule.items():
rule_expr0 = False
rule_expr1 = False
if 'expr' in nft_rule_item[1]:
for expr_item in nft_rule_item[1]['expr']:
if 'match' in expr_item and 'meta' in expr_item['match']['left'] and expr_item['match']['op'] == '==' and expr_item['match']['right'] == 1:
rule_expr0 = True
if 'drop' in expr_item and expr_item['drop'] == None:
rule_expr1 = True
if rule_expr0 and rule_expr1:
if nft_rule_item[1]['expr'][0]['match']['left']['meta']['key'] == 'oifgroup':
rule_out_ipv4_exists = True
elif nft_rule_item[1]['expr'][0]['match']['left']['meta']['key'] == 'iifgroup':
rule_in_ipv4_exists = True
for nft_rule in nft_rules_ip6:
for nft_rule_item in nft_rule.items():
rule_expr0 = False
rule_expr1 = False
if 'expr' in nft_rule_item[1]:
for expr_item in nft_rule_item[1]['expr']:
if 'match' in expr_item and 'meta' in expr_item['match']['left'] and expr_item['match']['op'] == '==' and expr_item['match']['right'] == 1:
rule_expr0 = True
if 'drop' in expr_item and expr_item['drop'] == None:
rule_expr1 = True
if rule_expr0 and rule_expr1:
if nft_rule_item[1]['expr'][0]['match']['left']['meta']['key'] == 'oifgroup':
rule_out_ipv6_exists = True
elif nft_rule_item[1]['expr'][0]['match']['left']['meta']['key'] == 'iifgroup':
rule_in_ipv6_exists = True
if rule_out_ipv4_exists and rule_in_ipv4_exists and rule_out_ipv6_exists and rule_in_ipv6_exists:
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
main()
"; then
exit 0
elif [ $i = 3 ]; then
echo "Error: Firewall rule(s) not enabled!"
Expand Down Expand Up @@ -119,27 +162,30 @@ case "$1" in

if [ ! -d vpn ]; then exit 1; fi
chown -R root:root *
chmod +x rc.local qubes-vpn-ns qubes-vpn-setup proxy-firewall-restrict
chmod +x rc.local qubes-vpn-ns qubes-vpn-openvpn-script \
qubes-vpn-setup proxy-firewall-restrict

if [ -e /var/run/qubes/this-is-templatevm ]; then
echo "Install into templateVM..."
groupadd -rf qvpn
cp -a qubes-vpn-handler.service* /lib/systemd/system
sync; sleep 2s; systemctl daemon-reload
systemctl enable qubes-vpn-handler.service
cp -a qubes-vpn-ns qubes-vpn-setup proxy-firewall-restrict \
-t /usr/lib/qubes
cp -a qubes-vpn-ns qubes-vpn-openvpn-script qubes-vpn-setup \
proxy-firewall-restrict -t /usr/lib/qubes
echo "Almost done..."
echo "Next, shutdown this template then start proxyVM and run:"
echo "sudo /usr/lib/qubes/qubes-vpn-setup --config"

elif [ -e /var/run/qubes/this-is-proxyvm ]; then
echo -n "Isolated install for proxyVM..."
cp -a vpn rc.local qubes-vpn-handler.* qubes-vpn-setup \
qubes-vpn-ns proxy-firewall-restrict -t /rw/config
qubes-vpn-ns qubes-vpn-openvpn-script proxy-firewall-restrict \
-t /rw/config
firewall_link /rw/config
ln -s -f /rw/config/qubes-vpn-setup /usr/lib/qubes/qubes-vpn-setup
ln -s -f /rw/config/qubes-vpn-ns /usr/lib/qubes/qubes-vpn-ns
ln -s -f /rw/config/qubes-vpn-openvpn-script /usr/lib/qubes/qubes-vpn-openvpn-script
echo "copy complete."
do_userpass
echo "Done!"
Expand All @@ -159,14 +205,16 @@ case "$1" in
rm -rf /lib/systemd/system/qubes-vpn-handler.service.d
systemctl daemon-reload
cd /usr/lib/qubes
rm -f qubes-vpn-ns qubes-vpn-setup proxy-firewall-restrict
rm -f qubes-vpn-ns qubes-vpn-openvpn-script qubes-vpn-setup \
proxy-firewall-restrict
)
echo qubes-vpn-handler removed.
elif is_proxyvm ; then
( cd /rw/config
rm -rf qubes-vpn-handler.service
rm -rf qubes-vpn-handler.service.d
rm -f qubes-vpn-ns qubes-vpn-setup proxy-firewall-restrict
rm -f qubes-vpn-ns qubes-vpn-openvpn-script qubes-vpn-setup \
proxy-firewall-restrict
mv -v rc.local rc.local-qvpn-disabled
)
echo qubes-vpn-handler removed.
Expand Down
1 change: 1 addition & 0 deletions files-main/rc.local
Expand Up @@ -11,6 +11,7 @@ fi
cp -a /rw/config/qubes-vpn-handler.* /lib/systemd/system
sync
ln -s -f /rw/config/qubes-vpn-ns /usr/lib/qubes/qubes-vpn-ns
ln -s -f /rw/config/qubes-vpn-openvpn-script /usr/lib/qubes/qubes-vpn-openvpn-script
ln -s -f /rw/config/qubes-vpn-setup /usr/lib/qubes/qubes-vpn-setup

# Start tunnel service
Expand Down