Logging en Java: ¿qué debes registrar y qué no?
11 de noviembre de 2020
0 minutos de lecturaLos registros son una herramienta práctica para detectar errores y depurar código. Para los ingenieros y, en particular, en un entorno de DevOps, son muy valiosos.
Además de su función operativa, los registros también son fundamentales desde el punto de vista de la seguridad en Java. Cuando ocurre una brecha de seguridad, los archivos de registro son el primer lugar donde buscar pistas sobre lo sucedido. Pero, con tantos frameworks distintos para registrar eventos en aplicaciones Java, ¿cómo elegir el adecuado? Y, más importante aún, ¿cómo registrar eventos correctamente en Java?
La calidad de los registros es fundamental. Registrar demasiada o muy poca información puede ser catastrófico desde el punto de vista de la seguridad en Java. De hecho, el registro y la supervisión insuficientes (A10:2017) forman parte de la lista OWASP Top 10.
En este artículo, te guiaré por un enfoque práctico para registrar eventos en Java: qué debemos registrar, qué no y cómo hacerlo correctamente.
¿Qué debemos registrar?
Cuando estaba en el ejército, cada vez que tenía que informar sobre algo, debíamos usar el método 5W-H: quién, qué, dónde, cuándo, por qué y cómo. Este método también es una excelente regla para redactar mensajes de registro.
¿Quién hizo esto?
Identifica al usuario o sistema que hizo la solicitud. Si es posible, incluye un userID y una dirección IP.
¿Qué está pasando?
¿Qué tipo de mensaje de registro es? Muchos desarrolladores ya conocen los distintos niveles de registro para indicar la importancia de un mensaje (ERROR, WARN, INFO, DEBUG, TRACE). Conviene distinguir entre los registros funcionales, los de seguridad y, si corresponde, los de auditoría.
¿Dónde ocurrió?
Identifica la ubicación. Por lo general, defines un logger por clase, así que ese es un buen comienzo. Sin embargo, también necesitamos conocer el método específico. Además, si trabajas con una arquitectura de microservicios, por ejemplo, necesitamos saber cuál es el nodo en particular.
¿Cuándo ocurrió?
El tiempo es esencial, así que asegúrate de registrar una marca de tiempo. Ten en cuenta las diferencias de zona horaria y, sobre todo, que las zonas horarias cambian dos veces al año. Mi consejo es que siempre uses UTC para que todo el mundo pueda entenderlo.
¿Por qué ocurrió?
Por lo general, necesitas averiguar el «por qué». No siempre puedes incluirlo en una sola línea de registro. En algunos casos, es evidente. Si es así, inclúyelo en el mensaje. Muchas veces, solo podemos explicar el «por qué» al ver la relación entre los registros. Por eso, asegúrate de que tus mensajes sean totalmente claros.
¿Cómo ocurrió?
Al igual que el «por qué», normalmente no se puede registrar el «cómo» en un solo mensaje. Por eso, es fundamental correlacionar los mensajes, especialmente en una arquitectura basada en servicios. Para lograrlo, debes crear un ID de correlación único para cada solicitud entrante en cada servicio con una interfaz de salida. Una vez que tengas este ID, debes registrarlo en cada mensaje. Si necesitas transferirlo a otro servicio, puedes usar el encabezado HTTP específico X-Correlation-ID para correlacionar las solicitudes y mantener la arquitectura sin estado.
Registro de eventos de seguridad
Java se usa principalmente en procesos de backend e interactúa directamente con los datos. Por eso, debemos llevar un registro de los eventos relacionados con la seguridad. Debemos priorizar estos eventos según corresponda y considerar aún más críticos los que requieren privilegios elevados (eventos de administrador).
En general, puedes tener en cuenta lo siguiente:
inicios de sesión exitosos y fallidos
cierres de sesión
cambios y restablecimientos de contraseñas
creación y eliminación de usuarios
fallas de autorización
cambios en los niveles de acceso
Además, también debes registrar indicadores que puedan señalar ataques específicos.
errores de validación de entradas: detecta etiquetas de script que provoquen ataques XSS o parámetros como UNION que indiquen una inyección SQL
respuestas HTTP 4xx y 5xx
cantidades desmesuradas de solicitudes desde una IP específica
revisa los rangos de IP; por ejemplo, si un servicio interno recibe solicitudes desde una dirección IP externa
Una excelente táctica es establecer umbrales para ciertos eventos de registro de seguridad. Puedes enviar alertas automáticamente cuando se superen esos umbrales. Así, puedes intervenir según corresponda y evitar daños mayores.
¿Qué no debemos registrar?
Registrar demasiada información puede ser tan perjudicial como registrar muy poca. No quieres que los registros se llenen de información que no sea útil en producción. Por eso, procura siempre establecer un nivel de registro adecuado en los sistemas de producción, como WARN o ERROR.
Durante el desarrollo, los ingenieros registran entidades confidenciales que podrían contener datos personales para depurar aplicaciones. Aunque debemos procurar evitarlo, los mensajes internos de depuración deben tener el nivel de registro adecuado, por ejemplo, DEBUG o TRACE, y no deben ser visibles en el entorno de producción.
Como regla general, una aplicación no debe revelar datos específicos de la aplicación ni de los usuarios, ya que un atacante podría usarlos. Además, al exponer información personal, es muy probable que incumplas las normativas de privacidad. Aunque solo personas internas puedan acceder a los registros, estos representan un punto único de falla. En definitiva, no queremos que esta información caiga en las manos equivocadas.
Al desarrollar, piensa en lo que necesitas registrar. ¿De verdad necesitamos registrar un ID interno, la contraseña sin cifrar o los datos de la tarjeta de crédito de alguien? Ten cuidado de no generar un objeto de datos completo en Java, ya que el resultado depende del método toString() de ese objeto.
Al registrar información personal para depurar, eludimos muchas de nuestras propias políticas de seguridad; lo más sensato sería no registrar este tipo de información. Si necesitamos registrar datos específicos, asegúrate de establecer el nivel de registro adecuado, como DEBUG. También debes configurar el nivel de registro del sistema de producción en un nivel superior, como WARN.
En resumen: la información personal y confidencial no debe aparecer en los registros de producción.
Registro de seguridad en Java
Ante todo, usa un framework de registro en Java. Parece obvio, pero en muchísimas aplicaciones todavía se usa el antiguo System.out.println() para escribir directamente en la consola. Esto debe evitarse a toda costa. Un buen linter (Spotbugs, PMD, SonarLint) puede ayudarte.
Hay muchos frameworks de registro distintos en Java. En este artículo nos enfocaremos en Logback y Log4j2, que actualmente son los más usados. Sin embargo, independientemente del framework, conviene separar los registros de seguridad de los registros generales de la aplicación. Tanto en Logback como en Log4j2, puedes hacerlo fácilmente agregando marcadores al logger. Con un marcador específico, como SECURITY o incluso AUDIT, puedes filtrarlos fácilmente en tus registros. También podrías agregar otro appender al logger y filtrar por el marcador específico para almacenar o mostrar estos registros por separado.
Logback
Para implementar Logback, solo necesitas la dependencia logback-classic, como se muestra a continuación. Esto incluirá los paquetes logback-core y slf4j-api.
Marcadores
Puedes definir marcadores con cualquier cadena y agregarlos al logger, sin importar el nivel de registro, como se muestra a continuación.
Configuración
En el siguiente ejemplo de configuración de Logback, creé dos appenders en mi archivo logback.xml.
El primer appender muestra todas las líneas de registro en la consola, incluido cualquier marcador que haya agregado. El segundo filtra únicamente los registros de seguridad según el marcador SECURITY y los guarda en un archivo de registro de seguridad independiente para cada día.
Log4j2
Para usarlo, debes incluir los paquetes log4j-api y log4j-core, como se muestra a continuación.
Marcadores
Crear y agregar marcadores es similar al ejemplo de Logback; la única diferencia es que esta vez usamos el administrador específico de Log4j para crear el logger y el marcador.
Configuración
Al igual que con la configuración de Logback, creé una configuración de Log4j2 con dos appenders, donde el registro de seguridad se guarda en un archivo independiente según los marcadores. Esta configuración debe estar en el archivo log4j2.xml ubicado en el classpath.
Registro para sistemas distribuidos
En los sistemas distribuidos, no conviene registrar todo en un único archivo por servicio. En su lugar, es mejor usar una ubicación centralizada. Almacenar los registros en un lugar central facilita la correlación de las líneas de registro y la detección de posibles problemas.
Consejo profesional: Al implementar el registro centralizado, suele ser útil configurar las líneas de registro en formato JSON. Así, será más fácil y rápido indexarlas y buscarlas.
Además del registro centralizado, también deberías considerar la supervisión activa y las alertas sobre las líneas de registro. Muchos productos pueden generar gráficos en tiempo real a partir de tus registros y enviarte alertas automáticamente cuando se superan los umbrales predefinidos.
Conclusión
Registrar eventos no es algo que simplemente haces. Analizar con atención qué registras, cuándo lo haces y cómo aprovechas esos registros es fundamental para investigar rápidamente o intervenir. Aunque prevenir es mejor que curar, siempre existe la posibilidad de que alguien vulnere tu sistema, y querrás evitar que cause más daños.
Si alguna vez llega ese momento difícil, ¡asegúrate de que tus registros puedan ayudarte!
Comienza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.



