#!/bin/bash
#############################################################################################
# Script original: Daniel Brunod (Craftz) - https://github.com/CraftzAdmin/homelab
# Uso autorizado, adaptado e ampliado com base no espírito de colaboração do Software Livre.
# Por favor, mantenha os créditos ao autor original se for usar ou compartilhar este script.
#
# Adaptações e melhorias por Caio Monteiro (Projeto Empresa Livre):
# - Instalação 100% limpa com verificação e correção de conflitos anteriores
# - Proteção modular e separada para: Admin Proxmox (8006), Admin PBS (8007), SSH (22) e pfSense (10443)
# - Opção para adicionar proteção de porta personalizada
# - Detecção e remoção segura de entradas duplicadas ou incorretas
# - Monitoramento funcional e real dos IPs banidos por jail
# - Interface simples e prática para ensino e administração segura em ambientes Proxmox
#
# Projeto Empresa Livre – Conheça nossa iniciativa de ensino prático em infraestrutura Open Source:
# 🌐 https://www.projetoempresalivre.com.br
#############################################################################################

set -euo pipefail

function header_info {
    clear
    cat <<"EOF"

╔════════════════════════════════════════════════════════════════╗
║        Gestão de Segurança - Projeto Empresa Livre (P.E.L)     ║
║     Proteja sua infraestrutura com Fail2Ban de forma limpa     ║
║                Simples, Didático e 100% Funcional              ║
╚════════════════════════════════════════════════════════════════╝

EOF
}

# --- Utilidades ---

function ensure_dirs {
    mkdir -p /etc/fail2ban/jail.d
    mkdir -p /etc/fail2ban/filter.d
}

function is_active {
    systemctl is-active --quiet fail2ban
}

# Garante que o arquivo de log da jail exista. Sem isso, em instalações sem
# rsyslog (comum em containers/Debian recentes), o Fail2Ban não encontra o
# logpath configurado e recusa iniciar o serviço inteiro, não só a jail.
function ensure_logpath {
    local logpath="${1}"
    if [[ ! -e "${logpath}" ]]; then
        echo "Aviso: ${logpath} não existe ainda. Criando arquivo vazio para evitar falha na inicialização do Fail2Ban."
        mkdir -p "$(dirname "${logpath}")"
        touch "${logpath}"
    fi
}

function start_fail2ban_if_needed {
    if ! is_active; then
        echo "Fail2Ban não está ativo. Iniciando..."
        systemctl start fail2ban || true
        sleep 1
        if ! is_active; then
            echo "Não foi possível iniciar o Fail2Ban. Verifique logs com: journalctl -u fail2ban -xe"
            return 1
        fi
    fi
}

# --- Ações principais ---

# Configura defaults globais (ban progressivo + allowlist) em jail.local.
# Pode ser rodada isoladamente (opção de menu) ou como parte da instalação.
function configure_global_defaults {
    echo ""
    echo "Configurando defaults globais (jail.local)..."
    read -p "Informe um IP/faixa fixo para NUNCA ser bloqueado (Enter para pular): " safe_ip

    local ignoreip="127.0.0.1/8 ::1"
    if [[ -n "${safe_ip}" ]]; then
        ignoreip="${ignoreip} ${safe_ip}"
    fi

    ensure_dirs
    cat <<EOL > /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = ${ignoreip}
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4w
EOL

    echo "jail.local criado com ignoreip='${ignoreip}' e ban progressivo (dobra a cada reincidência, até 4 semanas)."
}

# Jail que observa o próprio log do Fail2Ban e bane por muito mais tempo (todas
# as portas) quem já foi banido várias vezes — útil contra bots insistentes.
function setup_recidive_jail {
    ensure_dirs
    cat <<EOL > /etc/fail2ban/jail.d/recidive.conf
[recidive]
enabled   = true
filter    = recidive
logpath   = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime   = 1w
findtime  = 1d
maxretry  = 3
EOL
    echo "Jail 'recidive' configurada (bane por 1 semana quem levar 3 bans em 1 dia)."
}

