#!/usr/bin/env bash
#=============================================================================
# Sentinela ? Intelligent Linux Security Framework
# lib/rollback.sh ? Sistema de rollback automático
#=============================================================================
#
# Este módulo garantiza que NUNCA se pierda la sesión SSH.
#
# Flujo:
# 1. Antes de aplicar reglas, se detecta la IP de la sesión SSH actual
# 2. Se agrega automáticamente a la lista blanca de TRUSTED
# 3. Se toma un snapshot de iptables (backup)
# 4. Se aplican las reglas
# 5. Se pide confirmación (con timeout)
# 6. Si no hay confirmación, se restaura el snapshot
# 7. Si hay confirmación, se guarda el snapshot como bueno
#=============================================================================
# shellcheck source=lib/common.sh
source "${SENTINELA_DIR}/lib/common.sh"
#=============================================================================
# Variables de estado
#=============================================================================
ROLLBACK_SNAPSHOT="${BACKUP_DIR}/snapshot.pre-apply"
ROLLBACK_ACTIVE=false
ROLLBACK_SSH_IP=""
#=============================================================================
# Iniciar transacción de firewall
# Crea snapshot, registra IP SSH, y prepara rollback
#=============================================================================
rollback_start() {
require_root
# Detectar IP SSH actual
ROLLBACK_SSH_IP=$(get_ssh_ip)
if is_valid_ip "${ROLLBACK_SSH_IP}"; then
log_info "Sesión SSH detectada desde: ${ROLLBACK_SSH_IP}"
log_info "La IP ${ROLLBACK_SSH_IP} será automáticamente excluida de bloqueos"
else
log_warn "No se pudo detectar IP de sesión SSH"
# Si no hay terminal interactiva (systemd/boot), continuar sin rollback
if [[ "${ROLLBACK_ENABLE}" == "yes" ]] && [[ -t 0 ]]; then
log_fatal "Rollback habilitado pero no se detectó IP SSH. Operación cancelada."
fi
if [[ ! -t 0 ]]; then
log_info "Modo no interactivo detectado (systemd/boot). Omitiendo rollback."
ROLLBACK_ACTIVE=false
return 0
fi
fi
# Crear snapshot
log_info "Creando snapshot de reglas actuales: ${ROLLBACK_SNAPSHOT}"
mkdir -p "${BACKUP_DIR}"
if ! ${IPTABLES_SAVE} > "${ROLLBACK_SNAPSHOT}" 2>/dev/null; then
log_warn "No se pudo crear snapshot de iptables (continuando de todas formas)"
fi
ROLLBACK_ACTIVE=true
log_info "Transacción de firewall iniciada. Rollback preparado."
return 0
}
#=============================================================================
# Confirmar transacción (éxito)
# Elimina el snapshot y marca como bueno
#=============================================================================
rollback_commit() {
require_root
if [[ "${ROLLBACK_ACTIVE}" != "true" ]]; then
log_debug "No hay transacción activa para confirmar"
return 0
fi
# Guardar snapshot como bueno
local good_snapshot="${BACKUP_DIR}/snapshot.last-good"
if [[ -f "${ROLLBACK_SNAPSHOT}" ]]; then
cp "${ROLLBACK_SNAPSHOT}" "${good_snapshot}"
log_info "Snapshot bueno guardado: ${good_snapshot}"
rm -f "${ROLLBACK_SNAPSHOT}"
fi
ROLLBACK_ACTIVE=false
log_info "Transacción confirmada. Rollback desactivado."
return 0
}
#=============================================================================
# Ejecutar rollback
# Restaura el snapshot si existe
#=============================================================================
rollback_exec() {
require_root
if [[ "${ROLLBACK_ENABLE}" != "yes" ]]; then
log_info "Rollback deshabilitado en configuración. Omitiendo."
return 0
fi
if [[ ! -f "${ROLLBACK_SNAPSHOT}" ]]; then
log_warn "No hay snapshot disponible para rollback"
return 1
fi
log_warn "=== EJECUTANDO ROLLBACK ==="
log_warn "Restaurando snapshot: ${ROLLBACK_SNAPSHOT}"
# Restaurar reglas
if ${IPTABLES_RESTORE} < "${ROLLBACK_SNAPSHOT}" 2>/dev/null; then
log_info "Rollback completado exitosamente"
rm -f "${ROLLBACK_SNAPSHOT}"
ROLLBACK_ACTIVE=false
return 0
else
log_error "Fallo al ejecutar rollback"
return 1
fi
}
#=============================================================================
# Verificar conectividad SSH (post-aplicación)
# Pide confirmación y ejecuta rollback si no responde
#=============================================================================
rollback_verify() {
local timeout="${1:-${ROLLBACK_TIMEOUT}}"
if [[ "${ROLLBACK_ENABLE}" != "yes" ]] || [[ "${ROLLBACK_ACTIVE}" != "true" ]]; then
log_info "Rollback deshabilitado o inactivo. Omitiendo verificación."
return 0
fi
log_info "Verificando conectividad..."
log_info "Timeout: ${timeout}s"
if confirm_with_timeout "${timeout}"; then
log_info "Confirmación recibida. Commit."
rollback_commit
return 0
else
log_warn "No se recibió confirmación en ${timeout}s"
log_warn "Ejecutando rollback automático..."
rollback_exec
return 1
fi
}
#=============================================================================
# Verificar si rollback está activo
#=============================================================================
rollback_is_active() {
[[ "${ROLLBACK_ACTIVE}" == "true" ]]
return $?
}
|