Перейти к содержимому

Десять обязательных проверок безопасности перед внедрением Odoo 19 и PostgreSQL

Архитектурный стандарт для технических директоров (CTO) и офицеров безопасности (CISO): методология системного устранения 10 критических векторов уязвимостей на уровне ядра сервера.

В первых трёх отчётах нашего цикла прикладных исследований мы детально препарировали системные уязвимости, возникающие при использовании бизнесом самописных интерфейсов и непроверенных облачных надстроек:

  1. Неконтролируемая утечка данных возникает из-за публичных анонимных эндпоинтов и открытой регистрации аккаунтов, позволяющих выкачивать десятки тысяч персональных карточек клиентов;
  2. Клиентское массовое присвоение (CWE-915) открывает возможность для любого рядового сотрудника выдать себе права супер-администратора через консоль браузера;
  3. Компрометация телефонии (CWE-522) происходит при передаче открытых мастер-паролей от АТС и статических ключей TURN-релеев в браузер, что создаёт прямую угрозу международного телефонного фрода;
  4. Крах финансовой достоверности вызывается лавинообразными триггерами с расходом 622 000 GB-секунд и миллионными кассовыми разрывами из-за отказа от принципа двойной записи.

У всех этих инцидентов одна первопричина: ошибочное предположение о том, что браузер пользователя может участвовать в проверке прав доступа или расчёте бизнес-правил.

При миграции операционной деятельности на промышленное ядро Odoo 19 CE на выделенном сервере под управлением PostgreSQL данная поверхность атаки полностью ликвидируется. Безопасность перестаёт быть декоративной надстройкой на фронтенде и становится детерминированным свойством серверного ядра и базы данных.

В настоящем документе изложен Стандарт Zero Client-Side Trust в 10 пунктах, представляющий собой практическое руководство для технических руководителей по защите корпоративных систем.

Защита Odoo 19: Показатели ликвидации векторов уязвимостей

Вектор 1: Неавторизованный перебор заказов BOLA (OWASP API1:2023) 100%
Вектор 2: Открытая публичная саморегистрация пользователей (CWE-250) 100%
Вектор 3: Массовое присвоение атрибутов и захват Admin (CWE-915) 100%
Вектор 4: Утечка инфраструктурных ключей через настройки (CWE-522) 100%
Вектор 5: Скачивание всей базы через визуальные фильтры UI (CWE-284) 100%
Вектор 6: Хранение паролей в открытом виде или без соли (CWE-256) 100%
Вектор 7: Слив мастер-паролей АТС и статические пароли TURN (CWE-798) 100%
Вектор 8: Уничтожение повторных заявок грубой дедупликацией 100%
Вектор 9: Реактивные каскады NoSQL и перерасход облачной памяти 100%
Вектор 10: Бухгалтерия на текстовых строках и отрицательные складские остатки 100%

Трёхуровневый барьер изоляции Zero-Trust


Ключевое отличие промышленной платформы Odoo 19 CE от архитектур «толстого клиента» (thick-client) заключается в физическом разделении трёх контуров:

Трёхуровневый контур безопасности Zero-Trust в Odoo 19

01

Недоверенный клиентский периметр

Веб-браузеры, мобильные терминалы и внешние API работают во внешней открытой зоне. Взаимодействие идет строго через шифрованный протокол HTTPS / JSON-RPC.

02

Шлюз безопасности Odoo 19 ORM (Python)

Каждый сетевой запрос перехватывается сервером приложений. Ядро сверяет таблицы доступа (ACL), внедряет строковые правила (ir.rule) и вырезает закрытые поля.

03

Изолированная СУБД PostgreSQL

PostgreSQL слушает исключительно локальный сокет 127.0.0.1 или приватный контур VPC. Прямой доступ к порту 5432 из интернета физически невозможен.

04

Транзакционный контроль ACID

База данных выполняет атомарные транзакции с блокировкой строк (SELECT FOR UPDATE) и проверяет математическое равенство дебета и кредита.

