#!/usr/bin/env bash
#=============================================================================
# Sentinela ? Intelligent Linux Security Framework
# lib/common.sh ? Funciones comunes y utilidades
#=============================================================================
set -Eeuo pipefail
#=============================================================================
# Constantes
#=============================================================================
SENTINELA_VERSION="1.0.0"
SENTINELA_NAME="Sentinela"
SENTINELA_DESC="Intelligent Linux Security Framework"
SENTINELA_DIR="/root/sentinela"
SENTINELA_CONFIG="${SENTINELA_DIR}/config.conf"
SENTINELA_LIB="${SENTINELA_DIR}/lib"
SENTINELA_FIREWALL="${SENTINELA_DIR}/firewall"
SENTINELA_IDS="${SENTINELA_DIR}/ids"
SENTINELA_IPSET="${SENTINELA_DIR}/ipset"
SENTINELA_TELEGRAM="${SENTINELA_DIR}/telegram"
SENTINELA_BACKUP="${SENTINELA_DIR}/backup"
SENTINELA_CRON="${SENTINELA_DIR}/cron"
SENTINELA_GEOIP="${SENTINELA_DIR}/geoip"
SENTINELA_LOGS="${SENTINELA_DIR}/logs"
SENTINELA_DASHBOARD="${SENTINELA_DIR}/dashboard"
SENTINELA_API="${SENTINELA_DIR}/api"
SENTINELA_SYSTEMD="${SENTINELA_DIR}/systemd"
SENTINELA_CLI="${SENTINELA_DIR}/cli"
#=============================================================================
# Cargar configuración
#=============================================================================
load_config() {
# Defaults antes de cargar config (para evitar unbound vars)
LOG_DIR="${LOG_DIR:-/var/log/sentinela}"
BACKUP_DIR="${BACKUP_DIR:-/var/backups/sentinela}"
DASHBOARD_DIR="${DASHBOARD_DIR:-/var/www/sentinela}"
TELEGRAM_ENABLE="${TELEGRAM_ENABLE:-no}"
ROLLBACK_ENABLE="${ROLLBACK_ENABLE:-yes}"
ROLLBACK_TIMEOUT="${ROLLBACK_TIMEOUT:-90}"
IDS_APACHE_LOG="${IDS_APACHE_LOG:-/var/log/apache2/access.log}"
IDS_SSH_LOG="${IDS_SSH_LOG:-/var/log/auth.log}"
REPUTATION_ENABLE="${REPUTATION_ENABLE:-no}"
BAN_TIME="${BAN_TIME:-600}"
DDOS_BAN_TIME="${DDOS_BAN_TIME:-600}"
PORT_SSH="${PORT_SSH:-7890}"
PORT_HTTP="${PORT_HTTP:-80}"
PORT_HTTPS="${PORT_HTTPS:-443}"
PORT_MYSQL="${PORT_MYSQL:-3306}"
SSH_RATE_LIMIT="${SSH_RATE_LIMIT:-5}"
HTTP_RATE_LIMIT="${HTTP_RATE_LIMIT:-60}"
HTTPS_RATE_LIMIT="${HTTPS_RATE_LIMIT:-60}"
SSH_CONNLIMIT="${SSH_CONNLIMIT:-3}"
HTTP_CONNLIMIT="${HTTP_CONNLIMIT:-30}"
HTTPS_CONNLIMIT="${HTTPS_CONNLIMIT:-30}"
LOG_LEVEL="${LOG_LEVEL:-info}"
BACKUP_RETENTION="${BACKUP_RETENTION:-7}"
ENABLE_SSH="${ENABLE_SSH:-yes}"
ENABLE_HTTP="${ENABLE_HTTP:-yes}"
ENABLE_HTTPS="${ENABLE_HTTPS:-yes}"
ENABLE_MYSQL="${ENABLE_MYSQL:-yes}"
# Preservar LOG_SYSTEM del entorno (permite override temporal)
local env_log_system="${LOG_SYSTEM:-}"
if [[ -f "${SENTINELA_CONFIG}" ]]; then
# shellcheck source=config.conf
source "${SENTINELA_CONFIG}"
fi
# Restaurar override del entorno si existe
if [[ -n "${env_log_system}" ]]; then
LOG_SYSTEM="${env_log_system}"
else
LOG_SYSTEM="${LOG_SYSTEM:-rsyslog}"
fi
# Si no se definió IFACE, detectarla
IFACE="${IFACE:-$(ip route get 1 2>/dev/null | awk '{print $5; exit}')}"
# Asegurar directorios de log
mkdir -p "${LOG_DIR}" "${BACKUP_DIR}" "${DASHBOARD_DIR}" 2>/dev/null || true
}
#=============================================================================
# Logging
#=============================================================================
log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" | tee -a "${LOG_DIR}/sentinela.log" >&2; }
log_warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $*" | tee -a "${LOG_DIR}/sentinela.log" >&2; }
log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "${LOG_DIR}/sentinela.log" >&2; }
log_fatal() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [FATAL] $*" | tee -a "${LOG_DIR}/sentinela.log" >&2; exit 1; }
log_debug() { if [[ "${LOG_LEVEL:-info}" == "debug" ]]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] [DEBUG] $*" | tee -a "${LOG_DIR}/sentinela.log" >&2; fi; }
log_attack() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ATTACK] $*" | tee -a "${LOG_DIR}/attacks.log" >&2; }
log_ban() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [BAN] $*" | tee -a "${LOG_DIR}/banned.log" >&2; }
#=============================================================================
# Verificar ejecución como root
#=============================================================================
require_root() {
if [[ $EUID -ne 0 ]]; then
log_fatal "Este comando debe ejecutarse como root"
fi
}
#=============================================================================
# Verificar binarios requeridos
#=============================================================================
require_binary() {
local cmd="$1"
if ! command -v "$cmd" &>/dev/null; then
log_fatal "Binario requerido no encontrado: ${cmd}. Instálelo con: apt-get install -y ${cmd}"
fi
}
#=============================================================================
# Detectar IP de sesión SSH actual
#=============================================================================
get_ssh_ip() {
local ssh_ip
ssh_ip=$(who -m 2>/dev/null | awk '{print $NF}' | tr -d '()')
if [[ -z "${ssh_ip}" ]] || [[ "${ssh_ip}" == "("?*")" ]]; then
ssh_ip=$(who am i 2>/dev/null | awk '{print $NF}' | tr -d '()')
fi
if [[ -z "${ssh_ip}" ]] || [[ "${ssh_ip}" == "("?*")" ]]; then
local sc="${SSH_CLIENT:-}"
ssh_ip="${sc%% *}"
fi
if [[ -z "${ssh_ip}" ]]; then
local sc="${SSH_CONNECTION:-}"
ssh_ip="${sc%% *}"
fi
echo "${ssh_ip}"
}
#=============================================================================
# Verificar si una IP es válida
#=============================================================================
is_valid_ip() {
local ip="$1"
if [[ "${ip}" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
local IFS='.'
read -r -a octets <<< "${ip}"
for octet in "${octets[@]}"; do
if ((octet < 0 || octet > 255)); then
return 1
fi
done
return 0
fi
return 1
}
#=============================================================================
# Ejecutar comando con validación
#=============================================================================
safe_exec() {
local cmd="$1"
local msg="${2:-}"
if [[ -n "${msg}" ]]; then
log_debug "${msg}"
fi
if ! eval "${cmd}"; then
log_error "Fallo al ejecutar: ${cmd}"
return 1
fi
}
#=============================================================================
# Confirmación con timeout (rollback)
#=============================================================================
confirm_with_timeout() {
local timeout="${1:-${ROLLBACK_TIMEOUT}}"
local message="${2:-Firewall aplicado. ¿Todo funciona? [SI/s/N]: }"
local response
log_info "Esperando confirmación... (timeout: ${timeout}s)"
read -r -t "${timeout}" -p "${message}" response || true
if [[ "${response,,}" == "si" ]] || [[ "${response,,}" == "s" ]]; then
return 0
else
return 1
fi
}
#=============================================================================
# Mostrar banner
#=============================================================================
show_banner() {
cat << 'EOF'
???????????????????????????????????????????????????????
? ?
? ???????????????????? ??????????????????? ??? ?
? ????????????????????? ???????????????????? ??? ?
? ?????????????? ?????? ??? ??? ????????? ??? ?
? ?????????????? ?????????? ??? ????????????? ?
? ??????????????????? ?????? ??? ?????? ?????? ?
? ??????????????????? ????? ??? ?????? ????? ?
? ?
? Intelligent Linux Security Framework ?
? Versión 1.0.0 ?
???????????????????????????????????????????????????????
EOF
}
#=============================================================================
# Sistema de logging del SO (rsyslog | journald)
#
# LOG_SYSTEM define cómo se leen los logs del sistema:
# rsyslog ? archivos tradicionales (/var/log/auth.log, etc.)
# journald ? systemd-journald via journalctl
#
# Identificadores de log:
# apache_access ? IDS_APACHE_LOG (rsyslog) / journalctl -u apache2.service
# ssh_auth ? IDS_SSH_LOG (rsyslog) / journalctl _COMM=sshd
#=============================================================================
# Obtener filtro de journalctl según identificador
_journalctl_filter() {
local identifier="$1"
case "${identifier}" in
apache_access) echo "-u apache2.service" ;;
ssh_auth) echo "_COMM=sshd" ;;
*) return 1 ;;
esac
}
# Verificar si una fuente de log está disponible
log_check_exists() {
local identifier="$1"
case "${LOG_SYSTEM}" in
journald)
local filter
filter=$(_journalctl_filter "${identifier}") || return 1
journalctl ${filter} -n 1 --no-pager &>/dev/null
;;
*) # rsyslog
local logfile=""
case "${identifier}" in
apache_access) logfile="${IDS_APACHE_LOG}" ;;
ssh_auth) logfile="${IDS_SSH_LOG}" ;;
*) return 1 ;;
esac
[[ -f "${logfile}" ]]
;;
esac
}
# Leer nuevas líneas desde la última posición registrada
# Uso: log_read_new_lines <identifier>
# Exit: 0 si hay contenido nuevo (imprime en stdout), 1 si no hay nada
log_read_new_lines() {
local identifier="$1"
case "${LOG_SYSTEM}" in
journald)
local filter
filter=$(_journalctl_filter "${identifier}") || return 1
local state_file="${SENTINELA_DIR}/logs/.${identifier}_cursor"
local cursor=""
if [[ -f "${state_file}" ]]; then
cursor=$(cat "${state_file}")
fi
local output=""
if [[ -n "${cursor}" ]]; then
output=$(journalctl ${filter} --after-cursor "${cursor}" --no-pager 2>/dev/null) || return 1
else
output=$(journalctl ${filter} -n 1000 --no-pager 2>/dev/null) || return 1
fi
if [[ -z "${output}" ]] || [[ "${output}" == "-- No entries --"* ]]; then
return 1
fi
# Guardar cursor de la última línea leída
journalctl ${filter} -n 1 --output=export --no-pager 2>/dev/null | \
strings | grep '^__CURSOR=' | cut -d= -f2- > "${state_file}" || true
echo "${output}"
;;
*) # rsyslog
local logfile=""
local state_file=""
case "${identifier}" in
apache_access) logfile="${IDS_APACHE_LOG}"; state_file="${SENTINELA_DIR}/logs/.${identifier}_pos" ;;
ssh_auth) logfile="${IDS_SSH_LOG}"; state_file="${SENTINELA_DIR}/logs/.${identifier}_pos" ;;
*) return 1 ;;
esac
if [[ ! -f "${logfile}" ]]; then
return 1
fi
local last_pos=0
if [[ -f "${state_file}" ]]; then
last_pos=$(cat "${state_file}")
fi
local current_size
current_size=$(stat -c%s "${logfile}" 2>/dev/null || echo 0)
if [[ "${current_size}" -lt "${last_pos}" ]]; then
last_pos=0
fi
if [[ "${current_size}" -eq "${last_pos}" ]] || [[ "${current_size}" -eq 0 ]]; then
return 1
fi
local new_bytes=$((current_size - last_pos))
tail -c "${new_bytes}" "${logfile}" 2>/dev/null || return 1
echo "${current_size}" > "${state_file}"
;;
esac
}
#=============================================================================
# Inicialización
#=============================================================================
init() {
load_config
}
|