Gestión de Logs de Auditoría y Retención a Largo Plazo

Los logs de auditoría son el registro inmutable de las acciones realizadas en un sistema y son fundamentales para el cumplimiento normativo (GDPR, PCI-DSS, SOC2, ISO 27001), la investigación de incidentes de seguridad y la trazabilidad legal. Implementar correctamente la retención a largo plazo con almacenamiento inmutable, rotación automática y archivado a S3 garantiza que estos registros estén disponibles cuando más se necesitan.

Requisitos Previos

  • Ubuntu 22.04/20.04, Debian 11, o CentOS/Rocky Linux 8+
  • auditd instalado para auditoría del sistema
  • Credenciales de S3 o almacenamiento compatible para archivado
  • Espacio en disco suficiente para el período de retención local

Configuración de auditd en Linux

# Instalar auditd (Ubuntu/Debian)
apt update && apt install -y auditd audispd-plugins

# Para CentOS/Rocky Linux
yum install -y audit audit-libs

# Habilitar e iniciar auditd
systemctl enable auditd && systemctl start auditd

# Verificar el estado
auditctl -s  # Ver el estado del demonio de auditoría
# Configurar las reglas de auditoría
# Archivo: /etc/audit/rules.d/audit.rules

cat > /etc/audit/rules.d/50-cis.rules << 'EOF'
# Reglas de auditoría recomendadas para cumplimiento CIS

# Registrar cambios en la hora del sistema
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change
-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change
-a always,exit -F arch=b64 -S clock_settime -k time-change
-a always,exit -F arch=b32 -S clock_settime -k time-change
-w /etc/localtime -p wa -k time-change

# Registrar cambios en la identidad del sistema
-w /etc/group -p wa -k identity
-w /etc/passwd -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/security/opasswd -p wa -k identity

# Registrar cambios en la configuración de red
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale
-a always,exit -F arch=b32 -S sethostname -S setdomainname -k system-locale
-w /etc/hosts -p wa -k system-locale
-w /etc/network -p wa -k system-locale

# Registrar eventos de inicio de sesión y autenticación
-w /var/log/lastlog -p wa -k logins
-w /var/run/faillock/ -p wa -k logins

# Registrar acceso a archivos de sesión
-w /var/run/utmp -p wa -k session
-w /var/log/wtmp -p wa -k logins
-w /var/log/btmp -p wa -k logins

# Registrar cambios en el Control de Acceso Discrecional (DAC)
-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod
-a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod

# Registrar intentos de acceso no autorizado
-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access
-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access

# Registrar el uso de comandos privilegiados (sudo, su, etc.)
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged

# Registrar modificaciones al kernel (módulos)
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules

# Hacer las reglas inmutables (requiere reinicio para cambiarlas)
-e 2
EOF

# Cargar las reglas
augenrules --load
auditctl -l  # Listar las reglas activas

Almacenamiento Inmutable

# Configurar auditd para mayor seguridad de los logs
cat > /etc/audit/auditd.conf << 'EOF'
# Directorio de logs de auditoría
log_dir = /var/log/audit
log_file = /var/log/audit/audit.log
log_format = RAW

# Retención local
max_log_file = 100         # Tamaño máximo de cada archivo (MB)
num_logs = 10              # Número de archivos de log a mantener
max_log_file_action = rotate

# Acción cuando el disco se llena (suspend=no escribir; keep_logs=mantener logs sin rotar)
space_left = 1024          # Espacio libre mínimo en MB
space_left_action = email
admin_space_left = 50      # Espacio de emergencia en MB
admin_space_left_action = halt  # Detener el sistema si no hay espacio

# Email de alertas
action_mail_acct = root@localhost

# Dispatcher para procesamiento en tiempo real
dispatcher = /sbin/audispd
disp_qos = lossy
EOF

# Hacer el directorio de logs inmutable con chattr (solo root puede añadir)
# NOTA: esto previene la eliminación de archivos de log pero permite escritura
chattr +a /var/log/audit/audit.log

# Para hacer el directorio completamente inmutable (solo lectura después de escribir)
# Requiere configuración especial o un sistema de archivos con soporte WORM

# Crear un sistema de archivos de solo adición con bindfs (para entornos de alta seguridad)
mkdir -p /mnt/audit-worm /var/log/audit-secure
mount -o bind /var/log/audit /mnt/audit-worm

Rotación y Compresión de Logs

# Configurar logrotate para los logs de auditoría y otros logs críticos
cat > /etc/logrotate.d/audit-retention << 'EOF'
/var/log/audit/audit.log {
  # Rotar diariamente
  daily
  
  # Comprimir los logs rotados
  compress
  delaycompress
  
  # Mantener 90 días de logs comprimidos localmente
  rotate 90
  
  # No fallar si el archivo no existe
  missingok
  
  # No rotar si el archivo está vacío
  notifempty
  
  # Crear el nuevo archivo con los permisos correctos
  create 0600 root root
  
  # Notificar a auditd después de rotar
  postrotate
    /sbin/service auditd restart 2> /dev/null > /dev/null || true
  endscript
}

