addons-cm/docs/openspec/changes/security-audit-hardening/proposal.md
2026-08-07 16:46:33 +02:00

106 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Proposal — Auditoría de seguridad y hardening (Odoo 18.0)
## Why
Detectamos ataques automatizados cada vez más sofisticados que la heurística de fail2ban no frena,
y somos responsables de la seguridad de nuestra infra y la de nuestros clientes. Necesitamos (a) un
proceso de auditoría **reproducible** tipo "checklist de prevuelo" para verificar la resiliencia
ante fuerza bruta, acceso indebido a datos vía API y vectores inadvertidos; y (b) corregir cinco
hallazgos ya confirmados leyendo el propio código de `website_sale_aplicoop`.
## Objetivo
Dejar la infra Odoo con una postura de seguridad auditable y repetible, y cerrar los hallazgos
H1H5 con tests que impidan su regresión.
## Alcance
- **Solo nuestra infra** (autorización directa, sin terceros). Producción está tras nginx/traefik
con TLS.
- Auditoría **activa autorizada** (fuerza bruta controlada contra cuenta de test y fuzzing de
endpoints) desde la máquina Kali de la red y desde `localhost` vía SSH, preferentemente contra un
clon de staging o en ventana de mantenimiento con snapshot previo.
- Remediación de los hallazgos confirmados en `website_sale_aplicoop` (+ `groups_id` en la server
action de mandatos SEPA de `account_banking_mandate_batch`).
## No-objetivos
- No se audita ni escanea infra de terceros ni hosting ajeno.
- No se corrige aquí ningún hallazgo **nuevo** que surja durante la auditoría activa: se registra en
el findings register y se aborda en un change posterior (salvo severidad crítica, que se escalará).
- No se modifica `ocb/` ni los addons OCA originales.
## What Changes
- **Proceso de auditoría**: se añaden entregables de documentación (`docs/SECURITY_AUDIT_CHECKLIST.md`,
`docs/SECURITY_AUDIT_RUNBOOK.md`, `docs/SECURITY_FINDINGS.md`) y scripts no destructivos en
`scripts/security/` (probe de ACL vía API, probe de IDOR, harness de fuerza bruta, PoC de CSRF,
recon web) parametrizados con cuentas de test.
- **Fix H1 (Alta)** — ACL con `group_id` vacío que concede write+create global sobre `group.order` y
`group.order.slot`: se reemplaza por ACLs de mínimo privilegio (lectura interna/portal explícitas;
escritura solo manager). Los writes de los controladores usan `sudo()` y no dependen de estas ACLs.
- **Fix H2 (Alta)** — record rule de `product.supplierinfo` con `domain=[(1,'=',1)]` que expone todos
los precios de proveedor al portal. El portal usa `supplierinfo` para pintar el **origen** del
producto y el **proveedor principal** en la web, así que no se elimina sin más: o se preparan esos
valores server-side (vía `sudo()` en el controlador, según la regla "sin lógica en QWeb") o se acota
el dominio a los productos accesibles por el usuario, sin exponer campos de coste/precio.
- **Fix H3 (Media)** — endpoints de cambio de estado con `csrf=False`: se convierten a `type="json"`
(bloquea el POST de formulario cross-site), tomando como plantilla `confirm_order_from_portal`.
- **Fix H4 (Media)** — endpoints de lectura/ajax que solo comprueban `exists()`+`state`: se añade
comprobación de pertenencia al grupo reutilizando `_validate_user_group_access` /
`_get_consumer_group_for_user`.
- **Fix H5 (Baja)** — `t-raw` inyectado en `<script>`: se sustituye por un patrón de salida seguro.
- **Hardening de config**: `admin_passwd`, `list_db=False`, `proxy_mode=True`, password de BD fuerte,
rate-limit/cabeceras en nginx, jails de fail2ban (web + API). Documentado como requisitos; los
cambios de infra no versionable se ejecutan en el servidor, los versionables (`groups_id` en la
server action) en el repo.
## Capabilities
### New Capabilities
- `eskaera-access-control`: cómo `website_sale_aplicoop` restringe acceso a datos y acciones —
ACL de mínimo privilegio, alcance de `product.supplierinfo`, autorización por pertenencia al grupo
de consumo en todos los endpoints, protección CSRF en endpoints de estado, y escapado de salida en
plantillas. Cubre H1H5.
- `security-audit-process`: proceso repetible de auditoría — reglas de compromiso/autorización,
checklist de prevuelo, runbook de auditoría activa, scripts de prueba y registro de hallazgos.
- `infra-hardening`: requisitos de endurecimiento de producción — `odoo.conf`, reverse proxy
(nginx/traefik) con TLS/rate-limit/cabeceras, fail2ban (web + API), y gestión de secretos/BD.
### Modified Capabilities
- (ninguna — no existen specs previas en `openspec/specs/`)
## Impact
- **Addons afectados**: `website_sale_aplicoop` (security CSV + record rules, controladores,
plantilla, tests, bump de `__manifest__.py`); `account_banking_mandate_batch` (`groups_id` en la
server action).
- **Addons NO afectados**: `ocb/`, los addons OCA originales, y el resto de custom (solo se auditan;
cualquier hallazgo se registra, no se modifica en este change).
- **Nuevas rutas en el repo**: `docs/SECURITY_*`, `scripts/security/`.
- **Impacto UI/website/QWeb**: al convertir endpoints a `type="json"` hay que ajustar el transporte
en el JS de `website_sale_aplicoop` (solo transporte, sin lógica). La plantilla
`load_from_history_templates.xml` cambia su forma de inyectar JSON. Sin cambios visibles de UX.
- **Infra (no versionable)**: `odoo.conf`, config de nginx/traefik y fail2ban en el servidor de prod.
## Riesgos
- La auditoría activa (fuerza bruta/fuzzing) puede degradar o afectar a producción: mitigado con
clon de staging o ventana de mantenimiento, snapshot previo y cuentas de test dedicadas (nunca
contra cuentas reales, por riesgo de lockout/DoS de usuarios legítimos).
- Convertir endpoints a `type="json"` cambia el contrato de request/response: riesgo de regresión en
el carrito/checkout del portal; mitigado con tests y verificación end-to-end.
- Recortar el acceso a `product.supplierinfo` podría romper la visualización de precios si algún
punto del portal dependía de la ACL abierta: verificar que el pricing se computa vía `sudo()`.
## Criterios de aceptación
- Los probes `odoo_acl_probe.py` e `idor_probe.py` deniegan tras los fixes lo que permitían antes
(write/create de `group.order`, lectura de `supplierinfo` ajeno, datos de grupos ajenos).
- El PoC de CSRF funciona antes del fix y falla después.
- El harness de fuerza bruta acaba con la IP de Kali baneada por fail2ban en `/web/login` y en la
ruta API.
- Tests de `website_sale_aplicoop` en verde:
`docker-compose run odoo odoo -d odoo --test-enable --stop-after-init -u website_sale_aplicoop`.
- El checklist de prevuelo cubre los tres frentes + infra, con criterio pass/fail por ítem, y el
findings register queda con H1H5 en estado resuelto/verificado.