Fail2ban网络安全防护

一、前言

Fail2ban是一款基于Python开发的开源网络安全防护软件,主要应用于UNIX/Linux系统,通过实时监控SSH、Apache、FTP等服务日志文件,自动识别并拦截恶意访问行为。

二、Fail2ban安装

# 安装 EPEL 仓库(CentOS/RHEL 7/8 需要)
yum install epel-release -y

# 安装 Fail2Ban 及相关包
yum install -y fail2ban fail2ban-systemd iptables-services iptables

# 启动并设置开机自启
systemctl start fail2ban
systemctl enable fail2ban

# 检查状态
systemctl status fail2ban

三、Fail2ban 防护脚本

#!/bin/bash
# ============================================
# Fail2Ban 一键安装配置脚本(永久封禁版)
# 版本:7.1 - 动态时间范围扫描 + 永久豁免列表
# ============================================

set -e

# 颜色定义(必须在最前面)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'

# 配置参数
SSH_PORT="22"
MAX_RETRY="3"
FIND_TIME="600"
BAN_TIME="-1"  # 改为 -1 永久封禁
IGNORE_IP="127.0.0.1/8 ::1"

# 性能优化参数
PARALLEL_JOBS=2
SCAN_MONTHS="24"  # 默认扫描24个月

# 日志文件
INSTALL_LOG="/var/log/fail2ban-install.log"
REPORT_DIR="/var/log/fail2ban-reports"
ATTACK_LOG="/var/log/fail2ban-attack-details.log"
FULL_SCAN_LOG="/var/log/fail2ban-full-scan.log"
mkdir -p ${REPORT_DIR}

# ============================================
# 定义所有打印函数
# ============================================
print_info() {
    echo -e "${GREEN}[INFO]${NC} $1" | tee -a ${INSTALL_LOG}
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1" | tee -a ${INSTALL_LOG}
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a ${INSTALL_LOG}
}

print_step() {
    echo ""
    echo -e "${BLUE}========================================${NC}"
    echo -e "${CYAN}步骤 $1: $2${NC}"
    echo -e "${BLUE}========================================${NC}"
}

# ============================================
# 验证函数是否可用
# ============================================
test_functions() {
    print_info "测试打印函数"
    print_warning "这是警告信息测试"
    print_error "这是错误信息测试"
}

# ============================================
# 检查root权限
# ============================================
check_root() {
    if [ "$EUID" -ne 0 ]; then
        print_error "请使用root权限运行此脚本: sudo $0"
        exit 1
    fi
}

# ============================================
# 安装依赖
# ============================================
install_dependencies() {
    print_step "1" "安装依赖包"
    yum install epel-release -y >> ${INSTALL_LOG} 2>&1
    yum install -y fail2ban fail2ban-systemd iptables-services iptables >> ${INSTALL_LOG} 2>&1
    print_info "依赖包安装完成"
}

# ============================================
# 配置SSH端口
# ============================================
configure_ssh() {
    print_step "2" "配置SSH端口为 ${SSH_PORT}"
    cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d_%H%M%S) 2>/dev/null
    
    if grep -q "^Port" /etc/ssh/sshd_config; then
        sed -i "s/^Port.*/Port ${SSH_PORT}/" /etc/ssh/sshd_config
    else
        echo "Port ${SSH_PORT}" >> /etc/ssh/sshd_config
    fi
    sed -i '/^#Port/d' /etc/ssh/sshd_config
    
    systemctl restart sshd
    if systemctl is-active --quiet sshd; then
        print_info "SSH服务运行正常,端口: ${SSH_PORT}"
        netstat -tlnp 2>/dev/null | grep sshd || ss -tlnp 2>/dev/null | grep sshd
    else
        print_error "SSH服务启动失败"
        exit 1
    fi
    
    # 防火墙配置
    if command -v firewall-cmd &> /dev/null; then
        firewall-cmd --permanent --add-port=${SSH_PORT}/tcp 2>/dev/null
        firewall-cmd --permanent --remove-service=ssh 2>/dev/null
        firewall-cmd --reload 2>/dev/null
        print_info "firewalld已配置"
    elif command -v iptables &> /dev/null; then
        iptables -I INPUT -p tcp --dport ${SSH_PORT} -j ACCEPT 2>/dev/null
        service iptables save 2>/dev/null
        print_info "iptables已配置"
    fi
}

# ============================================
# 配置Fail2Ban(永久封禁)
# ============================================
configure_fail2ban() {
    print_step "3" "配置Fail2Ban(端口 ${SSH_PORT},永久封禁)"
    
    systemctl stop fail2ban 2>/dev/null
    rm -f /etc/fail2ban/jail.local
    rm -f /var/run/fail2ban/fail2ban.sock
    mkdir -p /var/lib/fail2ban /etc/fail2ban/action.d /var/log
    
    # 创建基础jail配置(只配置SSH)
    cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = -1
findtime = ${FIND_TIME}
maxretry = ${MAX_RETRY}
ignoreip = ${IGNORE_IP}
banaction = iptables-multiport

[sshd]
enabled = true
port = ${SSH_PORT}
filter = sshd
logpath = /var/log/secure
maxretry = ${MAX_RETRY}
bantime = -1
action = iptables-multiport[name=sshd, port=${SSH_PORT}, protocol=tcp]
EOF

    touch /var/lib/fail2ban/permanent-bans.list
    chmod 666 /var/lib/fail2ban/permanent-bans.list
    
    # 创建永久豁免列表文件(手动解封的IP将记录在此)
    touch /var/lib/fail2ban/unban-permanent.list
    chmod 666 /var/lib/fail2ban/unban-permanent.list
    
    touch /var/log/fail2ban.log
    chmod 644 /var/log/fail2ban.log
    touch ${ATTACK_LOG}
    chmod 644 ${ATTACK_LOG}
    touch ${FULL_SCAN_LOG}
    chmod 644 ${FULL_SCAN_LOG}
    
    print_info "Fail2Ban配置完成(永久封禁模式)"
    print_info "永久豁免列表: /var/lib/fail2ban/unban-permanent.list"
}

