Auditoría de acciones en Azure Blob Storage: una bitácora sencilla y segura

  • Imagen de redactorDaniel J. Saldaña
  • 17 de octubre de 2026
Auditoría de acciones en Azure Blob Storage: una bitácora sencilla y segura

Cuando alguien cambia una conexión de Azure, refresca un inventario o ejecuta una operación administrativa desde un dashboard, es útil poder responder tres preguntas:

  1. ¿Qué ocurrió?
  2. ¿Cuándo ocurrió?
  3. ¿Qué identidad de la aplicación lo solicitó?

Para empezar no necesitas una plataforma de auditoría compleja. Goliat Dashboard usa una pieza muy pequeña: guardar un documento JSON por acción en un contenedor privado de Azure Blob Storage.

Acción protegida en el dashboard
↓
Evento de auditoría con datos mínimos
↓
Blob privado: actions/2026-10-17/...
↓
Proceso de consulta, retención o exportación

No es un reemplazo de Azure Activity Log, Log Analytics ni una solución SIEM. Es la bitácora de lo que sucede dentro de tu aplicación, donde Azure no conoce el significado de «un usuario conectó esta suscripción a este proyecto».

Diseña primero el evento, no el contenedor

Un evento de auditoría debe ser pequeño, estable y seguro de transportar. Este contrato es suficiente para muchas integraciones:

type AuditEvent = {
id: string;
occurredAt: string;
action: 'azure_connection.created' | 'inventory.refreshed' | 'resource.exported';
outcome: 'success' | 'failure';
actorId: string;
projectId?: string;
target?: {
kind: 'azure-connection' | 'resource' | 'report';
id: string;
};
requestId: string;
metadata?: Record<string, string | number | boolean>;
};

No copies aquí la cabecera Authorization, tokens de Azure, secretos, cuerpos de formularios ni el correo completo si un identificador interno basta. La auditoría tiende a retenerse más tiempo que los datos operativos, así que conviene aplicar minimización desde el principio.

Una ruta de blobs que luego puedas consultar

Guardar un blob por evento hace que las escrituras sean simples y evita problemas de concurrencia. Una convención de nombre útil es:

actions/2026-10-17/2026-10-17T08-43-21-122Z_5d2e.json

El prefijo de fecha permite localizar, exportar o aplicar políticas de ciclo de vida por periodos. El sufijo aleatorio evita colisiones cuando varias solicitudes ocurren en el mismo milisegundo.

Autenticación sin claves en el código

En el recurso que ejecuta el backend, configura una identidad administrada y otórgale el rol de datos de Blob con el mínimo alcance posible: idealmente el contenedor de auditoría, o la cuenta de almacenamiento si tu organización no permite un alcance más fino.

Para local puedes usar DefaultAzureCredential; no hace falta copiar una clave de la cuenta de almacenamiento a .env.

Terminal window
npm install @azure/identity @azure/storage-blob
import { DefaultAzureCredential } from '@azure/identity';
import { BlobServiceClient } from '@azure/storage-blob';
import { randomUUID } from 'node:crypto';
const accountUrl = process.env.AZURE_AUDIT_STORAGE_URL;
if (!accountUrl) throw new Error('Falta AZURE_AUDIT_STORAGE_URL');
const service = new BlobServiceClient(accountUrl, new DefaultAzureCredential());
const container = service.getContainerClient('dashboard-audit');
function blobName(event: AuditEvent) {
const day = event.occurredAt.slice(0, 10);
const safeTime = event.occurredAt.replaceAll(':', '-').replace('.', '-');
return `actions/${day}/${safeTime}_${event.id}.json`;
}
export async function writeAuditEvent(event: Omit<AuditEvent, 'id' | 'occurredAt'>) {
const record: AuditEvent = {
...event,
id: randomUUID(),
occurredAt: new Date().toISOString(),
};
const payload = JSON.stringify(record);
const blob = container.getBlockBlobClient(blobName(record));
await blob.upload(payload, Buffer.byteLength(payload), {
blobHTTPHeaders: { blobContentType: 'application/json; charset=utf-8' },
});
return record;
}

