106 lines
6.5 KiB
Markdown
106 lines
6.5 KiB
Markdown
# 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
|
||
H1–H5 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 H1–H5.
|
||
- `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 H1–H5 en estado resuelto/verificado.
|