Row-Level Security и безопасность данных

Рейтинг: 64.6% · 12 голосов
Подробный курс по PostgreSQL: SQL по демобазе, типы данных и JSONB, индексы, транзакции и MVCC, VACUUM, EXPLAIN и планировщик, роли и привилегии, репликация, бэкап, настройка под нагрузку и под 1С. Актуально на 2026.
Ответить
Аватара пользователя
Egor_DBA
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Row-Level Security и безопасность данных

Сообщение Egor_DBA »

Оглавление курса (47)
  1. Что такое PostgreSQL: история, философия, где применяется
  2. Урок 1. Что нового в PostgreSQL 15 и далее (15 -> 16 -> 17)
  3. Установка и первый запуск: Linux, Windows, Docker
  4. psql детально: метакоманды и работа в консоли
  5. Демобаза Авиаперевозки Postgres Pro: структура и развёртывание
  6. SELECT: проекция, фильтрация, сортировка
  7. Транзакции и ACID
  8. Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable
  9. Соединения таблиц (JOIN)
  10. Агрегация: GROUP BY, HAVING, GROUPING SETS
  11. Подзапросы и CTE (WITH), рекурсия
  12. Оконные функции
  13. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE
  14. Числовые и булевы типы, NULL и трёхзначная логика
  15. Строки, текст, дата и время, таймзоны
  16. Массивы, диапазоны, enum, составные типы
  17. JSON и JSONB: операторы и индексация
  18. DDL: CREATE TABLE, схемы, ALTER
  19. Ограничения целостности
  20. Представления и материализованные представления
  21. Последовательности, IDENTITY, генерируемые столбцы
  22. Индексы: B-tree и когда индекс не используется
  23. Типы индексов: Hash, GiST, SP-GiST, GIN, BRIN
  24. Продвинутые индексы: частичные, по выражению, покрывающие
  25. Обслуживание индексов и таблиц: bloat, REINDEX, CONCURRENTLY
  26. Полнотекстовый поиск
  27. Роли и пользователи
  28. Привилегии: GRANT/REVOKE
  29. Row-Level Security и безопасность данных (вы здесь)
  30. Подключение приложения: pg_hba.conf, SSL, пулы соединений
  31. Серверное программирование: функции и процедуры
  32. Триггеры и события
  33. Архитектура PostgreSQL: процессы и память
  34. MVCC: версии строк и видимость
  35. VACUUM, autovacuum, freeze и wraparound
  36. WAL, контрольные точки и долговечность
  37. Блокировки и взаимоблокировки
  38. Планировщик запросов
  39. EXPLAIN и EXPLAIN ANALYZE: чтение планов
  40. Конфигурация сервера: ключевые параметры
  41. Настройка под нагрузку и под 1С
  42. Параллельные запросы и партиционирование
  43. Резервное копирование и восстановление
  44. Репликация: потоковая и hot standby
  45. Логическая репликация и кластерные решения
  46. Внешние данные, расширения и сертификация
  47. Разбор планов запросов на explain.tensor.ru: глубокое чтение EXPLAIN ANALYZE
Урок 28. Row-Level Security и безопасность данных

До сих пор права в postgresql мы раздавали грубо: GRANT на таблицу открывает все её строки целиком. Но в жизни часто нужно тоньше - чтобы менеджер видел только своих клиентов, а арендатор сервиса - только свои заказы в общей таблице. Этот урок про Row-Level Security (RLS): механизм, который фильтрует строки автоматически, на уровне ядра, какой бы запрос ни прислал клиент. Заодно разберём шифрование чувствительных полей через pgcrypto, маскирование данных и в двух словах - что такое SE-PostgreSQL для мандатного доступа.

Изображение

Как это работает

Идея RLS простая: к каждому обращению к таблице сервер незаметно дописывает условие WHERE, которое вы задали заранее в виде политики. Пользователь пишет SELECT * FROM orders, а планировщик подставляет ваш фильтр, и человек физически не может вытащить чужие строки - даже подбором, даже хитрым запросом. Защита живёт в базе, а не в коде приложения, поэтому её нельзя обойти через прямое подключение.