# ============================================
# 获取需要扫描的日志文件列表(基于时间范围)
# ============================================
get_log_files_by_months() {
    local months=$1
    local log_path=$2
    local log_files=()
    
    # 当前日志文件
    if [ -f "${log_path}" ]; then
        log_files+=("${log_path}")
    fi
    
    # 计算起始日期
    local start_date=$(date -d "-${months} months" +%s 2>/dev/null || echo "")
    if [ -n "$start_date" ]; then
        # 扫描轮转日志
        for rotated in ${log_path}-* ${log_path}-*.gz ${log_path}.* ${log_path}.*.gz; do
            if [ -f "$rotated" ]; then
                # 提取日志文件中的日期
                local file_date=""
                if [[ "$rotated" =~ ([0-9]{8}) ]]; then
                    file_date="${BASH_REMATCH[1]}"
                elif [[ "$rotated" =~ ([0-9]{6}) ]]; then
                    file_date="${BASH_REMATCH[1]}"
                elif [[ "$rotated" =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}) ]]; then
                    file_date=$(echo "${BASH_REMATCH[1]}" | tr -d '-')
                fi
                
                if [ -n "$file_date" ] && [ ${#file_date} -ge 6 ]; then
                    local file_timestamp=$(date -d "${file_date:0:4}-${file_date:4:2}-${file_date:6:2}" +%s 2>/dev/null)
                    if [ -n "$file_timestamp" ] && [ $file_timestamp -ge $start_date ]; then
                        log_files+=("$rotated")
                    fi
                else
                    # 无法判断日期,包含所有轮转日志
                    log_files+=("$rotated")
                fi
            fi
        done
    else
        # 无法计算日期,包含所有轮转日志
        for rotated in ${log_path}-* ${log_path}-*.gz ${log_path}.* ${log_path}.*.gz; do
            [ -f "$rotated" ] && log_files+=("$rotated")
        done
    fi
    
    # 去重
    printf '%s\n' "${log_files[@]}" | sort -u
}

# ============================================
# 优化的IP提取函数(使用grep -oP)
# ============================================
extract_ips_optimized() {
    local log_file=$1
    local temp_file="/tmp/ip_extract_$$.txt"
    
    if [[ "$log_file" == *.gz ]]; then
        zcat "$log_file" 2>/dev/null | grep -a "Failed password" | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_file}
    else
        grep -a "Failed password" "$log_file" 2>/dev/null | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_file}
    fi
    
    cat ${temp_file}
    rm -f ${temp_file}
}

# ============================================
# 获取攻击详情(优化版)
# ============================================
get_attack_details_fast() {
    local ip=$1
    local log_file=$2
    local username="unknown"
    local password="unknown"
    local attack_time=""
    
    # 快速获取最后一条攻击记录
    local last_line=""
    if [[ "$log_file" == *.gz ]]; then
        last_line=$(zcat "$log_file" 2>/dev/null | grep -a "Failed password.*from ${ip}" | tail -1)
    else
        last_line=$(grep -a "Failed password.*from ${ip}" "$log_file" 2>/dev/null | tail -1)
    fi
    
    if [ -n "$last_line" ]; then
        # 提取时间
        attack_time=$(echo "$last_line" | grep -oP '^[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}')
        # 提取用户名
        if echo "$last_line" | grep -q "for user"; then
            username=$(echo "$last_line" | grep -oP 'for user \K[^ ]+' | head -1)
        elif echo "$last_line" | grep -q "user"; then
            username=$(echo "$last_line" | grep -oP 'user \K[^ ]+' | head -1)
        elif echo "$last_line" | grep -q "invalid user"; then
            username=$(echo "$last_line" | grep -oP 'invalid user \K[^ ]+' | head -1)
        fi
        
        # 提取密码
        if echo "$last_line" | grep -q "password"; then
            password=$(echo "$last_line" | grep -oP 'password \K[^ ]+' | head -1 | sed 's/ //g')
        fi
    fi
    
    [ -z "$username" ] && username="unknown"
    [ -z "$password" ] && password="unknown"
    echo "${attack_time}|${username}|${password}"
}

# ============================================
# 创建优化的quick_ban脚本(支持永久豁免列表)
# ============================================
create_quick_ban_script() {
    print_step "4" "创建优化的quick_ban脚本(支持永久豁免列表)"
    
    cat > /usr/local/bin/quick_ban << 'EOF'
#!/bin/bash

BAN_LIST="/var/lib/fail2ban/permanent-bans.list"
UNBAN_PERMANENT="/var/lib/fail2ban/unban-permanent.list"
SECURE_LOG="/var/log/secure"
ATTACK_LOG="/var/log/fail2ban-attack-details.log"
FULL_SCAN_LOG="/var/log/fail2ban-full-scan.log"
SSH_PORT="22"
REPORT_DIR="/var/log/fail2ban-reports"
SCAN_MONTHS="${SCAN_MONTHS:-24}"

# 性能优化参数
export LC_ALL=C
export LANG=C

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'

# ============================================
# 核心函数:豁免列表管理
# ============================================

# 检查IP是否在永久豁免列表中
is_ip_exempted() {
    local ip=$1
    [ -f "${UNBAN_PERMANENT}" ] && grep -q "^${ip}$" ${UNBAN_PERMANENT} 2>/dev/null
}

# 将IP添加到永久豁免列表
add_to_exempt_list() {
    local ip=$1
    local reason=$2
    if ! is_ip_exempted "$ip"; then
        echo "${ip}" >> ${UNBAN_PERMANENT}
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] EXEMPT_ADD|ip=${ip}|reason=${reason}" >> ${BAN_LIST}
        echo -e "${GREEN}  → IP ${ip} 已加入永久豁免列表${NC}"
    fi
}

# 从永久豁免列表移除IP
remove_from_exempt_list() {
    local ip=$1
    if [ -f "${UNBAN_PERMANENT}" ]; then
        sed -i "/^${ip}$/d" ${UNBAN_PERMANENT}
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] EXEMPT_REMOVE|ip=${ip}" >> ${BAN_LIST}
        echo -e "${GREEN}  → IP ${ip} 已从豁免列表移除${NC}"
    fi
}

