Настройка ограничений прав доступа на уровне записей и полей базы данных

Прокачай безопасность: настрой права на уровне записей и полей за 5 минут

Бесплатная консультация юриста

+7 (800) 301-07-30

Реклама jurik.ru

В современных информационных системах защита данных – одна из ключевых задач․ Часто требуется ограничить доступ не только к целой базе, но и к отдельным записям (строкам) и полям (столбцам) в зависимости от роли пользователя, его отделения, географии и прочих атрибутов․ В этой статье мы подробно разберём, как реализовать такие ограничения в популярных СУБД, какие подходы существуют на уровне СУБД и на уровне приложения, а также рассмотрим лучшие практики и типичные ошибки․


Содержание

  1. Основные концепции
  2. PostgreSQL: Row Level Security (RLS)
  3. Microsoft SQL Server: политики безопасности и динамические фильтры
  4. Oracle: VPD (Virtual Private Database) и колонные политики
  5. MySQL / MariaDB: обходные пути и плагины
  6. Контроль доступа на уровне приложения
  7. Лучшие практики
  8. Типичные подводные камни

Традиционный подход к контролю доступа в СУБД подразумевает предоставление пользователю прав на уровне базы или схемы (например, SELECT, INSERT, UPDATE, DELETE)․ При росте количества пользователей и усложнении бизнес‑логики такой «грубый» контроль становится недостаточным․ Примеров, когда нужен более тонкий гранулированный доступ, множество:

  • Сотрудники разных отделов могут видеть только свои клиентские заявки․
  • Менеджеры могут редактировать только те поля, которые относятся к их компетенции (например, только статус сделки, но не финансовую часть)․
  • Регулятивные требования (GDPR, HIPAA) требуют скрывать персональные данные от большинства сотрудников․
  • Мультиарендные (multitenant) приложения, где каждый клиент (арендатор) видит только свои данные․

Для решения этих задач применяются два основных механизма:

  1. Ограничения уровня записей (Row‑Level Security, RLS) – фильтрация строк в зависимости от контекста пользователя․
  2. Ограничения уровня полей (Column‑Level Security, CLS) – скрытие или маскирование отдельных столбцов․

Основные концепции

Контекст безопасности

Для применения RLS/CLS необходимо, чтобы СУБД «знала», кто сейчас выполняет запрос․ Это достигается через:

  • Текущий пользователь (login) – CURRENT_USER, SESSION_USER․
  • Сессионные переменные (например, SET ROLE, SET SESSION в PostgreSQL)․
  • Контекст выполнения (SQL Server – SESSION_CONTEXT, Oracle – SYS_CONTEXT)․

Политики доступа

Политика – набор правил, определяющих, какие строки/столбцы доступны пользователю․ В большинстве СУБД политики реализуются в виде:

  • Виртуальных представлений (VIEW) с предикатами․
  • Триггеров/фильтров, которые автоматически добавляются к запросу․
  • Плагинов/расширений, которые перехватывают запросы․

Модели реализации

Модель Плюсы Минусы
Встроенные возможности СУБД (RLS, VPD) Прозрачность, минимальный оверхед, централизованное управление․ Не во всех СУБД поддерживается, иногда ограничена только строками․
Вьюхи + функции Работает в любой СУБД, гибко настраивается․ Требует изменения кода запросов, может ухудшить производительность․
Плагины/расширения Максимальная гибкость, могут реализовать маскирование столбцов․ Сложнее в поддержке, иногда требует перезапуска сервера․

PostgreSQL: Row Level Security (RLS)

Начиная с версии 9․5 PostgreSQL поддерживает встроенный механизм Row Level Security․ Он позволяет задавать политику доступа к таблице, которая будет автоматически применяться к каждому запросу․

Включение RLS для таблицы

-- Предположим, есть таблица orders
CREATE TABLE orders (
 id SERIAL PRIMARY KEY,
 client_id INT NOT NULL,
 amount NUMERIC(10,2),
 status TEXT,
 created_at TIMESTAMP DEFAULT now
);
-- Включаем RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

Создание политики

Допустим, каждый пользователь имеет роль sales_rep и может видеть только свои заказы (client_id = текущий пользователь)․

-- В качестве примера будем хранить id текущего пользователя в переменной
-- PostgreSQL позволяет задавать пользовательские переменные через set_config
-- (или использовать pg_catalog․current_user, если логины совпадают с client_id)
CREATE POLICY sales_rep_policy ON orders
 USING (client_id = current_setting('app․current_user_id')::int);

Установка контекста в приложении

-- При каждом соединении приложение должно задать переменную:
SELECT set_config('app․current_user_id', '42', true);
-- Теперь любой запрос к orders будет отфильтрован:
SELECT * FROM orders; -- вернёт только строки, где client_id = 42

Ограничения уровня полей