function install_fail2ban {
    echo "Instalando e limpando Fail2Ban..."
    systemctl stop fail2ban 2>/dev/null || true
    apt-get update -y
    # Remove pacotes e config anteriores para instalação limpa
    apt-get purge -y fail2ban || true
    rm -rf /etc/fail2ban
    # rsyslog é necessário porque os jails padrão (Proxmox/PBS/pfSense) monitoram
    # /var/log/syslog; sem ele o arquivo nunca é criado e o Fail2Ban falha ao iniciar.
    apt-get install -y fail2ban rsyslog
    systemctl enable --now rsyslog
    systemctl enable fail2ban
    ensure_dirs
    # O pacote do Fail2Ban já habilita uma jail "sshd" por padrão (via
    # jail.d/defaults-debian.conf) apontando para /var/log/auth.log, mesmo antes
    # de você mexer em qualquer opção do menu. Se esse arquivo ainda não existir,
    # a validação falha. Garantimos os dois logs padrão aqui, de antemão.
    ensure_logpath "/var/log/syslog"
    ensure_logpath "/var/log/auth.log"
    configure_global_defaults
    setup_recidive_jail
    echo "Fail2Ban instalado e limpo com sucesso."
}

# add_jail NAME PORT FILTER LOGPATH [create_filter=true|false]
# Por padrão, cria um filtro básico. Para SSH, usamos o filtro nativo (create_filter=false).
function add_jail {
    local name="${1}"
    local port="${2}"
    local filter_name="${3}"
    local logpath="${4}"
    local create_filter="${5:-true}"

    ensure_dirs
    ensure_logpath "${logpath}"

    echo "Adicionando proteção para: ${name}"

    cat <<EOL > "/etc/fail2ban/jail.d/${name}.conf"
[${name}]
enabled  = true
port     = ${port}
filter   = ${filter_name}
logpath  = ${logpath}
maxretry = 3
bantime  = 1d
findtime = 2d
EOL

    # Cria filtro somente se solicitado (para não sobrescrever filtros nativos como o "sshd")
    if [[ "${create_filter}" == "true" ]]; then
        cat <<EOL > "/etc/fail2ban/filter.d/${filter_name}.conf"
[Definition]
# Ajuste este failregex conforme o padrão real de logs do seu serviço.
# Marcador <HOST> é obrigatório para que o Fail2Ban capture o IP.
failregex = .*authentication failure.*<HOST>.*
ignoreregex =
EOL
    fi

    echo "Jail ${name} configurado. Rode a opção de validar/reiniciar quando terminar de adicionar jails."
}

# Fluxo interativo para proteger uma porta/serviço que não está nas opções padrão.
function add_custom_jail {
    read -p "Nome da jail (identificador curto, ex: meuservico): " name
    if [[ -z "${name}" ]]; then
        echo "Nome inválido."
        return 1
    fi

    read -p "Porta a proteger: " port
    if [[ -z "${port}" ]]; then
        echo "Porta inválida."
        return 1
    fi

    read -p "Caminho do log a monitorar [/var/log/syslog]: " logpath
    logpath="${logpath:-/var/log/syslog}"

    echo "Será criado um filtro genérico (procura por 'authentication failure' com o IP de origem)."
    echo "Se o serviço tiver um padrão de log diferente, edite depois em /etc/fail2ban/filter.d/${name}.conf"
    read -p "Pressione Enter para continuar..."

    add_jail "${name}" "${port}" "${name}" "${logpath}" "true"
}

