Odoo Security Hardening: Authentication, Access Rights, and Encryption

Comprehensive Odoo 19 security hardening guide covering authentication, access rights, record rules, encryption, network security, and audit logging for enterprise deployments.

E
ECOSIRE Research and Development Team
|19 de marzo de 202610 min de lectura2.1k Palabras|

Refuerzo de seguridad de Odoo: autenticación, derechos de acceso y cifrado

Los sistemas ERP empresariales son objetivos de alto valor para los ciberataques. Una instancia de Odoo que contiene PII del cliente, registros financieros, contratos de proveedores y datos de empleados representa una importante superficie de ataque que exige controles de seguridad deliberados y en capas.

Esta guía cubre el proceso completo de fortalecimiento de la seguridad para Odoo 19 Enterprise: desde el fortalecimiento de la autenticación y el diseño de los derechos de acceso hasta el fortalecimiento de la red, el cifrado, el registro de auditoría y la preparación para la respuesta a incidentes.

Conclusiones clave

  • Las configuraciones predeterminadas de Odoo no están listas para producción desde una perspectiva de seguridad
  • La autenticación de dos factores (2FA) debería ser obligatoria para todos los usuarios con acceso confidencial
  • Los derechos de acceso siguen el principio de privilegio mínimo: los usuarios obtienen solo lo que necesitan
  • Las reglas de registro brindan seguridad a nivel de fila para imponer el aislamiento de datos
  • Toda comunicación entre servicios debe utilizar TLS; nunca HTTP en producción
  • Las credenciales de la base de datos deben rotarse periódicamente y nunca codificarse
  • Los registros de auditoría deben ser a prueba de manipulaciones y conservarse para cumplir con los requisitos
  • Los parches de seguridad deben aplicarse dentro de las 48 horas posteriores a los anuncios de Odoo SA

Refuerzo de autenticación

Política de contraseñas

Odoo 19 admite políticas de contraseña configurables en Configuración → Usuarios y empresas → Política de restablecimiento de contraseña.

Configuraciones recomendadas:

  • Longitud mínima: 12 caracteres
  • Requerir mayúsculas, minúsculas, números y caracteres especiales.
  • Caducidad de la contraseña: 90 días
  • Evitar la reutilización de las últimas 12 contraseñas
  • Bloqueo de cuenta después de 5 intentos fallidos (bloqueo de 15 minutos)
# Custom password validator in a security module
from odoo import api, models
from odoo.exceptions import UserError
import re

class ResUsers(models.Model):
    _inherit = 'res.users'

    @api.constrains('password')
    def _check_password_strength(self):
        for user in self:
            if user.password:
                pwd = user.password
                if len(pwd) < 12:
                    raise UserError("Password must be at least 12 characters.")
                if not re.search(r'[A-Z]', pwd):
                    raise UserError("Password must contain an uppercase letter.")
                if not re.search(r'[a-z]', pwd):
                    raise UserError("Password must contain a lowercase letter.")
                if not re.search(r'\d', pwd):
                    raise UserError("Password must contain a number.")
                if not re.search(r'[!@#$%^&*(),.?":{}|<>]', pwd):
                    raise UserError("Password must contain a special character.")

Autenticación de dos factores (2FA)

Habilite 2FA en Configuración → Permisos → Autenticación de dos factores.

Opciones de configuración:

  • Opcional: los usuarios eligen si desean habilitar 2FA
  • Requerido para grupos específicos: Aplicar a administradores y contadores
  • Requerido para todos los usuarios: Máxima seguridad

Para implementaciones empresariales, requiera 2FA para:

  • Cuentas de administrador (base.group_system)
  • Usuarios de finanzas (account.group_account_manager)
  • Gerentes de recursos humanos con acceso a nómina
  • Todos los usuarios con acceso a clave API

Integración de OAuth y SSO

Para organizaciones con proveedores de identidad existentes, configure Odoo para usar SAML 2.0 u OAuth 2.0:

# In odoo.conf — configure OAuth
oauth_providers = [
    {
        'name': 'Your SSO Provider',
        'client_id': 'odoo-client-id',
        'auth_endpoint': 'https://sso.company.com/auth',
        'validation_endpoint': 'https://sso.company.com/userinfo',
        'scope': 'openid email profile',
    }
]

