#!/usr/bin/env bash
#=============================================================================
# Sentinela ? Intelligent Linux Security Framework
# firewall/firewall.sh ? Motor principal del firewall
#=============================================================================
#
# Este módulo construye las reglas de iptables de forma modular.
# NO ejecuta iptables -F ni iptables -P INPUT DROP.
# Solo crea/actualiza sus propias cadenas: SENTINELA_*
#
# Flujo de tráfico:
# INPUT ? SENTINELA_INPUT ? SENTINELA_NEW ? DROP
#
# Reglas en SENTINELA_INPUT (orden):
# 1. loopback (ACCEPT)
# 2. ESTABLISHED,RELATED (ACCEPT)
# 3. ICMP (ACCEPT con límite)
# 4. Anti-spoofing (DROP)
# 5. Port scan protection (LOG + DROP)
# 6. SYN flood protection
# 7. TRUSTED ipset (ACCEPT)
# 8. BLACKLIST ipset (DROP + LOG)
# 9. DDOS_ATTACKERS ipset (DROP + LOG)
# 10. REPUTATION_NET ipset (DROP + LOG)
# 11. HONEY_TRAP ipset (DROP + LOG)
# 12. Reglas de servicio (SSH, HTTP, etc.)
# 13. Log y DROP final
#=============================================================================
# shellcheck source=lib/common.sh
source "${SENTINELA_DIR}/lib/common.sh"
source "${SENTINELA_DIR}/lib/ssh-session.sh"
#=============================================================================
# Constantes de cadenas
#=============================================================================
CHAIN_INPUT="SENTINELA_INPUT"
CHAIN_NEW="SENTINELA_NEW"
#=============================================================================
# Inicializar cadenas de Sentinela
# Crea las cadenas si no existen y las engancha a INPUT
#=============================================================================
firewall_init_chains() {
log_info "Inicializando cadenas Sentinela..."
# Crear cadena INPUT
if ! ${IPTABLES} -L "${CHAIN_INPUT}" &>/dev/null; then
${IPTABLES} -N "${CHAIN_INPUT}"
log_info "Cadena ${CHAIN_INPUT} creada"
else
# Flush pero no borrar
${IPTABLES} -F "${CHAIN_INPUT}"
log_info "Cadena ${CHAIN_INPUT} limpiada"
fi
# Crear cadena NEW
if ! ${IPTABLES} -L "${CHAIN_NEW}" &>/dev/null; then
${IPTABLES} -N "${CHAIN_NEW}"
log_info "Cadena ${CHAIN_NEW} creada"
else
${IPTABLES} -F "${CHAIN_NEW}"
log_info "Cadena ${CHAIN_NEW} limpiada"
fi
# Enganchar a INPUT si no está
if ! ${IPTABLES} -C INPUT -j "${CHAIN_INPUT}" &>/dev/null; then
${IPTABLES} -A INPUT -j "${CHAIN_INPUT}"
log_info "INPUT ? ${CHAIN_INPUT} enganchado"
fi
}
#=============================================================================
# Reglas base de SENTINELA_INPUT
#=============================================================================
firewall_base_rules() {
log_info "Aplicando reglas base..."
# 1. loopback
${IPTABLES} -A "${CHAIN_INPUT}" -i lo -j ACCEPT
# 2. ESTABLISHED,RELATED
${IPTABLES} -A "${CHAIN_INPUT}" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 3. ICMP (con límite)
${IPTABLES} -A "${CHAIN_INPUT}" -p icmp --icmp-type echo-request -m limit --limit 1/second -j ACCEPT
${IPTABLES} -A "${CHAIN_INPUT}" -p icmp -j ACCEPT
# 4. Anti-spoofing (RFC 1918 y marians en interfaces WAN)
local iface="${IFACE}"
${IPTABLES} -A "${CHAIN_INPUT}" -i "${iface}" -s 10.0.0.0/8 -j LOG --log-prefix "SENTINELA:SPOOF: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -i "${iface}" -s 10.0.0.0/8 -j DROP
${IPTABLES} -A "${CHAIN_INPUT}" -i "${iface}" -s 172.16.0.0/12 -j LOG --log-prefix "SENTINELA:SPOOF: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -i "${iface}" -s 172.16.0.0/12 -j DROP
${IPTABLES} -A "${CHAIN_INPUT}" -i "${iface}" -s 192.168.0.0/16 -j LOG --log-prefix "SENTINELA:SPOOF: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -i "${iface}" -s 192.168.0.0/16 -j DROP
# 5. TRUSTED ipset (sesión SSH y whitelist)
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set "${TRUSTED_IPSET}" src -j ACCEPT
# 6. BLACKLIST ipset
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_BLACKLIST src -j LOG --log-prefix "SENTINELA:BLACKLIST: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_BLACKLIST src -j DROP
# 7. DDOS_ATTACKERS ipset
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_DDOS src -j LOG --log-prefix "SENTINELA:DDOS: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_DDOS src -j DROP
# 8. REPUTATION_NET ipset
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_REPUTATION src -j LOG --log-prefix "SENTINELA:REPUTATION: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_REPUTATION src -j DROP
# 9. HONEY_TRAP ipset
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_HONEY src -j LOG --log-prefix "SENTINELA:HONEY: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -m set --match-set SENTINELA_HONEY src -j DROP
# 10. Port scan protection
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --tcp-flags ALL NONE -j LOG --log-prefix "SENTINELA:SCAN_NULL: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --tcp-flags ALL NONE -j DROP
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --tcp-flags ALL ALL -j LOG --log-prefix "SENTINELA:SCAN_XMAS: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --tcp-flags ALL ALL -j DROP
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --tcp-flags SYN,FIN SYN,FIN -j LOG --log-prefix "SENTINELA:SCAN_SYNFIN: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
# 11. SYN flood protection
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --syn -m limit --limit 100/second --limit-burst 200 -j ACCEPT
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --syn -j LOG --log-prefix "SENTINELA:SYNFLOOD: " --log-uid
${IPTABLES} -A "${CHAIN_INPUT}" -p tcp --syn -j DROP
log_info "Reglas base aplicadas"
}
#=============================================================================
# Reglas de servicio (despacha a módulos específicos)
#=============================================================================
firewall_service_rules() {
log_info "Aplicando reglas de servicio..."
# SSH
if [[ "${ENABLE_SSH}" == "yes" ]]; then
source "${SENTINELA_FIREWALL}/ssh.sh"
ssh_firewall_rules
fi
# HTTP
if [[ "${ENABLE_HTTP}" == "yes" ]]; then
source "${SENTINELA_FIREWALL}/http.sh"
http_firewall_rules
fi
# HTTPS
if [[ "${ENABLE_HTTPS}" == "yes" ]]; then
source "${SENTINELA_FIREWALL}/https.sh"
https_firewall_rules
fi
# MySQL
if [[ "${ENABLE_MYSQL}" == "yes" ]]; then
source "${SENTINELA_FIREWALL}/mysql.sh"
mysql_firewall_rules
fi
}
#=============================================================================
# Reglas finales de SENTINELA_INPUT (log y drop)
#=============================================================================
firewall_final_rules() {
log_info "Aplicando reglas finales..."
# Enviar a SENTINELA_NEW para paquetes NEW
${IPTABLES} -A "${CHAIN_INPUT}" -m conntrack --ctstate NEW -j "${CHAIN_NEW}"
# Log de todo lo que llega al final
${IPTABLES} -A "${CHAIN_INPUT}" -j LOG --log-prefix "SENTINELA:DROP: " --log-uid
# Drop final
${IPTABLES} -A "${CHAIN_INPUT}" -j DROP
}
#=============================================================================
# Reglas de SENTINELA_NEW (conexiones nuevas)
#=============================================================================
firewall_new_rules() {
log_info "Aplicando reglas para conexiones nuevas..."
# Log de nuevas conexiones que caen aquí
${IPTABLES} -A "${CHAIN_NEW}" -m limit --limit 5/min -j LOG --log-prefix "SENTINELA:NEW_DROP: " --log-uid
# Drop
${IPTABLES} -A "${CHAIN_NEW}" -j DROP
}
#=============================================================================
# Aplicar firewall completo
#=============================================================================
firewall_apply() {
require_root
log_info "=== Aplicando firewall ==="
# Proteger sesión SSH actual antes de tocar reglas
ssh_protect_current_session
# Inicializar cadenas
firewall_init_chains
# Reglas base
firewall_base_rules
# Reglas de servicio
firewall_service_rules
# Reglas finales
firewall_final_rules
# Reglas NEW
firewall_new_rules
log_info "=== Firewall aplicado exitosamente ==="
log_info "Resumen de reglas:"
${IPTABLES} -L "${CHAIN_INPUT}" -n --line-numbers 2>/dev/null | tail -n +2 | awk '{print " " $0}'
${IPTABLES} -L "${CHAIN_NEW}" -n --line-numbers 2>/dev/null | tail -n +2 | awk '{print " " $0}'
return 0
}
#=============================================================================
# Limpiar solo las cadenas de Sentinela
# NO toca otras reglas ni políticas por defecto
#=============================================================================
firewall_stop() {
require_root
log_info "Deteniendo firewall Sentinela..."
# Remover enganche de INPUT
if ${IPTABLES} -C INPUT -j "${CHAIN_INPUT}" &>/dev/null; then
${IPTABLES} -D INPUT -j "${CHAIN_INPUT}"
fi
# Flush y borrar cadenas
${IPTABLES} -F "${CHAIN_NEW}" 2>/dev/null || true
${IPTABLES} -F "${CHAIN_INPUT}" 2>/dev/null || true
${IPTABLES} -X "${CHAIN_NEW}" 2>/dev/null || true
${IPTABLES} -X "${CHAIN_INPUT}" 2>/dev/null || true
log_info "Firewall Sentinela detenido. Reglas originales intactas."
log_warn "Los ipsets (BLACKLIST, DDOS, etc.) NO se borraron."
}
#=============================================================================
# Mostrar estado del firewall
#=============================================================================
firewall_status() {
echo
echo "??? Sentinela ? Estado del Firewall ???"
echo
# Cadenas Sentinela
if ${IPTABLES} -L "${CHAIN_INPUT}" -n &>/dev/null; then
local count
count=$(${IPTABLES} -L "${CHAIN_INPUT}" -n 2>/dev/null | tail -n +3 | wc -l)
echo " ? ${CHAIN_INPUT}: ${count} reglas activas"
else
echo " ? ${CHAIN_INPUT}: no existe"
fi
if ${IPTABLES} -L "${CHAIN_NEW}" -n &>/dev/null; then
local count
count=$(${IPTABLES} -L "${CHAIN_NEW}" -n 2>/dev/null | tail -n +3 | wc -l)
echo " ? ${CHAIN_NEW}: ${count} reglas activas"
else
echo " ? ${CHAIN_NEW}: no existe"
fi
# Verificar enganche
if ${IPTABLES} -C INPUT -j "${CHAIN_INPUT}" &>/dev/null; then
echo " ? INPUT ? ${CHAIN_INPUT}: enganchado"
else
echo " ? INPUT ? ${CHAIN_INPUT}: NO enganchado"
fi
echo
echo "??? Ipsets ???"
for set_name in SENTINELA_TRUSTED SENTINELA_BLACKLIST SENTINELA_DDOS SENTINELA_REPUTATION SENTINELA_HONEY; do
if ${IPSET} list "${set_name}" &>/dev/null; then
local entries
entries=$(${IPSET} list "${set_name}" 2>/dev/null | grep -cE '^[0-9]' || true)
echo " ? ${set_name}: ${entries} entradas"
else
echo " - ${set_name}: no existe"
fi
done
echo
echo "??? Tráfico (conexiones activas) ???"
local conn_count
conn_count=$(ss -tn state established 2>/dev/null | tail -n +2 | wc -l)
echo " Conexiones establecidas: ${conn_count}"
local listen_count
listen_count=$(ss -tln 2>/dev/null | tail -n +2 | wc -l)
echo " Puertos en escucha: ${listen_count}"
echo
}
|