Files
Linux_post_install/bin/pos-system-firewall
T
2026-08-24 14:44:25 -04:00

326 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# POS: system firewall — Interactive UFW management
IFS=$'\n\t'
if [[ $EUID -ne 0 ]]; then
echo "ERROR: Please run as root (sudo)."
echo "Usage: sudo pos system firewall"
exit 1
fi
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
HISTORY=()
DRY_RUN=0
usage() {
cat <<'EOF'
Usage: sudo pos system firewall [--dry-run]
Interactive UFW management menu.
Options:
--dry-run Preview commands without executing them
-h, --help Show this help
EOF
exit 0
}
case "${1:-}" in
-h|--help) usage ;;
esac
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
fi
log() { echo "[+] $*"; }
warn() { echo "[!] $*"; }
err() { echo "ERROR: $*" >&2; exit 1; }
# Interactive input seam: every prompt reads the controlling terminal
# (/dev/tty), so the menu survives stdout redirection / command substitution,
# and fails closed on EOF or a missing TTY — it prints a pointer to the CLI
# instead of hanging under cron/pipes (repo-standard menu mechanics; see
# lib/menu-lib.sh contracts).
tty_read() {
local prompt="$1"
shift
if ! read -rp "$prompt" "$@" < /dev/tty; then
printf '[!] Terminal closed or unavailable (EOF) — stopping; nothing more was executed.\n' >&2
printf '[!] Re-open interactively with: sudo pos system firewall (see --help)\n' >&2
exit 1
fi
}
run_cmd() {
local -a cmd=("$@")
printf "\n>>> %s\n" "${cmd[*]}"
tty_read "Execute this command? [y/N]: " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "(dry-run) skipping execution"
else
"${cmd[@]}"
# Alert on mutating commands only (status queries are read-only)
if [[ "${cmd[1]:-}" != "status" ]]; then
notify_send "Firewall: ${cmd[*]}"
fi
fi
HISTORY+=("${cmd[*]}")
else
echo "Cancelled."
fi
}
build_ufw_cmd() {
local action="$1"
local direction="$2"
local proto="$3"
local from="$4"
local to="$5"
local port="$6"
local onif="$7"
local logmode="$8"
local comment="$9"
local insert_pos="${10:-}"
local suffix="${11:-}"
local -a cmd=(ufw)
if [[ -n "$insert_pos" ]]; then
if [[ "$insert_pos" == "prepend" ]]; then
cmd+=(prepend)
else
cmd+=(insert "$insert_pos")
fi
fi
cmd+=("$action")
[[ -n "$direction" ]] && cmd+=("$direction")
[[ -n "$onif" ]] && cmd+=(on "$onif")
[[ -n "$proto" ]] && cmd+=(proto "$proto")
[[ -n "$from" ]] && cmd+=(from "$from")
cmd+=(to "$to")
[[ -n "$port" ]] && cmd+=(port "$port")
[[ -n "$logmode" ]] && cmd+=("$logmode")
if [[ -n "$comment" ]]; then
local safe="${comment// /_}"
cmd+=(comment "$safe")
fi
[[ "$suffix" == "v6" ]] && cmd+=(v6)
run_cmd "${cmd[@]}"
}
prompt_ipver() {
# Assigns `ipver` in the caller's scope (bash dynamic scoping); direct call
# instead of command substitution so an EOF exits the whole tool gracefully.
tty_read "IP version (4 / 6 / both): " ipver
}
apply_for_versions() {
local action="$1" direction="$2" proto="$3" from="$4" to="$5"
local port="$6" onif="$7" logmode="$8" comment="$9"
local insert_pos="${10:-}"
local ipver
prompt_ipver
case "$ipver" in
4) build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" "" ;;
6) build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" "v6" ;;
both)
build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" ""
build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" "v6"
;;
*) echo "Invalid choice. Choose 4, 6 or both." ;;
esac
}
add_rule() {
echo
echo "Choose rule type:"
echo "1) Port/service (eg: port 8080 or 'ssh')"
echo "2) IP-based (from X to Y)"
echo "3) Directional port rule (in/out to any port ...)"
tty_read "Choice: " rtype
case "$rtype" in
1)
tty_read "Action (allow/deny/reject/limit) [allow]: " action
action=${action:-allow}
tty_read "Enter port number or service name (eg 'ssh' or '8080'): " port_or_svc
if [[ "$port_or_svc" =~ ^[0-9]+$ ]]; then
tty_read "Protocol (tcp/udp/any) [tcp]: " proto
proto=${proto:-tcp}
[[ "$proto" == "any" ]] && proto=""
tty_read "Interface (leave empty for any): " onif
tty_read "Log? (none/log/log-all) [none]: " logmode
[[ "$logmode" == "none" ]] && logmode=""
tty_read "Comment (optional): " comment
apply_for_versions "$action" "" "$proto" "" "any" "$port_or_svc" "$onif" "$logmode" "$comment"
else
tty_read "IP version (4 / 6 / both) [4]: " ipver
ipver=${ipver:-4}
case "$ipver" in
4) run_cmd ufw "$action" "$port_or_svc" ;;
6) run_cmd ufw "$action" "$port_or_svc" v6 ;;
both) run_cmd ufw "$action" "$port_or_svc"
run_cmd ufw "$action" "$port_or_svc" v6 ;;
*) echo "invalid ipver" ;;
esac
fi
;;
2)
tty_read "Action (allow/deny/reject) [deny]: " action
action=${action:-deny}
tty_read "From address/CIDR (eg 192.168.1.5 or 10.0.0.0/24): " from
tty_read "To address (leave empty for 'any') [any]: " to
to=${to:-any}
tty_read "Direction (in/out) [in]: " direction
direction=${direction:-in}
tty_read "Port (leave empty if not applicable): " port
tty_read "Protocol (tcp/udp/any) [any]: " proto
[[ "$proto" == "any" ]] && proto=""
tty_read "Interface (leave empty for any): " onif
tty_read "Log? (none/log/log-all) [none]: " logmode
[[ "$logmode" == "none" ]] && logmode=""
tty_read "Comment (optional): " comment
apply_for_versions "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment"
;;
3)
tty_read "Action (allow/deny/reject/limit) [allow]: " action
action=${action:-allow}
tty_read "Direction (in/out) [in]: " direction
direction=${direction:-in}
tty_read "Port number: " port
tty_read "Protocol (tcp/udp/any) [tcp]: " proto
[[ "$proto" == "any" ]] && proto=""
tty_read "On interface (leave empty for any): " onif
tty_read "From address (optional): " from
from=${from:-}
tty_read "To address [any]: " to
to=${to:-any}
tty_read "Log? (none/log/log-all) [none]: " logmode
[[ "$logmode" == "none" ]] && logmode=""
tty_read "Comment (optional): " comment
tty_read "Insert position (number/prepend/empty): " insert_pos
apply_for_versions "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos"
;;
*) echo "Unknown choice." ;;
esac
}
delete_rule() {
echo
echo "Delete rule by:"
echo "1) rule number (use 'ufw status numbered' to see numbers)"
echo "2) rule text (eg: 'allow 22/tcp')"
tty_read "Choice: " dch
case "$dch" in
1)
ufw status numbered
tty_read "Number to delete: " num
run_cmd ufw delete "$num"
;;
2)
tty_read "Exact rule text to delete (eg: deny 80/tcp): " ruletext
run_cmd ufw delete $ruletext
;;
*) echo "Unknown choice." ;;
esac
}
show_status() {
echo
echo "1) Simple status"
echo "2) Verbose status"
echo "3) Numbered status (useful for delete)"
tty_read "Choice: " sc
case "$sc" in
1) run_cmd ufw status ;;
2) run_cmd ufw status verbose ;;
3) run_cmd ufw status numbered ;;
*) echo "Unknown choice." ;;
esac
}
while true; do
{
cat <<'MENU'
==============================
UFW POWER — human friendly
==============================
1) Add rule (port/service/ip/directional)
2) Delete rule (by number or text)
3) Show status (simple / verbose / numbered)
4) Enable UFW
5) Disable UFW
6) Reset UFW (delete all rules)
7) Set default policy (incoming/outgoing)
8) Show executed commands history (so far)
0) Exit
------------------------------
MENU
} >&2
tty_read "Choose: " opt
case "$opt" in
1) add_rule ;;
2) delete_rule ;;
3) show_status ;;
4) run_cmd ufw enable ;;
5) run_cmd ufw disable ;;
6)
echo "WARNING: ufw reset will disable and remove all rules."
tty_read "Type 'RESET' to confirm: " c
[[ "$c" == "RESET" ]] && run_cmd ufw reset || echo "Reset aborted."
;;
7)
tty_read "Default incoming policy (allow/deny/reject) [deny]: " defin
defin=${defin:-deny}
tty_read "Default outgoing policy (allow/deny/reject) [allow]: " defout
defout=${defout:-allow}
run_cmd ufw default "$defin" incoming
run_cmd ufw default "$defout" outgoing
;;
8)
echo
echo "Executed commands so far:"
echo
if [[ "${#HISTORY[@]}" -eq 0 ]]; then
echo "(none yet)"
else
for c in "${HISTORY[@]}"; do echo " - $c"; done
fi
;;
0)
echo
echo "Final executed commands summary:"
if [[ "${#HISTORY[@]}" -eq 0 ]]; then
echo "(no commands executed)"
else
for c in "${HISTORY[@]}"; do echo " - $c"; done
fi
echo "Goodbye — firewall remains watchful."
exit 0
;;
*) echo "Unknown option." ;;
esac
echo
tty_read "Press Enter to continue..." REPLY
clear
done