✨ initial codebase
This commit is contained in:
parent
ffef501736
commit
146c1d5c3a
36
lib/color
Normal file
36
lib/color
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
## EMPTY DEFAULTS ##
|
||||||
|
color_bold=""
|
||||||
|
color_underline=""
|
||||||
|
color_standout=""
|
||||||
|
color_normal=""
|
||||||
|
color_black=""
|
||||||
|
color_red=""
|
||||||
|
color_green=""
|
||||||
|
color_yellow=""
|
||||||
|
color_blue=""
|
||||||
|
color_magenta=""
|
||||||
|
color_cyan=""
|
||||||
|
color_white=""
|
||||||
|
|
||||||
|
|
||||||
|
## ONLY IF PRINTING TO A TERMINAL ##
|
||||||
|
if test -t 1; then
|
||||||
|
# check count of supported colors
|
||||||
|
color_count=$(tput colors)
|
||||||
|
if [[ ! -z "$color_count" && "$color_count" -ge 8 ]]; then
|
||||||
|
color_bold="$(tput bold)"
|
||||||
|
color_underline="$(tput smul)"
|
||||||
|
color_standout="$(tput smso)"
|
||||||
|
color_normal="$(tput sgr0)"
|
||||||
|
color_black="$(tput setaf 0)"
|
||||||
|
color_red="$(tput setaf 1)"
|
||||||
|
color_green="$(tput setaf 2)"
|
||||||
|
color_yellow="$(tput setaf 3)"
|
||||||
|
color_blue="$(tput setaf 4)"
|
||||||
|
color_magenta="$(tput setaf 5)"
|
||||||
|
color_cyan="$(tput setaf 6)"
|
||||||
|
color_white="$(tput setaf 7)"
|
||||||
|
fi
|
||||||
|
fi
|
40
lib/log
Normal file
40
lib/log
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Log a line with debug level.
|
||||||
|
#
|
||||||
|
log_debug(){
|
||||||
|
echo -ne "${color_green}[DEBUG]${color_normal} " 1>&2
|
||||||
|
echo "$1" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Log a line with info level.
|
||||||
|
#
|
||||||
|
log_info(){
|
||||||
|
echo -ne "${color_blue}[INFO ]${color_normal} " 1>&2
|
||||||
|
echo "$1" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Log a line with warning level.
|
||||||
|
#
|
||||||
|
log_warn(){
|
||||||
|
echo -ne "${color_yellow}[WARN ]${color_normal} " 1>&2
|
||||||
|
echo "$1" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Log a line with error level and exit.
|
||||||
|
#
|
||||||
|
log_error(){
|
||||||
|
echo -ne "${color_red}[ERROR]${color_normal} " 1>&2
|
||||||
|
echo "$1" 1>&2
|
||||||
|
exit 1
|
||||||
|
}
|
10
lib/sudocheck
Normal file
10
lib/sudocheck
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure we have sudo permissions.
|
||||||
|
#
|
||||||
|
sudocheck(){
|
||||||
|
if [[ "$EUID" -ne 0 ]]; then
|
||||||
|
log_error "This script needs sudo permissions"
|
||||||
|
fi
|
||||||
|
}
|
592
xynat
Executable file
592
xynat
Executable file
@ -0,0 +1,592 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
## INIT ##
|
||||||
|
# get script base directory
|
||||||
|
SCRIPT_PATH=$(readlink -f "${BASH_SOURCE[0]}")
|
||||||
|
SCRIPT_DIR=$(dirname -- "$SCRIPT_PATH")
|
||||||
|
|
||||||
|
# store arguments globally
|
||||||
|
ARG_LIST=("$@")
|
||||||
|
|
||||||
|
# get version number
|
||||||
|
VERSION=$(cat ${SCRIPT_DIR}/VERSION | xargs)
|
||||||
|
|
||||||
|
# load libraries
|
||||||
|
source "${SCRIPT_DIR}/lib/color"
|
||||||
|
source "${SCRIPT_DIR}/lib/log"
|
||||||
|
#source "${SCRIPT_DIR}/lib/toolcheck"
|
||||||
|
source "${SCRIPT_DIR}/lib/sudocheck"
|
||||||
|
|
||||||
|
# regex definitions
|
||||||
|
regex_ip_address="^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}$"
|
||||||
|
regex_ip_or_net="^([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||||
|
regex_local_address="^(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.).*$"
|
||||||
|
|
||||||
|
# subnet list
|
||||||
|
subnet_list_special_use="0.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 192.0.0.0/24 192.0.0.0/24 192.88.99.0/24 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/4 233.252.0.0/24 240.0.0.0/4 255.255.255.255/32"
|
||||||
|
subnet_list_local="10.0.0.0/8 172.16.0.0/12 192.168.0.0/16"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Remove a firewall.
|
||||||
|
#
|
||||||
|
xynat_fw_remove(){
|
||||||
|
## REMOVE CHAINS ##
|
||||||
|
# flush chains
|
||||||
|
iptables -F "${chain_id}_IN" 2> /dev/null || true
|
||||||
|
iptables -F "${chain_id}_OUT" 2> /dev/null || true
|
||||||
|
iptables -F "${chain_id}_FWI" 2> /dev/null || true
|
||||||
|
iptables -F "${chain_id}_FWO" 2> /dev/null || true
|
||||||
|
iptables -t nat -F "${chain_id}_NI" 2> /dev/null || true
|
||||||
|
iptables -t nat -F "${chain_id}_NO" 2> /dev/null || true
|
||||||
|
|
||||||
|
# remove ipv4 references
|
||||||
|
iptables -D INPUT -i "$arg_iface" -j "${chain_id}_IN" 2> /dev/null || true
|
||||||
|
iptables -D OUTPUT -o "$arg_iface" -j "${chain_id}_OUT" 2> /dev/null || true
|
||||||
|
iptables -D FORWARD -i "$arg_iface" -j "${chain_id}_FWI" 2> /dev/null || true
|
||||||
|
iptables -D FORWARD -o "$arg_iface" -j "${chain_id}_FWO" 2> /dev/null || true
|
||||||
|
iptables -t nat -D PREROUTING -j "${chain_id}_NI" 2> /dev/null || true
|
||||||
|
iptables -t nat -D POSTROUTING -j "${chain_id}_NO" 2> /dev/null || true
|
||||||
|
|
||||||
|
# remove ipv6 references
|
||||||
|
ip6tables -D INPUT -i "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
ip6tables -D OUTPUT -o "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
ip6tables -D FORWARD -i "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
ip6tables -D FORWARD -o "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
|
||||||
|
# delete chains
|
||||||
|
iptables -X "${chain_id}_IN" 2> /dev/null || true
|
||||||
|
iptables -X "${chain_id}_OUT" 2> /dev/null || true
|
||||||
|
iptables -X "${chain_id}_FWI" 2> /dev/null || true
|
||||||
|
iptables -X "${chain_id}_FWO" 2> /dev/null || true
|
||||||
|
iptables -t nat -X "${chain_id}_NI" 2> /dev/null || true
|
||||||
|
iptables -t nat -X "${chain_id}_NO" 2> /dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Add a firewall.
|
||||||
|
#
|
||||||
|
xynat_fw_add(){
|
||||||
|
## RESET ##
|
||||||
|
# remove references
|
||||||
|
iptables -D INPUT -i "$arg_iface" -j "${chain_id}_IN" 2> /dev/null || true
|
||||||
|
iptables -D OUTPUT -o "$arg_iface" -j "${chain_id}_OUT" 2> /dev/null || true
|
||||||
|
iptables -D FORWARD -i "$arg_iface" -j "${chain_id}_FWI" 2> /dev/null || true
|
||||||
|
iptables -D FORWARD -o "$arg_iface" -j "${chain_id}_FWO" 2> /dev/null || true
|
||||||
|
iptables -t nat -D PREROUTING -j "${chain_id}_NI" 2> /dev/null || true
|
||||||
|
iptables -t nat -D POSTROUTING -j "${chain_id}_NO" 2> /dev/null || true
|
||||||
|
|
||||||
|
# remove ipv6 references
|
||||||
|
ip6tables -D INPUT -i "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
ip6tables -D OUTPUT -o "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
ip6tables -D FORWARD -i "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
ip6tables -D FORWARD -o "$arg_iface" -j REJECT --reject-with icmp6-no-route 2> /dev/null || true
|
||||||
|
|
||||||
|
|
||||||
|
## ADD CHAINS ##
|
||||||
|
# create chains
|
||||||
|
iptables -N "${chain_id}_IN" 2> /dev/null || true
|
||||||
|
iptables -N "${chain_id}_OUT" 2> /dev/null || true
|
||||||
|
iptables -N "${chain_id}_FWI" 2> /dev/null || true
|
||||||
|
iptables -N "${chain_id}_FWO" 2> /dev/null || true
|
||||||
|
iptables -t nat -N "${chain_id}_NI" 2> /dev/null || true
|
||||||
|
iptables -t nat -N "${chain_id}_NO" 2> /dev/null || true
|
||||||
|
|
||||||
|
# insert references
|
||||||
|
iptables -I INPUT -i "$arg_iface" -j "${chain_id}_IN"
|
||||||
|
iptables -I OUTPUT -o "$arg_iface" -j "${chain_id}_OUT"
|
||||||
|
iptables -I FORWARD -i "$arg_iface" -j "${chain_id}_FWI"
|
||||||
|
iptables -I FORWARD -o "$arg_iface" -j "${chain_id}_FWO"
|
||||||
|
iptables -t nat -I PREROUTING -j "${chain_id}_NI"
|
||||||
|
iptables -t nat -I POSTROUTING -j "${chain_id}_NO"
|
||||||
|
|
||||||
|
# block ipv6
|
||||||
|
ip6tables -I INPUT -i "$arg_iface" -j REJECT --reject-with icmp6-no-route
|
||||||
|
ip6tables -I OUTPUT -o "$arg_iface" -j REJECT --reject-with icmp6-no-route
|
||||||
|
ip6tables -I FORWARD -i "$arg_iface" -j REJECT --reject-with icmp6-no-route
|
||||||
|
ip6tables -I FORWARD -o "$arg_iface" -j REJECT --reject-with icmp6-no-route
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update all rulesets.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update(){
|
||||||
|
xynat_ruleset_update_in
|
||||||
|
xynat_ruleset_update_out
|
||||||
|
xynat_ruleset_update_fwi
|
||||||
|
xynat_ruleset_update_fwo
|
||||||
|
xynat_ruleset_update_ni
|
||||||
|
xynat_ruleset_update_no
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update ruleset: `in`.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update_in(){
|
||||||
|
## FLUSH CHAIN ##
|
||||||
|
iptables -F "${chain_id}_IN"
|
||||||
|
|
||||||
|
|
||||||
|
## ADD RULES ##
|
||||||
|
# reject all packets
|
||||||
|
iptables -A "${chain_id}_IN" -j REJECT --reject-with icmp-host-unreachable
|
||||||
|
|
||||||
|
# default: return
|
||||||
|
iptables -A "${chain_id}_IN" -j RETURN
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update ruleset: `out`.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update_out(){
|
||||||
|
## FLUSH CHAIN ##
|
||||||
|
iptables -F "${chain_id}_OUT"
|
||||||
|
|
||||||
|
|
||||||
|
## ADD RULES ##
|
||||||
|
# allow icmp messages
|
||||||
|
iptables -A "${chain_id}_OUT" -p icmp -j RETURN
|
||||||
|
|
||||||
|
# reject all other packets
|
||||||
|
iptables -A "${chain_id}_OUT" -j REJECT --reject-with icmp-host-unreachable
|
||||||
|
|
||||||
|
# default: return
|
||||||
|
iptables -A "${chain_id}_OUT" -j RETURN
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update ruleset: `fwi`.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update_fwi(){
|
||||||
|
## FLUSH CHAIN ##
|
||||||
|
iptables -F "${chain_id}_FWI"
|
||||||
|
|
||||||
|
|
||||||
|
## ADD RULES ##
|
||||||
|
# enforce correct vm address
|
||||||
|
iptables -A "${chain_id}_FWI" ! -s "$arg_vm_address" -j REJECT --reject-with icmp-admin-prohibited
|
||||||
|
|
||||||
|
# ignore allowed local addresses
|
||||||
|
for a in ${arg_allow:-""}; do
|
||||||
|
iptables -A "${chain_id}_FWI" -d "$a" -j RETURN
|
||||||
|
done
|
||||||
|
|
||||||
|
# ignore allowed incomming local addresses
|
||||||
|
for a in ${arg_allow_in:-""}; do
|
||||||
|
iptables -A "${chain_id}_FWI" -d "$a" -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
|
||||||
|
done
|
||||||
|
|
||||||
|
# reject filtered packets
|
||||||
|
for a in $subnet_list_special_use $subnet_list_local; do
|
||||||
|
iptables -A "${chain_id}_FWI" -d "$a" -j REJECT --reject-with icmp-net-unreachable
|
||||||
|
done
|
||||||
|
|
||||||
|
# default: return
|
||||||
|
iptables -A "${chain_id}_FWI" -j RETURN
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update ruleset: `fwo`.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update_fwo(){
|
||||||
|
## FLUSH CHAIN ##
|
||||||
|
iptables -F "${chain_id}_FWO"
|
||||||
|
|
||||||
|
|
||||||
|
## ADD RULES ##
|
||||||
|
# enforce correct vm address
|
||||||
|
iptables -A "${chain_id}_FWO" ! -d "$arg_vm_address" -j REJECT --reject-with icmp-admin-prohibited
|
||||||
|
|
||||||
|
# ignore allowed local addresses
|
||||||
|
for a in ${arg_allow:-""} ${arg_allow_in:-""}; do
|
||||||
|
iptables -A "${chain_id}_FWO" -s "$a" -j RETURN
|
||||||
|
done
|
||||||
|
|
||||||
|
# reject filtered packets
|
||||||
|
for a in $subnet_list_special_use $subnet_list_local; do
|
||||||
|
iptables -A "${chain_id}_FWO" -s "$a" -j REJECT --reject-with icmp-net-unreachable
|
||||||
|
done
|
||||||
|
|
||||||
|
# default: return
|
||||||
|
iptables -A "${chain_id}_FWO" -j RETURN
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update ruleset: `ni`.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update_ni(){
|
||||||
|
## FLUSH CHAIN ##
|
||||||
|
iptables -t nat -F "${chain_id}_NI"
|
||||||
|
|
||||||
|
|
||||||
|
## ADD RULES ##
|
||||||
|
# filter for vm public ip
|
||||||
|
iptables -t nat -A "${chain_id}_NI" ! -d "$arg_public_ip" -j RETURN # TODO
|
||||||
|
|
||||||
|
# translate destination address (forward to vm)
|
||||||
|
iptables -t nat -A "${chain_id}_NI" -j DNAT --to-destination "$arg_vm_address"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: Update ruleset: `no`.
|
||||||
|
#
|
||||||
|
xynat_ruleset_update_no(){
|
||||||
|
## FLUSH CHAIN ##
|
||||||
|
iptables -t nat -F "${chain_id}_NO"
|
||||||
|
|
||||||
|
|
||||||
|
## ADD RULES ##
|
||||||
|
# filter for vm ip address
|
||||||
|
iptables -t nat -A "${chain_id}_NO" ! -s "$arg_vm_address" -j RETURN
|
||||||
|
|
||||||
|
# translate source address (forward to internet)
|
||||||
|
iptables -t nat -A "${chain_id}_NO" -j SNAT --to-source "$arg_public_ip" # TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Explain usage of script.
|
||||||
|
#
|
||||||
|
xynat_help(){
|
||||||
|
echo "XYNat v${VERSION}"
|
||||||
|
echo
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo
|
||||||
|
echo "Options:"
|
||||||
|
echo " -a, --add - Add new firewall (default, fallback: MODE=start)"
|
||||||
|
echo " -r, --remove - Remove existing firewall (fallback: MODE=stop)"
|
||||||
|
echo
|
||||||
|
echo " -i, --iface=iface - Interface name for virtual switch (required, fallback: IFACE)"
|
||||||
|
echo " -s, --vm-address=ip - IP address of virtual machine (required)"
|
||||||
|
echo " -p, --public-ip=ip - IP address to use for outgoing traffic and DNAT"
|
||||||
|
echo
|
||||||
|
echo " -w, --allow=ip-or-net - Allow address(es) for incomming and outgoing connections (multi-use allowed)"
|
||||||
|
echo " -x, --allow-in=ip-or-net - Allow address(es) for incomming connections only (multi-use allowed)"
|
||||||
|
echo " -y, --allow-host - Allow local host for incomming and outgoing connections"
|
||||||
|
echo " -z, --allow-host-in - Allow local host for incomming connections only"
|
||||||
|
echo
|
||||||
|
echo " -h, --help - Display this help message and exit"
|
||||||
|
echo " -v, --version - Display version information and exit"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Display version information.
|
||||||
|
#
|
||||||
|
xynat_version(){
|
||||||
|
echo "XYNat v${VERSION}"
|
||||||
|
echo "(c) 2024 DrMaxNix"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# VALIDATOR: `iface`.
|
||||||
|
#
|
||||||
|
xynat_validate_iface(){
|
||||||
|
## CHECK IF VALID NAME ##
|
||||||
|
local iface_list="$(xynat_iface_list)"
|
||||||
|
local found="no"
|
||||||
|
for i in $iface_list; do
|
||||||
|
if [[ "$i" == "$1" ]]; then
|
||||||
|
found="yes"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$found" != "yes" ]]; then
|
||||||
|
log_warn "Unknown iface name '$1'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# VALIDATOR: `vm-address`.
|
||||||
|
#
|
||||||
|
xynat_validate_vm_address(){
|
||||||
|
## CHECK FORMAT ##
|
||||||
|
if [[ ! "$1" =~ $regex_ip_address ]]; then
|
||||||
|
log_error "Malformed vm-address '$1'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# VALIDATOR: `public-ip`.
|
||||||
|
#
|
||||||
|
xynat_validate_public_ip(){
|
||||||
|
## CHECK FORMAT ##
|
||||||
|
if [[ ! "$1" =~ $regex_ip_address ]]; then
|
||||||
|
log_error "Malformed vm-address '$1'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
## CHECK IP IFACE ASSIGNMENT ##
|
||||||
|
# make sure this is not a primary ip address
|
||||||
|
local primary_ip_list="$(xynat_primary_ip_list)"
|
||||||
|
for p in $primary_ip_list; do
|
||||||
|
if [[ "$p" == "$1" ]]; then
|
||||||
|
log_error "Virtual machine's public-ip is in use as primary host address"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# check whether it exists as a secondary address
|
||||||
|
local secondary_ip_list="$(xynat_secondary_ip_list)"
|
||||||
|
local found="no"
|
||||||
|
for s in $secondary_ip_list; do
|
||||||
|
if [[ "$s" == "$1" ]]; then
|
||||||
|
found="yes"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$found" != "yes" ]]; then
|
||||||
|
log_warn "Virtual machine's public-ip '$1' is not assigned to any host interface"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# VALIDATOR: `allow`.
|
||||||
|
#
|
||||||
|
xynat_validate_allow(){
|
||||||
|
for a in $1; do
|
||||||
|
## VALIDATE SYNTAX ##
|
||||||
|
if [[ ! "$a" =~ $regex_ip_or_net ]]; then
|
||||||
|
log_error "Malformed ip address or subnet in allowlist: '$a'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
## CHECK FOR KNOWN LOCAL PREFIX ##
|
||||||
|
if [[ ! "$a" =~ $regex_local_address ]]; then
|
||||||
|
log_warn "Allowlist entry does not look like a local address: '$a'"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: List names of all interfaces.
|
||||||
|
#
|
||||||
|
xynat_iface_list(){
|
||||||
|
ip link show | grep -Pe "^[0-9]+: (.*): <.*$" | sed -E "s/^[0-9]+: (.*): <.*$/\1/g"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: List all primary ip addresses of the host.
|
||||||
|
#
|
||||||
|
xynat_primary_ip_list(){
|
||||||
|
ip address show | grep -Pe "^\s+inet ([0-9\.]+)\/[0-9]+.*$" | grep -v "\ssecondary\s" | sed -E "s/^\s+inet ([0-9\.]+)\/[0-9]+.*$/\1/g"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# HELPER: List all secondary ip addresses of the host.
|
||||||
|
#
|
||||||
|
xynat_secondary_ip_list(){
|
||||||
|
ip address show | grep -Pe "^\s+inet ([0-9\.]+)\/[0-9]+.*$" | grep "\ssecondary\s" | sed -E "s/^\s+inet ([0-9\.]+)\/[0-9]+.*$/\1/g"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## PARSE ARGUMENTS ##
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
# help
|
||||||
|
-h|--help)
|
||||||
|
xynat_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
# version
|
||||||
|
-v|--version)
|
||||||
|
xynat_version
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
# add
|
||||||
|
-a|--add)
|
||||||
|
if [[ "${arg_mode:+x}" ]]; then log_error "Cannot set mode twice; already set to '$arg_mode'"; fi
|
||||||
|
arg_mode="add"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# remove
|
||||||
|
-r|--remove)
|
||||||
|
if [[ "${arg_mode:+x}" ]]; then log_error "Cannot set mode twice; already set to '$arg_mode'"; fi
|
||||||
|
arg_mode="remove"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# iface
|
||||||
|
-i|--iface|-i=*|--iface=*)
|
||||||
|
if [[ "${arg_iface:+x}" ]]; then log_error "Cannot set iface twice; already set to '$arg_iface'"; fi
|
||||||
|
if [[ "$1" =~ ^[a-z\-]+=(.+)$ ]]; then
|
||||||
|
arg_iface=$(echo $1 | sed -E "s/^[a-z\-]+=(.*)$/\1/g")
|
||||||
|
shift; continue; fi
|
||||||
|
|
||||||
|
shift; if [[ $# -gt 0 ]]; then
|
||||||
|
arg_iface="$1"
|
||||||
|
else log_error "Value expected for parameter 'iface'"; fi; shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# vm-address
|
||||||
|
-s|--vm-address|-s=*|--vm-address=*)
|
||||||
|
if [[ "${arg_vm_address:+x}" ]]; then log_error "Cannot set vm-address twice; already set to '$arg_vm_address'"; fi
|
||||||
|
if [[ "$1" =~ ^[a-z\-]+=(.+)$ ]]; then
|
||||||
|
arg_vm_address=$(echo $1 | sed -E "s/^[a-z\-]+=(.*)$/\1/g")
|
||||||
|
shift; continue; fi
|
||||||
|
|
||||||
|
shift; if [[ $# -gt 0 ]]; then
|
||||||
|
arg_vm_address="$1"
|
||||||
|
else log_error "Value expected for parameter 'vm-address'"; fi; shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# public-ip
|
||||||
|
-p|--public-ip|-p=*|--public-ip=*)
|
||||||
|
if [[ "${arg_public_ip:+x}" ]]; then log_error "Cannot set public-ip twice; already set to '$arg_public_ip'"; fi
|
||||||
|
if [[ "$1" =~ ^[a-z\-]+=(.+)$ ]]; then
|
||||||
|
arg_public_ip=$(echo $1 | sed -E "s/^[a-z\-]+=(.*)$/\1/g")
|
||||||
|
shift; continue; fi
|
||||||
|
|
||||||
|
shift; if [[ $# -gt 0 ]]; then
|
||||||
|
arg_public_ip="$1"
|
||||||
|
else log_error "Value expected for parameter 'public-ip'"; fi; shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# allow
|
||||||
|
-w|--allow|-w=*|--allow=*)
|
||||||
|
if [[ "$1" =~ ^[a-z\-]+=(.+)$ ]]; then
|
||||||
|
arg_allow=(${arg_allow[@]:-""} "$(echo $1 | sed -E "s/^[a-z\-]+=(.*)$/\1/g")")
|
||||||
|
shift; continue; fi
|
||||||
|
|
||||||
|
shift; if [[ $# -gt 0 ]]; then
|
||||||
|
arg_allow=(${arg_allow[@]:-""} "$1")
|
||||||
|
else log_error "Value expected for parameter 'allow'"; fi; shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# allow-in
|
||||||
|
-x|--allow-in|-x=*|--allow-in=*)
|
||||||
|
if [[ "$1" =~ ^[a-z\-]+=(.+)$ ]]; then
|
||||||
|
arg_allow_in=(${arg_allow_in[@]:-""} "$(echo $1 | sed -E "s/^[a-z\-]+=(.*)$/\1/g")")
|
||||||
|
shift; continue; fi
|
||||||
|
|
||||||
|
shift; if [[ $# -gt 0 ]]; then
|
||||||
|
arg_allow_in=(${arg_allow_in[@]:-""} "$1")
|
||||||
|
else log_error "Value expected for parameter 'allow-in'"; fi; shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# allow-host
|
||||||
|
-y|--allow-host|-y=*|--allow-host=*)
|
||||||
|
if [[ "${arg_allow_host_mode:+x}" ]]; then log_error "Cannot set allow-host mode twice; already set to '$arg_allow_host_mode'"; fi
|
||||||
|
arg_allow_host_mode="in_out"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# allow-host-in
|
||||||
|
-z|--allow-host-in|-z=*|--allow-host-in=*)
|
||||||
|
if [[ "${arg_allow_host_mode:+x}" ]]; then log_error "Cannot set allow-host mode twice; already set to '$arg_allow_host_mode'"; fi
|
||||||
|
arg_allow_host_mode="in"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
# unknown
|
||||||
|
*)
|
||||||
|
log_error "Unexpected argument '$1'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
## MAKE SURE WE HAVE SUDO PERMISSION ##
|
||||||
|
sudocheck
|
||||||
|
|
||||||
|
|
||||||
|
## ARGUMENT FALLBACKS AND VALIDATION ##
|
||||||
|
# mode
|
||||||
|
if [[ -z "${arg_mode:+x}" ]]; then
|
||||||
|
case "${MODE:-""}" in
|
||||||
|
"stop") arg_mode="remove";;
|
||||||
|
"start"|*) arg_mode="add";;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# iface
|
||||||
|
if [[ -z "${arg_iface:+x}" ]]; then
|
||||||
|
if [[ -z "${IFACE:+x}" ]]; then log_error "Missing required argument 'iface'; See '$0 --help' for usage information"; fi
|
||||||
|
arg_iface="$IFACE"
|
||||||
|
fi
|
||||||
|
xynat_validate_iface "$arg_iface"
|
||||||
|
|
||||||
|
# vm-address
|
||||||
|
if [[ -z "${arg_vm_address:+x}" ]]; then
|
||||||
|
log_error "Missing required argument 'vm-address'; See '$0 --help' for usage information"
|
||||||
|
fi
|
||||||
|
xynat_validate_vm_address "$arg_vm_address"
|
||||||
|
|
||||||
|
# public-ip
|
||||||
|
if [[ "${arg_public_ip:+x}" ]]; then
|
||||||
|
xynat_validate_public_ip "$arg_public_ip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# allow
|
||||||
|
xynat_validate_allow "${arg_allow[*]:-""}"
|
||||||
|
|
||||||
|
# allow-in
|
||||||
|
xynat_validate_allow "${arg_allow_in[*]:-""}"
|
||||||
|
|
||||||
|
|
||||||
|
## BUILD MISSING VALUES ##
|
||||||
|
# firewall chain id
|
||||||
|
chain_id="XYNAT_$(echo "$arg_iface" | tr a-z A-Z | tr - _)"
|
||||||
|
|
||||||
|
|
||||||
|
## EXECUTE ACTION ##
|
||||||
|
case "$arg_mode" in
|
||||||
|
# add
|
||||||
|
"add")
|
||||||
|
xynat_fw_add
|
||||||
|
xynat_ruleset_update
|
||||||
|
;;
|
||||||
|
|
||||||
|
# remove
|
||||||
|
"remove")
|
||||||
|
xynat_fw_remove
|
||||||
|
;;
|
||||||
|
|
||||||
|
# should not happen
|
||||||
|
*)
|
||||||
|
log_error "Invalid mode '$arg_mode'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Refine icmp filter to only allow related packets
|
||||||
|
# TODO: Wire up public-ip being empty (SNAT/DNAT)
|
||||||
|
# TODO: Wire up allow-host and allow-host-in
|
Loading…
Reference in New Issue
Block a user