4. Trazabilidad en MongoDB Community
Temas del programa: 3.1 y 3.2
Una limitación real que debes conocer
Section titled “Una limitación real que debes conocer”La auditoría nativa de MongoDB (--auditDestination) es exclusiva de MongoDB Enterprise y MongoDB Atlas (M10 o superior). No existe en MongoDB Community Edition, que es la edición de esta materia.
Si una pregunta no puede responderse con Community, la respuesta es "No hay evidencia con esta edición" y su razón técnica
Un DBA reconoce y comunica la limitación, por ejemplo al evaluar si conviene licenciar Enterprise
Fuente: MongoDB Manual, “Auditing”
Qué registraría la auditoría nativa
Section titled “Qué registraría la auditoría nativa”Según la documentación oficial (sin practicarlo en esta materia):
Cambios de esquema (DDL), eventos de réplica y fragmentación, autenticación y autorización, y operaciones CRUD (con auditAuthorizationSuccess: true)
Consola, syslog, archivo JSON o archivo BSON
Comprueba tu edición: db.adminCommand({ buildInfo: 1 }).modules. En Community devuelve []. En Enterprise incluiría "enterprise".
Lo que sí existe en Community
Section titled “Lo que sí existe en Community”Registra operaciones en la colección system.profile de cada base. Diseñado para desempeño, no para seguridad.
mongodDocumentos JSON con conexiones, autenticaciones, errores y cambios de configuración. Se consulta con getLog o en el archivo de systemLog.
| Nivel del perfilador | Comportamiento |
|---|---|
0 | Apagado (por defecto) |
1 | Más lentas que slowms (100 ms) o que cumplan un filtro |
2 | Todas las operaciones |
Fuente: MongoDB Manual, “Database Profiler”
Ejemplo guiado: preparar MongoDB con autenticación
Section titled “Ejemplo guiado: preparar MongoDB con autenticación”use admindb.createUser({ user: "admin", pwd: "Admin_Practica_2026", roles: ["root"] })db.auth("admin", "Admin_Practica_2026")
use comercialdb.createUser({ user: "app_ventas", pwd: "Ventas_Practica_2026", roles: [{ role: "readWrite", db: "comercial" }] })db.clientes.insertMany([ /* 5 clientes ficticios de la Unidad 2 */ ])Mismo orden que en la Unidad 2: use admin, createUser, db.auth. Sin db.auth la sesión no queda autenticada.
Ejemplo guiado: el incidente sin perfilado
Section titled “Ejemplo guiado: el incidente sin perfilado”db.getProfilingStatus(){ was: 0, slowms: 100, sampleRate: 1, ok: 1 }La aplicación cambia el RFC:
mongosh "mongodb://app_ventas:Ventas_Practica_2026@127.0.0.1:27017/comercial?appName=app-ventas-web" --quiet --eval 'db.clientes.updateOne({ _id: 3 }, { $set: { rfc: "GAMA780912XXX" } })'system.profile está vacía y la bitácora solo muestra la autenticación de app_ventas, no la operación.
La conexión a MongoDB, pieza por pieza
Section titled “La conexión a MongoDB, pieza por pieza”mongodb://app_ventas:Ventas_Practica_2026@127.0.0.1:27017/comercial?appName=app-ventas-web| Parte | Significado |
|---|---|
mongodb:// | Protocolo de MongoDB |
app_ventas:Ventas_Practica_2026@ | Usuario y contraseña |
127.0.0.1:27017 | Servidor y puerto |
/comercial | Base de datos donde se autentica y trabaja |
?authSource=admin | (Para el DBA) base donde está definido el usuario |
?appName=app-ventas-web | Nombre de la aplicación. Aparece como appName en el perfilador |
--quiet --eval '...' | Ejecuta ese código y termina, sin mensaje de bienvenida |
Usa comillas simples alrededor del código de --eval. Dentro de comillas dobles, la terminal interpreta $set como una variable vacía.
Ejemplo guiado: activar el perfilador
Section titled “Ejemplo guiado: activar el perfilador”db.setProfilingLevel(2) // { was: 0, ... } ← devuelve el nivel ANTERIORdb.getProfilingStatus() // { was: 2, slowms: 100, sampleRate: 1, ok: 1 }{ op: 'update', ns: 'comercial.clientes', command: { q: { _id: 3 }, u: { '$set': { rfc: 'GAMA780912XYZ' } }, multi: false, upsert: false }, docsExamined: 1, nModified: 1, ts: ISODate('2026-10-25T22:52:45.909Z'), client: '127.0.0.1', appName: 'app-ventas-web', user: 'app_ventas@comercial'}Fuente: MongoDB Manual, “Database Profiler Output”
Interpretar system.profile
Section titled “Interpretar system.profile”| Campo | Valor observado | Responde |
|---|---|---|
op | query, update, remove | ¿Qué tipo de operación? |
ns | comercial.clientes | ¿Sobre qué colección? |
command | Filtro y cambio aplicados | ¿Qué se pidió? |
user | app_ventas@comercial | ¿Quién? (usuario y base de autenticación) |
client / appName | 127.0.0.1 / app-ventas-web | ¿Desde dónde y con qué aplicación? |
ts | 2026-10-25T22:52:45.909Z | ¿Cuándo? |
No hay valor anterior del RFC. Tampoco los datos de un documento eliminado. Y el nivel 2 registra los filtros de las consultas, que pueden contener datos personales.
Ejemplo guiado: reducir volumen con un filtro
Section titled “Ejemplo guiado: reducir volumen con un filtro”db.setProfilingLevel(0)db.system.profile.drop() // exporta antes lo que necesitesdb.setProfilingLevel(1, { filter: { op: { $in: ["insert", "update", "remove"] } } }){ was: 1, slowms: 100, sampleRate: 1, filter: { op: { '$in': [ 'insert', 'remove', 'update' ] } }, note: 'When a filter expression is set, slowms and sampleRate are not used ...', ok: 1}Tras una consulta y una actualización de la aplicación, system.profile contiene solo el update.
Límites del perfilador como evidencia
Section titled “Límites del perfilador como evidencia”system.profile es una colección limitada. Al llenarse, sobrescribe lo más antiguo: la evidencia desaparece sola.
Tras reiniciar el servicio, el nivel volvió a 0. Para que sea permanente: operationProfiling.mode en el archivo de configuración de mongod.
La documentación advierte que puede degradar el rendimiento, sobre todo en nivel 2.
Analizar operaciones lentas. No fue diseñado como auditoría de seguridad.
Fuente: MongoDB Manual, “Database Profiler”
La bitácora de mongod
Section titled “La bitácora de mongod”Cada línea es un documento JSON: t (fecha), s (severidad), c (componente: NETWORK, ACCESS, COMMAND), id, ctx (conexión, por ejemplo conn12), msg y attr.
| ¿Dónde queda? | Cómo se controla |
|---|---|
| Archivo | systemLog.destination: file y systemLog.path en el archivo de configuración |
| Salida estándar | Si no se configura systemLog (frecuente en contenedores) |
| Memoria del servidor | Siempre: los 1024 eventos más recientes, consultables con getLog |
function bitacora() { return db.adminCommand({ getLog: "global" }).log.map(l => JSON.parse(l))}bitacora().filter(e => e.c === "ACCESS").slice(-5)Fuente: MongoDB Manual, “Log Messages”, “getLog” y “Self-Managed Configuration File Options”
Ejemplo guiado: acceso fallido en mongod
Section titled “Ejemplo guiado: acceso fallido en mongod”bitacora() .filter(e => e.c === "ACCESS" && e.msg === "Failed to authenticate") .map(e => ({ t: e.t, id: e.id, ctx: e.ctx, user: e.attr.user, client: e.attr.client, error: e.attr.error, result: e.attr.result })){ t: { '$date': '2026-10-25T22:52:58.122+00:00' }, id: 5286307, ctx: 'conn41', user: 'app_ventas', client: '127.0.0.1:37276', error: 'AuthenticationFailed: SCRAM authentication failed, storedKey mismatch', result: 18},{ ..., user: 'usuario_inexistente', error: 'UserNotFound: Could not find user "usuario_inexistente" for db "comercial"', result: 11}Un acceso exitoso aparece como "Successfully authenticated" con result: 0 y el nombre de la aplicación en attr.doc.application.name.
Ejemplo guiado: correlacionar por conexión
Section titled “Ejemplo guiado: correlacionar por conexión”{ ctx: 'conn31', msg: 'Profiler settings changed', attr: { from: { level: 0, ... }, to: { level: 1, ..., filter: { ... } }, db: 'comercial' } }El evento no dice quién hizo el cambio. Sí dice qué conexión:
bitacora().filter(e => e.ctx === "conn31" && e.msg === "Successfully authenticated") .map(e => ({ t: e.t, ctx: e.ctx, user: e.attr.user, db: e.attr.db }))[ { t: { '$date': '2026-10-25T22:52:56.959+00:00' }, ctx: 'conn31', user: 'admin', db: 'admin' } ]La correlación solo es válida dentro del mismo periodo de ejecución: los números de conexión y la memoria de getLog se reinician con el servidor.
Cambios de privilegios: el límite más importante
Section titled “Cambios de privilegios: el límite más importante”db.grantRolesToUser("app_ventas", [{ role: "dbAdmin", db: "comercial" }])En la verificación del laboratorio, el cambio no apareció en system.profile ni en la bitácora de mongod.
Control compensatorio: instantáneas periódicas de privilegios.
db.getSiblingDB("auditoria").instantaneas_privilegios.insertOne({ momento: new Date(), tomada_por: db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUsers, usuarios: db.getUsers().users.map(u => ({ user: u.user, db: u.db, roles: u.roles }))})Lo que responde una instantánea
Section titled “Lo que responde una instantánea”| Pregunta | ¿Responde? |
|---|---|
¿Cambiaron los privilegios de app_ventas? | Sí |
| ¿Qué rol se agregó? | Sí: dbAdmin en comercial |
| ¿Quién lo agregó? | No |
| ¿Cuándo exactamente? | No, solo entre dos instantáneas |
Un control compensatorio reduce un riesgo cuando el control preferido no existe, pero no lo sustituye y debe documentarse como tal.
Comparación de los tres escenarios
Section titled “Comparación de los tres escenarios”| Evento | PostgreSQL | MongoDB Community | Enterprise / Atlas |
|---|---|---|---|
| Acceso exitoso y fallido | Sí | Sí (bitácora) | Documentado |
| Cambio de privilegios | Sí | No (solo instantáneas) | Documentado |
| Modificación de datos | Sí | Sí (perfilador) | Documentado |
| Valor anterior | Sí (disparador) | No | No lo describe el manual de MongoDB |
| Lectura de datos sensibles | Con pgaudit o all | Nivel 2, con su costo | Documentado |
| Cambio de configuración | Sí | Sí (ctx) | Documentado |
“Documentado” significa descrito en el manual oficial de MongoDB, no practicado en la materia
Relación con el Laboratorio 02
Section titled “Relación con el Laboratorio 02”La edición, el perfilador, la bitácora de mongod, la correlación por conexión y las instantáneas de privilegios se practican en el Laboratorio 2 — Trazabilidad en MongoDB Community.
Qué sigue
Section titled “Qué sigue”Continúa con 5. Conservación e interpretación de bitácoras.