Esto centraliza la autenticación en su proveedor de identidad, habilitando funciones como inicio de sesión único, MFA centralizado y desaprovisionamiento automático de cuentas cuando los empleados se van.

Seguridad de clave API

# Restrict API key creation to administrators only
# In custom security module:

# ir.rule to prevent non-admin users from creating API keys
<record id="rule_api_key_admin_only" model="ir.rule">
    <field name="name">API Keys: Admin Only</field>
    <field name="model_id" ref="base.model_auth_api_key"/>
    <field name="domain_force">[('create_uid', '=', user.id)]</field>
    <field name="groups" eval="[(4, ref('base.group_user'))]"/>
    <field name="perm_create" eval="False"/>
    <field name="perm_unlink" eval="False"/>
</record>

Aplique una política: las claves API caducan después de 90 días, están vinculadas a cuentas de servicio dedicadas con permisos mínimos y se rotan ante cualquier sospecha de compromiso.


Arquitectura de derechos de acceso

Diseño de jerarquía de grupo

El control de acceso de Odoo se basa en grupos. Diseñe una jerarquía de grupo que refleje su estructura organizacional:

base.group_user (Internal User)
├── sales.group_sale_salesman (Salesperson)
│   └── sales.group_sale_manager (Sales Manager)
├── account.group_account_user (Accounting User)
│   └── account.group_account_manager (Accounting Manager)
├── stock.group_stock_user (Inventory User)
│   └── stock.group_stock_manager (Inventory Manager)
└── base.group_system (Administrator)

Principio de privilegio mínimo: Cada usuario debe tener el acceso mínimo requerido para realizar su trabajo. Esto limita el radio de explosión de las cuentas comprometidas.

Listas de control de acceso (ACL)

Las entradas de ACL en ir.model.access definen permisos CRUD a nivel de modelo:

id,name,model_id,group_id,perm_read,perm_write,perm_create,perm_unlink
# Salesperson: can read/write own orders, cannot delete
access_sale_order_salesman,sale.order.salesman,sale.model_sale_order,sales.group_sale_salesman,1,1,1,0
# Manager: full CRUD
access_sale_order_manager,sale.order.manager,sale.model_sale_order,sales.group_sale_manager,1,1,1,1
# Accounting user: read-only on sales orders
access_sale_order_accountant,sale.order.accountant,sale.model_sale_order,account.group_account_user,1,0,0,0

Reglas de registro (seguridad a nivel de fila)

Las reglas de registro filtran qué registros puede ver un usuario dentro de un modelo:

<!-- Salesperson sees only their own opportunities -->
<record id="rule_crm_salesman" model="ir.rule">
    <field name="name">CRM: Own Leads</field>
    <field name="model_id" ref="crm.model_crm_lead"/>
    <field name="domain_force">
        ['|', ('user_id', '=', user.id), ('user_id', '=', False)]
    </field>
    <field name="groups" eval="[(4, ref('sales.group_sale_salesman'))]"/>
</record>

<!-- Multi-company isolation: users see only their company's records -->
<record id="rule_sale_order_company" model="ir.rule">
    <field name="name">Sale Order: Company</field>
    <field name="model_id" ref="sale.model_sale_order"/>
    <field name="domain_force">
        [('company_id', 'in', company_ids)]
    </field>
    <field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>

Advertencia de rendimiento: Las reglas de registro complejas con muchos operadores search() o child_of pueden ralentizar significativamente las vistas de lista. Pruebe todas las reglas de registro con grandes conjuntos de datos antes de implementarlas en producción.


Seguridad de la red

Aplicación de TLS/HTTPS

Nunca ejecute Odoo sobre HTTP simple en producción. Todo el tráfico debe estar cifrado.

Configuración TLS de Nginx:

server {
    listen 80;
    server_name your-odoo.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-odoo.com;

    ssl_certificate /etc/ssl/certs/your-odoo.com.pem;
    ssl_certificate_key /etc/ssl/private/your-odoo.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
                ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;

    # HSTS: 2 years, include subdomains
    add_header Strict-Transport-Security
        "max-age=63072000; includeSubDomains; preload" always;

    # Security headers
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    add_header Permissions-Policy
        "camera=(), microphone=(), geolocation=()" always;
}

Configuración del cortafuegos