В этой архитектуре браузер является лишь терминалом отображения. Он не имеет сетевого соединения с PostgreSQL, не может выполнять произвольные SQL-команды и не имеет возможности обойти правила проверки прав, заложенные на сервере.

Сравнительная матрица в 10 пунктах


В приведённой ниже таблице сопоставлены типовые дефекты самописных бессерверных систем и механизмы защиты, действующие в промышленном стандарте Odoo 19 dlab.md:

Сравнительная матрица: Уязвимая самописная система против Odoo 19 Hardened

Уязвимый самописный стек (React + NoSQL)

Опасный анти-паттерн
  • Доступ к нарядам по порядковым номерам без авторизации (BOLA)
  • Открытая публичная регистрация для удобства добавления дилеров
  • Клиентский оператор spread позволяет перезаписать роль 'admin' (CWE-915)
  • Приватные ключи облака и SMS-шлюза доступны в общей коллекции настроек
  • Разграничение доступа основано на сокрытии кнопок в меню браузера
  • Пароли сотрудников сохраняются открытым текстом в карточках клиентов
  • Мастер-пароль sip:admin от всей АТС передается в JSON софтфона
  • Грубое сравнение номеров телефонов блокирует повторные заявки
  • Реактивные триггеры выкачивают всю базу в память (622 000 GB-s)
  • Статусы оплаты на строках ('PAID' против 'paid') и минус на складе

Промышленный стандарт dlab.md Odoo 19 CE

Корпоративный стандарт
  • Доступ по криптографическому токену UUIDv4 с проверкой const_eq
  • Периметр закрыт: регистрация строго по приглашению (auth_signup = b2b)
  • Поля защищены через SELF_WRITEABLE_FIELDS; подмена роли вызывает отказ
  • Секреты изолированы в ir.config_parameter для base.group_system
  • Принудительное внедрение условий WHERE в SQL через правила ir.rule
  • Пароли хэшируются с солью по алгоритму PBKDF2-SHA512
  • Вызовы шлюзуются через B2BUA, а релеи защищены токенами RFC 5389
  • Реляционные связи: существующий клиент открывает новую сделку
  • Агрегация данных выполняется СУБД по B-Tree индексам за 2 мс
  • Двойная запись (account.move.line) и блокировка складских строк FOR UPDATE

Подробный разбор 10 векторов безопасности

Неавторизованный перебор заказов (CWE-639 / BOLA)

В уязвимом решении данные наряда открывались по порядковому номеру (ORD-202508-0117). В Odoo 19 доступ через публичный портал защищён абстрактным классом portal.mixin. Просмотр заказа анонимным посетителем возможен исключительно при передаче 36-значного криптографического UUID токена access_token. Ядро проверяет токен функцией с постоянным временем выполнения (const_eq), что предотвращает атаки по времени. Попытка перебора номеров SO001, SO002 вызывает ошибку 403 Forbidden.

Периметр публичной саморегистрации (CWE-250)

В заказной системе форму регистрации оставили открытой для всего интернета, чтобы диспетчеры могли регистрировать сотрудников из интерфейса. В Odoo 19 параметр auth_signup.invitation_scope установлен в значение b2b (Регистрация только по приглашению). Сторонний человек не может зарегистрироваться сам и получить доступ к внутренним ресурсам.

Клиентское массовое присвоение (CWE-915)

В исследованной системе запрос updateDoc(userRef, { role: "admin" }) успешно срабатывал из-за отсутствия схемы валидации на сервере. В Odoo 19 права на поля защищены на уровне ORM атрибутом groups="base.group_system". Самостоятельное обновление профиля пользователем фильтруется через белый список SELF_WRITEABLE_FIELDS. Попытка передать привилегированные поля вызывает исключение AccessError.

Утечка секретов через глобальные настройки (CWE-522)