# 手动解封IP并加入豁免列表
manual_unban() {
    local ip=$1
    if [ -z "$ip" ]; then
        echo -e "${RED}用法: quick_ban -u <IP地址>${NC}"
        return 1
    fi
    
    echo -e "${YELLOW}正在解封并豁免 IP: ${ip}${NC}"
    
    # 从fail2ban解封
    fail2ban-client set sshd unbanip ${ip} 2>/dev/null && echo -e "  ✓ fail2ban解封成功"
    
    # 从iptables移除
    iptables -D fail2ban-sshd -s ${ip} -j DROP 2>/dev/null && echo -e "  ✓ iptables规则移除成功"
    
    # 加入永久豁免列表
    add_to_exempt_list "$ip" "manual_unban"
    
    echo -e "${GREEN}✓ IP ${ip} 已解封并加入永久豁免列表,后续扫描将不再封禁${NC}"
}

# 列出永久豁免列表
list_exempted() {
    echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${BLUE}║              永久豁免IP列表(手动解封后不再封禁)                                      ║${NC}"
    echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
    
    if [ -f "${UNBAN_PERMANENT}" ] && [ -s "${UNBAN_PERMANENT}" ]; then
        echo ""
        local count=0
        cat ${UNBAN_PERMANENT} | while read ip; do
            [ -n "$ip" ] && echo "  • ${ip}" && count=$((count+1))
        done
        echo ""
        echo -e "${GREEN}总计豁免IP数: $(wc -l < ${UNBAN_PERMANENT})${NC}"
    else
        echo -e "${YELLOW}暂无永久豁免IP${NC}"
    fi
}

# ============================================
# 原有函数(增强豁免检查)
# ============================================