# UFW firewall rules for Odoo server
ufw default deny incoming
ufw default allow outgoing

# Allow SSH from management IP only
ufw allow from 10.0.1.100 to any port 22

# Allow HTTPS from anywhere
ufw allow 443/tcp

# Allow HTTP (for Let's Encrypt renewal)
ufw allow 80/tcp

# Allow PostgreSQL only from Odoo server (if separate DB server)
ufw allow from 10.0.1.10 to any port 5432

# Block direct access to Odoo port (must go through Nginx)
ufw deny 8069/tcp
ufw deny 8072/tcp

ufw enable

Limitación de velocidad

Configure la limitación de velocidad en Nginx para evitar ataques de fuerza bruta:

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;

server {
    # Apply rate limiting to login endpoint
    location /web/login {
        limit_req zone=login burst=3 nodelay;
        limit_req_status 429;
        proxy_pass http://odoo;
    }

    # Apply rate limiting to API
    location /web/dataset/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://odoo;
    }
}

Cifrado de datos

Cifrado a nivel de base de datos

Para datos altamente confidenciales, los campos de Odoo se pueden cifrar en reposo:

# Custom encrypted field using Fernet symmetric encryption
from cryptography.fernet import Fernet
from odoo import api, fields, models
import base64, os

class EncryptedField(models.Model):
    _name = 'my.sensitive.model'

    _fernet_key = os.environ.get('ODOO_FIELD_ENCRYPTION_KEY')

    ssn_encrypted = fields.Char(string='SSN (Encrypted)', groups='hr.group_hr_user')
    ssn_display = fields.Char(
        string='SSN', compute='_compute_ssn_display',
        inverse='_inverse_ssn_display'
    )

    def _get_fernet(self):
        return Fernet(self._fernet_key.encode())

    def _compute_ssn_display(self):
        f = self._get_fernet()
        for record in self:
            if record.ssn_encrypted:
                decrypted = f.decrypt(record.ssn_encrypted.encode()).decode()
                record.ssn_display = f"***-**-{decrypted[-4:]}"
            else:
                record.ssn_display = False

    def _inverse_ssn_display(self):
        f = self._get_fernet()
        for record in self:
            if record.ssn_display:
                encrypted = f.encrypt(record.ssn_display.encode()).decode()
                record.ssn_encrypted = encrypted

Seguridad de variables de entorno

Nunca almacene secretos en odoo.conf en texto sin formato. Utilice variables de entorno:

# /etc/systemd/system/odoo.service
[Service]
Environment="ODOO_DB_PASSWORD=your_secure_password"
Environment="ODOO_ADMIN_PASSWD=your_master_password"
Environment="ODOO_FIELD_ENCRYPTION_KEY=your_fernet_key"
EnvironmentFile=/etc/odoo/secrets.env
# odoo.conf — reference env vars
[options]
db_password = ${ODOO_DB_PASSWORD}
admin_passwd = ${ODOO_ADMIN_PASSWD}

Registro de auditoría

El registro de auditoría integrado de Odoo rastrea todos los cambios del modelo. Habilítelo para modelos sensibles:

# Enable audit logging via the Audit Trail module
# Or implement custom logging:

class AuditableMixin(models.AbstractModel):
    _name = 'audit.mixin'

    def write(self, vals):
        # Log what changed before the write
        for record in self:
            old_vals = {f: record[f] for f in vals if f in record._fields}
            self.env['audit.log'].create({
                'model_name': self._name,
                'record_id': record.id,
                'user_id': self.env.uid,
                'action': 'write',
                'old_values': str(old_vals),
                'new_values': str(vals),
                'ip_address': self.env.context.get('remote_addr'),
            })
        return super().write(vals)

    def unlink(self):
        for record in self:
            self.env['audit.log'].create({
                'model_name': self._name,
                'record_id': record.id,
                'user_id': self.env.uid,
                'action': 'unlink',
                'old_values': str({f: record[f] for f in ['name', 'id']}),
            })
        return super().unlink()

Modelos críticos de cumplimiento para auditar:

  • res.users (creación de usuarios, cambios de permisos)
  • account.move (modificaciones de factura)
  • hr.payslip (cambios de nómina)
  • res.partner (cambios de PII del cliente/proveedor)
  • ir.model.access, ir.rule (cambios de permiso)
  • ir.config_parameter (cambios de configuración del sistema)