El contenedor debe ser privado. La aplicación escribe mediante Microsoft Entra ID; ni el navegador ni una URL pública de blob necesitan acceso. Si ejecutas el servicio en Azure, evita habilitar claves compartidas sólo para simplificar esta integración.

Llámalo desde una acción de negocio

La auditoría tiene sentido cuando queda al lado de la acción que explica. Este handler ilustra el orden:

export async function POST({ locals, request }: ApiContext) {
const user = await requireUser(locals);
const input = await validateConnectionInput(request);
try {
const connection = await createAzureConnection(user.organizationId, input);
await writeAuditEvent({
action: 'azure_connection.created',
outcome: 'success',
actorId: user.id,
projectId: connection.projectId,
target: { kind: 'azure-connection', id: connection.id },
requestId: locals.requestId,
});
return Response.json(connection, { status: 201 });
} catch (error) {
await writeAuditEvent({
action: 'azure_connection.created',
outcome: 'failure',
actorId: user.id,
requestId: locals.requestId,
metadata: { errorType: error instanceof Error ? error.name : 'UnknownError' },
});
throw error;
}
}

Decide por acción si un fallo al auditar debe bloquearla. Para una exportación no crítica puede ser aceptable encolar un reintento y avisar al sistema de observabilidad. Para un cambio de permisos o un borrado sensible quizá prefieras fallar la operación si no se puede registrar. Lo que no recomiendo es ignorar silenciosamente el fallo.

Consulta: separa lectura de escritura

El backend que ejecuta acciones sólo necesita escribir. Una herramienta de soporte o un trabajo de exportación puede recibir permisos de lectura por separado y recorrer el prefijo de un día:

for await (const item of container.listBlobsFlat({ prefix: 'actions/2026-10-17/' })) {
console.log(item.name);
}

No construyas una pantalla que descargue todos los JSON de la cuenta de almacenamiento cada vez que se abre. Para una vista administrativa:

  • filtra por fecha, proyecto o requestId;
  • pagina los resultados;
  • indexa los eventos en una base de datos o Log Analytics si el volumen crece;
  • conserva Blob Storage como archivo económico y verificable.

Retención y protección: decide cuánto dura cada dato

Antes de desplegar, define una política de retención con seguridad, legal y producto. Blob Storage puede ayudarte con reglas de ciclo de vida y protección frente a borrados accidentales, pero ninguna opción sustituye una decisión sobre qué guardar y durante cuánto tiempo.

Una configuración inicial razonable suele incluir:

Medida Objetivo
Contenedor sin acceso público Evitar que una bitácora se convierta en un índice de actividad
Identidad administrada y RBAC Eliminar claves de larga vida del código
Versionado o protección contra borrado Reducir el riesgo de pérdida accidental
Regla de ciclo de vida Borrar o archivar cuando termina la retención
Alertas de errores de escritura Detectar que hay acciones sin registrar

Si los eventos deben ser inmutables por requisitos regulatorios, estudia las políticas de inmutabilidad de Blob Storage con el equipo responsable. Es una decisión de gobierno, no una casilla que deba activarse sin un periodo de retención validado.

Lista de comprobación

  1. El evento contiene identificadores internos, no secretos ni datos personales innecesarios.
  2. El nombre del blob incluye fecha y un identificador único.
  3. El contenedor no permite acceso anónimo.
  4. La identidad de ejecución sólo tiene permisos de datos necesarios.
  5. Los errores de auditoría se registran y tienen una estrategia de reintento o bloqueo.
  6. Hay una política explícita de retención, borrado y acceso de lectura.

La guía de Azure Blob Storage para JavaScript cubre los fundamentos del cliente y la autenticación. Con esta integración, cada nuevo flujo del dashboard puede emitir el mismo contrato de evento sin acoplarse a una base de datos concreta. Es una base pequeña, pero deja preparada la ruta hacia exportaciones, investigación de incidencias y trazabilidad real.

¡Suscríbete y recibe actualizaciones sobre tecnología, diseño, productividad, programación y mucho más!
0