# 转换时间格式函数
convert_time_format() {
    local time_str="$1"
    local converted=""
    if [[ "$time_str" =~ ^([A-Za-z]{3})\s+([0-9]{1,2})\s+([0-9]{2}:[0-9]{2}:[0-9]{2})$ ]]; then
        local mon="${BASH_REMATCH[1]}"
        local day="${BASH_REMATCH[2]}"
        local time="${BASH_REMATCH[3]}"
        local current_year=$(date +%Y)
        local current_month=$(date +%m)
        local month_num=""
        case $mon in
            Jan) month_num="01" ;;
            Feb) month_num="02" ;;
            Mar) month_num="03" ;;
            Apr) month_num="04" ;;
            May) month_num="05" ;;
            Jun) month_num="06" ;;
            Jul) month_num="07" ;;
            Aug) month_num="08" ;;
            Sep) month_num="09" ;;
            Oct) month_num="10" ;;
            Nov) month_num="11" ;;
            Dec) month_num="12" ;;
        esac
        local year=$current_year
        if [ $((10#$month_num)) -gt $((10#$current_month)) ]; then
            year=$((current_year - 1))
        fi
        printf -v converted "%04d-%02d-%02d %s" "$year" "$((10#$month_num))" "$((10#$day))" "$time"
        echo "$converted"
    else
        echo "$time_str"
    fi
}

# 记录攻击详情
log_attack_detail() {
    local ip=$1
    local username=$2
    local password=$3
    local port=$4
    local attack_time=$5
    local service=$6
    local formatted_time=$(convert_time_format "$attack_time")
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ATTACK_DETAIL|service=${service}|ip=${ip}|username=${username}|password=${password}|port=${port}|attack_time=${formatted_time}" >> ${ATTACK_LOG}
}

# 增强版:检查IP是否已被封禁(包含豁免检查)
is_ip_banned() {
    local ip=$1
    
    # 优先检查豁免列表
    if is_ip_exempted "$ip"; then
        return 0
    fi
    
    # 检查fail2ban封禁列表
    if fail2ban-client get sshd banip 2>/dev/null | grep -qw "${ip}"; then
        return 0
    fi
    
    # 检查iptables规则
    if iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${ip}"; then
        return 0
    fi
    
    # 检查本地记录
    if grep -q "ip=${ip}" ${BAN_LIST} 2>/dev/null; then
        return 0
    fi
    
    return 1
}

# 修复iptables规则并确保生效
fix_iptables_rules() {
    # 确保fail2ban-sshd链存在
    if ! iptables -L fail2ban-sshd &>/dev/null; then
        iptables -N fail2ban-sshd 2>/dev/null
    fi
    
    # 确保INPUT链有跳转到fail2ban-sshd的规则
    if ! iptables -L INPUT -n | grep -q "fail2ban-sshd.*${SSH_PORT}"; then
        iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
    fi
    
    # 确保规则生效
    if ! iptables -L INPUT -n | grep -q "fail2ban-sshd"; then
        iptables -A INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
    fi
    
    # 保存iptables规则
    service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables 2>/dev/null
}

# 自动修复 iptables 链
auto_fix_iptables() {
    local need_fix=0
    if ! iptables -L fail2ban-sshd &>/dev/null; then
        echo -e "${YELLOW}[修复] 创建 fail2ban-sshd 链...${NC}"
        iptables -N fail2ban-sshd 2>/dev/null
        need_fix=1
    fi
    if ! iptables -L INPUT -n | grep -q "fail2ban-sshd.*${SSH_PORT}"; then
        echo -e "${YELLOW}[修复] 添加 INPUT 链跳转规则 (端口 ${SSH_PORT})...${NC}"
        iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
        need_fix=1
    fi
    if [ $need_fix -eq 1 ]; then
        service iptables save 2>/dev/null
        echo -e "${GREEN}[修复] iptables 链修复完成${NC}"
    fi
}

# 生成拦截报告
generate_report() {
    local report_file="${REPORT_DIR}/fail2ban-report-$(date +%Y%m%d_%H%M%S).txt"
    local banned_ips=$(fail2ban-client get sshd banip 2>/dev/null)
    local total_bans=0
    if [ -n "$banned_ips" ] && [ "$banned_ips" != "None" ]; then
        total_bans=$(echo "$banned_ips" | wc -w)
    fi
    
    {
        echo "╔════════════════════════════════════════════════════════════════════════════════════════════╗"
        echo "║                        Fail2Ban 永久封禁拦截报告                                           ║"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║ 报告时间: $(date '+%Y-%m-%d %H:%M:%S')"
        echo "║ SSH端口: ${SSH_PORT}"
        echo "║ 封禁模式: 永久封禁 (bantime = -1)"
        echo "║ 失败阈值: ${MAX_RETRY}次/${FIND_TIME}秒"
        echo "║ 当前封禁IP数: ${total_bans}"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║                         封禁IP详细列表(含攻击详情)                                        ║"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        if [ -n "$banned_ips" ] && [ "$banned_ips" != "None" ]; then
            local count=1
            echo "$banned_ips" | tr ' ' '\n' | while read ip; do
                if [ -n "$ip" ]; then
                    local ban_date=$(grep "ip=${ip}" ${BAN_LIST} 2>/dev/null | tail -1 | grep -oP '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' || echo "永久")
                    local attack_info=$(get_attack_details_fast "$ip")
                    local last_time=$(echo "$attack_info" | cut -d'|' -f1)
                    local username=$(echo "$attack_info" | cut -d'|' -f2)
                    local password=$(echo "$attack_info" | cut -d'|' -f3)
                    echo "║ [$count] IP: ${ip}"
                    echo "║     封禁时间: ${ban_date}"
                    echo "║     最后攻击: ${last_time}"
                    echo "║     用户名: ${username}"
                    echo "║     密码: ${password}"
                    echo "║     ──────────────────────────────────────────────────────────────────────────"
                    count=$((count + 1))
                fi
            done
        else
            echo "║                          暂无封禁IP                                                   ║"
        fi
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║ 永久豁免IP列表(手动解封后不再封禁)                                                        ║"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        if [ -f "${UNBAN_PERMANENT}" ] && [ -s "${UNBAN_PERMANENT}" ]; then
            local exempt_count=1
            cat ${UNBAN_PERMANENT} | while read ip; do
                [ -n "$ip" ] && echo "║ [$exempt_count] ${ip}"
                exempt_count=$((exempt_count + 1))
            done
        else
            echo "║                          暂无豁免IP                                                   ║"
        fi
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║ iptables DROP规则状态                                                                      ║"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        local drop_rules=$(iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | wc -l)
        echo "║ DROP规则数量: ${drop_rules}"
        if [ $drop_rules -gt 0 ]; then
            echo "║ 详细规则:"
            iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | while read line; do
                echo "║   ${line}"
            done
        fi
        echo "╚════════════════════════════════════════════════════════════════════════════════════════════╝"
    } > "$report_file"
    
    echo -e "${GREEN}[报告] 拦截报告已保存: ${report_file}${NC}"
    echo ""
    echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════════════════${NC}"
    echo -e "${GREEN}拦截报告摘要${NC}"
    echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════════════════${NC}"
    echo -e "报告时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo -e "当前封禁IP数: ${total_bans}"
    echo -e "当前豁免IP数: $(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)"
    echo -e "报告位置: ${report_file}"
    echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════════════════${NC}"
}

# 获取攻击详情(快速版)
get_attack_details_fast() {
    local ip=$1
    local username="unknown"
    local password="unknown"
    local attack_time=""
    
    # 快速获取最后一条攻击记录
    local last_line=$(grep -a "Failed password.*from ${ip}" ${SECURE_LOG} 2>/dev/null | tail -1)
    if [ -z "$last_line" ]; then
        for log in ${SECURE_LOG}-* ${SECURE_LOG}-*.gz; do
            if [ -f "$log" ]; then
                if [[ "$log" == *.gz ]]; then
                    last_line=$(zcat "$log" 2>/dev/null | grep -a "Failed password.*from ${ip}" | tail -1)
                else
                    last_line=$(grep -a "Failed password.*from ${ip}" "$log" 2>/dev/null | tail -1)
                fi
                [ -n "$last_line" ] && break
            fi
        done
    fi
    
    if [ -n "$last_line" ]; then
        attack_time=$(echo "$last_line" | grep -oP '^[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}')
        attack_time=$(convert_time_format "$attack_time")
        if echo "$last_line" | grep -q "for user"; then
            username=$(echo "$last_line" | grep -oP 'for user \K[^ ]+' | head -1)
        elif echo "$last_line" | grep -q "user"; then
            username=$(echo "$last_line" | grep -oP 'user \K[^ ]+' | head -1)
        fi
        if echo "$last_line" | grep -q "password"; then
            password=$(echo "$last_line" | grep -oP 'password \K[^ ]+' | head -1 | sed 's/ //g')
        fi
    fi
    
    [ -z "$username" ] && username="unknown"
    [ -z "$password" ] && password="unknown"
    echo "${attack_time}|${username}|${password}"
}

# 优化的单文件扫描函数(核心:跳过豁免IP)
scan_single_log_optimized() {
    local log_file=$1
    local temp_ban_file="/tmp/ban_ips_$$.txt"
    local processed_ips="/tmp/processed_ips_$$.txt"
    
    # 使用grep + awk组合,极速提取
    if [[ "$log_file" == *.gz ]]; then
        zcat "$log_file" 2>/dev/null | grep -a "Failed password" | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_ban_file}
    else
        grep -a "Failed password" "$log_file" 2>/dev/null | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_ban_file}
    fi
    
    # 批量处理封禁
    if [ -s "${temp_ban_file}" ]; then
        while IFS='|' read -r ip count; do
            # 检查是否已处理过
            if grep -q "^${ip}$" ${processed_ips} 2>/dev/null; then
                continue
            fi
            echo "${ip}" >> ${processed_ips}
            
            # 【关键】检查是否在豁免列表中
            if is_ip_exempted "$ip"; then
                echo -e "    ${YELLOW}→ 跳过豁免IP: ${ip} (手动解封后不再封禁)${NC}"
                continue
            fi
            
            if ! is_ip_banned "$ip"; then
                echo -e "    ${GREEN}→ 封禁IP: ${ip} (攻击${count}次)${NC}"
                # 获取攻击详情
                local attack_info=$(get_attack_details_fast "$ip")
                local attack_time=$(echo "$attack_info" | cut -d'|' -f1)
                local username=$(echo "$attack_info" | cut -d'|' -f2)
                local password=$(echo "$attack_info" | cut -d'|' -f3)
                echo "[$(date '+%Y-%m-%d %H:%M:%S')] FULL_SCAN|service=ssh|ip=${ip}|attacks=${count}|last_attack=${attack_time}|username=${username}" >> ${FULL_SCAN_LOG}
                log_attack_detail "$ip" "$username" "$password" "${SSH_PORT}" "$attack_time" "ssh"
                if fail2ban-client set sshd banip ${ip} 2>/dev/null; then
                    echo "[$(date '+%Y-%m-%d %H:%M:%S')] PERMANENT_BAN|ip=${ip}|attacks=${count}|service=ssh|username=${username}" >> ${BAN_LIST}
                else
                    echo -e "    ${RED}→ 封禁失败: ${ip}${NC}"
                fi
            else
                echo -e "    ${YELLOW}→ 跳过已封禁IP: ${ip}${NC}"
            fi
        done < ${temp_ban_file}
    fi
    
    rm -f ${temp_ban_file} ${processed_ips}
}

