#!/usr/bin/env bash # SPDX-FileCopyrightText: 2022 unman # SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. # # SPDX-License-Identifier: AGPL-3.0-or-later ## Credits: https://github.com/unman/shaker/blob/main/i2p/in.sh ## Recursively open ports through the firewall to allow remote access to a qube. ## TODO: remove iptables in favor of nft. It doesn't work if the upstream net ## qubes are disposables, instead, the rule should be applied on the ## disposable template. This would work well if users used our project that ## creates a template per service, but if user is using a default diposable ## template for that, such as debian-XX-dvm, the firewall would allow many ## qubes to be exposed. me="${0##*/}" usage(){ cat </dev/null)" if test -z "$found"; then qvm-run -q -u root "${my_netvms[$i]}" -- "iptables -I QBS-FORWARD -i $iface -p $proto --dport $portnum_target -d ${my_ips[$i-1]} -j ACCEPT" qvm-run -q -u root "${my_netvms[$i]}" -- "iptables -t nat -I PR-QBS-SERVICES -i $iface -p $proto --dport $portnum_used -j DNAT --to-destination ${my_ips[$i-1]}:$portnum_target" if test "$permanent" = "1"; then qvm-run -q -u root "${my_netvms[$i]}" -- "echo iptables -I QBS-FORWARD -i $iface -p $proto --dport $portnum_target -d ${my_ips[$i-1]} -j ACCEPT >> /rw/config/rc.local" qvm-run -q -u root "${my_netvms[$i]}" -- "echo iptables -t nat -I PR-QBS-SERVICES -i $iface -p $proto --dport $portnum_used -j DNAT --to-destination ${my_ips[$i-1]}:$portnum_target >> /rw/config/rc.local" fi else qvm-run -q -u root "${my_netvms[$i]}" -- nft insert rule nat PR-QBS-SERVICES meta iifname "$iface" "$proto" dport "$portnum_used" dnat to "${my_ips[$i-1]}:$portnum_target" qvm-run -q -u root "${my_netvms[$i]}" -- nft insert rule filter QBS-FORWARD meta iifname "$iface" ip daddr "${my_ips[$i-1]}" "$proto" dport "$portnum_target" ct state new accept if test "$permanent" = "1"; then qvm-run -q -u root "${my_netvms[$i]}" -- "echo nft insert rule nat PR-QBS-SERVICES meta iifname $iface $proto dport $portnum_used dnat to ${my_ips[$i-1]}:$portnum_target >> /rw/config/rc.local" qvm-run -q -u root "${my_netvms[$i]}" -- "echo nft insert rule filter QBS-FORWARD meta iifname $iface ip daddr ${my_ips[$i-1]} $proto dport $portnum_target ct state new accept >> /rw/config/rc.local" fi fi ((i++)) done } ## Teardown from top netvm down teardown(){ declare -a my_netvms=("${!1}") declare -a my_ips=("${!2}") declare -i numhops numhops=${#my_ips[@]} numhops=$((numhops-1)) local i=$numhops iface="eth0" echo "Removing firewall rules" while [ $i -gt 0 ]; do if [ $i -eq 1 ]; then portnum_used=$external_portnum portnum_target=$portnum else portnum_used=$external_portnum portnum_target=$external_portnum fi # Is it nft or iptables? echo "${my_netvms[$i]}" local found found="$( qvm-run -p -q -u root "${my_netvms[$i]}" -- "nft list table nat 2>/dev/null" )" if test -z "$found"; then qvm-run -q -u root "${my_netvms[$i]}" -- "iptables -D QBS-FORWARD -i $iface -p $proto --dport $portnum_target -d ${my_ips[$i-1]} -j ACCEPT" qvm-run -q -u root "${my_netvms[$i]}" -- "iptables -t nat -D PR-QBS-SERVICES -i $iface -p $proto --dport $external_portnum -j DNAT --to-destination ${my_ips[$i-1]}:$portnum_target" if [ "$permanent" -eq 1 ]; then qvm-run -q -u root "${my_netvms[$i]}" -- "sed -i '/iptables -D QBS-FORWARD -i $iface -p $proto --dport $portnum_target -d ${my_ips[$i-1]} -j ACCEPT/d' /rw/config/rc.local" qvm-run -q -u root "${my_netvms[$i]}" -- "sed -i '/iptables -t nat -D PR-QBS-SERVICES -i $iface -p $proto --dport $external_portnum -j DNAT --to-destination ${my_ips[$i-1]}:$portnum_target/d' /rw/config/rc.local" fi else local handle handle="$( get_handle "${my_netvms[$i]}" nat "dport $external_portnum " 1 )" qvm-run -q -u root "${my_netvms[$i]}" -- "nft delete rule nat PR-QBS-SERVICES handle $handle" local handle handle="$( get_handle "${my_netvms[$i]}" filter "dport $external_portnum " 1 )" qvm-run -q -u root "${my_netvms[$i]}" -- "nft delete rule filter QBS-FORWARD handle $handle" if [ "$permanent" -eq 1 ]; then qvm-run -q -u root "${my_netvms[$i]}" -- "sed -i '/nft insert rule nat PR-QBS-SERVICES meta iifname $iface $proto dport $portnum_used dnat to ${my_ips[$i-1]}:$portnum_target/d' /rw/config/rc.local" qvm-run -q -u root "${my_netvms[$i]}" -- "sed -i '/nft insert rule filter QBS-FORWARD meta iifname $iface ip daddr ${my_ips[$i-1]} $proto dport $portnum_target ct state new accept/d' /rw/config/rc.local" fi fi ((i--)) done local found found="$( qvm-run -p -q -u root "${my_netvms[$i]}" -- nft list table nat 2>/dev/null )" if test -z "$found"; then qvm-run -q -u root "${my_netvms[$i]}" " iptables -D INPUT -p $proto --dport $external_portnum -j ACCEPT" else handle=$( get_handle "${my_netvms[$i]}" filter "dport $portnum " 1 ) qvm-run -q -u root "${my_netvms[$i]}" -- nft delete rule filter INPUT handle "$handle" fi exit } list(){ return } ## Defaults auto=0 permanent=0 ## Get options optstring=":hap" while getopts ${optstring} option ; do case $option in h) usage;; a) auto=1;; p) permanent=1;; ?) usage;; esac done shift $((OPTIND -1)) ## Check inputs test "$#" -lt 4 && usage if ! qvm-check -q "$2" 2>/dev/null; then echo "$2 is not the name of any qube" exit 1 fi qube_name="$2" if test "$3" != "tcp" && test "$3" != "udp"; then echo "Specify tcp or udp" exit fi proto="$3" portnum="$(check_port "$3" "$4")" if [ $# -eq 5 ]; then external_portnum="$(check_port "$3" "$5")" else external_portnum=$portnum fi ## Get all netvms declare -a netvms declare -a ips declare -a external_ips hop=0 # shellcheck disable=SC2004 netvms[${hop}]="$qube_name" IFS='|' read -r netvms[$hop+1] ips[$hop] <<< "$(qvm-ls "$qube_name" --raw-data -O netvm,IP)" while [ "${netvms[hop+1]}" != "-" ] do ((hop++)) IFS='|' read -r netvms[$hop+1] ips[$hop] <<< "$(qvm-ls "${netvms[$hop]}" --raw-data -O netvm,IP)" done if test "$1" = "delete"; then teardown netvms[@] ips[@] elif test "$1" = "add"; then if [ "$hop" -eq 0 ]; then echo "$qube_name is not network connected" echo "Cannot set up a tunnel" exit fi # Check last hop has external IP address readarray -t external_ips < <( qvm-run -p "${netvms[$hop]}" "ip -4 -o a|grep -wv 'lo\|vif[0-9]*.*'"|awk '{print $2,$4}') #readarray -t external_ips < <( qvm-run -p ${netvms[$hop]} "ip -4 -o a|grep -wv 'vif[0-9]'"|awk '{print $2,$4}') num_ifs=${#external_ips[@]} if [ "$num_ifs" -eq 1 ]; then interface=0 elif [ $auto -eq 1 ]; then interface=0 elif [ "$num_ifs" -gt 1 ]; then echo "${netvms[$hop]} has more than 1 external interface" echo "Which one do you want to use?" for i in $(seq "$num_ifs"); do echo "$i. ${external_ips[$i-1]}" done read -r interface if ! [ "$interface" -eq "$interface" ] 2> /dev/null; then echo "No such interface" exit elif [ "$interface" -gt "$num_ifs" ] || [ "$interface" -lt 1 ]; then echo "No such interface" exit fi ((interface--)) else echo "${netvms[$hop]} does not have an external interface" echo "Cannot set up a tunnel" exit fi external_ip="${external_ips[$interface]}" external_iface="${external_ip%[[:space:]]*}" ip="${external_ip#*[0-9]}" ip="${ip%%/*}" # shellcheck disable=SC2004,SC2034 ips[$hop]="$ip" # Create tunnel found="$(qvm-run -p -q -u root "$qube_name" -- nft list table nat 2>/dev/null)" if test -z "$found"; then found=$(qvm-run -p -u root "$qube_name" "iptables -L -nv | grep -c '.*ACCEPT.*$proto dpt:$portnum' ") if [ "$found" -gt 0 ]; then echo "Input rule in $qube_name already exists" echo "Please check configuration - exiting now." exit else qvm-run -q -u root "$qube_name" "iptables -I INPUT -p $proto --dport $portnum -j ACCEPT " fi else if qvm-run -q -u root "$qube_name" "nft list table filter | grep '$proto dport $portnum accept' " then echo "Input rule in $qube_name already exists" echo "Please check configuration - exiting now." exit else handle="$(get_handle "$qube_name" filter related,established 1)" qvm-run -q -u root "$qube_name" -- nft add rule filter INPUT position "$handle" iifname eth0 "$proto" dport "$portnum" accept fi fi if ! tunnel netvms[@] ips[@]; then teardown netvms[@] ips[@] fi else usage fi