Gestión de parches de seguridad

Suscríbete a los avisos de seguridad de Odoo:

Odoo SA publica avisos de seguridad en https://github.com/odoo/odoo/security/advisories. Suscríbase a las notificaciones de GitHub para este repositorio.

Proceso de solicitud del parche:

# Check current Odoo version
python3 odoo-bin --version

# Pull latest security patches (within a minor version)
cd /opt/odoo
git fetch origin
git log origin/19.0..HEAD --oneline  # See what's changed

# Apply patches on staging first
git merge origin/19.0
python3 odoo-bin -d staging_db -u all --stop-after-init

# Test critical workflows on staging
# If all clear, apply to production during maintenance window

Objetivo: parchear dentro de las 48 horas de un aviso de gravedad crítica o alta. Severidad media: dentro de 1 semana. Gravedad baja: próxima ventana de mantenimiento planificada.


Preguntas frecuentes

¿Cómo audito quién eliminó un registro en Odoo?

Odoo no registra eliminaciones de forma predeterminada. Instale el módulo auditlog de Odoo Community Association (OCA) o implemente una anulación unlink() personalizada en sus modelos sensibles. El módulo OCA auditlog registra operaciones de creación, escritura y desvinculación a un modelo auditlog.log con seguimiento de cambios a nivel de usuario, marca de tiempo y campo.

¿Puedo restringir el acceso a Odoo por dirección IP?

No de forma nativa en Odoo. Implemente listas de IP permitidas a nivel de Nginx o firewall. Para el acceso administrativo, se requiere una conexión VPN como requisito previo. ir.config_parameter de Odoo admite una restricción web.base.url, pero el verdadero control de acceso basado en IP pertenece a la capa de red.

¿Cuál es la diferencia entre derechos de acceso (ACL) y reglas de registro?

Los derechos de acceso (ACL, almacenados en ir.model.access) controlan si un grupo de usuarios puede leer, escribir, crear o eliminar cualquier registro de un modelo; este es el control a nivel de modelo. Las reglas de registro filtran a qué registros específicos puede acceder un usuario dentro de un modelo mediante expresiones de dominio; esto es control a nivel de fila. Ambos funcionan juntos: un usuario necesita permiso de lectura de ACL Y debe pasar el filtro de dominio de regla de registro para ver un registro.

¿Cómo debo manejar la contraseña maestra de Odoo (admin_passwd)?

admin_passwd en odoo.conf controla el acceso a la interfaz de administración de la base de datos en /web/database/manager. En producción: configúrelo en una cadena aleatoria larga (más de 32 caracteres), guárdelo en un administrador de contraseñas, nunca lo comparta y considere deshabilitar el administrador de la base de datos por completo con list_db = False en odoo.conf si administra la base de datos mediante acceso directo a PostgreSQL.

¿Debo ejecutar Odoo como root?

Nunca. Cree un usuario del sistema odoo dedicado sin acceso al shell y con permisos limitados del sistema de archivos. El usuario de odoo necesita acceso de lectura al código base de Odoo y acceso de escritura solo al almacén de archivos y a los directorios de registro. Ejecute el servicio systemd como este usuario. Ejecutarlo como root viola el principio de privilegio mínimo y maximiza el daño causado por cualquier vulnerabilidad de ejecución de código.


Próximos pasos

El refuerzo de la seguridad no es una tarea única: es una práctica continua. Surgen nuevas vulnerabilidades, cambian los requisitos de acceso de los usuarios y evolucionan los marcos de cumplimiento. Una implementación segura de Odoo requiere revisiones de seguridad periódicas, disciplina en la gestión de parches y auditorías de derechos de acceso.

ECOSIRE proporciona evaluaciones de refuerzo de seguridad de Odoo que identifican configuraciones incorrectas, permisos excesivos, controles faltantes y brechas de cumplimiento. Nuestro equipo de seguridad tiene experiencia con los requisitos ISO 27001, SOC 2 y GDPR en entornos Odoo.

Solicitar una evaluación de seguridad de Odoo a ECOSIRE →

Revisaremos su configuración actual comparándola con los estándares de la industria y entregaremos un plan de remediación priorizado con soporte de implementación.

E

Escrito por

ECOSIRE Research and Development Team

Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.

Chatea en whatsapp