PostgreSQL сам по себе не предоставляет CLS, но можно реализовать маскирование через VIEW или policy using + SELECT ․․․ EXCLUDE:

-- Создаём представление, скрывающее колонку amount для роли analyst
CREATE VIEW orders_public AS
SELECT
 id,
 client_id,
 CASE
 WHEN current_setting('app․role') = 'analyst' THEN NULL
 ELSE amount
 END AS amount,
 status,
 created_at
FROM orders;

Пример полной конфигурации

-- 1․ Включаем RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- 2․ Политика для менеджеров (видят все)
CREATE POLICY manager_policy ON orders
 FOR SELECT USING (true) WITH CHECK (true);
-- 3․ Политика для продавцов (видят свои)
CREATE POLICY sales_rep_policy ON orders
 FOR SELECT USING (client_id = current_setting('app․current_user_id')::int)
 WITH CHECK (client_id = current_setting('app․current_user_id')::int);

-- 4․ Привязываем роли
GRANT SELECT ON orders TO sales_rep, manager;
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

После этого пользователи в роли sales_rep увидят только свои записи, а менеджеры – всё․


Microsoft SQL Server: политики безопасности и динамические фильтры

SQL Server реализует RLS начиная с версии 2016․ Основные компоненты:

  • Фильтрующая функция (inline table‑valued function) – определяет предикат доступа․
  • Политика безопасности (security policy) – связывает функцию с таблицей․

Пример функции фильтрации

CREATE SCHEMA Security;
GO

