Odoo 安全强化:身份验证、访问权限和加密
企业 ERP 系统是网络攻击的高价值目标。包含客户 PII、财务记录、供应商合同和员工数据的 Odoo 实例代表了一个重要的攻击面,需要精心设计的分层安全控制。
本指南涵盖了 Odoo 19 Enterprise 的完整安全强化流程:从身份验证强化和访问权限设计到网络强化、加密、审核日志记录和事件响应准备。
要点
- 从安全角度来看,默认的 Odoo 配置尚未准备好用于生产
- 对于具有敏感访问权限的所有用户,应强制执行双因素身份验证 (2FA)
- 访问权限遵循最小权限原则——用户仅获得他们需要的内容
- 记录规则提供行级安全性以强制数据隔离
- 所有服务间通信必须使用TLS;生产环境中从不使用 HTTP
- 数据库凭据必须定期轮换且切勿硬编码
- 审核日志必须防篡改并保留以符合合规要求
- 应在 Odoo SA 公告后 48 小时内应用安全补丁
身份验证强化
密码政策
Odoo 19 支持在设置→用户和公司→密码重置策略下配置密码策略。
推荐设置:
- 最小长度:12 个字符
- 需要大写、小写、数字和特殊字符
- 密码有效期:90 天
- 防止重复使用最后 12 个密码
- 尝试失败 5 次后帐户将被锁定(15 分钟锁定)
# 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.")
双因素身份验证 (2FA)
在设置→权限→双因素身份验证下启用 2FA。
配置选项:
- 可选:用户选择是否启用2FA
- 特定群体需要:适用于管理员和会计师
- 所有用户都需要:最大安全性
对于企业部署,需要 2FA:
- 管理员帐户 (
base.group_system) - 金融用户 (
account.group_account_manager) - 拥有薪资访问权限的人力资源经理
- 所有具有 API 密钥访问权限的用户
OAuth 和 SSO 集成
对于拥有现有身份提供商的组织,将 Odoo 配置为使用 SAML 2.0 或 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',
}
]
这可以在您的身份提供商中集中进行身份验证,从而实现单点登录、集中式 MFA 以及员工离职时自动帐户取消配置等功能。
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>
执行策略:API 密钥将在 90 天后过期,与具有最低权限的专用服务帐户绑定,并在任何可疑的泄露时轮换。
访问权限架构
组层次结构设计
Odoo 的访问控制基于组。设计反映您的组织结构的组层次结构:
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)
最小权限原则:每个用户都应该拥有执行其工作所需的最低访问权限。这限制了受感染帐户的爆炸半径。
访问控制列表 (ACL)
ir.model.access 中的 ACL 条目定义模型级 CRUD 权限:
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
记录规则(行级安全性)
记录规则过滤器,记录用户可以在模型中看到的记录:
<!-- 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>
性能警告:具有许多 search() 或 child_of 运算符的复杂记录规则会显着减慢列表视图的速度。在部署到生产之前,针对大型数据集测试所有记录规则。
网络安全
TLS/HTTPS 执行
切勿在生产中通过纯 HTTP 运行 Odoo。所有流量都必须加密。
Nginx TLS 配置:
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;
}
防火墙配置
# 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
速率限制
在Nginx中配置速率限制以防止暴力攻击:
# 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;
}
}
数据加密
数据库级加密
对于高度敏感的数据,Odoo 字段可以静态加密:
# 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
环境变量安全
切勿以明文形式将机密存储在 odoo.conf 中。使用环境变量:
# /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}
审计日志记录
Odoo 的内置审核日志记录会跟踪所有模型更改。为敏感模型启用它:
# 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()
要审核的合规性关键模型:
res.users(用户创建、权限更改)account.move(发票修改)hr.payslip(工资变更)res.partner(客户/供应商 PII 更改)ir.model.access、ir.rule(权限更改)ir.config_parameter(系统配置更改)
安全补丁管理
订阅 Odoo 安全公告:
Odoo SA 在 https://github.com/odoo/odoo/security/advisories 发布安全公告。订阅此存储库的 GitHub 通知。
补丁申请流程:
# 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
目标:在出现严重或高严重性通报后 48 小时内修复。中等严重程度:1 周内。低严重性:下一个计划维护时段。
常见问题
如何审核谁在 Odoo 中删除了记录?
默认情况下,Odoo 不记录删除。安装 Odoo 社区协会 (OCA) 的 auditlog 模块或在敏感模型中实现自定义 unlink() 覆盖。 OCA 审核日志模块将创建、写入和取消链接操作记录到 auditlog.log 模型,并提供用户、时间戳和字段级更改跟踪。
我可以通过 IP 地址限制 Odoo 访问吗?
不是 Odoo 原生的。在 Nginx 或防火墙级别实施 IP 白名单。对于管理访问,需要 VPN 连接作为先决条件。 Odoo 的 ir.config_parameter 确实支持 web.base.url 限制,但真正的基于 IP 的访问控制属于网络层。
访问权限(ACL)和记录规则有什么区别?
访问权限(ACL,存储在 ir.model.access 中)控制用户组是否可以读取、写入、创建或删除模型的任何记录 - 这是模型级控制。记录规则使用域表达式过滤用户可以在模型中访问哪些特定记录 - 这是行级控制。两者协同工作:用户需要 ACL 读取权限并且必须通过记录规则域过滤器才能查看记录。
我应该如何处理 Odoo 主密码 (admin_passwd)?
odoo.conf 中的 admin_passwd 控制对 /web/database/manager 处数据库管理界面的访问。在生产中:将其设置为长随机字符串(超过 32 个字符),将其存储在密码管理器中,切勿共享它,如果您通过直接 PostgreSQL 访问管理数据库,请考虑使用 odoo.conf 中的 list_db = False 完全禁用数据库管理器。
我应该以 root 身份运行 Odoo 吗?
绝不。创建一个没有 shell 访问权限且文件系统权限有限的专用 odoo 系统用户。 odoo 用户需要对 Odoo 代码库进行读取访问,并且仅对文件存储和日志目录进行写入访问。以该用户身份运行 systemd 服务。以 root 身份运行违反了最小权限原则,并且使任何代码执行漏洞造成的损害最大化。
后续步骤
安全强化不是一项一次性任务,而是一项持续的实践。新的漏洞不断出现,用户访问需求发生变化,合规框架不断发展。安全的 Odoo 部署需要定期安全审查、补丁管理规则和访问权限审核。
ECOSIRE 提供 Odoo 安全强化评估,可识别错误配置、过多权限、缺失控制和合规性差距。我们的安全团队在 Odoo 环境中满足 ISO 27001、SOC 2 和 GDPR 要求方面拥有丰富的经验。
我们将根据行业标准审查您当前的配置,并提供优先的修复计划和实施支持。
作者
ECOSIRE Research and Development Team
在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。
相关文章
Odoo Accounting vs QuickBooks: Detailed Comparison 2026
In-depth 2026 comparison of Odoo Accounting vs QuickBooks covering features, pricing, integrations, scalability, and which platform fits your business needs.
API Rate Limiting: Patterns and Best Practices
Master API rate limiting with token bucket, sliding window, and fixed counter patterns. Protect your backend with NestJS throttler, Redis, and real-world configuration examples.
Case Study: eCommerce Migration to Shopify with Odoo Backend
How a fashion retailer migrated from WooCommerce to Shopify and connected it to Odoo ERP, cutting order fulfillment time by 71% and growing revenue 43%.