function validate_and_restart {
    # Rede de segurança: garante os logs padrão mesmo que o usuário nunca tenha
    # passado pelas opções 2-6 (a jail "sshd" do próprio pacote já vem habilitada).
    ensure_logpath "/var/log/syslog"
    ensure_logpath "/var/log/auth.log"
    echo "Validando configuração do Fail2Ban..."
    if fail2ban-client -t &>/dev/null; then
        echo "Configuração válida. Reiniciando Fail2Ban..."
        systemctl restart fail2ban
        sleep 1
        if is_active; then
            echo "Fail2Ban ativo."
            fail2ban-client status || true
        else
            echo "Falha ao iniciar o Fail2Ban. Verifique logs com: journalctl -u fail2ban -xe"
            return 1
        fi
    else
        echo "Erro encontrado na configuração (saída do teste abaixo):"
        fail2ban-client -t || true
        echo "Corrija os erros antes de reiniciar."
        return 1
    fi
}

function list_banned_ips {
    echo "Listando IPs banidos por jail:"
    start_fail2ban_if_needed || return 1

    # Obtém lista de jails de forma robusta
    local jails
    jails=$(fail2ban-client status 2>/dev/null | sed -n 's/.*Jail list:\s*//p' | tr -d ' ' | tr ',' ' ')
    if [[ -z "${jails}" ]]; then
        echo "Nenhuma jail encontrada. Crie/configure jails e valide (opção 8)."
        return 0
    fi

    for jail in ${jails}; do
        echo -e "\n🔒 Jail: ${jail}"
        # Mostra total e lista (se houver)
        fail2ban-client status "${jail}" 2>/dev/null | grep -E "Currently banned:|Banned IP list:" || \
            echo "Não foi possível ler status da jail '${jail}'."
    done
}

function unban_ip {
    read -p "Digite o IP a ser desbloqueado: " ip
    start_fail2ban_if_needed || return 1

    local jails
    jails=$(fail2ban-client status 2>/dev/null | sed -n 's/.*Jail list:\s*//p' | tr -d ' ' | tr ',' ' ')
    if [[ -z "${jails}" ]]; then
        echo "Nenhuma jail encontrada para aplicar unban."
        return 0
    fi

    local changed=0
    for jail in ${jails}; do
        if fail2ban-client set "${jail}" unbanip "${ip}" &>/dev/null; then
            changed=1
        fi
    done

    if [[ "${changed}" -eq 1 ]]; then
        echo "IP ${ip} desbloqueado (onde estava banido)."
    else
        echo "IP ${ip} não estava banido em nenhuma jail."
    fi
}

# --- Menu ---

while true; do
    header_info
    echo ""
    echo "Escolha uma opção:"
    echo "1. Instalar Fail2Ban com Verificação e Limpeza"
    echo "2. Adicionar proteção Admin Proxmox (porta 8006)"
    echo "3. Adicionar proteção Admin PBS (porta 8007)"
    echo "4. Adicionar proteção SSH (porta 22)"
    echo "5. Adicionar proteção Admin pfSense (porta 10443)"
    echo "6. Adicionar proteção para porta personalizada"
    echo "7. Aplicar hardening (ban progressivo, allowlist e jail recidive)"
    echo "8. Validar configuração e Reiniciar Fail2Ban"
    echo "9. Listar IPs Banidos"
    echo "10. Desbloquear um IP"
    echo "11. Sair"
    echo ""

    read -p "Opção: " opt

    case "${opt}" in
        1) install_fail2ban ;;
        2) add_jail "proxmox" "8006" "proxmox" "/var/log/syslog" "true" ;;
        3) add_jail "pbs"     "8007" "pbs"     "/var/log/syslog" "true" ;;
        4) # Usa filtro nativo do SSHD e NÃO cria filter.d/sshd.conf
           add_jail "sshd"    "22"   "sshd"    "/var/log/auth.log" "false" ;;
        5) add_jail "pfsense" "10443" "pfsense" "/var/log/syslog" "true" ;;
        6) add_custom_jail ;;
        7) configure_global_defaults; setup_recidive_jail ;;
        8) validate_and_restart ;;
        9) list_banned_ips ;;
        10) unban_ip ;;
        11) echo "Saindo..."; exit 0 ;;
        *) echo "Opção inválida." ;;
    esac

    echo ""
    read -p "Pressione Enter para voltar ao menu..."
done