Web interface to view, ban and unban fail2ban IPs on several hosts via SSH, generalised from the private f2b-web tool. - Web UI (Flask/Waitress): hosts.yaml, local/OIDC/proxy authentication, English/German UI, origin map, optional Loki/Grafana/GeoIP, CLI helpers - Agent f2b-webctl with install.sh/uninstall.sh (dedicated user + sudo, from= restriction, protected networks, manual jail) - Hardened Docker image, examples, documentation, smoke test, testbed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
4.9 KiB
Bash
Executable File
126 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# =====================================================================
|
||
# fail2ban-webui agent – uninstaller
|
||
#
|
||
# Removes the key entry, the sudo rule, f2b-webctl and its config.
|
||
# The user and the manual jail are only removed on request, because
|
||
# removing the jail also lifts all manual bans.
|
||
# Every removed file is backed up to --backup-dir first.
|
||
# =====================================================================
|
||
set -euo pipefail
|
||
|
||
AGENT_BIN=/usr/local/sbin/f2b-webctl
|
||
CONF_FILE=/etc/f2b-webctl.conf
|
||
SUDOERS_FILE=/etc/sudoers.d/f2b-webctl
|
||
|
||
USER_NAME=f2bweb
|
||
AK_FILE=""
|
||
MANUAL_JAIL=""
|
||
REMOVE_USER=0
|
||
REMOVE_JAIL=0
|
||
BACKUP_DIR=/var/backups/fail2ban-webui-agent
|
||
DRY_RUN=0
|
||
YES=0
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
Usage: uninstall.sh [options]
|
||
|
||
--user NAME SSH user of the agent (default: f2bweb)
|
||
--authorized-keys-file PATH file with the key entry (default: ~USER/.ssh/authorized_keys)
|
||
--remove-user also delete the user (not possible for root)
|
||
--remove-jail also delete the manual jail – lifts all its bans!
|
||
--manual-jail NAME name of the manual jail (default: from /etc/f2b-webctl.conf)
|
||
--backup-dir DIR backups (default: /var/backups/fail2ban-webui-agent)
|
||
--dry-run only show what would be done
|
||
-y, --yes do not ask for confirmation
|
||
EOF
|
||
}
|
||
|
||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||
info() { echo "==> $*"; }
|
||
need() { [ $# -ge 2 ] && [ -n "$2" ] || die "option $1 needs a value"; }
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--user) need "$@"; USER_NAME="$2"; shift 2 ;;
|
||
--authorized-keys-file) need "$@"; AK_FILE="$2"; shift 2 ;;
|
||
--remove-user) REMOVE_USER=1; shift ;;
|
||
--remove-jail) REMOVE_JAIL=1; shift ;;
|
||
--manual-jail) need "$@"; MANUAL_JAIL="$2"; shift 2 ;;
|
||
--backup-dir) need "$@"; BACKUP_DIR="$2"; shift 2 ;;
|
||
--dry-run) DRY_RUN=1; shift ;;
|
||
-y|--yes) YES=1; shift ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) usage >&2; die "unknown option: $1" ;;
|
||
esac
|
||
done
|
||
|
||
[ "$(id -u)" -eq 0 ] || die "please run as root"
|
||
[[ "$USER_NAME" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || die "invalid user name: $USER_NAME"
|
||
[ "$USER_NAME" != root ] || [ $REMOVE_USER -eq 0 ] || die "root cannot be removed"
|
||
if [ -z "$MANUAL_JAIL" ]; then
|
||
MANUAL_JAIL=$(sed -n 's/^MANUAL_JAIL=//p' "$CONF_FILE" 2>/dev/null | tail -n1)
|
||
MANUAL_JAIL=${MANUAL_JAIL:-manual-block}
|
||
fi
|
||
[[ "$MANUAL_JAIL" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,40}$ ]] || die "invalid jail name: $MANUAL_JAIL"
|
||
HOME_DIR=$(getent passwd "$USER_NAME" | cut -d: -f6 || true)
|
||
AK_FILE=${AK_FILE:-${HOME_DIR:-/nonexistent}/.ssh/authorized_keys}
|
||
JAIL_FILES=("/etc/fail2ban/jail.d/$MANUAL_JAIL.local" "/etc/fail2ban/filter.d/$MANUAL_JAIL.conf")
|
||
|
||
KEY_LINES=0
|
||
[ -f "$AK_FILE" ] && KEY_LINES=$(grep -c 'command="[^"]*f2b-webctl"' "$AK_FILE" || true)
|
||
|
||
echo
|
||
echo "fail2ban-webui agent – removing from $(hostname -f 2>/dev/null || hostname):"
|
||
echo " key entries $KEY_LINES in $AK_FILE"
|
||
for f in "$SUDOERS_FILE" "$AGENT_BIN" "$CONF_FILE"; do
|
||
[ -e "$f" ] && echo " file $f"
|
||
done
|
||
[ $REMOVE_JAIL -eq 1 ] && for f in "${JAIL_FILES[@]}"; do [ -e "$f" ] && echo " jail file $f"; done
|
||
[ $REMOVE_USER -eq 1 ] && id "$USER_NAME" >/dev/null 2>&1 && echo " user $USER_NAME (with home directory)"
|
||
echo " backups $BACKUP_DIR"
|
||
echo
|
||
if [ $REMOVE_JAIL -eq 1 ]; then
|
||
echo "WARNING: removing jail $MANUAL_JAIL lifts all manual bans!" >&2
|
||
if fail2ban-client get "$MANUAL_JAIL" banip >/dev/null 2>&1; then
|
||
echo " currently banned: $(fail2ban-client get "$MANUAL_JAIL" banip | tr '\n' ' ')" >&2
|
||
fi
|
||
fi
|
||
[ $DRY_RUN -eq 0 ] || { info "dry run – nothing changed"; exit 0; }
|
||
if [ $YES -eq 0 ]; then
|
||
[ -t 0 ] || die "no terminal for the confirmation – use --yes"
|
||
read -r -p "Remove? [y/N] " answer
|
||
[[ "$answer" =~ ^[yYjJ] ]] || die "aborted, nothing changed"
|
||
fi
|
||
|
||
STAMP=$(date +%Y-%m-%d_%H-%M-%S)
|
||
BK="$BACKUP_DIR/uninstall_$STAMP"
|
||
mkdir -p "$BK"; chmod 700 "$BACKUP_DIR" "$BK"
|
||
for f in "$AK_FILE" "$SUDOERS_FILE" "$AGENT_BIN" "$CONF_FILE" "${JAIL_FILES[@]}"; do
|
||
[ -e "$f" ] && cp -a --parents "$f" "$BK/"
|
||
done
|
||
info "backup: $BK"
|
||
|
||
if [ "$KEY_LINES" -gt 0 ]; then
|
||
info "removing key entries from $AK_FILE"
|
||
tmp=$(mktemp)
|
||
grep -v 'command="[^"]*f2b-webctl"' "$AK_FILE" > "$tmp" || true
|
||
cat "$tmp" > "$AK_FILE"
|
||
rm -f "$tmp"
|
||
fi
|
||
for f in "$SUDOERS_FILE" "$AGENT_BIN" "$CONF_FILE"; do
|
||
[ -e "$f" ] && { info "removing $f"; rm -f "$f"; }
|
||
done
|
||
if [ $REMOVE_JAIL -eq 1 ]; then
|
||
for f in "${JAIL_FILES[@]}"; do
|
||
[ -e "$f" ] && { info "removing $f"; rm -f "$f"; }
|
||
done
|
||
fail2ban-client reload >/dev/null || echo "WARNING: fail2ban reload failed – check: fail2ban-client -t" >&2
|
||
fi
|
||
if [ $REMOVE_USER -eq 1 ] && id "$USER_NAME" >/dev/null 2>&1; then
|
||
info "removing user $USER_NAME"
|
||
userdel -r "$USER_NAME" 2>/dev/null || userdel "$USER_NAME"
|
||
fi
|
||
info "done"
|