Upload files to "/"
This commit is contained in:
parent
edefaf9b69
commit
85c6e357e9
847
05-decision-seguridad.md
Normal file
847
05-decision-seguridad.md
Normal file
|
|
@ -0,0 +1,847 @@
|
||||||
|
# Decision: Capa de seguridad (punto 2)
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Este servidor fue comprometido con un credential stealer via PAM que opero 4 dias sin ser detectado. Tenemos firewall bien configurado (policy DROP, SSH por IP, bloqueo de mineria) y aun asi entraron. La capa de seguridad debe cubrir lo que el firewall no puede: **detectar amenazas que ya estan dentro**.
|
||||||
|
|
||||||
|
4 subcapas:
|
||||||
|
- 2.1 Deteccion de intrusiones y file integrity
|
||||||
|
- 2.2 Escaneo de vulnerabilidades
|
||||||
|
- 2.3 Gestion de secretos
|
||||||
|
- 2.4 Acceso y autenticacion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.1 Deteccion de intrusiones y file integrity
|
||||||
|
|
||||||
|
### La pregunta: Wazuh, AIDE, o auditd?
|
||||||
|
|
||||||
|
No son excluyentes. Hacen cosas diferentes:
|
||||||
|
|
||||||
|
| Herramienta | Que hace | Analogia |
|
||||||
|
|-------------|----------|----------|
|
||||||
|
| **auditd** | Registra eventos del kernel: quien abrio/modifico/creo que fichero, que proceso hizo que syscall | La camara de seguridad |
|
||||||
|
| **AIDE** | Genera baseline de checksums y alerta si algo cambia | La foto del "antes" para comparar |
|
||||||
|
| **Wazuh** | HIDS completo: analiza logs + file integrity + rootkit detection + vulnerability detection + compliance + respuesta activa | El guardia de seguridad con camaras, alarmas y protocolo |
|
||||||
|
|
||||||
|
### Opcion A: Wazuh (RECOMENDADA)
|
||||||
|
|
||||||
|
#### Que es
|
||||||
|
Plataforma de seguridad open source (fork de OSSEC). Incluye:
|
||||||
|
- **HIDS**: deteccion de intrusiones basada en host
|
||||||
|
- **FIM**: file integrity monitoring (incluye lo que hace AIDE)
|
||||||
|
- **Rootkit detection**: busca artefactos conocidos de rootkits
|
||||||
|
- **Log analysis**: analiza auth.log, syslog, etc. con reglas de correlacion
|
||||||
|
- **Vulnerability detection**: escanea paquetes instalados contra bases de CVE
|
||||||
|
- **Active response**: puede bloquear IPs, matar procesos, ejecutar acciones automaticas
|
||||||
|
- **Compliance**: reportes CIS, PCI-DSS, GDPR
|
||||||
|
- **Dashboard**: Kibana/OpenSearch integrado, o Grafana via plugin
|
||||||
|
|
||||||
|
#### Que habria detectado en este incidente
|
||||||
|
|
||||||
|
| Evento | Regla Wazuh | Tiempo de deteccion |
|
||||||
|
|--------|-------------|-------------------|
|
||||||
|
| Creacion de `/usr/bin/login.sh` | FIM: fichero nuevo en directorio monitorizado | **Segundos** |
|
||||||
|
| Modificacion de `/etc/pam.d/common-auth` | FIM: cambio en fichero critico | **Segundos** |
|
||||||
|
| Creacion de `/etc/ld.so.preload` | FIM + rootkit check | **Segundos** |
|
||||||
|
| Creacion de `/usr/local/sbin/env/` | FIM: directorio nuevo en ruta de sistema | **Segundos** |
|
||||||
|
| Conexion saliente a 91.208.162.132:10480 | Log analysis de firewall + active response | **Minutos** |
|
||||||
|
| Exfiltracion de credenciales via curl | Log analysis / network anomaly | **Minutos** |
|
||||||
|
|
||||||
|
**Conclusion: el incidente habria durado minutos, no 4 dias.**
|
||||||
|
|
||||||
|
#### Arquitectura
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ Wazuh Manager │
|
||||||
|
│ (servidor central) │
|
||||||
|
│ │
|
||||||
|
│ - Recibe eventos de agentes │
|
||||||
|
│ - Evalua reglas │
|
||||||
|
│ - Genera alertas │
|
||||||
|
│ - Vulnerability scanner │
|
||||||
|
│ │
|
||||||
|
│ Wazuh Dashboard │
|
||||||
|
│ (o Grafana via plugin) │
|
||||||
|
└──────────┬──────────────────┘
|
||||||
|
│
|
||||||
|
┌────────────────┼────────────────────┐
|
||||||
|
│ │ │
|
||||||
|
┌────────▼────────┐ ┌────▼──────────┐ ┌──────▼───────┐
|
||||||
|
│ Wazuh Agent │ │ Wazuh Agent │ │ Wazuh Agent │
|
||||||
|
│ (PBS server 1) │ │ (PBS server 2│ │ (PVE node) │
|
||||||
|
│ │ │ )│ │ │
|
||||||
|
│ - FIM │ │ - FIM │ │ - FIM │
|
||||||
|
│ - Log analysis │ │ - Log anal. │ │ - Log anal. │
|
||||||
|
│ - Rootkit det. │ │ - Rootkit │ │ - Rootkit │
|
||||||
|
│ - Vuln scan │ │ - Vuln scan │ │ - Vuln scan │
|
||||||
|
└────────────────┘ └──────────────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Configuracion FIM para PBS/Proxmox
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- /var/ossec/etc/ossec.conf (en el agente) -->
|
||||||
|
<syscheck>
|
||||||
|
<!-- Frecuencia de escaneo: cada 5 minutos para criticos -->
|
||||||
|
<frequency>300</frequency>
|
||||||
|
|
||||||
|
<!-- FICHEROS CRITICOS - Alerta inmediata en tiempo real -->
|
||||||
|
<directories check_all="yes" realtime="yes" report_changes="yes">
|
||||||
|
/etc/pam.d
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes" realtime="yes" report_changes="yes">
|
||||||
|
/etc/ssh
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/etc/ld.so.preload
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/root/.ssh/authorized_keys
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/etc/sudoers,/etc/sudoers.d
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/etc/cron.d,/etc/crontab
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/etc/systemd/system
|
||||||
|
</directories>
|
||||||
|
|
||||||
|
<!-- BINARIOS DE SISTEMA - Detectar modificaciones/adiciones -->
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/usr/bin,/usr/sbin,/usr/local/bin,/usr/local/sbin
|
||||||
|
</directories>
|
||||||
|
|
||||||
|
<!-- CONFIGURACION PROXMOX/PBS -->
|
||||||
|
<directories check_all="yes" realtime="yes">
|
||||||
|
/etc/proxmox-backup
|
||||||
|
</directories>
|
||||||
|
<directories check_all="yes">
|
||||||
|
/etc/pve
|
||||||
|
</directories>
|
||||||
|
|
||||||
|
<!-- Ignorar ficheros que cambian legitimamente -->
|
||||||
|
<ignore>/etc/pve/authkey.pub</ignore>
|
||||||
|
<ignore>/etc/pve/authkey.pub.old</ignore>
|
||||||
|
<ignore>/etc/pve/priv/authkey.key</ignore>
|
||||||
|
<ignore>/etc/proxmox-backup/shadow.json</ignore>
|
||||||
|
<ignore type="sregex">/etc/proxmox-backup/.*.cfg</ignore>
|
||||||
|
<ignore>/etc/zfs/zpool.cache</ignore>
|
||||||
|
</syscheck>
|
||||||
|
|
||||||
|
<!-- Rootkit detection -->
|
||||||
|
<rootcheck>
|
||||||
|
<rootkit_files>/var/ossec/etc/shared/rootkit_files.txt</rootkit_files>
|
||||||
|
<rootkit_trojans>/var/ossec/etc/shared/rootkit_trojans.txt</rootkit_trojans>
|
||||||
|
<system_audit>/var/ossec/etc/shared/system_audit_rcl.txt</system_audit>
|
||||||
|
<frequency>3600</frequency>
|
||||||
|
</rootcheck>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Reglas custom para este tipo de ataque
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- /var/ossec/etc/rules/local_rules.xml (en el manager) -->
|
||||||
|
<group name="custom_pbs_security">
|
||||||
|
|
||||||
|
<!-- Detectar pam_exec añadido (el vector exacto de este incidente) -->
|
||||||
|
<rule id="100001" level="15">
|
||||||
|
<if_sid>550</if_sid>
|
||||||
|
<match>pam.d</match>
|
||||||
|
<description>Fichero PAM modificado - posible backdoor</description>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Detectar ld.so.preload creado o modificado -->
|
||||||
|
<rule id="100002" level="15">
|
||||||
|
<if_sid>554</if_sid>
|
||||||
|
<match>ld.so.preload</match>
|
||||||
|
<description>ld.so.preload creado/modificado - posible rootkit</description>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Nuevo fichero en /usr/bin que no es de un paquete -->
|
||||||
|
<rule id="100003" level="12">
|
||||||
|
<if_sid>554</if_sid>
|
||||||
|
<match>/usr/bin/|/usr/sbin/</match>
|
||||||
|
<description>Nuevo fichero en directorio de sistema</description>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Detectar curl a IPs externas desde scripts de sistema -->
|
||||||
|
<rule id="100004" level="10">
|
||||||
|
<if_sid>530</if_sid>
|
||||||
|
<match>curl.*--max-time|nohup.*curl</match>
|
||||||
|
<description>Ejecucion sospechosa de curl con nohup</description>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Integracion con el stack existente
|
||||||
|
|
||||||
|
**Opcion 1: Wazuh Dashboard (OpenSearch)**
|
||||||
|
- Dashboard propio de Wazuh con todos los modulos
|
||||||
|
- Requiere OpenSearch (fork Elasticsearch) + 4GB RAM extra
|
||||||
|
- Mejor experiencia para analisis de seguridad dedicado
|
||||||
|
|
||||||
|
**Opcion 2: Wazuh + Grafana (via plugin o Loki)**
|
||||||
|
- Wazuh envia alertas a Loki -> Grafana las muestra junto con metricas y logs
|
||||||
|
- No necesita OpenSearch (ahorra 4GB RAM)
|
||||||
|
- Un solo panel para todo, pero pierde dashboards especializados de Wazuh
|
||||||
|
|
||||||
|
**Recomendacion**: empezar con **Opcion 1** (Wazuh Dashboard completo). La seguridad merece su propio panel especializado. Las alertas criticas se reenvian a Telegram/email igual que el resto.
|
||||||
|
|
||||||
|
#### Recursos
|
||||||
|
|
||||||
|
| Componente | CPU | RAM | Disco |
|
||||||
|
|-----------|-----|-----|-------|
|
||||||
|
| Wazuh Manager + Dashboard (OpenSearch) | 4 vCPU | 12 GB | 100 GB |
|
||||||
|
| Wazuh Agent (por servidor) | <0.1 | 50-100 MB | 100 MB |
|
||||||
|
|
||||||
|
**VM dedicada recomendada**: no compartir con la VM de monitoring (VictoriaMetrics/Loki/Grafana). Si Wazuh detecta un problema, debe estar en infra separada.
|
||||||
|
|
||||||
|
#### Despliegue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manager (VM dedicada)
|
||||||
|
curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
|
||||||
|
bash wazuh-install.sh -a # instalacion all-in-one
|
||||||
|
|
||||||
|
# Agent (en cada servidor, via Ansible)
|
||||||
|
apt-get install wazuh-agent
|
||||||
|
# Configurar manager IP y registrar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opcion B: AIDE + auditd (ligero, sin servidor central)
|
||||||
|
|
||||||
|
#### Cuando tiene sentido
|
||||||
|
- Si no quereis/podeis mantener un Wazuh Manager
|
||||||
|
- Como complemento a Wazuh (belt and suspenders)
|
||||||
|
- Para servidores aislados sin conectividad al manager
|
||||||
|
|
||||||
|
#### AIDE (file integrity)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Instalar y generar baseline
|
||||||
|
apt install aide
|
||||||
|
aide --init
|
||||||
|
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
|
||||||
|
|
||||||
|
# Check diario via cron
|
||||||
|
0 5 * * * /usr/bin/aide --check | mail -s "AIDE $(hostname)" seguridad@empresa.com
|
||||||
|
|
||||||
|
# Tras cambios legitimos, regenerar baseline
|
||||||
|
aide --update
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuracion para PBS:
|
||||||
|
```
|
||||||
|
# /etc/aide/aide.conf.d/99_pbs_custom.conf
|
||||||
|
/etc/pam.d Full
|
||||||
|
/etc/ssh Full
|
||||||
|
/etc/ld.so.preload Full
|
||||||
|
/usr/bin Full
|
||||||
|
/usr/sbin Full
|
||||||
|
/usr/local/bin Full
|
||||||
|
/usr/local/sbin Full
|
||||||
|
/root/.ssh Full
|
||||||
|
/etc/cron.d Full
|
||||||
|
/etc/systemd/system Full
|
||||||
|
```
|
||||||
|
|
||||||
|
#### auditd (registro de eventos)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# /etc/audit/rules.d/pbs-security.rules
|
||||||
|
|
||||||
|
# Monitorizar cambios en PAM
|
||||||
|
-w /etc/pam.d/ -p wa -k pam_changes
|
||||||
|
|
||||||
|
# Monitorizar ld.so.preload
|
||||||
|
-w /etc/ld.so.preload -p wa -k ld_preload
|
||||||
|
|
||||||
|
# Monitorizar SSH config
|
||||||
|
-w /etc/ssh/ -p wa -k ssh_changes
|
||||||
|
|
||||||
|
# Monitorizar authorized_keys
|
||||||
|
-w /root/.ssh/authorized_keys -p wa -k ssh_keys
|
||||||
|
|
||||||
|
# Monitorizar crontabs
|
||||||
|
-w /etc/crontab -p wa -k cron_changes
|
||||||
|
-w /etc/cron.d/ -p wa -k cron_changes
|
||||||
|
-w /var/spool/cron/ -p wa -k cron_changes
|
||||||
|
|
||||||
|
# Monitorizar creacion de ficheros en /usr/bin
|
||||||
|
-w /usr/bin/ -p wa -k system_binaries
|
||||||
|
-w /usr/sbin/ -p wa -k system_binaries
|
||||||
|
-w /usr/local/bin/ -p wa -k local_binaries
|
||||||
|
-w /usr/local/sbin/ -p wa -k local_binaries
|
||||||
|
|
||||||
|
# Monitorizar sudoers
|
||||||
|
-w /etc/sudoers -p wa -k sudoers
|
||||||
|
-w /etc/sudoers.d/ -p wa -k sudoers
|
||||||
|
|
||||||
|
# Monitorizar systemd
|
||||||
|
-w /etc/systemd/system/ -p wa -k systemd_changes
|
||||||
|
|
||||||
|
# Monitorizar ejecucion de herramientas sospechosas
|
||||||
|
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/curl -k suspicious_exec
|
||||||
|
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/wget -k suspicious_exec
|
||||||
|
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/base64 -k suspicious_exec
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limitaciones sin Wazuh
|
||||||
|
- auditd registra pero **no alerta** (hay que leer los logs o enviarlos a Loki)
|
||||||
|
- AIDE detecta cambios pero **solo en el check programado** (no real-time)
|
||||||
|
- No hay correlacion de eventos entre servidores
|
||||||
|
- No hay vulnerability scanning
|
||||||
|
- No hay active response
|
||||||
|
|
||||||
|
### Opcion C: Lynis (auditoria puntual, no monitoring continuo)
|
||||||
|
|
||||||
|
#### Que es
|
||||||
|
Script de auditoria de seguridad que escanea el sistema y da puntuacion con recomendaciones. No es un agente permanente.
|
||||||
|
|
||||||
|
#### Uso
|
||||||
|
```bash
|
||||||
|
apt install lynis
|
||||||
|
lynis audit system
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Donde encaja
|
||||||
|
- Ejecutar tras el despliegue inicial de cada servidor (baseline)
|
||||||
|
- Ejecutar periodicamente (mensual) para verificar drift
|
||||||
|
- Generar informe de hardening pendiente
|
||||||
|
- **No reemplaza** ni a Wazuh ni a auditd, es complementario
|
||||||
|
|
||||||
|
### Comparativa 2.1
|
||||||
|
|
||||||
|
| Aspecto | Wazuh | AIDE + auditd | Lynis |
|
||||||
|
|---------|-------|--------------|-------|
|
||||||
|
| Deteccion real-time | Si (FIM + log analysis) | auditd si, AIDE no | No (puntual) |
|
||||||
|
| File integrity | Si (integrado) | AIDE (cron) | Check puntual |
|
||||||
|
| Rootkit detection | Si | No | Basico |
|
||||||
|
| Vulnerability scan | Si | No | Basico |
|
||||||
|
| Log analysis | Si (reglas de correlacion) | Hay que leer logs | No |
|
||||||
|
| Active response | Si (bloquear IP, matar proceso) | No | No |
|
||||||
|
| Alertas | Si (email, Telegram, webhook) | Manual (cron + mail) | Informe |
|
||||||
|
| Servidor central | Si (Manager) | No | No |
|
||||||
|
| RAM por servidor | 50-100 MB | 10-20 MB | Solo durante scan |
|
||||||
|
| Complejidad | Media-alta | Baja | Nula |
|
||||||
|
|
||||||
|
### DECISION 2.1 (confirmada)
|
||||||
|
|
||||||
|
**Wazuh como pilar principal** + **auditd como complemento** en todos los servidores + **Lynis** como check puntual post-despliegue.
|
||||||
|
|
||||||
|
- Wazuh: deteccion continua, alertas, correlacion, vulnerability scanning
|
||||||
|
- auditd: registro granular de quien hizo que (forensics, compliance)
|
||||||
|
- Lynis: auditoria de hardening tras cada despliegue y revision trimestral
|
||||||
|
|
||||||
|
**Visualizacion**: Wazuh Dashboard propio (OpenSearch). La seguridad merece su panel especializado con modulos dedicados (FIM, rootkits, vulnerabilidades, compliance). Al no tener experiencia previa en HIDS, los dashboards especializados aceleran el aprendizaje. Alertas criticas se reenvian a Telegram/email igualmente.
|
||||||
|
|
||||||
|
**Lynis como checklist**: se ejecuta en 4 momentos:
|
||||||
|
1. Instalacion nueva (baseline)
|
||||||
|
2. Post-hardening (verificar que Ansible aplico todo)
|
||||||
|
3. Revision periodica trimestral (detectar drift)
|
||||||
|
4. Post-incidente (evaluar estado tras limpieza)
|
||||||
|
|
||||||
|
**Overhead por servidor**: 50-100MB RAM del agente Wazuh. Perfectamente asumible en servidores de 32-256GB (pasa completamente desapercibido).
|
||||||
|
|
||||||
|
**Recursos VM Wazuh**: 4 vCPU, 12 GB RAM (8 Manager + 4 Dashboard/OpenSearch), 100 GB disco. VM dedicada separada de monitoring.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.2 Escaneo de vulnerabilidades
|
||||||
|
|
||||||
|
### Que necesitamos
|
||||||
|
|
||||||
|
Dos tipos de escaneo:
|
||||||
|
|
||||||
|
| Tipo | Que busca | Desde donde |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| **Interno (host)** | Paquetes con CVEs, configuraciones debiles | Dentro de cada servidor |
|
||||||
|
| **Externo (red)** | Puertos abiertos, servicios expuestos, CVEs accesibles | Desde fuera del servidor |
|
||||||
|
|
||||||
|
### Escaneo interno: ya cubierto por Wazuh
|
||||||
|
|
||||||
|
Wazuh incluye vulnerability detection que escanea los paquetes instalados contra bases de CVE (NVD, Debian Security). Si hemos desplegado Wazuh, esto ya esta cubierto.
|
||||||
|
|
||||||
|
### Escaneo externo: OpenVAS/Greenbone
|
||||||
|
|
||||||
|
| Herramienta | Que hace | Recursos |
|
||||||
|
|-------------|----------|----------|
|
||||||
|
| **OpenVAS/Greenbone** | Escaner de vulnerabilidades de red. Escanea puertos, servicios, prueba CVEs conocidos. | VM dedicada: 4 vCPU, 8 GB RAM, 20 GB disco |
|
||||||
|
|
||||||
|
**Frecuencia**: scan semanal o tras cambios significativos. No mantener scaneando continuamente (consume muchos recursos y puede afectar servicios).
|
||||||
|
|
||||||
|
**Alternativa ligera**: **Nuclei** (escaner basado en templates, mucho mas ligero que OpenVAS). Ideal para servicios web (paneles Proxmox, PBS GUI, PowerDNS Admin).
|
||||||
|
|
||||||
|
### Escaneo de contenedores: Trivy
|
||||||
|
|
||||||
|
Para la VM del DNS y cualquier otra VM con Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Escanear imagenes en uso
|
||||||
|
trivy image powerdns/pdns-auth
|
||||||
|
trivy image mariadb:latest
|
||||||
|
trivy image traefik:latest
|
||||||
|
|
||||||
|
# Escanear docker-compose
|
||||||
|
trivy config /opt/30-powerdns/docker-compose.yml
|
||||||
|
|
||||||
|
# Escanear filesystem de la VM
|
||||||
|
trivy fs --scanners vuln,misconfig /
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frecuencia**: tras cada `docker pull` o actualizacion de imagenes.
|
||||||
|
|
||||||
|
### Modelo de responsabilidad: VMs Linux vs Windows
|
||||||
|
|
||||||
|
| Tipo | Quien gestiona | Que hacemos nosotros |
|
||||||
|
|------|----------------|---------------------|
|
||||||
|
| **Hosts Proxmox/PBS** | Nosotros | Wazuh agent + todo el stack de seguridad |
|
||||||
|
| **VMs Linux** | Nosotros (mayoria) | Wazuh agent + auditd + hardening |
|
||||||
|
| **VMs Windows** | El cliente | Solo monitoring externo |
|
||||||
|
|
||||||
|
Para VMs Windows de clientes (IaaS):
|
||||||
|
- **Monitorizar desde fuera**: Uptime Kuma (disponibilidad), trafico anomalo desde el host (mineria, C2, DDoS)
|
||||||
|
- **NO instalar agentes dentro**, NO gestionar parches, NO escanear vulnerabilidades
|
||||||
|
- Si la VM "se desmadra", tenemos visibilidad de red/recursos para orientar al cliente
|
||||||
|
|
||||||
|
### DECISION 2.2 (confirmada)
|
||||||
|
|
||||||
|
| Herramienta | Ambito | Frecuencia | Prioridad |
|
||||||
|
|-------------|--------|------------|-----------|
|
||||||
|
| **Wazuh vuln detection** | Paquetes en hosts + VMs Linux gestionadas | Continuo (ya viene con Wazuh) | Fase 1 (gratis, ya incluido) |
|
||||||
|
| **Trivy** | Imagenes Docker en VMs | Tras updates de imagenes | Fase 2 |
|
||||||
|
| **Nuclei** | Servicios web expuestos (PBS:8007, PVE:8006, paneles) | Semanal (programado en Semaphore) | Fase 2 (mes 2-3) |
|
||||||
|
| **OpenVAS** | ~~Escaneo de red completo~~ | ~~Mensual~~ | **Aparcado** salvo requisitos de compliance (ISO 27001, PCI-DSS) |
|
||||||
|
| **VMs Windows** | Solo monitoring externo (red, disponibilidad) | Continuo desde el host | Siempre |
|
||||||
|
|
||||||
|
OpenVAS descartado por ahora: con Wazuh + Nuclei cubrimos el 90%. OpenVAS requiere VM dedicada (4 vCPU, 8GB RAM) y solo aporta valor marginal sin compliance obligatorio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.3 Gestion de secretos e identidad
|
||||||
|
|
||||||
|
### El problema real
|
||||||
|
|
||||||
|
En este entorno encontramos:
|
||||||
|
- Contraseñas en docker-compose.yml en texto plano (`PDNS_API_KEY=secret`, `MYSQL_ROOT_PASSWORD`)
|
||||||
|
- Contraseña de PBS en scripts (`modificar.sh` recibe password como argumento)
|
||||||
|
- API tokens en ficheros de configuracion
|
||||||
|
- Password de root capturada y enviada al atacante
|
||||||
|
- ~50 contraseñas distintas para diferentes servicios, compartidas por chat
|
||||||
|
|
||||||
|
### Dos estrategias complementarias
|
||||||
|
|
||||||
|
No basta con custodiar secretos (guardarlos bien). Tambien hay que **reducir la cantidad de secretos** (SSO):
|
||||||
|
|
||||||
|
| Estrategia | Que hace | Herramienta |
|
||||||
|
|------------|----------|-------------|
|
||||||
|
| **Custodiar secretos** | Guardar 50 contraseñas de forma segura | Vaultwarden |
|
||||||
|
| **Reducir secretos** | Tener 1 login con MFA que abre todo (SSO) | **Authentik** |
|
||||||
|
|
||||||
|
### Authentik: Identity Provider centralizado
|
||||||
|
|
||||||
|
**Que es**: Identity Provider open source (SSO, SAML, OAuth2, OIDC, LDAP provider). UI moderna e intuitiva.
|
||||||
|
|
||||||
|
**Por que Authentik y no Keycloak/Authelia**:
|
||||||
|
|
||||||
|
| Aspecto | Authentik | Keycloak | Authelia |
|
||||||
|
|---------|-----------|----------|----------|
|
||||||
|
| UI | Moderna, intuitiva | Funcional pero anticuada | Minima (solo login) |
|
||||||
|
| LDAP provider | Si (emite LDAP) | Si | No |
|
||||||
|
| Proxy de autenticacion | Si (protege apps sin SSO nativo) | No nativo | Si (es su funcion) |
|
||||||
|
| MFA integrado | TOTP, WebAuthn, Duo | TOTP, WebAuthn | TOTP, WebAuthn |
|
||||||
|
| Flows personalizables | Si (drag & drop) | Si (mas complejo) | No |
|
||||||
|
| Recursos | 2 vCPU, 2 GB RAM | 2 vCPU, 4 GB RAM | 1 vCPU, 512 MB RAM |
|
||||||
|
| Comunidad | Creciente, muy activa | Grande, madura | Pequeña |
|
||||||
|
|
||||||
|
**Que servicios se ponen detras de Authentik (SSO)**:
|
||||||
|
|
||||||
|
| Servicio | Soporte SSO | Como |
|
||||||
|
|----------|-------------|------|
|
||||||
|
| **Proxmox VE** | OpenID Connect (nativo desde PVE 7) | Configuracion directa |
|
||||||
|
| **Proxmox Backup Server** | OpenID Connect (nativo) | Configuracion directa |
|
||||||
|
| **Grafana** | OAuth2/OIDC nativo | Configuracion directa |
|
||||||
|
| **Netbox** | OAuth2/OIDC nativo | Plugin SSO |
|
||||||
|
| **Forgejo** | OAuth2/OIDC nativo | Configuracion directa |
|
||||||
|
| **Semaphore** | OIDC nativo | Configuracion directa |
|
||||||
|
| **Wazuh Dashboard** | SAML/OpenID | Configuracion en OpenSearch |
|
||||||
|
| **Vaultwarden** | SSO nativo (v1.29+) | Configuracion directa |
|
||||||
|
| **PowerDNS Admin** | OAuth2 nativo | Configuracion directa |
|
||||||
|
| **n8n** | OAuth2 nativo | Configuracion directa |
|
||||||
|
| **Uptime Kuma** | No nativo | Authentik proxy delante |
|
||||||
|
| **phpMyAdmin** | No nativo | Authentik proxy delante |
|
||||||
|
|
||||||
|
**Impacto**: un tecnico, una cuenta, un MFA, acceso a todo. Baja de la empresa = desactivar 1 cuenta, no 50.
|
||||||
|
|
||||||
|
### Evolucion de la gestion de secretos
|
||||||
|
|
||||||
|
```
|
||||||
|
FASE 0 (actual):
|
||||||
|
50 contraseñas distintas → en la cabeza, en un .txt, en Telegram
|
||||||
|
|
||||||
|
FASE 1 (dia 1 - Vaultwarden + .env):
|
||||||
|
50 contraseñas distintas → custodiadas en Vaultwarden
|
||||||
|
Secretos en docker-compose → ficheros .env con chmod 600
|
||||||
|
|
||||||
|
FASE 2 (dia 1 - Authentik):
|
||||||
|
1 cuenta Authentik con MFA → abre PVE, PBS, Grafana, Netbox,
|
||||||
|
Forgejo, Semaphore, Wazuh...
|
||||||
|
~10 contraseñas residuales → custodiadas en Vaultwarden
|
||||||
|
(APIs, root de emergencia, clientes)
|
||||||
|
|
||||||
|
FASE 3 (mes 6+ - HashiCorp Vault):
|
||||||
|
Authentik → humanos
|
||||||
|
Vault → maquinas (Ansible, CI/CD, API tokens con rotacion)
|
||||||
|
Vaultwarden → secretos residuales del equipo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Orden de despliegue critico
|
||||||
|
|
||||||
|
**Authentik debe desplegarse ANTES que los demas servicios** en el servidor nuevo. Si no, cada servicio nace con contraseña local y hay que reconfigurarlo despues.
|
||||||
|
|
||||||
|
Orden correcto:
|
||||||
|
```
|
||||||
|
Dia 1: Forgejo (git) → para versionar todo desde el minuto 0
|
||||||
|
Dia 1: Authentik → para que todo lo siguiente ya nazca con SSO
|
||||||
|
Dia 1: Vaultwarden → con SSO de Authentik
|
||||||
|
Dia 2: Wazuh Manager → con SSO
|
||||||
|
Dia 2: VictoriaMetrics + Grafana → con SSO
|
||||||
|
Dia 3: Loki + Alloy
|
||||||
|
Dia 3: Netbox → con SSO
|
||||||
|
Dia 4: Semaphore → con SSO
|
||||||
|
... Todo nace autenticado contra Authentik
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vaultwarden (gestor de contraseñas para el equipo)
|
||||||
|
|
||||||
|
**Que es**: fork self-hosted de Bitwarden. Gestor de contraseñas para equipos.
|
||||||
|
|
||||||
|
**Que resuelve**:
|
||||||
|
- Contraseñas residuales (APIs, root de emergencia, credenciales de clientes)
|
||||||
|
- Generacion de contraseñas fuertes
|
||||||
|
- Audit log de quien accedio a que secreto
|
||||||
|
- Eliminacion de contraseñas en chats/post-its
|
||||||
|
|
||||||
|
**Despliegue**: un contenedor Docker, 256MB RAM, 1GB disco.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
vaultwarden:
|
||||||
|
image: vaultwarden/server:latest
|
||||||
|
container_name: vaultwarden
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
environment:
|
||||||
|
- ADMIN_TOKEN=${ADMIN_TOKEN}
|
||||||
|
- SIGNUPS_ALLOWED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### HashiCorp Vault (secretos para automatizacion) - MES 6+
|
||||||
|
|
||||||
|
**Cuando implementar**: cuando Ansible este en produccion y la automatizacion lo justifique. No antes.
|
||||||
|
|
||||||
|
**Que resuelve**:
|
||||||
|
- Secretos en docker-compose.yml → referencia a Vault
|
||||||
|
- Passwords en playbooks → Ansible lee de Vault
|
||||||
|
- API tokens → rotacion automatica
|
||||||
|
- Certificados → PKI interna
|
||||||
|
|
||||||
|
### DECISION 2.3 (confirmada)
|
||||||
|
|
||||||
|
| Fase | Herramienta | Que cubre | Esfuerzo |
|
||||||
|
|------|-------------|-----------|----------|
|
||||||
|
| **Dia 1** | **Vaultwarden** | Custodiar contraseñas del equipo | 30 min |
|
||||||
|
| **Dia 1** | **Ficheros .env** (chmod 600) | Sacar secretos de docker-compose | 1h |
|
||||||
|
| **Dia 1** | **Authentik** | SSO + MFA centralizado para todos los paneles | 1-2 dias |
|
||||||
|
| **Mes 6+** | **HashiCorp Vault** | Secretos para automatizacion (Ansible, CI/CD) | 1 semana |
|
||||||
|
|
||||||
|
Recursos Authentik: 2 vCPU, 2 GB RAM, 20 GB disco (contenedor Docker o VM ligera).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.4 Acceso y autenticacion
|
||||||
|
|
||||||
|
### El problema real
|
||||||
|
|
||||||
|
- SSH con password habilitado (cloud-init lo reactivo)
|
||||||
|
- Solo clave SSH de `david@ansible` como acceso
|
||||||
|
- Sin MFA
|
||||||
|
- Sin registro centralizado de quien accede a que servidor
|
||||||
|
- Sin grabacion de sesiones SSH (no hay audit de que hizo cada tecnico)
|
||||||
|
- PBS:8007 abierto a internet con autenticacion basica
|
||||||
|
|
||||||
|
### Paneles web: ya cubierto por Authentik (2.3)
|
||||||
|
|
||||||
|
Con Authentik desplegado, todos los paneles web (PVE, PBS, Grafana, Netbox, Forgejo, Wazuh, etc.) tienen SSO + MFA centralizado. No hace falta configurar TOTP por separado en cada panel.
|
||||||
|
|
||||||
|
### SSH: Bastion host con grabacion de sesiones
|
||||||
|
|
||||||
|
#### Por que bastion y no Teleport
|
||||||
|
|
||||||
|
Se evaluo Teleport pero se descarto por:
|
||||||
|
- **Licencia AGPL con riesgo** de features migrando a enterprise (patron HashiCorp)
|
||||||
|
- **Cliente propietario** (`tsh`): no es SSH estandar, requiere instalar software en cada maquina de tecnico
|
||||||
|
- **Complejidad adicional** para Ansible (requiere configurar proxy SSH especial)
|
||||||
|
|
||||||
|
El bastion SSH usa **SSH estandar** (OpenSSH `ProxyJump`, disponible desde 2016):
|
||||||
|
|
||||||
|
| Cliente | Soporte ProxyJump (-J) |
|
||||||
|
|---------|----------------------|
|
||||||
|
| Linux (OpenSSH) | Si, nativo |
|
||||||
|
| macOS Terminal / iTerm | Si, nativo |
|
||||||
|
| Windows 10/11 (OpenSSH) | Si, preinstalado |
|
||||||
|
| MobaXterm | Si |
|
||||||
|
| WinSCP | Si (transferencia de ficheros) |
|
||||||
|
| Ansible | Si (`ansible_ssh_common_args`) |
|
||||||
|
| PuTTY | No directo (config propia, pero casi nadie lo usa ya) |
|
||||||
|
|
||||||
|
#### Como funciona
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────┐ ┌─────────────────────────────┐ ┌──────────────┐
|
||||||
|
│ Tecnico │────▶│ BASTION (VM pequeña) │────▶│ Servidor PBS │
|
||||||
|
│ │ │ │ └──────────────┘
|
||||||
|
│ ssh -J │ │ - ForceCommand graba sesion │────▶│ Servidor PVE │
|
||||||
|
│ bastion │ │ (script/asciinema) │ └──────────────┘
|
||||||
|
│ servidor │ │ - Grabacion → Loki │────▶│ Servidor DNS │
|
||||||
|
│ │ │ - Audit log completo │ └──────────────┘
|
||||||
|
└──────────┘ │ - Solo IP permitida en │
|
||||||
|
│ firewalls de servidores │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Los servidores destino **solo aceptan SSH desde el bastion**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Firewall de cada servidor de produccion (via Ansible)
|
||||||
|
iptables -A INPUT -p tcp --dport 22 -s IP_BASTION -j ACCEPT
|
||||||
|
iptables -A INPUT -p tcp --dport 22 -j DROP
|
||||||
|
```
|
||||||
|
|
||||||
|
No hay forma de saltarse el bastion.
|
||||||
|
|
||||||
|
#### Configuracion del tecnico (una sola vez)
|
||||||
|
|
||||||
|
```
|
||||||
|
# ~/.ssh/config del tecnico
|
||||||
|
Host bastion
|
||||||
|
HostName bastion.empresa.local
|
||||||
|
User david
|
||||||
|
IdentityFile ~/.ssh/id_ed25519
|
||||||
|
|
||||||
|
Host *.infra
|
||||||
|
ProxyJump bastion
|
||||||
|
User root
|
||||||
|
IdentityFile ~/.ssh/id_ed25519
|
||||||
|
```
|
||||||
|
|
||||||
|
Uso diario (nada cambia):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH normal, transparente, el tecnico ni nota el bastion:
|
||||||
|
ssh servidor-pbs.infra
|
||||||
|
ssh servidor-pve.infra
|
||||||
|
scp fichero.tar.gz servidor-pbs.infra:/tmp/
|
||||||
|
|
||||||
|
# Ansible tambien funciona sin cambios:
|
||||||
|
ansible all -m ping
|
||||||
|
ansible-playbook -i inventory/netbox.yml playbooks/hardening.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Grabacion de sesiones
|
||||||
|
|
||||||
|
El bastion usa `ForceCommand` en sshd_config para grabar cada sesion con `script` o `asciinema`:
|
||||||
|
- Cada sesion se guarda con: fecha, usuario, servidor destino, duracion
|
||||||
|
- Se puede reproducir con `scriptreplay` o `asciinema play`
|
||||||
|
- Los logs van a Loki para busqueda centralizada
|
||||||
|
- No es un video bonito como Teleport, pero es texto completo con replay y timing
|
||||||
|
|
||||||
|
#### Recursos
|
||||||
|
|
||||||
|
VM bastion: 1 vCPU, 1 GB RAM, 20 GB disco. Minima.
|
||||||
|
|
||||||
|
### Hardening SSH (Ansible playbook, dia 1)
|
||||||
|
|
||||||
|
Cada servidor nuevo nace con este hardening aplicado automaticamente:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Hardening SSH base
|
||||||
|
- PasswordAuthentication: "no"
|
||||||
|
- KbdInteractiveAuthentication: "no"
|
||||||
|
- PermitRootLogin: "prohibit-password" # solo clave
|
||||||
|
- MaxAuthTries: 3
|
||||||
|
- LoginGraceTime: 30
|
||||||
|
- AllowUsers: "root david" # solo usuarios conocidos
|
||||||
|
# Neutralizar cloud-init SSH override
|
||||||
|
- /etc/cloud/cloud.cfg.d/99-ssh-hardening.cfg: "ssh_pwauth: false"
|
||||||
|
# Firewall: SSH solo desde bastion
|
||||||
|
- iptables: -A INPUT -p tcp --dport 22 -s IP_BASTION -j ACCEPT
|
||||||
|
# Fail2ban
|
||||||
|
- Instalar y configurar con ban de 1h tras 5 intentos
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fail2ban (dia 1)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/fail2ban/jail.d/custom.conf
|
||||||
|
[sshd]
|
||||||
|
enabled = true
|
||||||
|
port = ssh
|
||||||
|
filter = sshd
|
||||||
|
maxretry = 5
|
||||||
|
findtime = 600
|
||||||
|
bantime = 3600
|
||||||
|
action = %(action_mwl)s
|
||||||
|
|
||||||
|
[proxmox]
|
||||||
|
enabled = true
|
||||||
|
port = 8006
|
||||||
|
filter = proxmox
|
||||||
|
maxretry = 5
|
||||||
|
bantime = 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
### CrowdSec (semana 2)
|
||||||
|
|
||||||
|
**Que es**: IPS colaborativo open source. Analiza logs, detecta ataques, y comparte IPs maliciosas con la comunidad.
|
||||||
|
|
||||||
|
**Diferencia con Fail2ban**:
|
||||||
|
- Fail2ban: reactivo, local, solo tu ves tus ataques
|
||||||
|
- CrowdSec: proactivo, colaborativo, bloqueas IPs que atacan a otros antes de que te ataquen a ti
|
||||||
|
|
||||||
|
**Complementa** a Fail2ban, no lo reemplaza.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s https://install.crowdsec.net | bash
|
||||||
|
cscli collections install crowdsecurity/sshd
|
||||||
|
cscli collections install crowdsecurity/linux
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flujo de acceso completo para un tecnico
|
||||||
|
|
||||||
|
```
|
||||||
|
Tecnico quiere acceder a:
|
||||||
|
|
||||||
|
Panel PVE/PBS/Grafana/Netbox/Wazuh/Forgejo:
|
||||||
|
→ Authentik SSO → MFA → acceso
|
||||||
|
→ 1 cuenta, 1 password, 1 TOTP
|
||||||
|
|
||||||
|
SSH a un servidor:
|
||||||
|
→ ssh servidor.infra (transparente, pasa por bastion)
|
||||||
|
→ Clave SSH (sin password)
|
||||||
|
→ Bastion graba toda la sesion
|
||||||
|
→ Fail2ban + CrowdSec protegen
|
||||||
|
→ auditd registra todo dentro del servidor
|
||||||
|
→ Wazuh alerta si algo raro
|
||||||
|
|
||||||
|
Secreto de un cliente:
|
||||||
|
→ Vaultwarden (SSO via Authentik) → buscar → copiar
|
||||||
|
→ Audit log de quien accedio
|
||||||
|
```
|
||||||
|
|
||||||
|
### DECISION 2.4 (confirmada)
|
||||||
|
|
||||||
|
| Fase | Accion | Herramienta |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| **Dia 1** | SSO + MFA para todos los paneles web | Authentik |
|
||||||
|
| **Dia 1** | Bastion SSH con grabacion de sesiones | VM bastion + asciinema |
|
||||||
|
| **Dia 1** | Hardening SSH en cada servidor nuevo | Ansible playbook |
|
||||||
|
| **Dia 1** | Fail2ban en cada servidor nuevo | Ansible playbook |
|
||||||
|
| **Cada servidor nuevo** | Firewall: SSH solo desde bastion | Ansible playbook |
|
||||||
|
| **Semana 2** | CrowdSec en servidores expuestos | Ansible playbook |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Capa 2 completa (DECISIONES FINALES)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 2: SEGURIDAD │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 2.1 DETECCION │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Wazuh Manager + Dashboard │ │
|
||||||
|
│ │ (VM dedicada, 4vCPU, 12GB RAM) │ │
|
||||||
|
│ │ - FIM (file integrity real-time) │ │
|
||||||
|
│ │ - Log analysis + correlacion │ │
|
||||||
|
│ │ - Rootkit detection │ │
|
||||||
|
│ │ - Vulnerability detection │ │
|
||||||
|
│ │ - Active response │ │
|
||||||
|
│ │ + auditd en cada servidor │ │
|
||||||
|
│ │ + Lynis post-despliegue/trimest. │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 2.2 VULNERABILIDADES │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Wazuh vuln detection (continuo) │ │
|
||||||
|
│ │ Trivy (contenedores Docker) │ │
|
||||||
|
│ │ Nuclei (servicios web, semanal) │ │
|
||||||
|
│ │ OpenVAS: DESCARTADO (salvo │ │
|
||||||
|
│ │ compliance futuro) │ │
|
||||||
|
│ │ VMs Windows: solo monitoring │ │
|
||||||
|
│ │ externo (red/disp.) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 2.3 SECRETOS E IDENTIDAD │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Authentik (SSO + MFA, dia 1) │ │
|
||||||
|
│ │ - PVE, PBS, Grafana, Netbox, │ │
|
||||||
|
│ │ Forgejo, Semaphore, Wazuh, │ │
|
||||||
|
│ │ Vaultwarden, PowerDNS Admin │ │
|
||||||
|
│ │ Vaultwarden (secretos residuales)│ │
|
||||||
|
│ │ .env con permisos 600 (interim) │ │
|
||||||
|
│ │ HashiCorp Vault (mes 6+) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 2.4 ACCESO │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Bastion SSH con grabacion │ │
|
||||||
|
│ │ (SSH estandar, ProxyJump -J) │ │
|
||||||
|
│ │ Hardening SSH (Ansible, dia 1) │ │
|
||||||
|
│ │ Fail2ban (dia 1) │ │
|
||||||
|
│ │ CrowdSec (semana 2) │ │
|
||||||
|
│ │ Teleport: DESCARTADO │ │
|
||||||
|
│ │ (AGPL, cliente propietario) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Recursos Capa 2:
|
||||||
|
- VM Wazuh: 4 vCPU, 12 GB RAM, 100 GB disco
|
||||||
|
- VM Bastion: 1 vCPU, 1 GB RAM, 20 GB disco
|
||||||
|
- Authentik: 2 vCPU, 2 GB RAM, 20 GB disco (contenedor)
|
||||||
|
- Vaultwarden: contenedor Docker, 256 MB RAM
|
||||||
|
- Wazuh agent por servidor: 50-100 MB RAM
|
||||||
|
- CrowdSec por servidor: ~50 MB RAM
|
||||||
|
- Fail2ban por servidor: ~30 MB RAM
|
||||||
|
- auditd: ya instalado, 0 extra
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prioridades de despliegue Capa 2
|
||||||
|
|
||||||
|
Contexto: se monta un servidor nuevo desde cero. La infra existente esta cerrada y se migrara progresivamente. Cada servicio nuevo nace ya con SSO, monitoring y seguridad desde el minuto 0.
|
||||||
|
|
||||||
|
| Prioridad | Que | Esfuerzo | Impacto |
|
||||||
|
|-----------|-----|----------|---------|
|
||||||
|
| **Dia 1** | Authentik (SSO + MFA) | 1-2h | Todo lo que se despliegue despues ya nace con SSO |
|
||||||
|
| **Dia 1** | Vaultwarden (con SSO) | 30min | Secretos custodiados desde el dia 1 |
|
||||||
|
| **Dia 1** | Bastion SSH + grabacion | 2h | Toda sesion SSH queda registrada |
|
||||||
|
| **Dia 1** | Hardening SSH + Fail2ban (Ansible) | 2h | Cierra la puerta principal |
|
||||||
|
| **Semana 1** | Wazuh Manager + Dashboard (con SSO) | 1 dia | Deteccion de intrusiones activa |
|
||||||
|
| **Semana 1** | Wazuh agents en servidores criticos | 2h | FIM + rootkit detection |
|
||||||
|
| **Semana 2** | auditd en todos los servidores (Ansible) | 2h | Registro forense completo |
|
||||||
|
| **Semana 2** | CrowdSec en servidores con puertos publicos | 2h | Inteligencia de amenazas colaborativa |
|
||||||
|
| **Mes 1** | Wazuh agents en todos los servidores | 1 dia | Cobertura completa |
|
||||||
|
| **Mes 1** | Lynis baseline en todos los servidores | 2h | Hardening score por servidor |
|
||||||
|
| **Mes 2** | Trivy en VMs con Docker | 2h | Vulnerabilidades en contenedores |
|
||||||
|
| **Mes 2-3** | Nuclei scans semanales | 2h | Vulnerabilidades en servicios web |
|
||||||
|
| **Mes 6+** | HashiCorp Vault | 1 semana | Secretos automatizados |
|
||||||
542
06-decision-automatizacion.md
Normal file
542
06-decision-automatizacion.md
Normal file
|
|
@ -0,0 +1,542 @@
|
||||||
|
# Decision: Capa de automatizacion (punto 3)
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Actualmente la operativa del PBS se gestiona con scripts bash en `/root/` (`modificar.sh`, `lista.sh`, `gc_jobs.sh`, `verify_jobs.sh`, `status_disk.sh`, etc.). Existe una clave SSH de `david@ansible` pero Ansible se ha usado muy poco (algun apt upgrade en batch). No hay experiencia real con playbooks ni roles. A 100-500 servidores, la forma actual no escala: scripts no versionados, sin idempotencia, sin audit trail, si el servidor se pierde se pierde toda la operativa.
|
||||||
|
|
||||||
|
Se desplegara un servidor nuevo desde cero donde se migraran VMs progresivamente. Ya existe un Forgejo con proyectos de desarrollo (solo git basico).
|
||||||
|
|
||||||
|
3 subcapas (Terraform/OpenTofu descartado):
|
||||||
|
- 3.1 Gestion de configuracion (Ansible + Semaphore)
|
||||||
|
- 3.2 Control de versiones y CI/CD (Forgejo existente + Actions)
|
||||||
|
- 3.3 Orquestacion de jobs (Semaphore + n8n futuro)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.1 Gestion de configuracion: Ansible
|
||||||
|
|
||||||
|
### Por que Ansible y no otras
|
||||||
|
|
||||||
|
| Herramienta | Modelo | Agente | Estado |
|
||||||
|
|-------------|--------|--------|--------|
|
||||||
|
| **Ansible** | Push (sin agente) | Solo SSH | Estandar de facto para infra Linux |
|
||||||
|
| **Puppet** | Pull (con agente) | Si (puppet-agent, ~200MB RAM) | En declive, comunidad menguante |
|
||||||
|
| **Chef** | Pull (con agente) | Si (chef-client) | Comprado por Progress, futuro incierto |
|
||||||
|
| **Salt** | Push/Pull | Opcional (salt-minion) | Bueno pero menor ecosistema |
|
||||||
|
|
||||||
|
**Ansible gana por**:
|
||||||
|
- Ya se usa en el entorno (clave SSH de `david@ansible`)
|
||||||
|
- Sin agente: solo necesita SSH, que ya esta configurado en todos los servidores
|
||||||
|
- Inventario dinamico desde Netbox (ya decidido en 1.1)
|
||||||
|
- Enorme ecosistema de colecciones para Proxmox (`community.general.proxmox*`)
|
||||||
|
- Curva de aprendizaje accesible para sysadmins (YAML, no Ruby ni DSL propios)
|
||||||
|
- Compatible con todo lo decidido: Wazuh, VictoriaMetrics, Loki, CrowdSec, Fail2ban
|
||||||
|
|
||||||
|
### De modificar.sh a Ansible: la transformacion
|
||||||
|
|
||||||
|
El script `modificar.sh` actual hace en secuencia:
|
||||||
|
1. Crear dataset ZFS con quota y reservation (multiplicador 6x)
|
||||||
|
2. Crear datastore en PBS
|
||||||
|
3. Crear usuario PBS
|
||||||
|
4. Setear password
|
||||||
|
5. Configurar ACL (DatastorePowerUser)
|
||||||
|
6. Crear sync job (dia aleatorio 11-20, 09:XX)
|
||||||
|
7. Crear verify job (outdated 30d, ignore-verified)
|
||||||
|
8. Opcionalmente eliminar sync job si "migrado"
|
||||||
|
|
||||||
|
Esto se transforma en un **role de Ansible** (`pbs_customer`) con tasks idempotentes:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/pbs_customer/tasks/main.yml
|
||||||
|
---
|
||||||
|
- name: Crear dataset ZFS
|
||||||
|
community.general.zfs:
|
||||||
|
name: "pool9/{{ customer_name }}"
|
||||||
|
state: present
|
||||||
|
extra_zfs_properties:
|
||||||
|
quota: "{{ customer_size_gb * 6 }}G"
|
||||||
|
reservation: "{{ customer_size_gb * 5 }}G"
|
||||||
|
recordsize: "1M"
|
||||||
|
compression: "lz4"
|
||||||
|
atime: "off"
|
||||||
|
|
||||||
|
- name: Crear datastore PBS
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://localhost:8007/api2/json/config/datastore"
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Authorization: "PBSAPIToken={{ pbs_api_token }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ customer_name }}"
|
||||||
|
path: "/pool9/{{ customer_name }}"
|
||||||
|
gc-schedule: "daily"
|
||||||
|
validate_certs: false
|
||||||
|
status_code: [200, 500] # 500 si ya existe
|
||||||
|
register: ds_result
|
||||||
|
|
||||||
|
- name: Crear usuario PBS
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >
|
||||||
|
proxmox-backup-manager user create
|
||||||
|
{{ customer_name }}@pbs
|
||||||
|
--comment "Cliente {{ customer_name }}"
|
||||||
|
register: user_result
|
||||||
|
failed_when: user_result.rc != 0 and 'already exists' not in user_result.stderr
|
||||||
|
|
||||||
|
- name: Setear password del usuario
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >
|
||||||
|
proxmox-backup-debug api set /access/password
|
||||||
|
--userid {{ customer_name }}@pbs
|
||||||
|
--password {{ customer_password }}
|
||||||
|
no_log: true # No mostrar password en logs
|
||||||
|
|
||||||
|
- name: Configurar ACL
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >
|
||||||
|
proxmox-backup-manager acl update
|
||||||
|
/datastore/{{ customer_name }}
|
||||||
|
DatastorePowerUser
|
||||||
|
--auth-id {{ customer_name }}@pbs
|
||||||
|
|
||||||
|
- name: Crear sync job
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >
|
||||||
|
proxmox-backup-manager sync-job create
|
||||||
|
{{ customer_name }}
|
||||||
|
--store {{ customer_name }}
|
||||||
|
--remote {{ sync_remote }}
|
||||||
|
--remote-store {{ customer_name }}
|
||||||
|
--schedule "*-{{ sync_day }} 09:{{ sync_minute }}"
|
||||||
|
--remove-vanished true
|
||||||
|
when: sync_server is defined and not sync_server.startswith('migrado')
|
||||||
|
register: sync_result
|
||||||
|
failed_when: sync_result.rc != 0 and 'already exists' not in sync_result.stderr
|
||||||
|
|
||||||
|
- name: Crear verify job
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >
|
||||||
|
proxmox-backup-manager verify-job create
|
||||||
|
v-{{ customer_name }}
|
||||||
|
--store {{ customer_name }}
|
||||||
|
--outdated-after 30
|
||||||
|
--ignore-verified true
|
||||||
|
register: verify_result
|
||||||
|
failed_when: verify_result.rc != 0 and 'already exists' not in verify_result.stderr
|
||||||
|
```
|
||||||
|
|
||||||
|
Variables desde Netbox (inventario dinamico):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# host_vars o custom fields de Netbox
|
||||||
|
customer_name: "acme"
|
||||||
|
customer_size_gb: 100
|
||||||
|
customer_password: "{{ lookup('hashi_vault', 'secret/pbs/acme') }}" # Fase 2
|
||||||
|
sync_server: "pbs3343.vpn9.com.es"
|
||||||
|
sync_day: 15
|
||||||
|
sync_minute: 37
|
||||||
|
```
|
||||||
|
|
||||||
|
### Playbooks base (Dia 1)
|
||||||
|
|
||||||
|
| Playbook | Que hace | Prioridad |
|
||||||
|
|----------|----------|-----------|
|
||||||
|
| `hardening-ssh.yml` | SSH hardening completo + cloud-init fix + Fail2ban | **Dia 1** |
|
||||||
|
| `deploy-wazuh-agent.yml` | Instalar y registrar agente Wazuh | **Dia 1** |
|
||||||
|
| `deploy-node-exporter.yml` | node_exporter + Alloy para logs | Semana 1 |
|
||||||
|
| `deploy-auditd.yml` | Reglas auditd para PBS/Proxmox | Semana 1 |
|
||||||
|
| `deploy-crowdsec.yml` | CrowdSec con collections SSH + Linux | Semana 2 |
|
||||||
|
| `pbs-customer.yml` | Provisionar/modificar cliente PBS | Semana 2 |
|
||||||
|
| `pbs-maintenance.yml` | GC, verify, optimizar ZFS en batch | Mes 1 |
|
||||||
|
| `lynis-audit.yml` | Ejecutar Lynis y recoger informes | Mes 1 |
|
||||||
|
|
||||||
|
### Ansible UI: Semaphore vs AWX
|
||||||
|
|
||||||
|
| Aspecto | Semaphore | AWX |
|
||||||
|
|---------|-----------|-----|
|
||||||
|
| Recursos | 1 vCPU, 512MB RAM | 4 vCPU, 8GB RAM (Kubernetes) |
|
||||||
|
| Complejidad | Docker compose simple | Necesita K8s o docker-compose complejo |
|
||||||
|
| RBAC | Basico (admin/user) | Completo (orgs, teams, permisos por recurso) |
|
||||||
|
| Inventario dinamico | Via CLI plugin | Nativo con sync automatico |
|
||||||
|
| Scheduling | Si | Si (mas potente) |
|
||||||
|
| API | Basica | REST completa |
|
||||||
|
| Audit log | Si | Si (mas detallado) |
|
||||||
|
| Ideal para | Equipos de 2-5 personas | Equipos de 5-20+ personas |
|
||||||
|
|
||||||
|
**Recomendacion**: **Semaphore** para empezar. Si el equipo crece mas alla de 5 personas o necesitais RBAC granular, migrar a AWX.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml de Semaphore
|
||||||
|
services:
|
||||||
|
semaphore:
|
||||||
|
image: semaphoreui/semaphore:latest
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
SEMAPHORE_DB_DIALECT: bolt
|
||||||
|
SEMAPHORE_ADMIN_PASSWORD: "${SEMAPHORE_ADMIN_PASSWORD}"
|
||||||
|
SEMAPHORE_ADMIN_NAME: admin
|
||||||
|
SEMAPHORE_ADMIN_EMAIL: admin@empresa.com
|
||||||
|
SEMAPHORE_ADMIN: admin
|
||||||
|
volumes:
|
||||||
|
- ./data:/var/lib/semaphore
|
||||||
|
- ./config:/etc/semaphore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inventario dinamico desde Netbox
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# inventory/netbox.yml
|
||||||
|
plugin: netbox.netbox.nb_inventory
|
||||||
|
api_endpoint: https://netbox.empresa.local
|
||||||
|
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
|
||||||
|
validate_certs: false
|
||||||
|
|
||||||
|
# Agrupar por rol
|
||||||
|
group_by:
|
||||||
|
- device_roles
|
||||||
|
- platforms
|
||||||
|
- regions
|
||||||
|
- sites
|
||||||
|
- tenants
|
||||||
|
|
||||||
|
# Custom fields como variables de host
|
||||||
|
compose:
|
||||||
|
pbs_commercial_size_gb: custom_fields.pbs_commercial_size_gb
|
||||||
|
pbs_quota_gb: custom_fields.pbs_quota_gb
|
||||||
|
pbs_sync_server: custom_fields.pbs_sync_server
|
||||||
|
ansible_host: primary_ip4.address | split('/') | first
|
||||||
|
```
|
||||||
|
|
||||||
|
Flujo completo:
|
||||||
|
```
|
||||||
|
ICSManager → API → Netbox (crea device + custom fields)
|
||||||
|
↓
|
||||||
|
Ansible lee inventario
|
||||||
|
↓
|
||||||
|
Ejecuta playbook pbs-customer.yml
|
||||||
|
↓
|
||||||
|
PBS provisionado + monitorizado
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2 Control de versiones y CI/CD
|
||||||
|
|
||||||
|
### El problema
|
||||||
|
|
||||||
|
Todos los scripts operativos (`modificar.sh`, `lista.sh`, `gc_jobs.sh`, `verify_jobs.sh`, `status_disk.sh`, `enable-maintenance.sh`, etc.) estan en `/root/` sin control de versiones. Si:
|
||||||
|
- El servidor se pierde → se pierde toda la operativa
|
||||||
|
- Un tecnico modifica un script y rompe algo → no hay rollback
|
||||||
|
- Dos tecnicos modifican el mismo script → conflictos sin resolver
|
||||||
|
- No hay registro de quien cambio que ni cuando
|
||||||
|
|
||||||
|
### DECISION: Forgejo existente (ya desplegado)
|
||||||
|
|
||||||
|
Ya tenemos un Forgejo en produccion con proyectos de desarrollo. No hay que desplegar nada nuevo, solo ampliar su uso a infraestructura.
|
||||||
|
|
||||||
|
Funcionalidades actuales en uso: solo git basico (push, pull, branches).
|
||||||
|
Funcionalidades a activar: Forgejo Actions (CI/CD), branch protection, repos de infraestructura.
|
||||||
|
|
||||||
|
### Estructura de repositorios
|
||||||
|
|
||||||
|
```
|
||||||
|
empresa/
|
||||||
|
├── ansible-playbooks/ # Todos los playbooks y roles
|
||||||
|
│ ├── roles/
|
||||||
|
│ │ ├── pbs_customer/
|
||||||
|
│ │ ├── hardening_ssh/
|
||||||
|
│ │ ├── deploy_wazuh/
|
||||||
|
│ │ ├── deploy_node_exporter/
|
||||||
|
│ │ ├── deploy_alloy/
|
||||||
|
│ │ ├── deploy_crowdsec/
|
||||||
|
│ │ └── deploy_auditd/
|
||||||
|
│ ├── playbooks/
|
||||||
|
│ │ ├── site.yml
|
||||||
|
│ │ ├── pbs-customer.yml
|
||||||
|
│ │ ├── hardening.yml
|
||||||
|
│ │ └── maintenance.yml
|
||||||
|
│ ├── inventory/
|
||||||
|
│ │ └── netbox.yml
|
||||||
|
│ └── .forgejo/workflows/
|
||||||
|
│ └── lint.yml # ansible-lint en cada push
|
||||||
|
│
|
||||||
|
├── pbs-scripts/ # Scripts operativos (migrados de /root)
|
||||||
|
│ ├── gc_jobs.sh
|
||||||
|
│ ├── verify_jobs.sh
|
||||||
|
│ ├── status_disk.sh
|
||||||
|
│ └── pbs-mantenimiento-secuencial.sh
|
||||||
|
│
|
||||||
|
├── monitoring-config/ # Configuracion de monitoring
|
||||||
|
│ ├── grafana/dashboards/
|
||||||
|
│ ├── victoriametrics/
|
||||||
|
│ ├── vmagent/
|
||||||
|
│ ├── alertmanager/
|
||||||
|
│ └── loki/
|
||||||
|
│
|
||||||
|
├── docker-stacks/ # Docker compose de servicios
|
||||||
|
│ ├── dns-powerdns/
|
||||||
|
│ ├── vaultwarden/
|
||||||
|
│ ├── semaphore/
|
||||||
|
│ └── uptime-kuma/
|
||||||
|
│
|
||||||
|
└── docs/ # Documentacion como codigo
|
||||||
|
├── runbooks/
|
||||||
|
├── architecture/
|
||||||
|
└── onboarding/
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD con Forgejo Actions
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .forgejo/workflows/ansible-lint.yml
|
||||||
|
name: Lint Ansible
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Run ansible-lint
|
||||||
|
uses: ansible/ansible-lint-action@v6
|
||||||
|
with:
|
||||||
|
path: "playbooks/"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .forgejo/workflows/deploy-hardening.yml
|
||||||
|
name: Deploy Hardening
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths: ['roles/hardening_ssh/**', 'playbooks/hardening.yml']
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted # Runner en la VM de automatizacion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Run hardening playbook
|
||||||
|
run: |
|
||||||
|
ansible-playbook -i inventory/netbox.yml playbooks/hardening.yml --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.3 Infraestructura como codigo: DESCARTADO
|
||||||
|
|
||||||
|
### Terraform / OpenTofu / Pulumi: no aplica
|
||||||
|
|
||||||
|
Descartados porque:
|
||||||
|
- Los servidores son fisicos dedicados con VMs que viven meses o años
|
||||||
|
- No hay infraestructura efimera (crear/destruir continuamente)
|
||||||
|
- No hay multi-cloud (todo es Proxmox on-premise)
|
||||||
|
- Ansible cubre el 100% de lo que se necesita: configurar lo que ya existe
|
||||||
|
|
||||||
|
Si algun dia se necesita crear 50 VMs identicas de golpe (nueva sede, nuevo cluster), se puede reevaluar. Para ahora, añadiria complejidad sin beneficio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.4 Orquestacion de jobs
|
||||||
|
|
||||||
|
### El problema actual
|
||||||
|
|
||||||
|
Los cron jobs actuales:
|
||||||
|
```bash
|
||||||
|
*/15 * * * * /root/gc_jobs.sh >> /var/log/gc_jobs.log 2>&1
|
||||||
|
*/30 * * * * /root/verify_jobs.sh >> /var/log/verify_jobs.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Funcionan en un solo servidor. A 100+ PBS:
|
||||||
|
- No hay visibilidad de que jobs corren donde
|
||||||
|
- Si un job falla, nadie se entera (log en el servidor local)
|
||||||
|
- No hay forma de pausar/cancelar un job sin SSH al servidor
|
||||||
|
- No hay RBAC (quien puede ejecutar que)
|
||||||
|
- No hay dependencias entre jobs
|
||||||
|
|
||||||
|
**Caso real de congestion PBS**: los GC y verify jobs programados con el scheduling nativo de PBS provocaban picos de IO que congestionaban el servidor. Solucion actual: scripts "caseros" (`gc_jobs.sh`, `verify_jobs.sh`) que comprueban si hay otro proceso corriendo antes de lanzar uno nuevo, limitando la concurrencia manualmente.
|
||||||
|
|
||||||
|
### DECISION 3.4 (confirmada)
|
||||||
|
|
||||||
|
#### Semaphore como scheduler (reemplaza cron)
|
||||||
|
|
||||||
|
Si ya usamos Semaphore como UI de Ansible (decidido en 3.1), podemos usarlo tambien para scheduling de jobs:
|
||||||
|
|
||||||
|
- **GC jobs**: playbook que consulta API de PBS, comprueba si hay GC corriendo, si no lanza el mas antiguo. Cada 15min en Semaphore.
|
||||||
|
- **Verify jobs**: misma logica. Comprueba si hay verify activo, si no lanza el siguiente. Cada 30min.
|
||||||
|
- **Mantenimiento**: playbook de mantenimiento secuencial, ejecutable manualmente desde la UI
|
||||||
|
- **Hardening**: playbook de hardening, ejecutable con boton en la UI
|
||||||
|
|
||||||
|
La logica de control de concurrencia actual (no lanzar si ya hay uno corriendo) se traduce directamente:
|
||||||
|
1. **Dentro del playbook**: consulta API de PBS → si hay tarea activa, exit limpio
|
||||||
|
2. **En Semaphore**: opcion "no ejecutar si la ejecucion anterior no ha terminado"
|
||||||
|
3. Ambas combinadas = mismo comportamiento que los scripts actuales
|
||||||
|
|
||||||
|
Ventajas sobre cron:
|
||||||
|
- Visibilidad: ves en la UI que se ejecuto, cuando, y si fallo
|
||||||
|
- Notificaciones: si un GC o verify falla, webhook a Telegram
|
||||||
|
- Control: pausar/cancelar jobs desde la web sin SSH
|
||||||
|
- Audit log de quien ejecuto que y cuando
|
||||||
|
- Los playbooks estan en git (Forgejo)
|
||||||
|
|
||||||
|
#### n8n para workflows reactivos (futuro, mes 3-6)
|
||||||
|
|
||||||
|
Herramienta de tipo "si pasa X, haz Y" con editor visual de workflows. Apuntada como futura, no prioritaria.
|
||||||
|
|
||||||
|
| Trigger | Accion |
|
||||||
|
|---------|--------|
|
||||||
|
| Wazuh detecta fichero nuevo en /usr/bin | Enviar alerta Telegram + ejecutar audit playbook |
|
||||||
|
| PBS datastore > 90% quota | Notificar al comercial del cliente |
|
||||||
|
| Nuevo cliente en Netbox (ICSManager) | Ejecutar playbook pbs-customer.yml |
|
||||||
|
| Certificado SSL expira en 14 dias | Ejecutar renovacion + notificar |
|
||||||
|
| Sync job fallido 2 veces seguidas | Escalar a nivel 2 de soporte |
|
||||||
|
|
||||||
|
n8n tiene nodos de IA (OpenAI, Ollama local) que permiten analisis automatizado de eventos. Ejemplo: Wazuh detecta actividad sospechosa → n8n pasa el evento a un LLM → analiza si es falso positivo → si es real, alerta con explicacion en lenguaje claro.
|
||||||
|
|
||||||
|
**Cuando introducir**: cuando Ansible + Semaphore esten rodando y el equipo tenga soltura. No antes del mes 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Capa 3 completa (DECISIONES FINALES)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 3: AUTOMATIZACION │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 3.1 CONFIGURACION │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Ansible (gestion de config) │ │
|
||||||
|
│ │ - Sin agente (solo SSH) │ │
|
||||||
|
│ │ - Roles: bootstrap, PBS customer,│ │
|
||||||
|
│ │ hardening, deploy agentes │ │
|
||||||
|
│ │ - Inventario: estatico → Netbox │ │
|
||||||
|
│ │ - UI: Semaphore (SSO Authentik) │ │
|
||||||
|
│ │ - API: integracion ICSManager │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 3.2 VERSION CONTROL + CI/CD │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Forgejo EXISTENTE (no desplegar) │ │
|
||||||
|
│ │ - Repos nuevos: infra/ansible, │ │
|
||||||
|
│ │ infra/pbs-scripts, infra/ │ │
|
||||||
|
│ │ monitoring-config, docker-stacks│ │
|
||||||
|
│ │ - Actions: lint ansible (mes 1-2)│ │
|
||||||
|
│ │ - Branch protection en main │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 3.3 IaC: DESCARTADO │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Terraform/OpenTofu/Pulumi: no │ │
|
||||||
|
│ │ Servidores permanentes, no cloud │ │
|
||||||
|
│ │ Ansible cubre el 100% │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 3.4 ORQUESTACION (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Semaphore (scheduling de jobs) │ │
|
||||||
|
│ │ - Reemplaza cron jobs │ │
|
||||||
|
│ │ - Control concurrencia PBS │ │
|
||||||
|
│ │ - Notif. fallos → Telegram │ │
|
||||||
|
│ │ n8n (workflows reactivos, mes 3+)│ │
|
||||||
|
│ │ - Futuro, con posible IA │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Recursos adicionales:
|
||||||
|
- Semaphore: contenedor Docker en VM servicios, 512 MB RAM
|
||||||
|
- Forgejo: YA DESPLEGADO (no requiere recursos nuevos)
|
||||||
|
- n8n (futuro): contenedor Docker, 512 MB RAM
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prioridades de despliegue Capa 3
|
||||||
|
|
||||||
|
Contexto: equipo con experiencia minima en Ansible (solo apt upgrade en batch). Forgejo ya existe con proyectos de desarrollo. Servidor nuevo desde cero.
|
||||||
|
|
||||||
|
| Prioridad | Que | Esfuerzo | Impacto |
|
||||||
|
|-----------|-----|----------|---------|
|
||||||
|
| **Dia 1** | Crear repos infra en Forgejo existente | 1h | Scripts versionados desde el minuto 0 |
|
||||||
|
| **Dia 1** | Migrar scripts de /root/ a repo git | 1h | Backup de toda la operativa |
|
||||||
|
| **Semana 1** | Primer playbook: apt upgrade en todos (lo que ya sabian) | 2h | Confianza con Ansible |
|
||||||
|
| **Semana 1** | Playbook bootstrap-server.yml (hardening + agentes) | 4h | Cada servidor nuevo nace seguro |
|
||||||
|
| **Semana 2** | Instalar Semaphore (SSO Authentik) | 2h | UI web para ejecutar playbooks |
|
||||||
|
| **Semana 2** | Playbook deploy-wazuh-agent.yml | 2h | Wazuh desplegado en masa |
|
||||||
|
| **Mes 1** | Inventario dinamico Netbox → Ansible | 1 dia | Fuente unica de verdad |
|
||||||
|
| **Mes 1** | Role pbs_customer (reemplazar modificar.sh) | 1 dia | Provisionamiento idempotente |
|
||||||
|
| **Mes 1** | Migrar cron jobs a Semaphore scheduling | 2h | Audit log, notificaciones de fallo |
|
||||||
|
| **Mes 1-2** | Forgejo Actions: ansible-lint en cada push | 2h | Validacion automatica |
|
||||||
|
| **Mes 1-2** | Branch protection en repos de infra | 30min | Review obligatorio |
|
||||||
|
| **Mes 3+** | n8n para workflows reactivos | 1 dia | Automatizacion de respuestas a eventos |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagrama del stack completo hasta ahora (Capas 1-3)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐
|
||||||
|
│ GRAFANA │
|
||||||
|
│ (UI unificada) │
|
||||||
|
└────────┬─────────┘
|
||||||
|
│
|
||||||
|
┌───────────────────┼───────────────────┐
|
||||||
|
│ │ │
|
||||||
|
┌─────────▼─────────┐ ┌──────▼──────┐ ┌─────────▼─────────┐
|
||||||
|
│ VictoriaMetrics │ │ Loki │ │ Wazuh Dashboard │
|
||||||
|
│ (metricas) │ │ (logs) │ │ (seguridad) │
|
||||||
|
└─────────┬─────────┘ └──────┬──────┘ └─────────┬─────────┘
|
||||||
|
│ │ │
|
||||||
|
┌─────────▼─────────┐ │ ┌─────────▼─────────┐
|
||||||
|
│ vmagent (scraper) │ │ │ Wazuh Manager │
|
||||||
|
└─────────┬─────────┘ │ └─────────┬─────────┘
|
||||||
|
│ │ │
|
||||||
|
╔══════════╪═══════════════════╪════════════════════╪══════════╗
|
||||||
|
║ Cada servidor de produccion: ║
|
||||||
|
║ ┌──────────────┐ ┌────────┐ ┌───────────┐ ┌─────────────┐ ║
|
||||||
|
║ │node_exporter │ │ Alloy │ │Wazuh Agent│ │ CrowdSec │ ║
|
||||||
|
║ │(metricas) │ │(logs) │ │(FIM+HIDS) │ │ + Fail2ban │ ║
|
||||||
|
║ │ 5MB RAM │ │ 30-50MB│ │ 50-100MB │ │ ~80MB │ ║
|
||||||
|
║ └──────────────┘ └────────┘ └───────────┘ └─────────────┘ ║
|
||||||
|
║ + auditd (ya instalado, 0 extra) ║
|
||||||
|
║ Total overhead por servidor: ~200-250MB RAM ║
|
||||||
|
╚═════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
┌─────────────────┐ ┌──────────────────┐
|
||||||
|
│ Netbox (CMDB) │────▶│ Ansible │
|
||||||
|
│ ← ICSManager │ │ + Semaphore (UI) │
|
||||||
|
└─────────────────┘ │ + Forgejo (git) │
|
||||||
|
└──────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────┐ ┌──────────────────┐
|
||||||
|
│ Uptime Kuma │ │ Vaultwarden │
|
||||||
|
│ (checks binarios│ │ (passwords equipo│
|
||||||
|
└─────────────────┘ └──────────────────┘
|
||||||
|
|
||||||
|
VMs centrales (modelo: 1 VM = 1 servicio, caja negra):
|
||||||
|
- VM Monitoring: 4 vCPU, 8-12 GB RAM, 200-500 GB disco
|
||||||
|
- VM Wazuh: 4 vCPU, 12 GB RAM, 100 GB disco
|
||||||
|
- VM Zammad: 2-3 vCPU, 4-6 GB RAM, 50 GB disco
|
||||||
|
- VM Authentik: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
|
||||||
|
- VM Netbox: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
|
||||||
|
- VM ICSManager: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
|
||||||
|
- VM WildDuck: 1-2 vCPU, 2-3 GB RAM, 30-50 GB disco
|
||||||
|
- VM Outline: 1 vCPU, 1-2 GB RAM, 20 GB disco
|
||||||
|
- VM Servicios: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
|
||||||
|
(Semaphore + Uptime Kuma + Technitium + Traefik, stateless)
|
||||||
|
- VM Bastion: 1 vCPU, 1 GB RAM, 20 GB disco
|
||||||
|
- VM WG Hub A: 1 vCPU, 512 MB RAM, 10 GB disco (+ NAT CGNAT)
|
||||||
|
- VM WG Hub B: 1 vCPU, 512 MB RAM, 10 GB disco
|
||||||
|
- VM PDM: 1 vCPU, 1-2 GB RAM, 20 GB disco (Proxmox Datacenter Manager)
|
||||||
|
- VM PMG: 1-2 vCPU, 2-3 GB RAM, 30 GB disco (Proxmox Mail Gateway)
|
||||||
|
- VM Forgejo: YA DESPLEGADA
|
||||||
|
- PowerDNS: ya existente
|
||||||
|
- PBS1 (Alemania): ya existente
|
||||||
|
- PBS2 (Polonia): servidor fisico nuevo
|
||||||
|
- PBS3 (opcional): por cliente premium
|
||||||
|
- Replica oficina: servidor Xeon 96GB existente (coste 0)
|
||||||
|
|
||||||
|
Total infra de gestion: ~24-32 vCPU, ~44-58 GB RAM, ~580-920 GB disco
|
||||||
|
Recomendacion servidor: 128 GB RAM, 8-12c/16-24t, 2x2TB NVMe
|
||||||
|
```
|
||||||
1147
07-decision-red.md
Normal file
1147
07-decision-red.md
Normal file
File diff suppressed because it is too large
Load diff
243
08-decision-incidentes.md
Normal file
243
08-decision-incidentes.md
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
# Decision: Capa de gestion de incidentes y comunicacion (punto 5)
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
El equipo gestiona incidencias via Zammad (ticketing) y comunicacion directa (telefono, email). Las alertas de infraestructura se envian a Telegram. No hay herramienta de comunicacion interna del equipo ni gestion formal de guardias. La documentacion operativa esta dispersa en ficheros, cabezas, y CLAUDE.md por servidor.
|
||||||
|
|
||||||
|
2 subcapas:
|
||||||
|
- 5.1 Ticketing, alertas y comunicacion
|
||||||
|
- 5.2 Documentacion operativa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5.1 Ticketing, alertas y comunicacion
|
||||||
|
|
||||||
|
### Lo que ya existe
|
||||||
|
|
||||||
|
| Herramienta | Estado | Funcion |
|
||||||
|
|-------------|--------|---------|
|
||||||
|
| **Zammad** | En produccion | Ticketing de soporte, gestion de incidencias de clientes |
|
||||||
|
| **Telegram** | En uso | Canal de alertas criticas (personal) |
|
||||||
|
| **Email** | En uso | Alertas informativas, comunicacion con clientes |
|
||||||
|
| **Telefono** | En uso | Escalado urgente ("atiende este ticket") |
|
||||||
|
|
||||||
|
### Opciones evaluadas (futuro)
|
||||||
|
|
||||||
|
| Herramienta | Que hace | Recursos | Estado |
|
||||||
|
|-------------|----------|----------|--------|
|
||||||
|
| **Mattermost** | Chat de equipo con canales tematicos + webhooks | 512 MB RAM | **Futuro** (mes 3+) |
|
||||||
|
| **Grafana OnCall** | Gestion de ciclo de vida de alertas (asignar, resolver, escalar, guardias) | Integrado en Grafana | **Futuro** (mes 3+) |
|
||||||
|
| **PagerDuty/Opsgenie** | Escalado alertas SaaS | SaaS | Descartado (coste, dependencia) |
|
||||||
|
|
||||||
|
### DECISION 5.1 (confirmada)
|
||||||
|
|
||||||
|
#### Dia 1: Zammad + Telegram (lo que ya funciona)
|
||||||
|
|
||||||
|
- **Zammad** sigue como centro de ticketing
|
||||||
|
- **Telegram** sigue como canal de alertas criticas
|
||||||
|
- **Email** para alertas informativas
|
||||||
|
|
||||||
|
Integraciones nuevas con el stack:
|
||||||
|
|
||||||
|
| Integracion | Que aporta | Prioridad |
|
||||||
|
|-------------|-----------|-----------|
|
||||||
|
| **Zammad SSO Authentik** | Misma cuenta para todo (SAML/OIDC nativo) | Semana 1 |
|
||||||
|
| **Alertmanager → Telegram** | Alertas de infraestructura al movil | Semana 1 |
|
||||||
|
| **Wazuh → Telegram** | Alertas de seguridad al movil | Semana 1 |
|
||||||
|
| **Alertmanager → Zammad** | Alerta critica no atendida en 15min → crea ticket automaticamente | Mes 2 |
|
||||||
|
| **Wazuh → Zammad** | Incidente seguridad → ticket prioridad critica | Mes 2 |
|
||||||
|
| **Panel diagnostico → Zammad** | Cliente adjunta informe al abrir ticket | Mes 3+ |
|
||||||
|
| **Netbox → Zammad** | Tecnico ve contexto del cliente en el ticket | Mes 3+ |
|
||||||
|
|
||||||
|
#### Futuro (mes 3-6): Mattermost
|
||||||
|
|
||||||
|
Chat interno del equipo como **panel de notificaciones centralizado**:
|
||||||
|
|
||||||
|
| Canal | Que recibe |
|
||||||
|
|-------|-----------|
|
||||||
|
| #alertas-criticas | Alertmanager → webhook (solo critical/warning) |
|
||||||
|
| #seguridad | Wazuh → webhook (FIM, rootkits, anomalias) |
|
||||||
|
| #backups | Semaphore → webhook (GC/verify fallidos) |
|
||||||
|
| #infra-general | Deploys, mantenimientos, cambios |
|
||||||
|
| #tickets | Zammad → webhook (tickets nuevos/escalados) |
|
||||||
|
|
||||||
|
Valor: historial buscable, contexto compartido, incorporacion de tecnicos nuevos.
|
||||||
|
|
||||||
|
No es para chatear entre el equipo (para eso ya hay canales). Es donde el stack vuelca sus eventos y el equipo tiene visibilidad compartida.
|
||||||
|
|
||||||
|
SSO con Authentik (OIDC nativo). Contenedor Docker, 512 MB RAM.
|
||||||
|
|
||||||
|
**Cuando desplegar**: cuando el stack base este rodando y el equipo tenga soltura. No antes del mes 3.
|
||||||
|
|
||||||
|
#### Futuro (mes 6+): Grafana OnCall
|
||||||
|
|
||||||
|
Gestion de ciclo de vida de alertas con rotacion de guardias:
|
||||||
|
|
||||||
|
- Alertas activas visibles, resueltas desaparecen
|
||||||
|
- Asignacion: "yo la miro"
|
||||||
|
- Escalado automatico: si nadie responde en X min → siguiente tecnico
|
||||||
|
- Rotacion de guardias (on-call schedule)
|
||||||
|
- Integrado en Grafana (mismo panel)
|
||||||
|
- Notifica por Telegram/Mattermost/SMS/llamada
|
||||||
|
|
||||||
|
**Cuando desplegar**: cuando el equipo crezca y haya guardias formales. No antes del mes 6.
|
||||||
|
|
||||||
|
```
|
||||||
|
Flujo futuro completo:
|
||||||
|
Alertmanager detecta problema
|
||||||
|
│
|
||||||
|
├──► Grafana OnCall (gestionar: asignar, resolver, escalar)
|
||||||
|
├──► Mattermost #alertas (ver: contexto compartido)
|
||||||
|
└──► Telegram (backup: al movil del que esta de guardia)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5.2 Documentacion operativa
|
||||||
|
|
||||||
|
### El problema
|
||||||
|
|
||||||
|
- Documentacion dispersa: CLAUDE.md por servidor, ficheros sueltos, conocimiento en cabezas
|
||||||
|
- Si un tecnico se va, se va su conocimiento
|
||||||
|
- Si llega un tecnico nuevo, no tiene donde aprender
|
||||||
|
- Los runbooks (que hacer cuando X pasa) no existen formalmente
|
||||||
|
- A 100+ servidores, "preguntale a David" no escala
|
||||||
|
|
||||||
|
### Opciones evaluadas
|
||||||
|
|
||||||
|
| Herramienta | Tipo | Editor | SSO | Recursos | Estado |
|
||||||
|
|-------------|------|--------|-----|----------|--------|
|
||||||
|
| **Outline** | Wiki moderna (tipo Notion) | Markdown visual, tiempo real | OIDC nativo | ~1 GB RAM | **ELEGIDA** |
|
||||||
|
| **Bookstack** | Wiki clasica | WYSIWYG + markdown | OIDC/SAML | 512 MB | Descartada (menos moderna) |
|
||||||
|
| **MkDocs + Git** | Docs como codigo | Ficheros .md en Forgejo | No aplica | 0 | Complementario (docs tecnicas con codigo) |
|
||||||
|
| **Confluence** | Wiki empresarial | WYSIWYG | SAML | SaaS/pesado | Descartada (SaaS/coste) |
|
||||||
|
|
||||||
|
### DECISION 5.2 (confirmada)
|
||||||
|
|
||||||
|
#### Outline para conocimiento operativo
|
||||||
|
|
||||||
|
Wiki moderna self-hosted. Interfaz tipo Notion, edicion colaborativa en tiempo real, busqueda full-text, permisos por coleccion.
|
||||||
|
|
||||||
|
Estructura propuesta:
|
||||||
|
|
||||||
|
```
|
||||||
|
📁 Runbooks
|
||||||
|
├── Servidor nuevo: checklist completo
|
||||||
|
├── Cliente nuevo PBS: paso a paso
|
||||||
|
├── Incidente de seguridad: protocolo
|
||||||
|
├── Disco lleno: procedimiento
|
||||||
|
├── Restore de backup: guia
|
||||||
|
├── Failover WG Hub: pasos
|
||||||
|
└── Rotar credenciales: procedimiento
|
||||||
|
|
||||||
|
📁 Arquitectura
|
||||||
|
├── Stack completo (diagramas)
|
||||||
|
├── Redes WG (topologia IPv6)
|
||||||
|
├── DNS (esquema PowerDNS + Technitium)
|
||||||
|
├── Decisiones tecnicas (resumen de estos documentos)
|
||||||
|
└── Mapa de servicios y VMs
|
||||||
|
|
||||||
|
📁 Onboarding
|
||||||
|
├── Primer dia del tecnico
|
||||||
|
├── Accesos (SSH bastion, WG, paneles)
|
||||||
|
├── Herramientas del stack
|
||||||
|
├── Como usar Semaphore
|
||||||
|
└── Contactos y escalado
|
||||||
|
|
||||||
|
📁 Clientes (acceso restringido)
|
||||||
|
├── ACME: infra, contactos, particularidades
|
||||||
|
├── BETA: infra, contactos
|
||||||
|
└── Plantilla cliente nuevo
|
||||||
|
```
|
||||||
|
|
||||||
|
Permisos:
|
||||||
|
- Tecnicos: leen y editan todo
|
||||||
|
- Comerciales: solo ven Clientes + Onboarding
|
||||||
|
- Clientes: no acceden (su info esta en Zammad + panel diagnostico)
|
||||||
|
|
||||||
|
SSO con Authentik desde el dia 1.
|
||||||
|
|
||||||
|
#### Forgejo para documentacion tecnica con codigo
|
||||||
|
|
||||||
|
Los docs que van pegados al codigo siguen en Forgejo (README.md, comentarios en playbooks, configs documentadas). No duplicar en Outline.
|
||||||
|
|
||||||
|
| Tipo de documentacion | Donde |
|
||||||
|
|-----------------------|-------|
|
||||||
|
| Runbooks, procedimientos, onboarding | **Outline** |
|
||||||
|
| Playbooks Ansible, configs, scripts | **Forgejo** (en el repo) |
|
||||||
|
| Diagramas de arquitectura | **Outline** |
|
||||||
|
| Decisiones tecnicas (estos documentos) | **Outline** (migrar cuando este listo) |
|
||||||
|
| API docs, integraciones | **Forgejo** (con el codigo) |
|
||||||
|
|
||||||
|
#### Recursos
|
||||||
|
|
||||||
|
| Componente | RAM |
|
||||||
|
|-----------|-----|
|
||||||
|
| Outline app | ~512 MB |
|
||||||
|
| PostgreSQL (puede compartir con otros) | ~256 MB |
|
||||||
|
| Redis (puede compartir con otros) | ~64 MB |
|
||||||
|
| **Total** | ~800 MB - 1 GB |
|
||||||
|
|
||||||
|
Contenedor Docker en VM Servicios.
|
||||||
|
|
||||||
|
#### Prioridades despliegue documentacion
|
||||||
|
|
||||||
|
| Prioridad | Que | Esfuerzo |
|
||||||
|
|-----------|-----|----------|
|
||||||
|
| **Semana 2** | Desplegar Outline con SSO Authentik | 2h |
|
||||||
|
| **Semana 2** | Crear estructura de colecciones (vacia) | 1h |
|
||||||
|
| **Mes 1** | Escribir runbooks criticos (servidor nuevo, incidente seguridad, restore) | 1 dia |
|
||||||
|
| **Mes 1** | Migrar decisiones tecnicas (estos docs) a Outline | 2h |
|
||||||
|
| **Mes 2** | Documentar onboarding de tecnicos | 4h |
|
||||||
|
| **Mes 2** | Documentar particularidades por cliente | Progresivo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Capa 5 completa (DECISIONES FINALES)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 5: GESTION DE INCIDENTES │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 5.1 TICKETING Y ALERTAS (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Zammad (ya en produccion) │ │
|
||||||
|
│ │ - Ticketing soporte │ │
|
||||||
|
│ │ - SSO Authentik │ │
|
||||||
|
│ │ - Integracion Alertmanager/Wazuh │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Telegram (alertas criticas) │ │
|
||||||
|
│ │ - Alertmanager → bot │ │
|
||||||
|
│ │ - Wazuh → bot │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Futuro (mes 3+): │ │
|
||||||
|
│ │ - Mattermost (notificaciones │ │
|
||||||
|
│ │ centralizadas del equipo) │ │
|
||||||
|
│ │ Futuro (mes 6+): │ │
|
||||||
|
│ │ - Grafana OnCall (guardias, │ │
|
||||||
|
│ │ escalado, ciclo de vida) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 5.2 DOCUMENTACION (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Outline (wiki tipo Notion) │ │
|
||||||
|
│ │ - Runbooks, arquitectura, │ │
|
||||||
|
│ │ onboarding, clientes │ │
|
||||||
|
│ │ - Edicion colaborativa real-time │ │
|
||||||
|
│ │ - SSO Authentik │ │
|
||||||
|
│ │ - Permisos por coleccion │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Forgejo (docs con codigo) │ │
|
||||||
|
│ │ - README, configs, playbooks │ │
|
||||||
|
│ │ - Versionado con git │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Recursos Capa 5:
|
||||||
|
- Zammad: ya existente
|
||||||
|
- Outline: ~1 GB RAM (contenedor Docker en VM Servicios)
|
||||||
|
- Mattermost (futuro): 512 MB RAM
|
||||||
|
- Grafana OnCall (futuro): integrado en Grafana, 0 extra
|
||||||
|
```
|
||||||
625
09-decision-almacenamiento.md
Normal file
625
09-decision-almacenamiento.md
Normal file
|
|
@ -0,0 +1,625 @@
|
||||||
|
# Decision: Capa de almacenamiento y datos (punto 6)
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Infraestructura de backup distribuida geográficamente:
|
||||||
|
- **PVE** (producción): Francia, datacenters OVH
|
||||||
|
- **PBS1** (backup tránsito): Alemania, OVH
|
||||||
|
- **PBS2** (backup archivo): Polonia, OVH (nuevo, por contratar)
|
||||||
|
|
||||||
|
ZFS se usa en PBS para crear datastores por cliente con quotas/reservations (modelo multiplicador 6x). En PVE se usa ZFS estándar para almacenamiento de VMs.
|
||||||
|
|
||||||
|
2 subcapas:
|
||||||
|
- 6.1 Gestión de ZFS a escala
|
||||||
|
- 6.2 Backup del backup (estrategia de retención y protección)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.1 Gestión de ZFS a escala
|
||||||
|
|
||||||
|
### Uso actual de ZFS
|
||||||
|
|
||||||
|
ZFS se usa como herramienta de gestión de almacenamiento:
|
||||||
|
- **En PBS**: un dataset por cliente con quota (size×6) y reservation (size×5)
|
||||||
|
- **En PVE**: almacenamiento estándar de VMs
|
||||||
|
- Propiedades optimizadas: recordsize=1M, compression=lz4, atime=off
|
||||||
|
|
||||||
|
### Herramientas evaluadas
|
||||||
|
|
||||||
|
| Herramienta | Qué hace | Estado |
|
||||||
|
|-------------|----------|--------|
|
||||||
|
| **Sanoid/Syncoid** | Snapshots automáticos + replicación ZFS | **Descartado**: PBS gestiona snapshots y replicación nativamente |
|
||||||
|
| **zrepl** | Replicación ZFS continua | **Descartado**: PBS sync jobs cubren esto |
|
||||||
|
| **zfswatcher** | Monitoring dedicado ZFS con alertas | **Descartado**: node_exporter ya exporta métricas ZFS |
|
||||||
|
|
||||||
|
### DECISION 6.1 (confirmada)
|
||||||
|
|
||||||
|
**Solo monitorización.** No se necesitan herramientas adicionales para ZFS. PBS y PVE gestionan sus snapshots y replicación con sus mecanismos nativos.
|
||||||
|
|
||||||
|
Monitorización ya cubierta por el stack existente:
|
||||||
|
|
||||||
|
| Métrica | Fuente | Alerta |
|
||||||
|
|---------|--------|--------|
|
||||||
|
| Pool health (degraded, faulted) | node_exporter → VictoriaMetrics | ZFSPoolDegraded (critical, 1min) |
|
||||||
|
| Pool space > 85% | node_exporter → VictoriaMetrics | ZFSPoolSpaceHigh (warning, 30min) |
|
||||||
|
| Scrub errors | node_exporter → VictoriaMetrics | ZFSScrubErrors (warning) |
|
||||||
|
| IO latency | node_exporter → VictoriaMetrics | Dashboard Grafana |
|
||||||
|
| ARC hit rate | node_exporter → VictoriaMetrics | Dashboard Grafana |
|
||||||
|
|
||||||
|
Gestión ZFS via Ansible:
|
||||||
|
- Playbook `optimizar_zfs.sh` convertido a role Ansible
|
||||||
|
- Aplicar recordsize=1M, compression=lz4, atime=off a nuevos servidores
|
||||||
|
- Crear/modificar datasets de clientes (role pbs_customer)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.2 Estrategia de backup: modelo de 3 capas geográficas
|
||||||
|
|
||||||
|
### El problema
|
||||||
|
|
||||||
|
- Los PVE hacen backup en PBS, pero si PBS se pierde, se pierden todos los backups
|
||||||
|
- Regla 3-2-1: 3 copias, 2 medios, 1 offsite
|
||||||
|
- OVH Estrasburgo ardió en 2021. Un solo DC no es suficiente.
|
||||||
|
- Si ransomware entra en PVE y PBS1, debe haber una copia inaccesible
|
||||||
|
|
||||||
|
### DECISION 6.2 (confirmada)
|
||||||
|
|
||||||
|
#### Arquitectura de 3 capas geográficas
|
||||||
|
|
||||||
|
```
|
||||||
|
Francia (OVH) Alemania (OVH) Polonia (OVH, nuevo)
|
||||||
|
┌──────────────┐ ┌──────────────────┐ ┌──────────────────────┐
|
||||||
|
│ PVE │ │ PBS1 (tránsito) │ │ PBS2 (archivo) │
|
||||||
|
│ (producción) │──backup─►│ │◄─pull──│ │
|
||||||
|
│ │ WG │ Retención corta: │ WG │ Retención larga: │
|
||||||
|
│ Permisos: │ │ keep-last: 5 │ │ keep-daily: 7 │
|
||||||
|
│ - Backup │ │ keep-daily: 3 │ │ keep-weekly: 4 │
|
||||||
|
│ - Reader │ │ │ │ keep-monthly: 5 │
|
||||||
|
│ - NO delete │ │ ~8 snapshots/VM │ │ ~16 snapshots/VM │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ Acceso: │ │ Acceso: │
|
||||||
|
│ │ │ - PVE escribe │ │ - MUY restrictivo │
|
||||||
|
│ │ │ - PBS2 lee (sync)│ │ - Solo PBS2 inicia │
|
||||||
|
│ │ │ - Técnicos leen │ │ conexiones │
|
||||||
|
│ │ │ │ │ - Invisible al resto │
|
||||||
|
└──────────────┘ └──────────────────┘ │ - Verify semanal │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Principio 1: PVE no puede borrar
|
||||||
|
|
||||||
|
El usuario PVE en PBS1 tiene permisos mínimos:
|
||||||
|
|
||||||
|
```
|
||||||
|
Rol en PBS1 para PVE:
|
||||||
|
- DatastoreBackup (puede crear backups)
|
||||||
|
- DatastoreReader (puede restaurar)
|
||||||
|
|
||||||
|
NO tiene:
|
||||||
|
- DatastoreAdmin (puede borrar, podar)
|
||||||
|
- DatastorePowerUser (puede podar)
|
||||||
|
```
|
||||||
|
|
||||||
|
Si ransomware compromete PVE, puede crear backups (inofensivo) y restaurar, pero **no puede borrar los existentes**.
|
||||||
|
|
||||||
|
#### Principio 2: PBS2 PULL de PBS1 (no push)
|
||||||
|
|
||||||
|
PBS2 es quien inicia la conexión hacia PBS1:
|
||||||
|
|
||||||
|
```
|
||||||
|
PBS2 tiene sync jobs:
|
||||||
|
- remote: PBS1 (Alemania)
|
||||||
|
- remote-store: cada datastore
|
||||||
|
- credenciales: usuario read-only en PBS1
|
||||||
|
- schedule: diario (o cada 12h)
|
||||||
|
|
||||||
|
PBS1 NO tiene:
|
||||||
|
- Credenciales de PBS2
|
||||||
|
- Conectividad hacia PBS2
|
||||||
|
- Conocimiento de que PBS2 existe
|
||||||
|
```
|
||||||
|
|
||||||
|
Si PBS1 es comprometido, el atacante no puede alcanzar PBS2 porque PBS1 no tiene credenciales ni ruta de red hacia PBS2.
|
||||||
|
|
||||||
|
#### Principio 3: PBS2 es invisible
|
||||||
|
|
||||||
|
```
|
||||||
|
Firewall PBS2 (Polonia):
|
||||||
|
INPUT:
|
||||||
|
- ACCEPT SSH desde IP bastion (solo emergencia)
|
||||||
|
- ACCEPT WG UDP desde peer de gestión
|
||||||
|
- DROP todo lo demás
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
- ACCEPT hacia PBS1:8007 (sync, por WG)
|
||||||
|
- ACCEPT DNS, NTP
|
||||||
|
- DROP todo lo demás
|
||||||
|
|
||||||
|
→ PBS2 no tiene puertos abiertos a internet
|
||||||
|
→ PBS2 no es alcanzable desde PVE ni desde PBS1
|
||||||
|
→ Solo PBS2 decide cuándo hablar con PBS1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Principio 4: PBS2 verifica integridad
|
||||||
|
|
||||||
|
PBS2 ejecuta verify jobs sobre los backups sincronizados:
|
||||||
|
|
||||||
|
```
|
||||||
|
PBS2 verify jobs:
|
||||||
|
- schedule: semanal
|
||||||
|
- outdated-after: 14 días
|
||||||
|
- ignore-verified: true
|
||||||
|
|
||||||
|
Si verify falla → alerta Telegram + ticket Zammad (crítico)
|
||||||
|
```
|
||||||
|
|
||||||
|
Valida que lo que llegó de PBS1 no está corrupto. Si un backup corrupto se sincroniza, lo sabéis antes de necesitarlo.
|
||||||
|
|
||||||
|
#### Conectividad entre DCs
|
||||||
|
|
||||||
|
| Ruta | Transporte | Cifrado |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| PVE (Francia) → PBS1 (Alemania) | WireGuard sobre vRack cross-DC o internet | WG cifrado |
|
||||||
|
| PBS2 (Polonia) → PBS1 (Alemania) | WireGuard sobre vRack cross-DC o internet | WG cifrado |
|
||||||
|
|
||||||
|
OVH vRack puede extenderse entre DCs y países (sin coste extra si ya está contratado). Si no, WireGuard sobre internet funciona bien: el tráfico de sync es eficiente (PBS deduplica, solo transfiere chunks nuevos).
|
||||||
|
|
||||||
|
Sync inicial (primera vez): puede ser grande. Syncs diarios: solo incrementales, poco tráfico.
|
||||||
|
|
||||||
|
#### Retención detallada
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# PBS1 (Alemania) - TRÁNSITO
|
||||||
|
# Objetivo: buffer rápido, pocas copias, rota frecuente
|
||||||
|
prune-schedule: daily
|
||||||
|
keep-last: 5 # últimos 5 backups independiente de fecha
|
||||||
|
keep-daily: 3 # 3 días adicionales
|
||||||
|
# Total estimado: ~8 snapshots por VM
|
||||||
|
# Espacio: moderado (pocos días de retención)
|
||||||
|
|
||||||
|
# PBS2 (Polonia) - ARCHIVO
|
||||||
|
# Objetivo: retención profunda, recuperación ante desastre
|
||||||
|
prune-schedule: daily
|
||||||
|
keep-daily: 7 # última semana completa
|
||||||
|
keep-weekly: 4 # último mes en semanas
|
||||||
|
keep-monthly: 5 # últimos 5 meses
|
||||||
|
# Total estimado: ~16 snapshots por VM
|
||||||
|
# Espacio: mayor (meses de retención, pero PBS deduplica)
|
||||||
|
```
|
||||||
|
|
||||||
|
PBS1 y PBS2 aplican su política de retención **independientemente**. PBS1 puede borrar snapshots viejos sin afectar a PBS2 (PBS2 ya tiene su copia).
|
||||||
|
|
||||||
|
#### Escenarios de desastre y procedimientos de recuperación
|
||||||
|
|
||||||
|
| Desastre | Impacto | Recuperación |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| Fallo HW en PVE (Francia) | VMs caídas | Restaurar desde PBS1 Alemania (minutos/horas) |
|
||||||
|
| Fallo HW en PBS1 (Alemania) | Backups de tránsito perdidos | PBS2 Polonia tiene todo con retención de meses |
|
||||||
|
| Ransomware en PVE | VMs cifradas | PVE no puede borrar PBS1. Restaurar desde PBS1. |
|
||||||
|
| Ransomware en PVE + PBS1 | VMs + backups tránsito | PBS2 inaccesible desde ambos. Restaurar desde PBS2. |
|
||||||
|
| Ransomware en PVE + PBS1 + PBS2 | Catastrófico | Extremadamente improbable: PBS2 no es alcanzable desde los otros |
|
||||||
|
| DC Francia cae (tipo OVH Estrasburgo 2021) | PVE caído | PBS1 (Alemania) + PBS2 (Polonia) intactos |
|
||||||
|
| DC Francia + Alemania caen | PVE + PBS1 caídos | PBS2 (Polonia) intacto |
|
||||||
|
| Error humano borra datastore en PBS1 | Backups cliente borrados | PBS2 tiene copias con retención de meses |
|
||||||
|
| Fallo HW en PBS2 (Polonia) | Copias históricas perdidas | Solo quedan copias de tránsito en PBS1 (retención corta). Opción PBS3 para clientes críticos. |
|
||||||
|
|
||||||
|
#### Procedimiento si PBS1 se pierde
|
||||||
|
|
||||||
|
1. Contratar/levantar un PBS1 nuevo (Alemania)
|
||||||
|
2. Crear datasets ZFS, datastores y usuarios (playbook Ansible `pbs_customer`)
|
||||||
|
3. PVE empieza a hacer backups al PBS1 nuevo → se rellena con copias frescas
|
||||||
|
4. Configurar WG peer en PBS1 nuevo para que PBS2 pueda conectar
|
||||||
|
5. PBS2 empieza a sincronizar (pull) del PBS1 nuevo cuando hay datos
|
||||||
|
|
||||||
|
**Hueco**: ~1 día sin capa de tránsito. Las VMs siguen funcionando. PBS2 conserva las copias históricas intactas. El riesgo real es perder una VM durante ese hueco (sin backup disponible en PBS1), pero PBS2 tiene la última sincronización previa al fallo.
|
||||||
|
|
||||||
|
#### Procedimiento si PBS2 se pierde
|
||||||
|
|
||||||
|
1. Se pierden las copias históricas (retención larga: semanas/meses)
|
||||||
|
2. PBS1 sigue funcionando con retención corta (3d/5 copias)
|
||||||
|
3. PVE sigue haciendo backups normalmente a PBS1
|
||||||
|
4. Contratar/levantar un PBS2 nuevo (Polonia) y configurar sync desde PBS1
|
||||||
|
|
||||||
|
**Impacto**: durante el período sin PBS2, solo hay 1 capa de backup (PBS1, retención corta). Si PBS1 también falla durante ese periodo → datos perdidos.
|
||||||
|
|
||||||
|
#### Opción PBS3 para clientes críticos (decisión de producto)
|
||||||
|
|
||||||
|
Clientes que requieran máxima protección pueden contratar una capa adicional:
|
||||||
|
|
||||||
|
```
|
||||||
|
PBS3 (opcional por cliente):
|
||||||
|
- Ubicación: otro país/proveedor (ej. Hetzner Finlandia, Scaleway París)
|
||||||
|
- Modelo: PULL de PBS2 (misma lógica)
|
||||||
|
- Retención: igual o más larga que PBS2
|
||||||
|
- Coste: repercutido al cliente
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto es una **decisión de producto/SLA**, no técnica:
|
||||||
|
- El producto base incluye 2 capas (PBS1 tránsito + PBS2 archivo)
|
||||||
|
- El producto premium añade PBS3 como 3ª capa
|
||||||
|
- Documentar claramente en la definición de producto los riesgos de cada nivel
|
||||||
|
- El cliente decide su nivel de protección según su criticidad y presupuesto
|
||||||
|
|
||||||
|
#### Monitorización del modelo de backup
|
||||||
|
|
||||||
|
| Alerta | Condición | Severidad |
|
||||||
|
|--------|-----------|-----------|
|
||||||
|
| PBSBackupOld48h | VM sin backup en 48h en PBS1 | Critical |
|
||||||
|
| PBSSyncFailed | Sync PBS2←PBS1 falla | Critical |
|
||||||
|
| PBSSyncOld48h | PBS2 no sincroniza en 48h | Critical |
|
||||||
|
| PBSVerifyErrors | Verify en PBS2 encuentra errores | Critical |
|
||||||
|
| PBS1DatastoreSpace90 | Datastore PBS1 > 90% quota | Warning |
|
||||||
|
| PBS2DatastoreSpace90 | Datastore PBS2 > 90% | Warning |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.3 Backup de configuraciones PVE/PBS a git
|
||||||
|
|
||||||
|
### El problema
|
||||||
|
|
||||||
|
Si un PVE o PBS se pierde, los datos de VMs están en PBS (backup). Pero la **configuración del propio nodo** (qué VMs había, cómo estaban configuradas, qué datastores, qué usuarios, qué red) está solo en el nodo. ICSManager sabe qué clientes hay, pero no tiene el detalle de cada `.conf` de VM ni cada regla de firewall.
|
||||||
|
|
||||||
|
Reconstruir un nodo sin tener su config es posible pero lento: hay que averiguar qué había, preguntar, deducir. Tenerla guardada en git permite saber exactamente qué había y reconstruir rápido.
|
||||||
|
|
||||||
|
### DECISION 6.3 (confirmada)
|
||||||
|
|
||||||
|
**Copia diaria de directorios de configuración a un repo en Forgejo.**
|
||||||
|
|
||||||
|
Son ficheros de texto, poca información (KB), y git muestra exactamente qué cambió y cuándo.
|
||||||
|
|
||||||
|
#### Qué se copia
|
||||||
|
|
||||||
|
| Servidor | Ruta | Contenido |
|
||||||
|
|----------|------|-----------|
|
||||||
|
| **PVE** | `/etc/pve/qemu-server/` | Configs de VMs (.conf: disco, RAM, red, opciones) |
|
||||||
|
| **PVE** | `/etc/pve/lxc/` | Configs de contenedores |
|
||||||
|
| **PVE** | `/etc/pve/storage.cfg` | Definiciones de almacenamiento |
|
||||||
|
| **PVE** | `/etc/pve/user.cfg` | Usuarios y permisos |
|
||||||
|
| **PVE** | `/etc/pve/datacenter.cfg` | Configuración del datacenter |
|
||||||
|
| **PVE** | `/etc/pve/firewall/` | Reglas de firewall por nodo y VM |
|
||||||
|
| **PVE** | `/etc/pve/corosync.conf` | Configuración del cluster |
|
||||||
|
| **PVE** | `/etc/network/interfaces` | Configuración de red (vmbr, vRack, VLANs) |
|
||||||
|
| **PBS** | `/etc/proxmox-backup/datastore.cfg` | Definiciones de datastores |
|
||||||
|
| **PBS** | `/etc/proxmox-backup/user.cfg` | Usuarios PBS |
|
||||||
|
| **PBS** | `/etc/proxmox-backup/acl.cfg` | Permisos por datastore |
|
||||||
|
| **PBS** | `/etc/proxmox-backup/remote.cfg` | Remotos configurados |
|
||||||
|
| **PBS** | `/etc/proxmox-backup/sync.cfg` | Sync jobs |
|
||||||
|
| **PBS** | `/etc/proxmox-backup/verify.cfg` | Verify jobs |
|
||||||
|
| **Ambos** | `/etc/wireguard/` | Configs WG + claves |
|
||||||
|
| **Ambos** | ZFS properties (exportadas) | Quotas, reservations, recordsize por dataset |
|
||||||
|
|
||||||
|
#### Implementación
|
||||||
|
|
||||||
|
Playbook Ansible ejecutado diariamente desde Semaphore:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# playbook: backup-configs.yml
|
||||||
|
# Semaphore: diario, 06:00
|
||||||
|
- hosts: all_pve:all_pbs
|
||||||
|
tasks:
|
||||||
|
- name: Exportar ZFS properties
|
||||||
|
shell: "zfs get -Hp quota,reservation,recordsize,compression -r pool9 2>/dev/null || true"
|
||||||
|
register: zfs_props
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Guardar ZFS properties en fichero temporal
|
||||||
|
copy:
|
||||||
|
content: "{{ zfs_props.stdout }}"
|
||||||
|
dest: /tmp/zfs-properties.txt
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Recoger configs PVE
|
||||||
|
fetch:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "backup-configs/{{ inventory_hostname }}/"
|
||||||
|
flat: no
|
||||||
|
loop:
|
||||||
|
- /etc/pve/storage.cfg
|
||||||
|
- /etc/pve/user.cfg
|
||||||
|
- /etc/pve/datacenter.cfg
|
||||||
|
- /etc/pve/corosync.conf
|
||||||
|
- /etc/network/interfaces
|
||||||
|
ignore_errors: true
|
||||||
|
when: "'pve' in group_names"
|
||||||
|
|
||||||
|
- name: Recoger configs de VMs PVE
|
||||||
|
shell: "ls /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf /etc/pve/firewall/*.fw 2>/dev/null || true"
|
||||||
|
register: vm_configs
|
||||||
|
changed_when: false
|
||||||
|
when: "'pve' in group_names"
|
||||||
|
|
||||||
|
- name: Fetch configs de VMs
|
||||||
|
fetch:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "backup-configs/{{ inventory_hostname }}/"
|
||||||
|
flat: no
|
||||||
|
loop: "{{ vm_configs.stdout_lines | default([]) }}"
|
||||||
|
when: "'pve' in group_names"
|
||||||
|
|
||||||
|
- name: Recoger configs PBS
|
||||||
|
fetch:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "backup-configs/{{ inventory_hostname }}/"
|
||||||
|
flat: no
|
||||||
|
loop:
|
||||||
|
- /etc/proxmox-backup/datastore.cfg
|
||||||
|
- /etc/proxmox-backup/user.cfg
|
||||||
|
- /etc/proxmox-backup/acl.cfg
|
||||||
|
- /etc/proxmox-backup/remote.cfg
|
||||||
|
- /etc/proxmox-backup/sync.cfg
|
||||||
|
- /etc/proxmox-backup/verify.cfg
|
||||||
|
ignore_errors: true
|
||||||
|
when: "'pbs' in group_names"
|
||||||
|
|
||||||
|
- name: Fetch WG configs
|
||||||
|
fetch:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "backup-configs/{{ inventory_hostname }}/"
|
||||||
|
flat: no
|
||||||
|
loop:
|
||||||
|
- /etc/wireguard/
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Fetch ZFS properties
|
||||||
|
fetch:
|
||||||
|
src: /tmp/zfs-properties.txt
|
||||||
|
dest: "backup-configs/{{ inventory_hostname }}/"
|
||||||
|
flat: no
|
||||||
|
|
||||||
|
- hosts: localhost
|
||||||
|
tasks:
|
||||||
|
- name: Commit y push al repo Forgejo
|
||||||
|
shell: |
|
||||||
|
cd backup-configs/
|
||||||
|
git add -A
|
||||||
|
git diff --cached --quiet || git commit -m "Config backup {{ ansible_date_time.date }}"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Resultado en Forgejo
|
||||||
|
|
||||||
|
```
|
||||||
|
infra/backup-configs/
|
||||||
|
├── pve-node1/
|
||||||
|
│ ├── etc/pve/qemu-server/
|
||||||
|
│ │ ├── 100.conf ← VM cliente ACME
|
||||||
|
│ │ ├── 101.conf ← VM cliente BETA
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── etc/pve/storage.cfg
|
||||||
|
│ ├── etc/pve/user.cfg
|
||||||
|
│ ├── etc/network/interfaces
|
||||||
|
│ └── tmp/zfs-properties.txt
|
||||||
|
├── pve-node2/
|
||||||
|
│ └── ...
|
||||||
|
├── pbs-alemania/
|
||||||
|
│ ├── etc/proxmox-backup/datastore.cfg
|
||||||
|
│ ├── etc/proxmox-backup/sync.cfg
|
||||||
|
│ └── tmp/zfs-properties.txt
|
||||||
|
└── pbs-polonia/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Cada commit muestra el diff: "se añadió VM 105, se cambió la RAM de la 100 de 4GB a 8GB, se creó datastore nuevo".
|
||||||
|
|
||||||
|
#### Valor operativo
|
||||||
|
|
||||||
|
| Escenario | Sin backup de configs | Con backup en git |
|
||||||
|
|-----------|----------------------|-------------------|
|
||||||
|
| PVE se pierde | "¿Qué VMs había? ¿Con qué config?" | `git show pve-node1/etc/pve/qemu-server/` → lista exacta |
|
||||||
|
| Alguien cambia config de VM | Nadie se entera | Diff en el commit diario |
|
||||||
|
| Técnico nuevo necesita ver infra | SSH a cada nodo | Lee el repo en Forgejo |
|
||||||
|
| Auditoría: "¿cuándo se cambió esto?" | No hay registro | `git log --follow fichero.conf` |
|
||||||
|
| Reconstruir nodo | Horas/días adivinando | Minutos leyendo configs exactas |
|
||||||
|
|
||||||
|
#### Complemento con Ansible (evolución)
|
||||||
|
|
||||||
|
Fase 1 (día 1): backup de configs a git = **foto de lo que hay**
|
||||||
|
Fase 2 (mes 1-2): Ansible gestiona configs = **declaración de lo que debe haber**
|
||||||
|
|
||||||
|
Ambos conviven: Ansible aplica la config deseada, el backup a git verifica que lo real coincide con lo declarado. Si alguien cambia algo manualmente, el diff del backup lo delata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.4 Réplica de VMs de gestión a oficina (contingencia fuera de OVH)
|
||||||
|
|
||||||
|
### El problema
|
||||||
|
|
||||||
|
Toda la infraestructura de gestión (monitoring, seguridad, SSOT, SSO) vive en un único servidor en OVH. Si OVH pierde ese servidor, nos quedamos ciegos: sin métricas, sin logs, sin alertas, sin Netbox, sin Authentik. Los datos de clientes están protegidos por 3 capas geográficas (6.2), pero la infra que nos da visibilidad no.
|
||||||
|
|
||||||
|
### DECISION 6.4 (confirmada)
|
||||||
|
|
||||||
|
**Replicación ZFS incremental de las VMs críticas de gestión al servidor de oficina (Xeon 96GB existente).**
|
||||||
|
|
||||||
|
Coste: cero (hardware que ya existe). Las VMs están apagadas en oficina, solo se encienden en contingencia.
|
||||||
|
|
||||||
|
#### Qué se replica
|
||||||
|
|
||||||
|
| VM | Replicar | Razón |
|
||||||
|
|----|----------|-------|
|
||||||
|
| **Monitoring** | Sí | Sin ella: sin Grafana, sin métricas, sin logs, ceguera total |
|
||||||
|
| **Wazuh** | Sí | Sin ella: sin detección de seguridad, sin FIM |
|
||||||
|
| **Servicios** | Sí | Netbox (SSOT), Authentik (SSO), Semaphore, Outline |
|
||||||
|
| **Bastion** | No | Sin estado, se recrea en minutos con Ansible |
|
||||||
|
| **WG Hub A/B** | No | Sin estado, configs en git (6.3), se recrean |
|
||||||
|
|
||||||
|
#### Arquitectura
|
||||||
|
|
||||||
|
```
|
||||||
|
OVH (servidor gestión) Oficina (Xeon 96GB, existente)
|
||||||
|
┌──────────────────────┐ ┌──────────────────────┐
|
||||||
|
│ PVE gestión │ │ PVE contingencia │
|
||||||
|
│ pool: local-zfs │ ZFS send/recv │ pool: local-zfs │
|
||||||
|
│ │ incremental │ │
|
||||||
|
│ VM Monitoring ──────│──── WG ────────►│ VM Monitoring (rpl) │
|
||||||
|
│ VM Wazuh ───────────│──── WG ────────►│ VM Wazuh (rpl) │
|
||||||
|
│ VM Servicios ───────│──── WG ────────►│ VM Servicios (rpl) │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Cada 4h │ VMs apagadas │
|
||||||
|
│ │ ~1-5 GB/sync │ Solo encienden en │
|
||||||
|
│ │ cifrado por WG │ contingencia │
|
||||||
|
└──────────────────────┘ └──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Implementación
|
||||||
|
|
||||||
|
Playbook Ansible ejecutado desde Semaphore cada 4 horas:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# playbook: replicate-to-office.yml
|
||||||
|
# Semaphore: cada 4h
|
||||||
|
- hosts: pve_gestion
|
||||||
|
vars:
|
||||||
|
office_host: "oficina-pve.mgmt.vpn6.com.es" # por WG
|
||||||
|
vms_to_replicate:
|
||||||
|
- vm-monitoring
|
||||||
|
- vm-wazuh
|
||||||
|
- vm-servicios
|
||||||
|
tasks:
|
||||||
|
- name: Crear snapshot incremental
|
||||||
|
shell: "zfs snapshot local-zfs/{{ item }}@replicate-$(date +%Y%m%d-%H%M)"
|
||||||
|
loop: "{{ vms_to_replicate }}"
|
||||||
|
|
||||||
|
- name: Enviar incremental a oficina
|
||||||
|
shell: |
|
||||||
|
LATEST=$(zfs list -t snapshot -o name -s creation -r local-zfs/{{ item }} | grep replicate | tail -1)
|
||||||
|
PREV=$(zfs list -t snapshot -o name -s creation -r local-zfs/{{ item }} | grep replicate | tail -2 | head -1)
|
||||||
|
if [ "$PREV" != "$LATEST" ]; then
|
||||||
|
zfs send -i "$PREV" "$LATEST" | ssh {{ office_host }} zfs recv -F tank/{{ item }}
|
||||||
|
else
|
||||||
|
zfs send "$LATEST" | ssh {{ office_host }} zfs recv -F tank/{{ item }}
|
||||||
|
fi
|
||||||
|
loop: "{{ vms_to_replicate }}"
|
||||||
|
|
||||||
|
- name: Rotar snapshots antiguos (mantener últimos 12 = 48h)
|
||||||
|
shell: |
|
||||||
|
zfs list -t snapshot -o name -s creation -r local-zfs/{{ item }} | grep replicate | head -n -12 | xargs -r -n1 zfs destroy
|
||||||
|
loop: "{{ vms_to_replicate }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Ancho de banda
|
||||||
|
|
||||||
|
| Parámetro | Valor |
|
||||||
|
|-----------|-------|
|
||||||
|
| Tamaño total VMs a replicar | ~400-700 GB |
|
||||||
|
| Sync inicial (primera vez) | Grande: programar de noche o seed con disco físico |
|
||||||
|
| Sync incremental cada 4h | ~1-5 GB (solo bloques cambiados) |
|
||||||
|
| Ancho de banda sostenido | ~5-10 Mbps (factible con fibra de oficina) |
|
||||||
|
| Transporte | WireGuard cifrado (red de gestión) |
|
||||||
|
|
||||||
|
#### Plan de contingencia
|
||||||
|
|
||||||
|
| Paso | Acción | Tiempo |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 1 | Detectar que el servidor OVH está caído | Minutos (Uptime Kuma externo, ping, o aviso OVH) |
|
||||||
|
| 2 | Encender VMs réplica en PVE oficina | 2 minutos |
|
||||||
|
| 3 | Actualizar DNS/WG para apuntar a oficina | 5 minutos |
|
||||||
|
| 4 | Monitoring + Wazuh + Servicios operativos | ~10 minutos total |
|
||||||
|
|
||||||
|
**No es HA automático** (no hace falta). Es un plan B manual que se activa en emergencia. Runbook documentado en Outline.
|
||||||
|
|
||||||
|
#### Qué NO se replica aquí
|
||||||
|
|
||||||
|
- VMs de clientes (producción): protegidas por 3 capas geográficas PBS (6.2)
|
||||||
|
- Configs de PVE/PBS: protegidas por backup a git (6.3)
|
||||||
|
- WG Hub / Bastion: sin estado, se recrean con Ansible
|
||||||
|
|
||||||
|
Esta capa protege específicamente la **infraestructura de gestión** para no quedarse ciego si OVH tiene un fallo mayor.
|
||||||
|
|
||||||
|
#### Monitorización
|
||||||
|
|
||||||
|
| Alerta | Condición | Severidad |
|
||||||
|
|--------|-----------|-----------|
|
||||||
|
| ReplicaOfficeOld8h | Última réplica a oficina > 8h | Warning |
|
||||||
|
| ReplicaOfficeFailed | Playbook de réplica falla | Critical |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Capa 6 completa (DECISIONES FINALES)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ CAPA 6: ALMACENAMIENTO Y DATOS │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 6.1 ZFS (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Solo monitorización │ │
|
||||||
|
│ │ - node_exporter → métricas ZFS │ │
|
||||||
|
│ │ - Alertas: degraded, space, scrub│ │
|
||||||
|
│ │ - Ansible: optimización nuevos │ │
|
||||||
|
│ │ Sanoid/Syncoid/zrepl: DESCARTADO │ │
|
||||||
|
│ │ (PBS gestiona nativamente) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 6.2 BACKUP 3 CAPAS (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ PVE (Francia) │ │
|
||||||
|
│ │ → backup a PBS1 (Alemania) │ │
|
||||||
|
│ │ → permisos: solo Backup+Reader │ │
|
||||||
|
│ │ → NO puede borrar │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ PBS1 (Alemania) - TRÁNSITO │ │
|
||||||
|
│ │ → retención corta (3d/5 copias) │ │
|
||||||
|
│ │ → PVE escribe, PBS2 lee │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ PBS2 (Polonia) - ARCHIVO │ │
|
||||||
|
│ │ → retención larga (7d/4w/5m) │ │
|
||||||
|
│ │ → PULL de PBS1 (no push) │ │
|
||||||
|
│ │ → Invisible: no alcanzable │ │
|
||||||
|
│ │ → Verify semanal │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Conectividad: WG sobre vRack │ │
|
||||||
|
│ │ cross-DC o internet │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 3 países, 3 jurisdicciones │ │
|
||||||
|
│ │ Francia → Alemania → Polonia │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Recuperación PBS1 perdido: │ │
|
||||||
|
│ │ → Nuevo PBS1 + PVE replica │ │
|
||||||
|
│ │ → PBS2 pull cuando hay datos │ │
|
||||||
|
│ │ → Hueco ~1 día │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Recuperación PBS2 perdido: │ │
|
||||||
|
│ │ → Copias históricas perdidas │ │
|
||||||
|
│ │ → PBS1 sigue con retención corta│ │
|
||||||
|
│ │ → Opción PBS3 para clientes │ │
|
||||||
|
│ │ críticos (decisión de producto)│ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 6.3 CONFIGS A GIT (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ Backup diario de /etc/pve/ y │ │
|
||||||
|
│ │ /etc/proxmox-backup/ a Forgejo │ │
|
||||||
|
│ │ - Configs VMs, storage, users │ │
|
||||||
|
│ │ - Red, firewall, WG, ZFS props │ │
|
||||||
|
│ │ - Playbook Ansible + Semaphore │ │
|
||||||
|
│ │ - Cada commit = diff de cambios │ │
|
||||||
|
│ │ - Coste: 0 (texto, KB, Forgejo │ │
|
||||||
|
│ │ ya existe) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 6.4 RÉPLICA OFICINA (confirmada) │
|
||||||
|
│ ┌───────────────────────────────────┐ │
|
||||||
|
│ │ ZFS send/recv incremental cada │ │
|
||||||
|
│ │ 4h a servidor oficina (Xeon 96GB)│ │
|
||||||
|
│ │ - VMs: Monitoring, Wazuh, │ │
|
||||||
|
│ │ Servicios │ │
|
||||||
|
│ │ - VMs apagadas, solo emergencia │ │
|
||||||
|
│ │ - Transporte: WG cifrado │ │
|
||||||
|
│ │ - Contingencia: ~10 min levant. │ │
|
||||||
|
│ │ - Coste: 0 (HW ya existe) │ │
|
||||||
|
│ │ - Protege: infra de gestión │ │
|
||||||
|
│ │ (no datos de clientes) │ │
|
||||||
|
│ └───────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Recursos Capa 6:
|
||||||
|
- PBS1: ya existente (Alemania)
|
||||||
|
- PBS2: servidor nuevo por contratar (Polonia)
|
||||||
|
- Dimensionado según volumen de datos × retención
|
||||||
|
- Solo necesita disco (CPU/RAM mínimos, PBS es eficiente)
|
||||||
|
- PBS3 (opcional): por cliente premium, otro proveedor/país
|
||||||
|
- ZFS monitoring: ya cubierto por node_exporter (0 extra)
|
||||||
|
- Réplica oficina: servidor Xeon 96GB existente (0 coste)
|
||||||
|
```
|
||||||
Loading…
Reference in a new issue