# 获取需要扫描的日志文件列表(基于时间范围)
get_log_files_by_months() {
    local months=$1
    local log_files=()
    
    # 当前日志文件
    if [ -f "${SECURE_LOG}" ]; then
        log_files+=("${SECURE_LOG}")
    fi
    
    # 计算起始日期
    local start_date=$(date -d "-${months} months" +%s 2>/dev/null)
    if [ -n "$start_date" ]; then
        # 扫描轮转日志
        for rotated in ${SECURE_LOG}-* ${SECURE_LOG}-*.gz; do
            if [ -f "$rotated" ]; then
                # 提取日志文件中的日期
                local file_date=""
                if [[ "$rotated" =~ ([0-9]{8}) ]]; then
                    file_date="${BASH_REMATCH[1]}"
                elif [[ "$rotated" =~ ([0-9]{6}) ]]; then
                    file_date="${BASH_REMATCH[1]}"
                fi
                
                if [ -n "$file_date" ] && [ ${#file_date} -ge 6 ]; then
                    local year="${file_date:0:4}"
                    local month="${file_date:4:2}"
                    local day="${file_date:6:2}"
                    local file_timestamp=$(date -d "${year}-${month}-${day}" +%s 2>/dev/null)
                    if [ -n "$file_timestamp" ] && [ $file_timestamp -ge $start_date ]; then
                        log_files+=("$rotated")
                    fi
                else
                    # 无法判断日期,包含所有轮转日志
                    log_files+=("$rotated")
                fi
            fi
        done
    else
        # 无法计算日期,包含所有轮转日志
        for rotated in ${SECURE_LOG}-* ${SECURE_LOG}-*.gz; do
            [ -f "$rotated" ] && log_files+=("$rotated")
        done
    fi
    
    # 去重并返回
    printf '%s\n' "${log_files[@]}" | sort -u
}

# 优化的SSH扫描(支持动态时间范围)
scan_ssh_dynamic() {
    local scan_months=${1:-24}
    auto_fix_iptables
    fix_iptables_rules
    
    echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${BLUE}║       动态时间范围SSH攻击扫描 - 永久封禁模式(极致优化版)                           ║${NC}"
    echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
    echo -e "${YELLOW}扫描时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
    echo -e "${YELLOW}扫描范围: 最近 ${scan_months} 个月${NC}"
    echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
    echo -e "${YELLOW}豁免列表: 自动跳过手动解封的IP${NC}"
    echo -e "${YELLOW}豁免文件: ${UNBAN_PERMANENT}${NC}"
    echo ""
    
    echo -e "${CYAN}[1/3] 智能收集日志文件(最近${scan_months}个月)...${NC}"
    local LOG_FILES=($(get_log_files_by_months ${scan_months}))
    echo -e "${GREEN}✓ 找到 ${#LOG_FILES[@]} 个日志文件${NC}"
    for log in "${LOG_FILES[@]}"; do
        echo -e "  - $(basename $log)"
    done
    echo ""
    
    echo -e "${CYAN}[2/3] 极速扫描并实时封禁(自动跳过豁免IP)...${NC}"
    echo ""
    local file_count=0
    local total_files=${#LOG_FILES[@]}
    for log_file in "${LOG_FILES[@]}"; do
        file_count=$((file_count + 1))
        echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
        echo -e "${CYAN}[${file_count}/${total_files}] 正在处理: $(basename $log_file)${NC}"
        scan_single_log_optimized "$log_file"
        local current_bans=$(grep -c "PERMANENT_BAN" ${BAN_LIST} 2>/dev/null || echo 0)
        local current_exempt=$(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)
        echo -e "${GREEN}  当前已封禁IP总数: ${current_bans} | 豁免IP数: ${current_exempt}${NC}"
        echo ""
    done
    
    echo -e "${CYAN}[3/3] 生成扫描报告...${NC}"
    echo ""
    echo -e "${GREEN}══════════════════════════════════════════════════════════════════════════════════════${NC}"
    echo -e "${GREEN}动态扫描完成统计:${NC}"
    echo -e "  扫描时间范围: 最近 ${scan_months} 个月"
    echo -e "  扫描日志文件数: ${#LOG_FILES[@]}"
    echo -e "  当前总封禁IP数: $(grep -c "PERMANENT_BAN" ${BAN_LIST} 2>/dev/null || echo 0)"
    echo -e "  当前豁免IP数: $(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)"
    echo -e "  处理方式: grep+awk极速处理 + 自动跳过豁免IP"
    echo -e "${GREEN}══════════════════════════════════════════════════════════════════════════════════════${NC}"
    
    # 确保iptables规则生效
    fix_iptables_rules
    generate_report
}

# 兼容旧版scan_2years函数
scan_2years() {
    scan_ssh_dynamic 24
}

# 优化的全面扫描函数
full_scan_optimized() {
    auto_fix_iptables
    fix_iptables_rules
    
    echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${BLUE}║       优化全面扫描所有服务 - 永久封禁模式(极致优化版)                               ║${NC}"
    echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
    echo -e "${YELLOW}扫描时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
    echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
    echo -e "${YELLOW}豁免列表: 自动跳过手动解封的IP${NC}"
    echo ""
    echo -e "${CYAN}[1/2] 扫描secure日志(SSH攻击)...${NC}"
    scan_ssh_dynamic ${SCAN_MONTHS}
    echo -e "${CYAN}[2/2] 生成全面扫描报告...${NC}"
    generate_full_report
}

# 生成全面扫描报告
generate_full_report() {
    local report_file="${REPORT_DIR}/full-scan-report-$(date +%Y%m%d_%H%M%S).txt"
    {
        echo "╔════════════════════════════════════════════════════════════════════════════════════════════╗"
        echo "║                    Fail2Ban 优化全面扫描永久封禁报告                                       ║"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║ 报告时间: $(date '+%Y-%m-%d %H:%M:%S')"
        echo "║ 封禁模式: 永久封禁 (bantime = -1)"
        echo "║ 扫描范围: SSH服务"
        echo "║ 扫描方式: grep+awk极速扫描 + 自动跳过豁免IP"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║ 封禁IP统计:"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        if [ -f "${BAN_LIST}" ]; then
            local total_bans=$(grep -c "PERMANENT_BAN" ${BAN_LIST} 2>/dev/null || echo 0)
            echo "║  SSH服务封禁数: ${total_bans}"
        fi
        if [ -f "${UNBAN_PERMANENT}" ]; then
            local exempt_count=$(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)
            echo "║  永久豁免IP数: ${exempt_count}"
        fi
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        echo "║ iptables 全局封禁统计:"
        echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
        local total_drop=$(iptables-save 2>/dev/null | grep "^-A fail2ban" | grep "DROP" | wc -l)
        echo "║  fail2ban链DROP规则总数: ${total_drop}"
        echo "╚════════════════════════════════════════════════════════════════════════════════════════════╝"
    } > "$report_file"
    echo -e "${GREEN}[报告] 全面扫描报告已保存: ${report_file}${NC}"
}

# 查看全面扫描日志
view_full_scan_log() {
    if [ -f "${FULL_SCAN_LOG}" ]; then
        echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
        echo -e "${BLUE}║              全面扫描日志(最近50条)                                                ║${NC}"
        echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
        tail -50 ${FULL_SCAN_LOG} | while read line; do
            echo "$line" | sed 's/|/ │ /g'
        done
    else
        echo -e "${YELLOW}全面扫描日志文件不存在${NC}"
    fi
}

# 列出所有封禁IP
list_all_banned() {
    echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${BLUE}║              所有服务永久封禁IP列表                                                    ║${NC}"
    echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
    echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
    echo -e "${YELLOW}查询时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
    echo ""
    
    local banned=$(fail2ban-client get sshd banip 2>/dev/null)
    if [ -n "$banned" ] && [ "$banned" != "None" ]; then
        local ban_count=$(echo "$banned" | wc -w)
        echo -e "${CYAN}当前封禁IP列表 (${ban_count}个):${NC}"
        echo "────────────────────────────────────────────────────────"
        echo "$banned" | tr ' ' '\n' | while read ip; do
            [ -n "$ip" ] && echo "  • $ip"
        done
        echo ""
        echo -e "${GREEN}总计封禁IP数: ${ban_count}${NC}"
    else
        echo -e "${GREEN}暂无任何封禁IP${NC}"
    fi
    
    echo ""
    echo -e "${YELLOW}iptables DROP 规则:${NC}"
    iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | while read line; do
        echo "  $line"
    done
}

# 列出封禁IP(带详情)
list_banned() {
    echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${BLUE}║              当前永久封禁IP列表(含攻击详情)                                          ║${NC}"
    echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
    echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
    echo -e "${YELLOW}查询时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
    echo ""
    
    BANNED_IPS=$(fail2ban-client get sshd banip 2>/dev/null)
    if [ -n "$BANNED_IPS" ] && [ "$BANNED_IPS" != "None" ]; then
        echo "┌────┬──────────────────┬────────────────────┬────────────────────────┬──────────────┬───────────────┐"
        printf "│%-4s│%-18s│%-20s│%-24s│%-14s│%-15s│\n" "序号" "IP地址" "封禁时间" "最后攻击时间" "用户名" "密码"
        echo "├────┼──────────────────┼────────────────────┼────────────────────────┼──────────────┼───────────────┤"
        local count=1
        echo "$BANNED_IPS" | tr ' ' '\n' | while read ip; do
            if [ -n "$ip" ]; then
                local ban_date=$(grep "ip=${ip}" ${BAN_LIST} 2>/dev/null | tail -1 | grep -oP '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' || echo "永久")
                local attack_info=$(get_attack_details_fast "$ip")
                local last_time=$(echo "$attack_info" | cut -d'|' -f1)
                local username=$(echo "$attack_info" | cut -d'|' -f2)
                local password=$(echo "$attack_info" | cut -d'|' -f3)
                printf "│%-4s│%-18s│%-20s│%-24s│%-14s│%-15s│\n" "$count" "$ip" "$ban_date" "$last_time" "${username:0:12}" "${password:0:13}"
                count=$((count + 1))
            fi
        done
        echo "└────┴──────────────────┴────────────────────┴────────────────────────┴──────────────┴───────────────┘"
    else
        echo -e "${GREEN}暂无封禁IP${NC}"
    fi
    
    echo ""
    echo -e "${YELLOW}iptables DROP 规则:${NC}"
    iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | while read line; do
        echo "  $line"
    done
}

# 查看攻击日志
view_attack_log() {
    if [ -f "${ATTACK_LOG}" ]; then
        echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
        echo -e "${BLUE}║              攻击详情日志(最近50条)                                                 ║${NC}"
        echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
        tail -50 ${ATTACK_LOG} | while read line; do
            echo "$line" | sed 's/|/ │ /g'
        done
    else
        echo -e "${YELLOW}攻击详情日志文件不存在${NC}"
    fi
}

# 动态月份扫描函数
scan_custom_months() {
    local months=$1
    if [ -z "$months" ] || [ "$months" -lt 1 ]; then
        echo -e "${RED}错误: 请输入有效的月份数(至少1个月)${NC}"
        echo "用法: quick_ban -m <月份数>"
        return 1
    fi
    scan_ssh_dynamic $months
}

# ============================================
# 命令行参数处理
# ============================================
case "$1" in
    -s) scan_2years ;;
    -m) shift; scan_custom_months $1 ;;
    -l) list_banned ;;
    -r) generate_report ;;
    -a) view_attack_log ;;
    -f) full_scan_optimized ;;
    -F) list_all_banned ;;
    -L) view_full_scan_log ;;
    -u) shift; manual_unban $1 ;;
    -e) list_exempted ;;
    -d) shift; remove_from_exempt_list $1 ;;
    *)
        echo "用法: quick_ban [选项]"
        echo ""
        echo "扫描选项:"
        echo "  -s              扫描最近2年攻击日志"
        echo "  -m <月份>       扫描最近N个月攻击日志"
        echo "  -f              优化全面扫描所有服务"
        echo ""
        echo "查看选项:"
        echo "  -l              查看封禁列表(含攻击详情)"
        echo "  -F              列出所有封禁IP"
        echo "  -r              生成拦截报告"
        echo "  -a              查看攻击日志"
        echo "  -L              查看全面扫描日志"
        echo "  -e              查看永久豁免列表"
        echo ""
        echo "管理选项:"
        echo "  -u <IP>         手动解封IP并加入永久豁免列表"
        echo "  -d <IP>         从豁免列表中移除IP"
        ;;