/var/log/auth.log {
  daily
  rotate 90
  compress
  delaycompress
  missingok
  notifempty
  create 0640 root adm
  
  postrotate
    if [ -d /run/systemd/system ]; then
      systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    fi
  endscript
}
EOF

# Ejecutar logrotate manualmente para verificar la configuración
logrotate --debug /etc/logrotate.d/audit-retention
logrotate -f /etc/logrotate.d/audit-retention  # Forzar rotación

Archivado a S3 con Retención Configurable

# Script para archivar logs de auditoría a S3 con retención configurada
cat > /usr/local/bin/archive-audit-logs.sh << 'SCRIPT'
#!/bin/bash
# Archivar logs de auditoría a S3 con política de retención configurable
set -euo pipefail

# Configuración
S3_BUCKET="s3://mi-empresa-audit-logs"
S3_PREFIX="audit-logs/$(hostname)"
LOG_DIR="/var/log/audit"
ARCHIVE_DIR="/var/log/audit-archive"
RETENTION_DAYS_LOCAL=30    # Días de retención local
LOG_FILE="/var/log/audit-archiver.log"

# Función de logging
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Crear directorio de archivo local
mkdir -p "$ARCHIVE_DIR"

# Comprimir y archivar los logs rotados del día anterior
YESTERDAY=$(date -d "yesterday" +%Y%m%d)

for log_file in ${LOG_DIR}/audit.log.${YESTERDAY}* ${LOG_DIR}/audit.log-${YESTERDAY}*; do
  if [ -f "$log_file" ]; then
    BASENAME=$(basename "$log_file")
    ARCHIVE_FILE="${ARCHIVE_DIR}/${BASENAME}.gz"
    
    # Comprimir si no está ya comprimido
    if [[ "$log_file" != *.gz ]]; then
      log "Comprimiendo $log_file..."
      gzip -c "$log_file" > "$ARCHIVE_FILE"
    else
      cp "$log_file" "$ARCHIVE_FILE"
    fi
    
    # Calcular el hash SHA-256 del archivo para verificar integridad
    CHECKSUM=$(sha256sum "$ARCHIVE_FILE" | awk '{print $1}')
    echo "$CHECKSUM  $BASENAME.gz" >> "${ARCHIVE_DIR}/checksums-${YESTERDAY}.sha256"
    
    log "Archivado: $BASENAME (SHA-256: $CHECKSUM)"
  fi
done

