# Stupid Simple Network Inventory **Aplicación web autoalojada para inventario de red y visualización de topología lógica** [English](README.md) · [Français](README.fr.md) ![Python](https://img.shields.io/badge/Python-3.11-3776AB?logo=python&logoColor=white) ![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi&logoColor=white) ![Vue.js](https://img.shields.io/badge/Vue.js-3-4FC08D?logo=vue.js&logoColor=white) ![SQLite](https://img.shields.io/badge/SQLite-003B57?logo=sqlite&logoColor=white) ![Nginx](https://img.shields.io/badge/Nginx-009639?logo=nginx&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=white)
--- ![Vista de Topología](img/tpology.png) --- ## ✨ Características - 🗂️ **Inventario manual** — añade y gestiona dispositivos (18 tipos) con IPs, VLANs, descripciones y enlaces web opcionales - 🗺️ **Vista de topología** — disposición en tarjetas por red (LAN / VLAN 802.1Q), con secciones WAN y puerta de enlace - 📡 **Ping ICMP** — comprueba la accesibilidad de todos los hosts conocidos con un clic - 🔍 **Descubrimiento automático** — ping sweep + consulta PTR DNS en una subred para importar nuevos hosts - 🏷️ **Logos de fabricantes** — detección y visualización automáticas de logos de editores/fabricantes (Proxmox, Cisco, Synology, Docker, y más de 30 más) - 🔐 **Autenticación** — inicio de sesión JWT con cambio de contraseña obligatorio en el primer uso - 🌙 **Modo oscuro** — alternancia entre tema claro y oscuro - 🌍 **i18n** — francés, inglés, español ## 🛠️ Stack | Capa | Tecnología | |------|-----------| | Backend | FastAPI + SQLAlchemy + SQLite (Python 3.11) | | Frontend | Vue 3 + Vite, servido por Nginx | | Auth | JWT HS256, expiración 24h | | Runtime | Docker Compose | --- ## 🚀 Inicio rápido ```bash # 1. Clonar y entrar en el proyecto git clone https://git.raspot.in/olivier/stupid-simple-network-inventory.git cd stupid-simple-network-inventory # 2. Crear el directorio de datos propiedad del usuario actual mkdir -p db_data # 3. Configurar el entorno (necesario para la propiedad correcta del bind-mount) cp .env.example .env # Editar .env: # DOCKER_UID / DOCKER_GID → resultado de: id -u && id -g # INITIAL_ADMIN_PASSWORD → definir para evitar el bootstrap admin/admin por defecto # 4. Construir e iniciar docker compose --env-file .env up --build -d # 5. Abrir http://localhost:8080 en el navegador ``` ### Primer inicio de sesión | Caso | Credenciales | Comportamiento | |------|-------------|----------------| | `INITIAL_ADMIN_PASSWORD` definido | `admin` / `` | Inicio de sesión normal | | `INITIAL_ADMIN_PASSWORD` no definido | `admin` / `admin` | Cambio de contraseña obligatorio antes de acceder a la aplicación | --- ## ⚙️ Configuración Toda la configuración se realiza mediante variables de entorno. Ver `.env.example` para la lista completa con descripciones. | Variable | Por defecto | Descripción | |----------|-------------|-------------| | `SECRET_KEY` | auto-generada | Clave de firma JWT. Definir explícitamente en producción. | | `INITIAL_ADMIN_PASSWORD` | _(vacío)_ | Contraseña de bootstrap del admin. Si no se define, se usa `admin/admin` con cambio forzado. | | `ALLOWED_ORIGINS` | `*` | Orígenes CORS permitidos (separados por comas). Definir en tu dominio en producción. | | `BIND_ADDRESS` | `0.0.0.0` | Dirección IP de escucha. Definir en la interfaz frente al reverse proxy. | | `DOCKER_UID` / `DOCKER_GID` | `1000` | UID/GID para el proceso backend. Debe coincidir con el usuario propietario de `./db_data/`. | ```bash cp .env.example .env # Editar .env — definir como mínimo DOCKER_UID, DOCKER_GID, INITIAL_ADMIN_PASSWORD docker compose --env-file .env up --build -d ``` --- ## 🔒 Seguridad ### Gestión de secretos Dos opciones según sus requisitos de seguridad. #### Opción A — Secreto auto-generado (recomendado para nodo único) Dejar `SECRET_KEY` sin definir (o vacío) en `.env`. En el primer arranque, el backend genera una clave hexadecimal aleatoria de 64 caracteres, la escribe en `db_data/secret_key.txt` con permisos **0600** y la reutiliza en cada reinicio posterior. El secreto nunca aparece en una variable de entorno, un archivo compose o un log. ```bash # .env — dejar la línea vacía o eliminarla SECRET_KEY= ``` El único requisito es que `db_data/` esté respaldado (ya contiene la base de datos). #### Opción B — Secreto mediante archivo Docker Compose Almacena el secreto en un archivo en el host, fuera del control de versiones, y lo monta en el contenedor. El valor nunca aparece en una variable de entorno. ```bash # Generar y almacenar el secreto fuera del directorio del proyecto mkdir -p ~/.secrets python3 -c "import secrets; print(secrets.token_hex(32))" > ~/.secrets/topologie_secret_key chmod 600 ~/.secrets/topologie_secret_key ``` Luego descomentar los bloques `secrets:` en `docker-compose.yml` (ver los comentarios en ese archivo) y eliminar `SECRET_KEY` de `.env`. Docker Compose fusiona la anulación automáticamente: ```bash docker compose up -d ``` ### Rotación de clave Para rotar el secreto JWT (invalida todas las sesiones activas): ```bash # Opción A — variable de entorno (recomendado) # Definir un nuevo SECRET_KEY en la configuración de despliegue y reiniciar # Opción B — rotación por archivo docker compose stop backend rm db_data/secret_key.txt docker compose start backend # Todos los usuarios deberán iniciar sesión de nuevo ``` ### HTTPS Esta aplicación no termina TLS. Para uso en producción, colócala detrás de un reverse proxy que gestione HTTPS. #### Nginx ```nginx server { listen 443 ssl; server_name inventory.example.com; ssl_certificate /etc/letsencrypt/live/inventory.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/inventory.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` #### Traefik — Labels Docker Añadir estas etiquetas al servicio `frontend` y conectarlo a la red compartida con Traefik: ```yaml # docker-compose.override.yml services: frontend: labels: - "traefik.enable=true" - "traefik.http.routers.inventory.rule=Host(`inventory.example.com`)" - "traefik.http.routers.inventory.entrypoints=websecure" - "traefik.http.routers.inventory.tls.certresolver=letsencrypt" - "traefik.http.services.inventory.loadbalancer.server.port=8080" networks: - internal - traefik_public # red compartida con tu instancia de Traefik networks: traefik_public: external: true ``` #### Traefik — Configuración dinámica (file provider) Si Traefik no corre en Docker (o prefieres el file provider), deposita un archivo en tu directorio de configuración dinámica: ```yaml # /etc/traefik/dynamic/inventory.yml http: routers: inventory: rule: "Host(`inventory.example.com`)" entryPoints: - websecure tls: certResolver: letsencrypt service: inventory-svc services: inventory-svc: loadBalancer: servers: - url: "http://127.0.0.1:8080" ``` Traefik detecta el archivo automáticamente — no se requiere reinicio. Para uso solo local, enlazar a loopback para evitar exposición accidental en la LAN: ```yaml # docker-compose.override.yml services: frontend: ports: - "127.0.0.1:8080:8080" ``` ### Endurecimiento de contenedores | Medida | Backend | Frontend | |--------|---------|----------| | Usuario no-root | `DOCKER_UID:DOCKER_GID` (usuario host) | `nginx` (UID 101) | | `cap_drop: ALL` | ✓ | ✓ | | `cap_add: NET_RAW` | ✓ (ping) | — | | `no-new-privileges` | — ¹ | ✓ | | Healthcheck | ✓ | ✓ | ¹ Omitido en el backend: el ping usa file capabilities (`cap_net_raw=ep`); `no-new-privileges` suprimiría el bit efectivo del archivo e impediría al subproceso adquirir `CAP_NET_RAW` en su conjunto efectivo aunque el padre lo tenga en su conjunto permitido. --- ## 💾 Persistencia de datos Todos los datos se almacenan en `./db_data/`: | Archivo | Descripción | |---------|-------------| | `topology.db` | Base de datos SQLite | | `secret_key.txt` | Secreto JWT auto-generado (permisos 0600) | **Copia de seguridad**: `cp -r db_data/ db_data.bak/` **Restauración**: detener la stack, reemplazar `db_data/`, reiniciar. --- ## 🧑‍💻 Desarrollo ### Tests del backend ```bash cd backend pip install -r requirements.txt -r requirements-test.txt pytest tests/ -v ``` ### Dev local (sin Docker) ```bash # Backend cd backend pip install -r requirements.txt uvicorn main:app --reload # Frontend (terminal separada) cd frontend npm install npm run dev # Servidor dev Vite en :5173, proxifica /api/ hacia :8000 ``` --- ## 🏗️ Arquitectura Ver [`docs/architecture.md`](docs/architecture.md) para el flujo de solicitudes detallado, la configuración Docker y el modelo de autenticación.