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 مارس 20269 دقائق قراءة1.9k كلمات|

تقوية أمان Odoo: المصادقة، وحقوق الوصول، والتشفير

تعد أنظمة تخطيط موارد المؤسسات (ERP) للمؤسسات أهدافًا عالية القيمة للهجمات الإلكترونية. يمثل مثيل Odoo الذي يحتوي على معلومات تحديد الهوية الشخصية للعميل والسجلات المالية وعقود الموردين وبيانات الموظفين سطحًا مهمًا للهجوم يتطلب ضوابط أمنية متعمدة ومتعددة الطبقات.

يغطي هذا الدليل عملية تعزيز الأمان الكاملة لـ Odoo 19 Enterprise: بدءًا من تعزيز المصادقة وتصميم حقوق الوصول وحتى تقوية الشبكة والتشفير وتسجيل التدقيق والاستعداد للاستجابة للحوادث.

الوجبات الرئيسية

  • تكوينات Odoo الافتراضية ليست جاهزة للإنتاج من منظور أمني
  • يجب أن تكون المصادقة الثنائية (2FA) إلزامية لجميع المستخدمين ذوي الوصول الحساس
  • تتبع حقوق الوصول مبدأ الامتيازات الأقل - حيث يحصل المستخدمون على ما يحتاجون إليه فقط
  • توفر قواعد السجل الأمان على مستوى الصف لفرض عزل البيانات
  • يجب أن تستخدم جميع الاتصالات بين الخدمات TLS؛ أبدا HTTP في الإنتاج
  • يجب تدوير بيانات اعتماد قاعدة البيانات بشكل منتظم وعدم ترميزها مطلقًا
  • يجب أن تكون سجلات التدقيق مقاومة للتلاعب ويتم الاحتفاظ بها لتلبية متطلبات الامتثال
  • يجب تطبيق التصحيحات الأمنية خلال 48 ساعة من إعلانات Odoo SA

تصلب المصادقة

سياسة كلمة المرور

يدعم 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) من أجل:

  • حسابات المسؤول (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 المركزي، وإلغاء توفير الحساب تلقائيًا عند مغادرة الموظفين.

أمان مفتاح واجهة برمجة التطبيقات

# 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)

تحدد إدخالات ACL في ir.model.access أذونات 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

لا تقم مطلقًا بتشغيل Odoo عبر HTTP العادي في مرحلة الإنتاج. يجب أن تكون جميع حركة المرور مشفرة.

تكوين 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 (تغييرات معلومات تحديد الهوية الشخصية للعميل/المورد)
  • 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 ساعة من التحذيرات الحرجة أو شديدة الخطورة. متوسطة الخطورة: خلال أسبوع واحد. خطورة منخفضة: نافذة الصيانة المخططة التالية.


الأسئلة المتداولة

كيف يمكنني تدقيق من قام بحذف سجل في Odoo؟

لا يقوم Odoo بتسجيل عمليات الحذف بشكل افتراضي. قم بتثبيت وحدة auditlog من جمعية مجتمع Odoo (OCA) أو قم بتنفيذ تجاوز unlink() مخصص في نماذجك الحساسة. تقوم سجلات وحدة سجل تدقيق OCA بإنشاء العمليات وكتابتها وإلغاء ربطها بنموذج auditlog.log مع المستخدم والطابع الزمني وتتبع التغيير على مستوى الحقل.

هل يمكنني تقييد الوصول إلى Odoo عن طريق عنوان IP؟

ليس أصليًا في Odoo. قم بتنفيذ القائمة المسموح بها لـ IP على مستوى Nginx أو جدار الحماية. للوصول الإداري، اطلب اتصال VPN كشرط أساسي. يدعم ir.config_parameter الخاص بـ Odoo تقييد web.base.url، ولكن التحكم الحقيقي في الوصول المستند إلى IP ينتمي إلى طبقة الشبكة.

ما الفرق بين حقوق الوصول (ACL) وقواعد التسجيل؟

تتحكم حقوق الوصول (ACL، المخزنة في ir.model.access) فيما إذا كان بإمكان مجموعة المستخدمين قراءة أي سجل لنموذج أو كتابته أو إنشائه أو حذفه - وهذا هو التحكم على مستوى النموذج. تقوم قواعد السجل بتصفية السجلات المحددة التي يمكن للمستخدم الوصول إليها داخل نموذج باستخدام تعبيرات المجال - وهذا هو التحكم على مستوى الصف. كلاهما يعملان معًا: يحتاج المستخدم إلى إذن قراءة قائمة التحكم بالوصول (ACL) ويجب عليه اجتياز عامل تصفية مجال قاعدة السجل لرؤية السجل.

كيف يمكنني التعامل مع كلمة مرور Odoo الرئيسية (admin_passwd)؟

يتحكم admin_passwd في odoo.conf في الوصول إلى واجهة إدارة قاعدة البيانات في /web/database/manager. في الإنتاج: اضبطها على سلسلة عشوائية طويلة (أكثر من 32 حرفًا)، وقم بتخزينها في مدير كلمات المرور، ولا تشاركها أبدًا، وفكر في تعطيل مدير قاعدة البيانات بالكامل باستخدام list_db = False في odoo.conf إذا كنت تدير قاعدة البيانات عبر الوصول المباشر إلى PostgreSQL.

هل يجب أن أقوم بتشغيل Odoo كجذر؟

أبداً. قم بإنشاء مستخدم نظام odoo مخصص بدون إمكانية الوصول إلى Shell وأذونات نظام الملفات المحدودة. يحتاج مستخدم odoo إلى حق الوصول للقراءة إلى قاعدة بيانات Odoo وحق الوصول للكتابة فقط إلى مخزن الملفات وأدلة السجل. قم بتشغيل خدمة systemd كهذا المستخدم. إن التشغيل كجذر ينتهك مبدأ الامتيازات الأقل ويزيد من الضرر الناتج عن أي ثغرة أمنية في تنفيذ التعليمات البرمجية.


الخطوات التالية

إن تعزيز الأمن ليس مهمة لمرة واحدة - بل هو ممارسة مستمرة. تظهر ثغرات أمنية جديدة، وتتغير متطلبات وصول المستخدم، وتتطور أطر الامتثال. يتطلب النشر الآمن لـ Odoo مراجعات أمنية منتظمة وانضباطًا في إدارة التصحيح وتدقيقًا لحقوق الوصول.

يوفر ECOSIRE تقييمات تقوية أمان Odoo التي تحدد التكوينات الخاطئة والأذونات الزائدة والضوابط المفقودة وفجوات الامتثال. يتمتع فريق الأمان لدينا بخبرة في التعامل مع متطلبات ISO 27001 وSOC 2 وGDPR في بيئات Odoo.

اطلب تقييم أمان Odoo من ECOSIRE →

سنقوم بمراجعة التكوين الحالي الخاص بك وفقًا لمعايير الصناعة ونقدم خطة إصلاح ذات أولوية مع دعم التنفيذ.

E

بقلم

ECOSIRE Research and Development Team

بناء منتجات رقمية بمستوى المؤسسات في ECOSIRE. مشاركة رؤى حول تكاملات Odoo وأتمتة التجارة الإلكترونية وحلول الأعمال المدعومة بالذكاء الاصطناعي.

الدردشة على الواتساب