Files
project.linkedcorp.net/docs/ARQUITECTURA-INVENTARIO.md
T
Milton Gonzalez e65111a7f4 feat: version 3.0.0 -- Correcciones de auditoria de seguridad, accesibilidad y redundancia
Permisos (superadmin, viewer, busqueda, tareas recurrentes), bootstrap atomico,
revocacion de JWT por version de sesion (028), control optimista de edicion (029),
logo sin SVG + CSP, ingesta endurecida, correos escapados, borradores, paginacion
de proyectos, accesibilidad de dialogos/controles e i18n.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:36:02 -05:00

95 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Arquitectura de inventario — menú "Equipos"
Diseño del módulo Equipos de Project. Se trajo de `linkedtrack.linkedcorp.net` (que sigue como
producto aparte, sin cambios) y se reescribió siguiendo las reglas de Project: mismo login, roles,
auditoría, UI Fluent y una sola base `project_linkedcorp`.
## Decisión de fondo
| Opción | Veredicto |
|---|---|
| Servidor GLPI completo | Descartado: demasiada administración para solo inventariar. |
| Agente 100% propio | Descartado: reescribir el sondeo de hardware (WMI, EDID, SNMP) es frágil y caro. |
| **Agente GLPI oficial + colector propio dentro de Project** | **Elegido.** El agente recolecta; Project recibe, guarda y muestra. |
## Flujo
```
GLPI Agent (cada PC/servidor) --POST inventario--> https://project.linkedcorp.net/api/inventory
(ingest.js: token -> inflar -> parsear JSON/XML
-> normalizer.js -> upsert por deviceid
-> diff en device_changes)
Navegador (superadmin/admin/it_manager) --JWT--> /api/devices -> menú "Equipos"
```
- **Handshake:** el agente primero manda un `contact` (JSON) o `PROLOG` (XML). Se responde sin
guardar nada (`{"status":"ok","expiration":"24"}` o `<REPLY><RESPONSE>SEND</RESPONSE>…`).
- **Ingesta autenticada por token**, no por JWT: tabla `inventory_tokens` (hash sha256 para validar
y copia cifrada para recuperar el instalador). El
agente lo manda como contraseña Basic (`user`/`password` del `agent.cfg`), `Bearer`,
`X-Ingest-Token` o `?token=`. Sin token válido → `401`.
- Compatibilidad: `?token=` sigue aceptado para agentes instalados; la aplicación censura ese
parámetro en sus logs. El proxy inverso debe censurarlo también en sus propios access logs.
- **Pendiente de seguridad:** hoy un instalador puede usar el mismo token en varios equipos.
Vincular automáticamente token y `deviceid` rompería esos agentes ya instalados. Antes de
activarlo se necesita inventariar instalaciones y migrar a un token por equipo; luego añadir
la asociación y rechazar inventarios de otro `deviceid`.
- **Rutas:** `POST /api/inventory` (propia; pasa por el Custom Location `/api` de NPM sin cambios)
y alias `POST /front/inventory.php` (ruta nativa de GLPI). Se montan ANTES de `express.json`
porque usan cuerpo crudo.
- **Límites:** cuerpo máx. 15 MB (`INVENTORY_MAX_BYTES`), inflado máx. 4× (anti zip-bomb).
- Cada IP tiene límite de 120 peticiones/minuto; máximo dos ingestiones simultáneas. El token se
valida antes de leer el cuerpo y el parseo se ejecuta en un worker con límite de tiempo.
- **Upsert por `deviceid`** (estable), nunca por hostname. Componentes se reemplazan completos en
una transacción; el diff (RAM, disco agregado/retirado, software instalado/desinstalado, SO,
hostname) queda en `device_changes`.
- **Baja lógica:** `status = retired`; un reporte nuevo NO reactiva un equipo dado de baja.
- **Crudo para depurar:** último reporte por equipo en `storage/inventories/<hash>.json|xml`
(se sobrescribe; descarga `GET /api/devices/:id/raw`).
## Normalización
`backend/src/modules/devices/normalizer.js`: el JSON nativo y el XML (en mayúsculas) comparten
nombres de campo, así que el XML se pasa a minúsculas y ambos usan el mismo mapeo. Todo campo
ausente queda `null` (el formato varía por versión del agente y SO). Tipo de equipo
(`desktop/laptop/server/virtual/printer/network/other`) se infiere de `chassis_type`, `vmsystem`,
nombre del SO e `itemtype`. Lo que no tiene columna (impresoras/red SNMP, antivirus, video,
batería) se guarda en `devices.extra_json`.
## Instalar el agente en un equipo
1. Superadmin/admin: menú **Equipos → Agentes → Generar token** (se muestra una sola vez).
2. En el equipo, instalar GLPI Agent (MSI en Windows; `.deb`/`.rpm`/tarball en Linux) con:
```
server = https://project.linkedcorp.net/api/inventory
user = project
password = <token>
tag = <opcional>
```
Windows (instalador): `SERVER=… USER=project PASSWORD=<token>`.
3. Forzar un envío: `glpi-agent --force` (Linux) / "Forzar inventario" en la página del agente
(`http://localhost:62354`) en Windows. El equipo aparece en **Equipos** al primer POST.
## Instalador con un clic (Windows)
Equipos → Agentes → "Instalador para Windows": genera `instalar-glpi-agent.bat` en el navegador con
`SERVER=<origen>/api/inventory`, `USER=project`, `PASSWORD=<token>` y `TAG` opcional. El .bat se
auto-eleva, descarga `GLPI-Agent-<ver>-x64.msi` de GitHub (versión fijada en
`frontend/src/pages/Equipos/agentInstaller.js#GLPI_AGENT_VERSION`; o usa el .msi que esté junto al
.bat), instala con `msiexec /qn` y `RUNNOW=1`, y verifica que el servicio `glpi-agent` corra. El
token solo se conoce al crearlo, por eso el .bat se genera en la pantalla y no en el servidor.
Antes de instalar, el .bat verifica el token (`POST` de prueba: `400` válido, `401` inválido, `000` sin
conexión) y se detiene si no es válido; después fuerza la configuración en el registro (cubre equipos que
ya tenían el agente). Proceso por cliente: generar UN token en su Project → descargar el .bat desde ahí →
ejecutarlo en cada PC.
Linux: usar el instalador oficial `glpi-agent-<ver>-linux-installer.pl --server=… --user=… --password=…`.
## Pendientes / por verificar con un agente real
- Confirmar que el agente acepta la respuesta del handshake tal cual (si no, ajustar `ingest.js`).
- Capturar un JSON real (`glpi-agent --local=/tmp/inv --json`) y contrastar campos con
`normalizer.js` (hoy basado en el formato documentado del agente).
- Impresoras/red por SNMP (`netinventory`): solo se guarda lo básico + `extra_json`; falta una
vista propia cuando haya un caso real.
- Panel de impresoras con contador de páginas/tóner y export CSV: fase 2.