esac
EOF
    
    chmod +x /usr/local/bin/quick_ban
    print_info "quick_ban脚本创建完成(优化版 - 支持永久豁免列表)"
}

# ============================================
# 启动服务
# ============================================
start_services() {
    print_step "5" "启动服务并验证(永久封禁模式)"
    
    # 重置失败状态
    systemctl reset-failed fail2ban.service 2>/dev/null
    rm -f /var/run/fail2ban/fail2ban.sock
    
    systemctl start fail2ban
    systemctl enable fail2ban
    sleep 3
    
    if systemctl is-active --quiet fail2ban; then
        print_info "Fail2Ban服务启动成功(永久封禁模式)"
        echo ""
        echo -e "${YELLOW}验证iptables规则:${NC}"
        
        # 创建iptables链
        if ! iptables -L fail2ban-sshd &>/dev/null; then
            echo -e "${YELLOW}[修复] 主动创建 iptables 链...${NC}"
            iptables -N fail2ban-sshd 2>/dev/null
            iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
            iptables -I INPUT -p tcp --dport 22 -j fail2ban-sshd 2>/dev/null
            service iptables save 2>/dev/null
            echo -e "${GREEN}[修复] iptables 链创建成功${NC}"
        fi
        
        # 测试永久封禁功能
        echo ""
        echo -e "${YELLOW}测试永久封禁功能:${NC}"
        TEST_IP="192.168.255.254"
        
        # 检查测试IP是否已存在
        if ! iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${TEST_IP}"; then
            if fail2ban-client set sshd banip ${TEST_IP} 2>/dev/null; then
                sleep 2
                if iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${TEST_IP}"; then
                    echo -e "${GREEN}[测试] 永久封禁功能正常${NC}"
                    fail2ban-client set sshd unbanip ${TEST_IP} 2>/dev/null
                    iptables -D fail2ban-sshd -s ${TEST_IP} -j DROP 2>/dev/null
                else
                    echo -e "${YELLOW}[测试] 封禁规则可能未生效,尝试手动修复...${NC}"
                    iptables -A fail2ban-sshd -s ${TEST_IP} -j DROP 2>/dev/null
                    if iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${TEST_IP}"; then
                        echo -e "${GREEN}[测试] 手动修复成功${NC}"
                        iptables -D fail2ban-sshd -s ${TEST_IP} -j DROP 2>/dev/null
                    fi
                fi
            else
                echo -e "${YELLOW}[测试] 封禁测试失败${NC}"
            fi
        else
            echo -e "${YELLOW}[测试] 测试IP已存在,跳过测试${NC}"
        fi
        
        # 确保iptables规则持久化
        service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables 2>/dev/null
        
    else
        echo -e "${RED}[ERROR]${NC} Fail2Ban服务启动失败"
        journalctl -u fail2ban -n 30 --no-pager
        exit 1
    fi
}