В уязвимой системе ключи сервисных аккаунтов и токены интеграций хранились в открытой для чтения NoSQL-коллекции. В Odoo 19 параметры конфигурации хранятся в защищённой таблице ir.config_parameter, доступ к которой разрешён исключительно группе base.group_system (Супер-администраторы). Рядовые менеджеры и технические специалисты не могут прочитать эти данные ни через интерфейс, ни через API.

Выкачивание данных через фильтрацию только на фронтенде (CWE-284)

В самописной программе ограничение прав заключалось лишь в скрытии пунктов меню, тогда как API отдавал все строки базы. В Odoo 19 безопасность работает на этапе построения SQL-запроса через ir.rule (Row-Level Security). Когда выездной инженер запрашивает задачи, сервер принудительно дописывает в SQL-запрос фильтр WHERE user_id = current_user.id.

Хранение паролей в открытом виде (CWE-256)

В уязвимой системе пароли сохранялись открытым текстом в карточки клиентов для удобства менеджеров. В Odoo 19 пароли никогда не хранятся в открытом виде: ядро использует криптографическое хэширование PBKDF2-SHA512 с уникальной солью, а первичный ввод пароля осуществляется через одноразовую временную ссылку сброса.

Слив мастер-пароля телефонии (CWE-798)

В уязвимом софтфоне логин и пароль от магистрального транка АТС отдавались в браузер каждому сотруднику. В Odoo 19 телефония шлюзуется через выделенный сервер B2BUA. Браузер общается с внутренними номерами с минимальными правами, а передача медиа-трафика использует эфемерные ключи TURN стандарта RFC 5389, сгорающие через 3 600 секунд.

Потеря повторных обращений клиентов

В самописном скрипте поиск дублей по номеру телефона блокировал создание новых заявок от старых клиентов. В Odoo 19 входящий запрос от существующего партнёра (res.partner) автоматически формирует новую коммерческую возможность (crm.lead), сохраняя при этом всю историю прошлых нарядов и установленного оборудования.

Лавины серверных вычислений NoSQL

В документоориентированной базе каждый триггер скачивал десятки тысяч документов в память, расходуя 622 000 GB-секунд в месяц. В Odoo 19 сводные отчёты формируются через оптимизированные B-Tree индексы PostgreSQL за 2 миллисекунды без перерасхода серверных мощностей.

Бухгалтерия на текстовых строках и отрицательный склад

В самописной программе статус оплаты проверялся простым текстом ("PAID" против "paid"), а параллельные списания приводили к отрицательным остаткам. В Odoo 19 все движения средств фиксируются методом двойной записи (account.move.line), а списание складских позиций защищено транзакционными блокировками (SELECT ... FOR UPDATE).

Архитектурные шаблоны и программная реализация защиты


Ниже приведены три реальных примера программного кода, демонстрирующих реализацию стандарта Zero-Trust на уровне XML-правил, моделей ORM Python и конфигурации СУБД PostgreSQL:

Шаблоны серверной защиты Odoo 19 и PostgreSQL

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Изоляция операционных задач по филиалам компании -->
<record id="fsm_task_multi_company_isolation_rule" model="ir.rule">
<field name="name">Изоляция задач по компаниям холдинга</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>

