From 4fde9d5b17544d1d0d3b7d4c5577240a72009e8a Mon Sep 17 00:00:00 2001 From: binhex Date: Mon, 21 Sep 2020 13:29:41 +0100 Subject: [PATCH] add in multi line remote support --- build/root/install.sh | 86 +++++++++++++++++++++++-------------------- run/root/iptable.sh | 32 ++++++++++++++-- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/build/root/install.sh b/build/root/install.sh index 5e96e2b..beb48ec 100644 --- a/build/root/install.sh +++ b/build/root/install.sh @@ -181,60 +181,66 @@ if [[ $VPN_ENABLED == "yes" ]]; then # convert CRLF (windows) to LF (unix) for ovpn /usr/local/bin/dos2unix.sh "${VPN_CONFIG}" - # get first matching 'remote' line in ovpn - vpn_remote_line=$(cat "${VPN_CONFIG}" | grep -P -o -m 1 '^(\s+)?remote\s.*' || true) + # get all remote lines in ovpn file and save comma separated + vpn_remote_line=$(cat "${VPN_CONFIG}" | grep -P -o '(?<=remote\s).*' | paste -s -d, - || true) - if [ -n "${vpn_remote_line}" ]; then + if [[ -n "${vpn_remote_line}" ]]; then - # remove all remote lines as we cannot cope with multi remote lines - sed -i -E '/^(\s+)?remote\s.*/d' "${VPN_CONFIG}" + # split comma separated string into list from vpn_remote_line variable + IFS=',' read -ra vpn_remote_line_list <<< "${vpn_remote_line}" - # if remote line contains comments then remove - vpn_remote_line=$(echo "${vpn_remote_line}" | sed -r 's~\s?+#.*$~~g') + # process each remote line from ovpn file + for vpn_remote_line_item in "${vpn_remote_line_list[@]}"; do - # if remote line contains old format 'tcp' then replace with newer 'tcp-client' format - vpn_remote_line=$(echo "${vpn_remote_line}" | sed "s/tcp$/tcp-client/g") + # if remote line contains comments then remove + vpn_remote_line_item=$(echo "${vpn_remote_line_item}" | sed -r 's~\s?+#.*$~~g') - # write the single remote line back to the ovpn file on line 1 - sed -i -e "1i${vpn_remote_line}" "${VPN_CONFIG}" + vpn_remote_server_cut=$(echo "${vpn_remote_line_item}" | cut -d " " -f1 || true) - echo "[info] VPN remote line defined as '${vpn_remote_line}'" | ts '%Y-%m-%d %H:%M:%.S' + if [[ -z "${vpn_remote_server_cut}" ]]; then + echo "[warn] VPN configuration file ${VPN_CONFIG} remote line is missing or malformed, skipping to next remote line..." | ts '%Y-%m-%d %H:%M:%.S' + continue + fi - else + vpn_remote_port_cut=$(echo "${vpn_remote_line_item}" | cut -d " " -f2 || true) - echo "[crit] VPN configuration file ${VPN_CONFIG} does not contain 'remote' line, showing contents of file before exit..." | ts '%Y-%m-%d %H:%M:%.S' - cat "${VPN_CONFIG}" && exit 1 + if [[ -z "${vpn_remote_port_cut}" ]]; then + echo "[warn] VPN configuration file ${VPN_CONFIG} remote port is missing or malformed, assuming port '1194'" | ts '%Y-%m-%d %H:%M:%.S' + vpn_remote_port_cut="1194" + fi - fi + vpn_remote_protocol_cut=$(cat "${VPN_CONFIG}" | grep -P -o '(?<=proto\s).*' || true) + if [[ -z "${vpn_remote_protocol_cut}" ]]; then + vpn_remote_protocol_cut=$(echo "${vpn_remote_line_item}" | cut -d " " -f3 || true) + if [[ -z "${vpn_remote_protocol_cut}" ]]; then + echo "[warn] VPN configuration file ${VPN_CONFIG} remote protocol is missing or malformed, assuming protocol 'udp'" | ts '%Y-%m-%d %H:%M:%.S' + vpn_remote_protocol_cut="udp" + fi + fi + if [[ "${vpn_remote_protocol_cut}" == "tcp" ]]; then + # if remote line contains old format 'tcp' then replace with newer 'tcp-client' format + vpn_remote_protocol_cut="tcp-client" + fi - export VPN_REMOTE=$(echo "${vpn_remote_line}" | grep -P -o -m 1 '(?<=remote\s)[^\s]+' | sed -e 's~^[ \t]*~~;s~[ \t]*$~~') - if [[ ! -z "${VPN_REMOTE}" ]]; then - echo "[info] VPN_REMOTE defined as '${VPN_REMOTE}'" | ts '%Y-%m-%d %H:%M:%.S' - else - echo "[crit] VPN_REMOTE not found in ${VPN_CONFIG}, exiting..." | ts '%Y-%m-%d %H:%M:%.S' && exit 1 - fi + vpn_remote_server+="${vpn_remote_server_cut}," + vpn_remote_port+="${vpn_remote_port_cut}," + vpn_remote_protocol+="${vpn_remote_protocol_cut}," - export VPN_PORT=$(echo "${vpn_remote_line}" | grep -P -o -m 1 '\d{2,5}(\s?)+(tcp|udp|tcp-client)?$' | grep -P -o -m 1 '\d+' | sed -e 's~^[ \t]*~~;s~[ \t]*$~~') - if [[ ! -z "${VPN_PORT}" ]]; then - echo "[info] VPN_PORT defined as '${VPN_PORT}'" | ts '%Y-%m-%d %H:%M:%.S' - else - echo "[crit] VPN_PORT not found in ${VPN_CONFIG}, exiting..." | ts '%Y-%m-%d %H:%M:%.S' && exit 1 - fi + done - # if 'proto' is old format 'tcp' then forcibly set to newer 'tcp-client' format - sed -i "s/^proto\stcp$/proto tcp-client/g" "${VPN_CONFIG}" + echo "[info] VPN remote server(s) defined as '${vpn_remote_server}'" | ts '%Y-%m-%d %H:%M:%.S' + echo "[info] VPN remote port(s) defined as '${vpn_remote_port}'" | ts '%Y-%m-%d %H:%M:%.S' + echo "[info] VPN remote protocol(s) defined as '${vpn_remote_protocol}'" | ts '%Y-%m-%d %H:%M:%.S' + + export VPN_REMOTE_SERVER="${vpn_remote_server}" + export VPN_REMOTE_PORT="${vpn_remote_port}" + export VPN_REMOTE_PROTOCOL="${vpn_remote_protocol}" - export VPN_PROTOCOL=$(cat "${VPN_CONFIG}" | grep -P -o -m 1 '(?<=^proto\s)[^\r\n]+' | sed -e 's~^[ \t]*~~;s~[ \t]*$~~') - if [[ ! -z "${VPN_PROTOCOL}" ]]; then - echo "[info] VPN_PROTOCOL defined as '${VPN_PROTOCOL}'" | ts '%Y-%m-%d %H:%M:%.S' else - export VPN_PROTOCOL=$(echo "${vpn_remote_line}" | grep -P -o -m 1 'udp|tcp-client|tcp$' | sed -e 's~^[ \t]*~~;s~[ \t]*$~~') - if [[ ! -z "${VPN_PROTOCOL}" ]]; then - echo "[info] VPN_PROTOCOL defined as '${VPN_PROTOCOL}'" | ts '%Y-%m-%d %H:%M:%.S' - else - echo "[warn] VPN_PROTOCOL not found in ${VPN_CONFIG}, assuming udp" | ts '%Y-%m-%d %H:%M:%.S' - export VPN_PROTOCOL="udp" - fi + + echo "[crit] VPN configuration file ${VPN_CONFIG} does not contain 'remote' line, showing contents of file before exit..." | ts '%Y-%m-%d %H:%M:%.S' + cat "${VPN_CONFIG}" && exit 1 + fi VPN_DEVICE_TYPE=$(cat "${VPN_CONFIG}" | grep -P -o -m 1 '(?<=^dev\s)[^\r\n\d]+' | sed -e 's~^[ \t]*~~;s~[ \t]*$~~') diff --git a/run/root/iptable.sh b/run/root/iptable.sh index 9a2e593..b312060 100644 --- a/run/root/iptable.sh +++ b/run/root/iptable.sh @@ -73,6 +73,12 @@ if [[ $iptable_mangle_exit_code == 0 ]]; then fi +# split comma separated string into array from VPN_REMOTE_PROTOCOL env var +IFS=',' read -ra vpn_remote_protocol_list <<< "${VPN_REMOTE_PROTOCOL}" + +# split comma separated string into array from VPN_REMOTE_PORT env var +IFS=',' read -ra vpn_remote_port_list <<< "${VPN_REMOTE_PORT}" + # input iptable rules ### @@ -85,8 +91,17 @@ ip6tables -P INPUT DROP 1>&- 2>&- # accept input to/from docker containers (172.x range is internal dhcp) iptables -A INPUT -s "${docker_network_cidr}" -d "${docker_network_cidr}" -j ACCEPT -# accept input to vpn gateway -iptables -A INPUT -i "${docker_interface}" -p $VPN_PROTOCOL --sport $VPN_PORT -j ACCEPT +# iterate over array and add all remote vpn ports and protocols +for index in "${!vpn_remote_port_list[@]}"; do + + # note grep -e is required to indicate no flags follow to prevent -A from being incorrectly picked up + rule_exists=$(iptables -S | grep -e "-A INPUT -i "${docker_interface}" -p "${vpn_remote_protocol_list[$index]}" -m "${vpn_remote_protocol_list[$index]}" --sport "${vpn_remote_port_list[$index]}" -j ACCEPT") + if [[ -z "${rule_exists}" ]]; then + # accept input to vpn gateway + iptables -A INPUT -i "${docker_interface}" -p "${vpn_remote_protocol_list[$index]}" --sport "${vpn_remote_port_list[$index]}" -j ACCEPT + fi + +done # accept input to qbittorrent port WEBUI_PORT iptables -A INPUT -i "${docker_interface}" -p tcp --dport "${WEBUI_PORT}" -j ACCEPT @@ -160,8 +175,17 @@ ip6tables -P OUTPUT DROP 1>&- 2>&- # accept output to/from docker containers (172.x range is internal dhcp) iptables -A OUTPUT -s "${docker_network_cidr}" -d "${docker_network_cidr}" -j ACCEPT -# accept output from vpn gateway -iptables -A OUTPUT -o "${docker_interface}" -p $VPN_PROTOCOL --dport $VPN_PORT -j ACCEPT +# iterate over array and add all remote vpn ports and protocols +for index in "${!vpn_remote_port_list[@]}"; do + + # note grep -e is required to indicate no flags follow to prevent -A from being incorrectly picked up + rule_exists=$(iptables -S | grep -e "-A OUTPUT -o "${docker_interface}" -p "${vpn_remote_protocol_list[$index]}" -m "${vpn_remote_protocol_list[$index]}" --dport "${vpn_remote_port_list[$index]}" -j ACCEPT") + if [[ -z "${rule_exists}" ]]; then + # accept output from vpn gateway + iptables -A OUTPUT -o "${docker_interface}" -p "${vpn_remote_protocol_list[$index]}" --dport "${vpn_remote_port_list[$index]}" -j ACCEPT + fi + +done # if iptable mangle is available (kernel module) then use mark if [[ $iptable_mangle_exit_code == 0 ]]; then