# Subir a S3 con metadatos de retención
if ls ${ARCHIVE_DIR}/*.log* 2>/dev/null | head -1 > /dev/null; then
  log "Subiendo logs a S3..."
  
  # Subir los logs con clase de almacenamiento GLACIER para reducir costes
  aws s3 sync "$ARCHIVE_DIR" "${S3_BUCKET}/${S3_PREFIX}/${YESTERDAY}/" \
    --storage-class GLACIER_IR \
    --sse AES256 \
    --metadata "retention-days=2555,audit-host=$(hostname),archive-date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  
  # Subir el archivo de checksums por separado en clase estándar
  aws s3 cp "${ARCHIVE_DIR}/checksums-${YESTERDAY}.sha256" \
    "${S3_BUCKET}/${S3_PREFIX}/${YESTERDAY}/checksums.sha256" \
    --storage-class STANDARD
  
  log "Logs subidos exitosamente a S3"
fi

# Limpiar archivos locales más antiguos que RETENTION_DAYS_LOCAL
log "Limpiando archivos locales de más de $RETENTION_DAYS_LOCAL días..."
find "$ARCHIVE_DIR" -type f -mtime "+$RETENTION_DAYS_LOCAL" -delete

log "Proceso de archivado completado"
SCRIPT

chmod +x /usr/local/bin/archive-audit-logs.sh

# Programar el archivado diario a las 3:00 AM
echo "0 3 * * * root /usr/local/bin/archive-audit-logs.sh" > /etc/cron.d/audit-archive

Verificación de Integridad

# Script para verificar la integridad de los logs archivados
cat > /usr/local/bin/verify-audit-integrity.sh << 'SCRIPT'
#!/bin/bash
# Verificar la integridad de los logs de auditoría en S3
set -euo pipefail

S3_BUCKET="s3://mi-empresa-audit-logs"
DATE=${1:-$(date -d "yesterday" +%Y%m%d)}
TEMP_DIR=$(mktemp -d)

echo "Verificando integridad de logs del $DATE..."

# Descargar los checksums desde S3
aws s3 cp "${S3_BUCKET}/audit-logs/$(hostname)/${DATE}/checksums.sha256" \
  "${TEMP_DIR}/checksums.sha256"

# Descargar los archivos de log y verificar
while IFS= read -r line; do
  CHECKSUM=$(echo "$line" | awk '{print $1}')
  FILENAME=$(echo "$line" | awk '{print $2}')
  
  # Descargar el archivo
  aws s3 cp "${S3_BUCKET}/audit-logs/$(hostname)/${DATE}/${FILENAME}" \
    "${TEMP_DIR}/${FILENAME}" --quiet
  
  # Verificar el checksum
  ACTUAL_CHECKSUM=$(sha256sum "${TEMP_DIR}/${FILENAME}" | awk '{print $1}')
  
  if [ "$CHECKSUM" == "$ACTUAL_CHECKSUM" ]; then
    echo "OK: $FILENAME"
  else
    echo "ERROR: $FILENAME - Checksum no coincide!"
    echo "  Esperado: $CHECKSUM"
    echo "  Actual:   $ACTUAL_CHECKSUM"
    exit 1
  fi
done < "${TEMP_DIR}/checksums.sha256"

# Limpiar directorio temporal
rm -rf "$TEMP_DIR"

echo "Verificación completada: todos los logs son íntegros"
SCRIPT

chmod +x /usr/local/bin/verify-audit-integrity.sh

Cumplimiento Normativo

# Configurar la política de retención en S3 (S3 Object Lock - WORM)
# Crear el bucket con Object Lock habilitado (solo al crear el bucket)
aws s3api create-bucket \
  --bucket mi-empresa-audit-logs \
  --region us-east-1 \
  --object-lock-enabled-for-bucket

# Configurar la retención por defecto (7 años para PCI-DSS)
aws s3api put-object-lock-configuration \
  --bucket mi-empresa-audit-logs \
  --object-lock-configuration '{
    "ObjectLockEnabled": "Enabled",
    "Rule": {
      "DefaultRetention": {
        "Mode": "COMPLIANCE",
        "Days": 2555
      }
    }
  }'

# Para GDPR: configurar una política de ciclo de vida para eliminar datos personales
# después del período de retención máximo
aws s3api put-bucket-lifecycle-configuration \
  --bucket mi-empresa-audit-logs \
  --lifecycle-configuration file://lifecycle-policy.json
// lifecycle-policy.json: política de ciclo de vida S3
{
  "Rules": [
    {
      "ID": "TransicionAGlacier",
      "Status": "Enabled",
      "Filter": {"Prefix": "audit-logs/"},
      "Transitions": [
        {"Days": 90, "StorageClass": "GLACIER"},
        {"Days": 180, "StorageClass": "DEEP_ARCHIVE"}
      ],
      "Expiration": {"Days": 2555}
    }
  ]
}

Búsqueda en Logs Archivados

# Buscar en logs de auditoría locales con ausearch
# Buscar por usuario específico
ausearch -ua 1001 --start today --end now

# Buscar por tipo de evento
ausearch -m USER_LOGIN --start "01/01/2024 00:00:00" --end "today"

# Buscar por palabra clave
ausearch -k privileged --start yesterday

# Buscar eventos de acceso a archivos específicos
ausearch -f /etc/passwd --start recent

# Ver el log de auditoría en formato legible
aureport --start today --end now --summary

# Generar informes de autenticación
aureport -au --start "01/01/2024" --end "today"

# Buscar en logs comprimidos archivados
# Descomprimir temporalmente y buscar
zcat /var/log/audit-archive/audit.log-20240101.gz | \
  ausearch --input - -k privileged

# Buscar en S3 sin descargar todo el archivo
# (usando S3 Select para logs JSON)
aws s3api select-object-content \
  --bucket mi-empresa-audit-logs \
  --key "audit-logs/servidor-01/20240101/audit.log.gz" \
  --expression "SELECT * FROM S3Object WHERE _1 LIKE '%sudo%'" \
  --expression-type SQL \
  --input-serialization '{"CSV": {}}' \
  --output-serialization '{"CSV": {}}' \
  /dev/stdout

Solución de Problemas

auditd no inicia:

# Verificar la configuración
auditd -f  # Modo foreground para ver errores

# Verificar espacio en disco
df -h /var/log/audit

# Ver los logs del sistema
journalctl -u auditd -f

Los logs de auditoría crecen demasiado rápido:

# Ver qué reglas generan más eventos
aureport --key --summary

# Reducir la verbosidad de las reglas más activas
# En /etc/audit/rules.d/, revisar y eliminar reglas poco necesarias

# Verificar el tamaño actual del log
ls -lh /var/log/audit/
du -sh /var/log/audit/

Error al subir a S3:

# Verificar las credenciales de AWS
aws sts get-caller-identity

# Verificar los permisos del bucket
aws s3 ls s3://mi-empresa-audit-logs/

# Probar la subida manualmente
aws s3 cp /tmp/test.txt s3://mi-empresa-audit-logs/test/

Conclusión

Una gestión adecuada de los logs de auditoría con almacenamiento inmutable y retención configurable es un requisito no negociable para cualquier organización que deba cumplir normativas de seguridad. La combinación de auditd para la recolección de eventos del sistema, logrotate para la rotación local y S3 con Object Lock para el archivado a largo plazo proporciona una solución completa que garantiza la disponibilidad y la integridad de los registros de auditoría durante todo el período de retención requerido por la normativa aplicable.