Files
configure-server/.includes/firewall.sh
2026-04-16 19:46:49 -04:00

299 lines
7.4 KiB
Bash

#!/usr/bin/env bash
sshConfigDropIn="/etc/ssh/sshd_config.d/99-stormux-server.conf"
ufw_installed() {
pacman -Q ufw &> /dev/null
}
ufw_status_output() {
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" ufw status 2>&1
}
ensure_ufw() {
if ufw_installed; then
return 0
fi
if [[ "$(yesno "ufw is not installed. Install it now and continue?")" != "Yes" ]]; then
msgbox "Firewall action cancelled."
return 1
fi
if ! install_package ufw; then
msgbox "Failed to install ufw."
return 1
fi
msgbox "ufw installed."
return 0
}
valid_port() {
local portValue="$1"
[[ "$portValue" =~ ^[0-9]+$ ]] && (( portValue >= 1 && portValue <= 65535 ))
}
valid_port_rule() {
local ruleValue="$1"
if [[ "$ruleValue" =~ ^[0-9]+$ ]]; then
valid_port "$ruleValue"
return $?
fi
if [[ "$ruleValue" =~ ^([0-9]+)/(tcp|udp)$ ]]; then
valid_port "${BASH_REMATCH[1]}"
return $?
fi
return 1
}
resolve_ssh_port() {
local configuredPort=""
if [[ -r "$sshConfigDropIn" ]]; then
configuredPort="$(awk '
BEGIN { IGNORECASE = 1 }
/^[[:space:]]*Port[[:space:]]+[0-9]+([[:space:]]*#.*)?$/ {
port = $2
}
END {
if (port != "") {
print port
}
}
' "$sshConfigDropIn")"
fi
if valid_port "$configuredPort"; then
printf '%s\n' "$configuredPort"
return 0
fi
configuredPort="$(inputbox "Unable to confirm the SSH port automatically. Enter the SSH port to allow before changing the firewall.")" || return 1
if valid_port "$configuredPort"; then
printf '%s\n' "$configuredPort"
return 0
fi
msgbox "A valid SSH port is required before enabling firewall rules."
return 1
}
allow_rule() {
local ruleValue="$1"
local description="$2"
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw allow "$ruleValue"; then
msgbox "Failed to allow ${description}."
return 1
fi
msgbox "${description} allowed."
return 0
}
delete_rule() {
local ruleValue="$1"
local description="$2"
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw delete allow "$ruleValue"; then
msgbox "Failed to remove ${description}."
return 1
fi
msgbox "${description} removed."
return 0
}
allow_ssh_port() {
local sshPort=""
ensure_ufw || return 1
sshPort="$(resolve_ssh_port)" || {
msgbox "Firewall change cancelled because the SSH port could not be confirmed."
return 1
}
allow_rule "${sshPort}/tcp" "SSH port ${sshPort}/tcp"
}
enable_firewall() {
ensure_ufw || return 1
allow_ssh_port || return 1
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if sudo "${sudoFlags[@]}" systemctl enable --now ufw; then
msgbox "Firewall enabled."
return 0
fi
msgbox "Failed to enable ufw."
return 1
}
disable_firewall() {
ensure_ufw || return 1
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if sudo "${sudoFlags[@]}" systemctl disable --now ufw; then
msgbox "Firewall disabled."
return 0
fi
msgbox "Failed to disable ufw."
return 1
}
open_custom_port() {
local portNumber=""
local protocolChoice=""
ensure_ufw || return 1
portNumber="$(inputbox "Enter the port number to open.")" || return 1
if ! valid_port "$portNumber"; then
msgbox "Enter a valid port number from 1 to 65535."
return 1
fi
protocolChoice="$(menulist "TCP" "UDP" "Both")" || return 1
case "$protocolChoice" in
"TCP")
allow_rule "${portNumber}/tcp" "Port ${portNumber}/tcp"
;;
"UDP")
allow_rule "${portNumber}/udp" "Port ${portNumber}/udp"
;;
"Both")
allow_rule "${portNumber}/tcp" "Port ${portNumber}/tcp" || return 1
allow_rule "${portNumber}/udp" "Port ${portNumber}/udp"
;;
esac
}
list_simple_allow_rules() {
local sshPort=""
local statusLine=""
declare -A tcpPorts=()
declare -A udpPorts=()
sshPort="$(resolve_ssh_port 2> /dev/null || true)"
while IFS= read -r statusLine; do
if [[ "$statusLine" =~ ^[[:space:]]*([0-9]+)/(tcp|udp)[[:space:]]+ALLOW[[:space:]]+Anywhere[[:space:]]*$ ]]; then
if [[ "${BASH_REMATCH[1]}" == "$sshPort" ]]; then
continue
fi
case "${BASH_REMATCH[2]}" in
tcp)
tcpPorts["${BASH_REMATCH[1]}"]=1
;;
udp)
udpPorts["${BASH_REMATCH[1]}"]=1
;;
esac
fi
done < <(ufw_status_output)
for portNumber in "${!tcpPorts[@]}"; do
if [[ -n "${udpPorts[$portNumber]:-}" ]]; then
printf '%s/both\n' "$portNumber"
unset 'udpPorts[$portNumber]'
else
printf '%s/tcp\n' "$portNumber"
fi
done
for portNumber in "${!udpPorts[@]}"; do
printf '%s/udp\n' "$portNumber"
done | sort -n -t / -k 1,1
}
close_port() {
local removableRules=()
local selectedRule=""
local ruleValue=""
ensure_ufw || return 1
while IFS= read -r ruleValue; do
[[ -n "$ruleValue" ]] && removableRules+=("$ruleValue")
done < <(list_simple_allow_rules | sort -n -t / -k 1,1)
if [[ "${#removableRules[@]}" -eq 0 ]]; then
msgbox "No simple removable port rules were found."
return 0
fi
selectedRule="$(menulist "${removableRules[@]}")" || return 0
case "$selectedRule" in
*/tcp|*/udp)
delete_rule "$selectedRule" "Port ${selectedRule}"
;;
*/both)
ruleValue="${selectedRule%/both}"
delete_rule "${ruleValue}/tcp" "Port ${ruleValue}/tcp" || return 1
delete_rule "${ruleValue}/udp" "Port ${ruleValue}/udp"
;;
esac
}
view_firewall_status() {
local tempFile=""
local statusText=""
ensure_ufw || return 1
tempFile="$(mktemp)"
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
statusText="$(sudo "${sudoFlags[@]}" ufw status verbose 2>&1)"
printf '%s\n' "$statusText" > "$tempFile"
textbox "$tempFile"
rm -f "$tempFile"
}
while true; do
firewallChoice="$(menulist \
"Enable firewall" \
"Disable firewall" \
"Allow SSH" \
"Open port" \
"Close port" \
"View status" \
"Back")" || break
case "$firewallChoice" in
"Enable firewall")
enable_firewall
;;
"Disable firewall")
disable_firewall
;;
"Allow SSH")
allow_ssh_port
;;
"Open port")
open_custom_port
;;
"Close port")
close_port
;;
"View status")
view_firewall_status
;;
"Back")
break
;;
esac
done