CREATE FUNCTION Security․fn_sales_rep_filter(@ClientId INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_securitypredicate_result
WHERE @ClientId = CAST(SESSION_CONTEXT(N'UserId') AS INT);
GO
CREATE SECURITY POLICY Security․SalesRepPolicy
ADD FILTER PREDICATE Security․fn_sales_rep_filter(ClientId) ON dbo․Orders
WITH (STATE = ON);
GO

Установка контекста из приложения

-- В ․NET:
using (var cmd = new SqlCommand("EXEC sp_set_session_context @key=N'UserId', @value=@uid", conn))
{
 cmd․Parameters․AddWithValue("@uid", userId);
 cmd․ExecuteNonQuery;
}

SQL Server поддерживает Dynamic Data Masking (DDM) – простой способ скрыть значения столбца․

ALTER TABLE dbo․Orders ALTER COLUMN Amount ADD MASKED WITH (FUNCTION = 'default');
-- Для роли, которой нужно видеть реальное значение, отключаем маскирование:
GRANT UNMASK TO manager_role;

Комбинация RLS и DDM

Можно одновременно использовать RLS (для строк) и DDM (для столбцов), получив гибкую схему доступа․


Oracle: VPD (Virtual Private Database) и колонные политики

Oracle реализует контроль доступа на уровне записей через Virtual Private Database (VPD) (пакет DBMS_RLS)․ Для колонок используется Data Redaction (начиная с 12c)․

Пример политики VPD

-- Предположим, таблица ORDERS
CREATE TABLE orders (
 id NUMBER PRIMARY KEY,
 client_id NUMBER,
 amount NUMBER(10,2),
 status VARCHAR2(20),
 created_at DATE DEFAULT SYSDATE
);
-- Функция, возвращающая предикат
CREATE OR REPLACE FUNCTION security_pkg․sales_rep_predicate (
 schema_name VARCHAR2,
 table_name VARCHAR2
) RETURN VARCHAR2 IS
 v_user_id NUMBER;
BEGIN
 SELECT user_id INTO v_user_id FROM app_sessions WHERE session_id = SYS_CONTEXT('USERENV','SESSIONID');
 RETURN 'client_id = ' || v_user_id;
END;
/

Привязка политики

BEGIN
 DBMS_RLS․ADD_POLICY(
 object_schema => 'HR',
 object_name => 'ORDERS',
 policy_name => 'sales_rep_policy',
 function_schema => 'SECURITY_PKG',
 policy_function => 'sales_rep_predicate',
 statement_types => 'SELECT, INSERT, UPDATE, DELETE',
 update_check => TRUE
 );
END;
/

Data Redaction (маскирование колонок)

BEGIN
 DBMS_REDACT․ADD_POLICY(
 object_schema => 'HR',
 object_name => 'ORDERS',
 column_name => 'AMOUNT',
 policy_name => 'mask_amount',
 expression => 'CASE WHEN SYS_CONTEXT(''USERENV'', ''SESSION_USER'') = ''ANALYST'' THEN NULL ELSE amount END',
 policy_type => DBMS_REDACT․FULL
 );
END;
/

Пользователи с ролью ANALYST будут получать NULL вместо реального значения AMOUNT․


MySQL / MariaDB: обходные пути и плагины

В отличие от PostgreSQL и SQL Server, MySQL до версии 8․0 не имел встроенного RLS․ Возможные решения:

  1. Создание вьюх с предикатом, где в условие включается переменная сессии․
  2. Использование плагина audit_log в комбинации с триггерами․
  3. Перенос логики контроля доступа в слой приложения․

Вьюха со сессионной переменной

-- При соединении задаём переменную:
SET @current_user_id = 42;

-- Вьюха, которая фильтрует строки:
CREATE VIEW orders_view AS
SELECT *
FROM orders
WHERE client_id = @current_user_id;

Недостаток: пользователь может изменить значение переменной, поэтому важно ограничить права на её изменение (GRANT EXECUTE ON ․․․)․

Плагин ProxySQL (для мультиарендных систем)

ProxySQL может маршрутизировать запросы к разным базам/схемам в зависимости от пользователя, тем самым реализуя «логический» RLS․

MariaDB Enterprise – Row Security Plugin

Начиная с MariaDB 10․5 существует коммерческий плагин mariadb-row-security, который реализует RLS аналогично PostgreSQL․


Контроль доступа на уровне приложения

Если СУБД не поддерживает нужные механизмы, часто используют подход «контроля доступа в коде»․

Преимущества

  • Полный контроль над бизнес‑логикой․
  • Возможность использовать любые условия (например, комбинацию ролей, географии, времени)․

Недостатки

  • Повышенный риск ошибок (забытый фильтр в одном из запросов)․
  • Труднее поддерживать единый набор правил при большом количестве микросервисов․

Типичный шаблон

// Псевдокод на Node․js (Express + Sequelize)
app․get('/orders', async (req, res) => {
 const user = req․user; // уже аутентифицирован
 const where = {};

 if (user․role === 'sales_rep') {
 where․clientId = user․id;
 } else if (user․role === 'manager') {
 // ничего не ограничиваем
 } else {
 return res․status(403)․send('Access denied');
 }

 const orders = await Order․findAll({ where });
 res․json(orders);
});

Рекомендация

Если используете приложение‑уровневый контроль, обязательно внедрите единый сервис авторизации (OAuth2, OpenID Connect) и храните политики доступа в центральном хранилище (например, в OPA – Open Policy Agent)․


Лучшие практики

  • Принцип «наименьших привилегий» – предоставляйте пользователям только те права, которые им действительно нужны․
  • Сохраняйте однородность политики: если RLS реализовано в СУБД, не дублируйте её в приложении․
  • Используйте именованные роли (sales_rep, manager, analyst) вместо прямых пользовательских логинов․
  • Регулярно тестируйте политики с помощью unit‑тестов и сканеров безопасности․
  • Ведите журналы доступа (audit) – в PostgreSQL это pg_audit, в SQL Server – SQL Server Audit, в Oracle – AUDIT․
  • При работе с конфиденциальными данными применяйте шифрование в покое и маскирование данных (Data Redaction, Dynamic Data Masking)․
  • Для мультиарендных приложений рекомендуется использовать отдельные схемы или separate databases, а не только RLS․
  • Обновляйте СУБД до последних патчей – в новых версиях часто появляются улучшения RLS/CLS․

Типичные подводные камни

Проблема Причина Как избежать
Политика не применяется к UPDATE/DELETE По умолчанию RLS может быть включён только для SELECT․ Укажите FOR UPDATE и FOR DELETE в политике․
Пользователь обходит ограничения через SUPERUSER Суперпользователи игнорируют RLS; Ограничьте количество суперпользователей; используйте роли без прав SUPERUSER․
Маскирование колонок нарушает агрегатные функции Masked значения могут быть NULL, что влияет на SUM, AVG․ Определите отдельные «не замаскированные» представления для аналитики․
Снижение производительности из‑за сложных предикатов Фильтры RLS могут препятствовать использованию индексов․ Создавайте покрывающие индексы по столбцам, участвующим в предикате․
Неочевидные конфликты между политиками Несколько политик могут «перекрываться» и давать неожиданные результаты․ Тщательно проверяйте порядок применения политик, используйте USING и WITH CHECK правильно․

Ограничения прав доступа на уровне записей и полей позволяют построить безопасные, гибкие и соответствующие нормативным требованиям информационные системы․ Современные СУБД предоставляют встроенные механизмы (PostgreSQL RLS, SQL Server security policies, Oracle VPD, Data Redaction) и позволяют реализовать гибридные решения в комбинации с бизнес‑логикой приложения․

Ключевые выводы:

  1. Выбирайте встроенные возможности СУБД там, где они доступны – это проще в поддержке и быстрее в исполнении․
  2. Обеспечьте единую точку управления контекстом пользователя (session variables, JWT, OPA)․
  3. Не забывайте о маскировании колонок и шифровании данных при работе с конфиденциальной информацией․
  4. Регулярно тестируйте и аудируйте политики, чтобы избежать «проколов» в безопасности․

Следуя этим рекомендациям, вы сможете построить надёжную систему контроля доступа, которая будет масштабироваться вместе с ростом вашего бизнеса․