mirror of
https://github.com/ben-grande/qusal.git
synced 2025-02-24 00:39:50 -05:00
312 lines
11 KiB
Bash
312 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# SPDX-FileCopyrightText: 2022 unman <unman@thirdeyesecurity.org>
|
|
# SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. <ben.grande.b@gmail.com>
|
|
#
|
|
# 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 <<HERE
|
|
Usage: ${me} [-h|a|p] [add|delete] [target] [tcp|udp] [port number|service] [external port]
|
|
|
|
Options:
|
|
h print this help
|
|
a auto mode, a port will be opened on the first external interface
|
|
p permanent rules, takes effect in each qube start up
|
|
Action add, delete
|
|
Protocol tcp, udp
|
|
Target Port port number or service name (e.g. ssh)
|
|
External Port port number or service name (e.g. ssh) (default: target port)
|
|
|
|
Example:
|
|
${me} OPTIONS ACTION TARGET_QUBE PROTOCOL TARGET_PORT EXTERNAL_PORT
|
|
${me} add QUBE tcp 80 80
|
|
${me} add QUBE tcp ssh ssh
|
|
${me} delete QUBE tcp https https
|
|
|
|
DO NOT use this script for qubes behind a Tor or VPN proxy.
|
|
At a minimum you risk breaking the security of those proxies.
|
|
HERE
|
|
exit 1
|
|
}
|
|
|
|
|
|
## Check input port
|
|
check_port(){
|
|
if test "$2" != "$2";then
|
|
status=1
|
|
else
|
|
if test "$2" -lt 65536; then
|
|
status=0
|
|
portnum="$2"
|
|
else
|
|
status=1
|
|
fi
|
|
fi
|
|
if [ "$status" -ne 0 ]; then
|
|
if ! grep -q -w "^$2 " /etc/services; then
|
|
echo "Specify usable port number or service name"
|
|
exit 1
|
|
else
|
|
portnum="$(getent services "$2" | awk '{split($2,a,"/");print a[1]}')"
|
|
if test -z "$portnum"; then
|
|
echo "Specify usable port number or service name"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
echo "$portnum"
|
|
}
|
|
|
|
|
|
get_handle(){
|
|
local my_handle
|
|
my_handle="$(qvm-run -q -u root -p "$1" -- "nft -a list table $2 | awk 'BEGIN{c=0} /$3/{c++; if (c==$4) print \$NF}'")"
|
|
echo "$my_handle"
|
|
}
|
|
|
|
|
|
## Tunnel through netvms
|
|
tunnel(){
|
|
declare -a my_netvms=("${!1}")
|
|
declare -a my_ips=("${!2}")
|
|
declare -i numhops
|
|
numhops="${#my_ips[@]}"
|
|
lasthop=$((numhops-1))
|
|
local i=1
|
|
iface="eth0"
|
|
if qvm-run -q -u root "${my_netvms[$lasthop]}" " nft list table nat|grep ' $proto dport $portnum dnat to ${my_ips[$numhops-1]}'"
|
|
then
|
|
echo "Are rules already set?"
|
|
exit 1
|
|
fi
|
|
while test "$i" != "$numhops"; do
|
|
if test "$i" = "1"; then
|
|
portnum_used=$external_portnum
|
|
portnum_target=$portnum
|
|
else
|
|
portnum_used=$external_portnum
|
|
portnum_target=$external_portnum
|
|
fi
|
|
echo "${my_netvms[$i]} $portnum_used"
|
|
if [ $i -eq $lasthop ]; then
|
|
iface=$external_iface
|
|
fi
|
|
# Is it nft or iptables?
|
|
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 -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
|