# ============================================
# 配置定时任务
# ============================================
configure_crontab() {
    print_step "6" "配置定时任务"
    
    (
        crontab -l 2>/dev/null | grep -v "quick_ban"
        echo "# Fail2Ban - 每小时执行一次永久封禁扫描(最近2年)"
        echo "0 * * * * /usr/local/bin/quick_ban -s >> /var/log/fail2ban-hourly.log 2>&1"
        echo "# Fail2Ban - 每天凌晨2点执行优化全面扫描"
        echo "0 2 * * * /usr/local/bin/quick_ban -f >> /var/log/fail2ban-full-scan-daily.log 2>&1"
        echo "# Fail2Ban - 每30分钟修复iptables规则"
        echo "*/30 * * * * /usr/sbin/iptables -L fail2ban-sshd &>/dev/null || (iptables -N fail2ban-sshd && iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd) 2>/dev/null"
    ) | crontab -
    
    systemctl restart crond
    print_info "定时任务配置完成(每小时执行SSH封禁扫描,每天凌晨2点执行优化全面扫描)"
}

# ============================================
# 显示总结
# ============================================
show_summary() {
    echo ""
    echo -e "${GREEN}╔════════════════════════════════════════════════╗${NC}"
    echo -e "${GREEN}║           安装完成!                           ║${NC}"
    echo -e "${GREEN}╚════════════════════════════════════════════════╝${NC}"
    
    echo -e "\n${YELLOW}配置信息:${NC}"
    echo "  SSH端口: ${SSH_PORT}"
    echo "  封禁模式: ${GREEN}永久封禁${NC} (bantime = -1)"
    echo "  失败阈值: ${MAX_RETRY}次/${FIND_TIME}秒"
    echo "  扫描范围: ${GREEN}动态时间范围(支持自定义月份)${NC}"
    echo "  优化模式: ${GREEN}grep+awk极速处理 + 永久豁免列表${NC}"
    
    echo -e "\n${GREEN}常用命令:${NC}"
    echo "  quick_ban -s           # 扫描最近2年攻击日志"
    echo "  ${CYAN}quick_ban -m 12      # 扫描最近12个月攻击日志${NC}"
    echo "  ${CYAN}quick_ban -m 6       # 扫描最近6个月攻击日志${NC}"
    echo "  ${CYAN}quick_ban -m 3       # 扫描最近3个月攻击日志${NC}"
    echo "  quick_ban -l           # 查看永久封禁列表(含攻击详情)"
    echo "  quick_ban -r           # 生成拦截报告"
    echo "  quick_ban -a           # 查看攻击详情日志"
    echo "  ${CYAN}quick_ban -f         # 优化全面扫描所有服务${NC}"
    echo "  ${CYAN}quick_ban -F         # 列出所有服务的封禁IP${NC}"
    echo "  ${CYAN}quick_ban -L         # 查看全面扫描日志${NC}"
    
    echo -e "\n${GREEN}豁免列表管理(新增):${NC}"
    echo "  ${CYAN}quick_ban -u 1.2.3.4  # 手动解封IP并加入永久豁免列表${NC}"
    echo "  ${CYAN}quick_ban -e          # 查看永久豁免列表${NC}"
    echo "  ${CYAN}quick_ban -d 1.2.3.4  # 从豁免列表移除IP${NC}"
    
    echo -e "\n${YELLOW}日志文件:${NC}"
    echo "  攻击详情日志: ${ATTACK_LOG}"
    echo "  全面扫描日志: ${FULL_SCAN_LOG}"
    echo "  报告目录: ${REPORT_DIR}"
    echo "  豁免列表: /var/lib/fail2ban/unban-permanent.list"
    
    echo -e "\n${YELLOW}性能提升说明:${NC}"
    echo "  ✓ 使用grep+awk替代bash循环(20-100倍速度提升)"
    echo "  ✓ 智能时间范围过滤,只扫描指定月份的日志"
    echo "  ✓ 防止重复封禁同一IP"
    echo "  ✓ 永久豁免列表,手动解封后不再封禁"
    echo "  ✓ iptables规则自动修复和持久化"
    
    echo -e "\n${YELLOW}永久封禁管理:${NC}"
    echo "  手动封禁: fail2ban-client set sshd banip IP地址"
    echo "  手动解封: quick_ban -u IP地址  # 会自动加入豁免列表"
    echo "  直接解封: fail2ban-client set sshd unbanip IP地址"
    echo "  查看封禁: iptables -L fail2ban-sshd -n"
    
    echo -e "\n${RED}⚠ 重要提醒:${NC}"
    echo "  SSH端口已修改为 ${SSH_PORT}"
    echo "  测试连接: ssh -p ${SSH_PORT} root@服务器IP"
    echo "  ${YELLOW}封禁模式为永久封禁,建议使用 quick_ban -u IP 解封${NC}"
    echo ""
}