Включается RLS в два шага. Сначала ALTER TABLE ... ENABLE ROW LEVEL SECURITY переводит таблицу в режим с проверкой. Важная деталь: сразу после включения и до создания политик действует принцип запрета по умолчанию - не подходящих под политику строк не видно, а если политик нет вообще, обычный пользователь не видит ничего. Это безопасное поведение: забыли описать правило - данные закрыты, а не открыты.

Сами правила создаются командой CREATE POLICY. У политики две независимые части. USING - это предикат видимости: к каким строкам разрешено читать и трогать (SELECT, UPDATE, DELETE). WITH CHECK - предикат записи: какие строки разрешено вставлять или каким делать после UPDATE. Их разделение нужно, чтобы нельзя было через INSERT или UPDATE записать строку, которую потом сам же не увидишь и не имеешь права создавать.

Две тонкости про обход. Владелец таблицы и роли с атрибутом BYPASSRLS политику игнорируют - это удобно для миграций, но опасно, если приложение ходит под владельцем. Чтобы правила действовали и на владельца, ставят ALTER TABLE ... FORCE ROW LEVEL SECURITY. Суперпользователь обходит RLS всегда.

Откуда политика берёт контекст текущего пользователя или арендатора? Из двух источников. Либо из current_user - имя роли подключения. Либо из параметра сессии, который приложение выставляет через set_config или SET, а политика читает функцией current_setting. Второй способ - основа мультиарендности одной таблицей: все клиенты лежат в одном orders, а tenant_id берётся из переменной сессии.

SQL и примеры

Покажем RLS на демобазе Авиаперевозки (схема bookings). Представим, что у нас несколько агентств, и каждое должно видеть только свои бронирования. Добавим колонку-владельца в копию таблицы, чтобы не ломать оригинал.

Код: Выделить всё

CREATE TABLE bookings.my_bookings AS
  SELECT b.*, 'agency_a'::text AS tenant FROM bookings.bookings b LIMIT 1000;
ALTER TABLE bookings.my_bookings ENABLE ROW LEVEL SECURITY;
Теперь политика. Арендатора возьмём из переменной сессии app.tenant. USING фильтрует чтение, WITH CHECK не даёт вставить чужую строку.

Код: Выделить всё

CREATE POLICY tenant_isolation ON bookings.my_bookings
  USING (tenant = current_setting('app.tenant', true))
  WITH CHECK (tenant = current_setting('app.tenant', true));
Второй аргумент true у current_setting означает не падать с ошибкой, если переменная не задана, а вернуть NULL - тогда не совпадёт ни одна строка, и это безопасно. Проверяем поведение под обычной ролью.

Код: Выделить всё

SET app.tenant = 'agency_a';
SELECT count(*) FROM bookings.my_bookings;  -- видны строки агентства A
SET app.tenant = 'agency_b';
SELECT count(*) FROM bookings.my_bookings;  -- 0 строк, чужое скрыто
Можно делать политики под конкретные действия и роли. Например, читать разрешим всем сотрудникам, а удалять - только своему арендатору.

Код: Выделить всё

CREATE POLICY read_all ON bookings.my_bookings
  FOR SELECT TO staff USING (true);
CREATE POLICY del_own ON bookings.my_bookings
  FOR DELETE TO staff USING (tenant = current_setting('app.tenant', true));
Когда на таблице несколько разрешающих (PERMISSIVE) политик, они объединяются через OR - достаточно одной подходящей. Если же политику пометить AS RESTRICTIVE, она добавляется через AND и сужает доступ. Так строят слоистые правила: одна задаёт базу, другая обязательное ограничение.

Теперь чувствительные поля. Хранить паспорт или телефон в открытом виде нельзя - шифруем расширением pgcrypto. Симметричное шифрование с ключом-фразой.

Код: Выделить всё

CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- зашифровать
SELECT pgp_sym_encrypt('1234 567890', 'secret_key');
-- расшифровать обратно
SELECT pgp_sym_decrypt(passport_enc, 'secret_key') FROM clients;
Для паролей шифрование не годится - нужен необратимый хеш с солью через crypt и gen_salt.

Код: Выделить всё