<!-- Ограничение доступа: инженеры видят только назначенные им наряды -->
<record id="fsm_technician_dispatch_personal_rule" model="ir.rule">
<field name="name">Инженеры видят только свои наряды</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[('user_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('project.group_project_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data>
</odoo>
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions, _

class ResPartnerEnterpriseSecurity(models.Model):
_inherit = 'res.partner'

# Финансовые лимиты строго защищены от изменений линейным персоналом
credit_limit = fields.Monetary(
string="Кредитный лимит контрагента",
groups="account.group_account_manager",
tracking=True
)
tax_compliance_status = fields.Selection([
('verified', 'Проверен ФНС'),
('hold', 'Административная блокировка'),
('blacklisted', 'В чёрном списке')
], default='verified', groups="base.group_system", tracking=True)

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
# Защита от массового присвоения кредитных лимитов через API
if 'credit_limit' in vals and not self.env.user.has_group('account.group_account_manager'):
raise exceptions.AccessError(_("Нарушение безопасности: Установка кредитных лимитов разрешена только финансовым контролёрам."))
return super().create(vals_list)

def write(self, vals):
if 'tax_compliance_status' in vals and not self.env.user.has_group('base.group_system'):
raise exceptions.AccessError(_("Нарушение безопасности: Изменение налогового статуса доступно только администраторам системы."))
return super().write(vals)
-- Регламент ужесточения параметров безопасности PostgreSQL
-- Отзыв опасных публичных прав на схему
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE ON SCHEMA public TO odoo_app_user;

-- Ограничение путей поиска и пула соединений
ALTER USER odoo_app_user SET search_path = public;
ALTER USER odoo_app_user CONNECTION LIMIT 100;

-- Таблица аудита: фиксация критических изменений прав и ролей
CREATE TABLE IF NOT EXISTS audit_security_log (
id SERIAL PRIMARY KEY,
event_timestamp TIMESTAMPTZ DEFAULT NOW(),
db_user VARCHAR(64) DEFAULT CURRENT_USER,
target_table VARCHAR(64),
operation VARCHAR(16),
old_data JSONB,
new_data JSONB
);

CREATE OR REPLACE FUNCTION log_critical_security_event()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_security_log (target_table, operation, old_data, new_data)
VALUES (TG_TABLE_NAME, TG_OP, to_jsonb(OLD), to_jsonb(NEW));
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE OR REPLACE TRIGGER trg_audit_user_groups
AFTER UPDATE ON res_groups_users_rel
FOR EACH ROW EXECUTE FUNCTION log_critical_security_event();

Верификация безопасности в промышленном контуре

Для обеспечения непрерывного контроля безопасности администраторы и аудиторы могут использовать следующие скрипты валидации непосредственно в тестовых и продуктовых средах.

Защищённая модель Odoo 19 на Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions, _

class EnterpriseSecurityPolicy(models.Model):
_name = 'enterprise.security.policy'
_description = 'Регламент безопасности Zero-Trust'
_check_company_auto = True

name = fields.Char(string="Идентификатор политики", required=True)
company_id = fields.Many2one('res.company', string="Организация", required=True, default=lambda s: s.env.company)
active_mfa_required = fields.Boolean(string="Обязательный аппаратный токен 2FA", default=True)
allowed_cidr = fields.Char(string="Разрешенные подсети CIDR", default="10.0.0.0/8,192.168.1.0/24")

@api.constrains('company_id', 'allowed_cidr')
def _check_security_isolation(self):
for record in self:
if not record.company_id:
raise exceptions.ValidationError(_("Нарушение Zero-Trust: Политика безопасности должна быть жестко привязана к компании."))

def write(self, vals):
if not self.env.user.has_group('base.group_system'):
raise exceptions.AccessError(_("Нарушение Zero-Trust: Изменение политик безопасности доступно только Root-Администраторам."))
return super().write(vals)

Триггер аудита прав и безопасности на уровне ядра PostgreSQL

-- Эшелонированная защита PostgreSQL: Неизменяемый аудит эскалации привилегий
CREATE TABLE IF NOT EXISTS sys_security_audit_vault (
audit_id BIGSERIAL PRIMARY KEY,
audit_timestamp TIMESTAMPTZ DEFAULT CLOCK_TIMESTAMP(),
operator_user VARCHAR(64) DEFAULT CURRENT_USER,
action_type VARCHAR(16) NOT NULL,
affected_user_id INT NOT NULL,
granted_group_id INT NOT NULL
);

CREATE OR REPLACE FUNCTION trap_privilege_escalation()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO sys_security_audit_vault (action_type, affected_user_id, granted_group_id)
VALUES (TG_OP, NEW.uid, NEW.gid);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE OR REPLACE TRIGGER trg_vault_user_privileges
AFTER INSERT OR UPDATE ON res_groups_users_rel
FOR EACH ROW
EXECUTE FUNCTION trap_privilege_escalation();

Четырёхэтапная методология безопасной миграции


При переводе предприятий с уязвимых самописных порталов на ядро Odoo 19 CE экспертная группа dlab.md реализует миграцию по четкому протоколу:

4-этапный регламент безопасной миграции на Odoo 19

01

Криминалистический аудит и экстренная изоляция (Дни 1–2)

Находим все открытые эндпоинты и скомпрометированные ключи. Немедленно проводим ротацию паролей телефонии и сервисных ключей облака.

02

Развертывание защищенного ядра Odoo 19 (Неделя 1)

Разворачиваем выделенный облачный сервер в европейском дата-центре с закрытым портом PostgreSQL и настроенными правилами ir.rule.

03

Очистка данных и восстановление баланса (Неделя 2)

Очищаем базу от мусорных логов, восстанавливаем дубликаты телефонных номеров и вводим корректные начальные остатки методом двойной записи.

04

Запуск в эксплуатацию и непрерывный мониторинг

Переключаем рабочий трафик, переводим звонки на безопасный шлюз B2BUA и подключаем систему к контуру мониторинга инцидентов.

Когда модель Zero-Trust избыточна, а когда обязательна


Инженерный подход требует взвешенного сопоставления требований безопасности и накладных расходов на их реализацию.

Границы применимости: Быстрое прототипирование против боевого контура ERP

Быстрое прототипирование (Низкий риск)

Временные среды
  • Внутренние хакатоны и исследовательские эксперименты
  • Одноразовые промо-лендинги со статическим контентом
  • Первичное тестирование макетов интерфейса с пользователями
  • Изолированные тестовые стенды с синтетическими данными
  • Открытые порталы технической документации

Боевой контур ERP Zero-Trust (Промышленный стандарт)

Обязательный стандарт
  • Системы, обрабатывающие реальные клиентские платежи и счета
  • Базы данных с персональными данными (PII) и зарплатными ведомостями
  • Телекоммуникационная инфраструктура, подключенная к операторам связи
  • Склады с реальными товарно-материальными ценностями
  • Контуры, подлежащие аудиту, требованиям GDPR и стандартам ISO 27001

Анализ компромиссов

  1. Соотношение скорости запуска и эшелонированной обороны показывает, что детальное описание матриц доступа в ir.model.access.csv, разработка правил ir.rule и разграничение прав на полях требуют дисциплинированного проектирования на ранних этапах. Для временного двухдневного проекта эти затраты избыточны. Для системы, управляющей финансами и складом, отказ от этого уровня является прямой халатностью.
  2. Баланс между временем отклика и безопасностью подтверждает, что проверка прав на сервере занимает минимальное процессорное время. Благодаря тому, что Odoo 19 компилирует правила безопасности в нативные SQL-условия WHERE, выполняемые индексами PostgreSQL, задержка составляет менее 2 миллисекунд на запрос, полностью исключая атаки с повышением привилегий.
  3. Выбор между кажущимся удобством пользователей и требованиями регуляторов означает, что система без строгого логирования действий может выглядеть проще в повседневной работе. Но при возникновении внутренних разногласий или налоговой проверки отсутствие неизменяемого журнала аудита оставляет руководство компании без юридической доказательной базы.

Чеклист предстартовой проверки ERP для CISO и технических директоров


Перед подписанием акта ввода новой учётной системы в промышленную эксплуатацию служба безопасности обязана верифицировать следующие контрольные пункты:

  • 1. Проверьте сетевую изоляцию порта базы данных, убедившись, что PostgreSQL слушает исключительно 127.0.0.1 или закрытую подсеть VPC, а доступ к порту 5432 из внешней сети заблокирован.
  • 2. Включите регистрацию строго по приглашениям, установив параметр auth_signup.invitation_scope в значение b2b и отключив свободную регистрацию аккаунтов.
  • 3. Проверьте стойкое хэширование паролей через запрос к базе, чтобы убедиться, что пароли пользователей хранятся в виде хэшей PBKDF2-SHA512 с индивидуальной солью.
  • 4. Протестируйте защиту системных параметров, попробовав прочитать таблицу ir.config_parameter из-под учетной записи рядового сотрудника через JSON-RPC и убедившись, что запрос завершается ошибкой AccessError.
  • 5. Подтвердите наличие правил фильтрации строк, проверив активность правил ir.rule для ключевых моделей (sale.order, project.task, account.move), исключающих доступ к чужим документам.
  • 6. Проведите аудит белых списков на конфиденциальных полях, убедившись, что поля финансового лимита, себестоимости и ролей содержат явный атрибут groups="...".
  • 7. Убедитесь в изоляции телефонии через B2BUA, проверив, что софтфон общается с сервером B2BUA, а в настройках TURN используются эфемерные ключи RFC 5389.
  • 8. Проверьте контроль баланса двойной записи, убедившись, что база данных отменяет транзакции, в которых сумма дебета не равна сумме кредита.
  • 9. Верифицируйте блокировки от состояния гонки, проверив, что списание остатков на складе использует блокировки SELECT ... FOR UPDATE.
  • 10. Протестируйте неизменяемый журнал аудита, убедившись, что изменение административных групп пользователей фиксируется триггером в изолированной таблице аудита.

Вопросы по аудиту безопасности Odoo 19 и PostgreSQL


Odoo 19 использует движок ir.rule для перехвата запросов до их выполнения. Когда пользователь обращается к записям, ядро вычисляет активные правила доступа и автоматически добавляет их в виде условий WHERE прямо в тело формируемого SQL-запроса к PostgreSQL. Это гарантирует отсечение неавторизованных данных на уровне самой базы данных независимо от переданных из браузера параметров.

Класс portal.mixin обеспечивает безопасный доступ клиентов к своим документам (счетам, заказам) без ввода постоянного логина и пароля. Вместо порядковых номеров формируется случайный криптографический UUID токен access_token. Просмотр возможен только при предъявлении токена, который проверяется алгоритмом постоянного времени const_eq для предотвращения атак по времени.

Развёртывание Odoo 19 Community Edition на выделенной виртуальной машине обеспечивает полный контроль над сетевым периметром, ключами шифрования и настройками безопасности. Это исключает риски совместного использования ресурсов несколькими компаниями и гарантирует соблюдение требований суверенитета данных.

В Odoo 19 изменение атрибутов пользователя фильтруется через список SELF_WRITEABLE_FIELDS, разрешающий менять только безопасные параметры вроде языка или часового пояса. Изменение полей групп безопасности (groups_id) доступно только администраторам (base.group_system), что полностью блокирует атаки класса Mass Assignment (CWE-915).

Итоги и стратегия защиты корпоративного ядра


Информационная безопасность предприятия определяется её архитектурным фундаментом. Использование непроверенных интерфейсов и отказ от серверной модели проверки прав создаёт неприемлемые риски: утечки данных, кражу телефонного трафика и потерю финансового контроля.

Стандарт Zero Client-Side Trust в 10 пунктах обеспечивает надёжную защиту: изоляцию базы данных за сервером приложений, математический контроль двойной записи, использование временных криптографических токенов и строгую фильтрацию каждого запроса на базе Odoo 19 CE и PostgreSQL.

Для проведения независимого аудита безопасности перед внедрением ERP или проектирования защищённой инфраструктуры Odoo 19 обратитесь к архитектурной группе dlab.md.

Архитектурный и Security Due Diligence аудит dlab.md Engineering Advisory

Готовите стек ИИ и ERP к корпоративному аудиту безопасности?

Наши системные архитекторы проводят фиксированный 10-дневный Zero-Trust аудит архитектуры (OAuth 2.0 CAB, EU AI Act ст. 12/14, GDPR ст. 25, харденинг PostgreSQL и Odoo 19) с готовой дорожной картой устранения уязвимостей.