# ============================================
# 主程序
# ============================================
main() {
    clear
    echo -e "${BLUE}"
    echo "╔════════════════════════════════════════════════╗"
    echo "║   Fail2Ban SSH安全防护一键安装脚本 v7.1       ║"
    echo "║   SSH端口: 22 | 永久封禁 | 永久豁免列表    ║"
    echo "║   优化: grep+awk极速处理 + 智能跳过豁免IP     ║"
    echo "╚════════════════════════════════════════════════╝"
    echo -e "${NC}"
    
    check_root
    test_functions
    install_dependencies
    configure_ssh
    configure_fail2ban
    create_quick_ban_script
    start_services
    configure_crontab
    show_summary
    
    echo -e "${YELLOW}请选择扫描范围:${NC}"
    echo "  1) 最近24个月(默认)"
    echo "  2) 最近12个月"
    echo "  3) 最近6个月"
    echo "  4) 最近3个月"
    echo "  5) 自定义月份"
    echo "  6) 跳过扫描"
    echo ""
    read -p "请选择 [1-6]: " scan_choice
    
    case $scan_choice in
        2) SCAN_MONTHS=12; /usr/local/bin/quick_ban -m 12 ;;
        3) SCAN_MONTHS=6; /usr/local/bin/quick_ban -m 6 ;;
        4) SCAN_MONTHS=3; /usr/local/bin/quick_ban -m 3 ;;
        5) read -p "请输入月份数: " custom_months
           if [[ $custom_months =~ ^[0-9]+$ ]] && [ $custom_months -ge 1 ]; then
               SCAN_MONTHS=$custom_months
               /usr/local/bin/quick_ban -m $custom_months
           else
               echo -e "${YELLOW}输入无效,跳过扫描${NC}"
           fi
           ;;
        6) echo -e "${YELLOW}跳过初始扫描${NC}" ;;
        *) SCAN_MONTHS=24; /usr/local/bin/quick_ban -m 24 ;;
    esac
    
    echo -e "\n${YELLOW}是否执行全面扫描(所有服务)?[y/N]${NC}"
    read -r answer
    if [[ $answer =~ ^[Yy]$ ]]; then
        /usr/local/bin/quick_ban -f
    fi
}

# 运行
main "$@"

四、安装过程记录

豁免列表管理(新增):
  quick_ban -u 1.2.3.4  # 手动解封IP并加入永久豁免列表
  quick_ban -e          # 查看永久豁免列表
  quick_ban -d 1.2.3.4  # 从豁免列表移除IP
日志文件:
  攻击详情日志: /var/log/fail2ban-attack-details.log
  全面扫描日志: /var/log/fail2ban-full-scan.log
  报告目录: /var/log/fail2ban-reports
  豁免列表: /var/lib/fail2ban/unban-permanent.list
性能提升说明:
  ✓ 使用grep+awk替代bash循环(20-100倍速度提升)
  ✓ 智能时间范围过滤,只扫描指定月份的日志
  ✓ 防止重复封禁同一IP
  ✓ 永久豁免列表,手动解封后不再封禁
  ✓ iptables规则自动修复和持久化
永久封禁管理:
  手动封禁: fail2ban-client set sshd banip IP地址
  手动解封: quick_ban -u IP地址  # 会自动加入豁免列表
  直接解封: fail2ban-client set sshd unbanip IP地址
  查看封禁: iptables -L fail2ban-sshd -n
  测试连接: ssh -p 22 root@服务器IP

posted on 2026-08-10 15:24  爱河  阅读(1)  评论(0)    收藏  举报

导航