-- сохранить хеш пароля
INSERT INTO users(login, pass) VALUES ('ivan', crypt('p@ss', gen_salt('bf')));
-- проверить при входе
SELECT login FROM users WHERE login='ivan' AND pass = crypt('p@ss', pass);
Маскирование - это показ части данных вместо полного значения, обычно для отчётов и поддержки. В ядре postgresql динамического маскирования нет, его эмулируют представлением.

Код: Выделить всё

CREATE VIEW tickets_masked AS
  SELECT ticket_no,
         left(passenger_name,1) || '***' AS passenger_name,
         '***' || right(passenger_id,4) AS passenger_id
  FROM bookings.tickets;
Если нужен полноценный движок маскирования по ролям - смотрите внешнее расширение postgresql_anonymizer.

Частые грабли
  • Включили ENABLE ROW LEVEL SECURITY, но забыли создать политику - и удивляетесь, что таблица отдаёт ноль строк. Это не баг, это запрет по умолчанию.
  • Приложение ходит в базу под владельцем таблицы - RLS на него не действует без FORCE ROW LEVEL SECURITY. Заводите отдельную непривилегированную роль для приложения.
  • Описали только USING и думаете, что INSERT защищён. Нет: для вставки нужен WITH CHECK, иначе чужие строки спокойно запишутся.
  • Не указали второй аргумент true в current_setting - при отсутствии переменной сессии запрос падает с ошибкой вместо тихого пустого результата.
  • Предикат политики тяжёлый (подзапрос, функция без индекса) - он выполняется для каждой строки и роняет производительность. Проверяйте план через explain analyze.
  • Ключ для pgcrypto лежит в коде или в SQL-логах - тогда шифрование бессмысленно. Ключами должно управлять приложение или внешнее хранилище.
  • Маскирующее представление обходится, если у пользователя остался прямой доступ к базовой таблице. Отзывайте GRANT на исходную таблицу.
Мини-лаба
  • Создайте таблицу-копию из bookings.bookings с колонкой tenant и заполните двумя значениями арендаторов.
  • Включите на ней RLS и убедитесь под обычной ролью, что строк не видно вообще.
  • Создайте политику с USING и WITH CHECK по current_setting('app.tenant', true).
  • Через SET app.tenant переключайтесь между арендаторами и проверьте, что count меняется.
  • Попробуйте INSERT строки с чужим tenant - убедитесь, что WITH CHECK её отвергает.
  • Подключите pgcrypto и сохраните одно поле через pgp_sym_encrypt, затем расшифруйте.
  • Включите FORCE ROW LEVEL SECURITY и проверьте, что теперь и владелец таблицы подчиняется политике.
Контрольные вопросы
  • Чем отличается роль USING от WITH CHECK в политике и какие команды каждая затрагивает?
  • Почему сразу после ENABLE ROW LEVEL SECURITY таблица может вернуть ноль строк?
  • Кто обходит RLS по умолчанию и как заставить правила действовать на владельца таблицы?
  • Как объединяются несколько PERMISSIVE политик и чем от них отличается RESTRICTIVE?
  • Почему для паролей применяют crypt с gen_salt, а не pgp_sym_encrypt?
  • Как реализовать мультиарендность одной таблицей через переменную сессии?
👍3 ❤️3 🔥1 😄 🤔1
Аватара пользователя
Brendan0
Сообщения: 1
Зарегистрирован: 14 май 2026, 19:09

Re: Row-Level Security и безопасность данных

Сообщение Brendan0 »

А если у меня приложение коннектится под одной ролью на всех арендаторов, RLS вообще поможет? Или только через current_setting гонять tenant_id перед каждым запросом?
👍1 ❤️ 🔥1 😄 🤔2
Аватара пользователя
hiline
Сообщения: 1
Зарегистрирован: 29 май 2026, 19:30

Re: Row-Level Security и безопасность данных

Сообщение hiline »

Проверил на своей таблице: FORCE ROW LEVEL SECURITY реально спас, до него под владельцем база отдавала все строки несмотря на политику. Думал это баг полдня.
👍1 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Привилегии: GRANT/REVOKE
Следующая глава →
Подключение приложения: pg_hba.conf, SSL, пулы соединений

Все главы курса «PostgreSQL: от первого запроса до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «PostgreSQL: от первого запроса до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя