Политики защиты строк в PostgreSQL (RLS) необходимы чтобы разграничить доступ к разным строкам. Применяется это редко, но знать про это нужно.

Что такое политики защиты строк

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

Пишется специальное логическое выражение и вычисляется для каждой строки. Если такое выражение истинно, то мы видим строку, а в противном случае не видим. Такое выражение называют предикатом.

Один предикат применяется для существующих строк и используется командами:

  • SELECT
  • UPDATE
  • DELETE

По умолчанию, если предикат возвращает ложь, то никакой ошибки не будет. Просто делается вид, что этой строчки в таблице нет. Это поведение можно изменить сбросив параметр row_security.

Второй предикат применяется для новых строк и используется командами:

  • INSERT
  • UPDATE

При работе второго предиката, если он возвращает значение ложь, то появляется ошибка. Ошибка говорит нам, что у нас нет права доступа вставить или обновить определённую строку.

Эти предикаты работают в дополнение к привилегиям. Сначала проверяются ваши привилегии на объект, а затем еще и предикаты, если они у вас используются. По умолчанию политики защиты строк выключены.

Условия применения политик

Включить разграничение прав к строкам можно для таблицы. При включении указывается для каких ролей и операторов мы включаем эту политику.

Эти политики не применяются:

  • при проверки ограничений целостности (внешние ключи будут работать в любом случае);
  • для суперпользователя и ролей с атрибутом BYPASSRLS;
  • для владельца объекта (если не включить принудительно).

На одну таблицу можно повесить несколько политик.

По умолчанию в PostgreSQL если вы включили RLS (политика защиты строк) для таблицы то все запрещается. Дальше вы должны создать разрешительные политики.

Также существуют ограничительные политики дополнительно к разрешительным.

Практика

Создание предиката для существующих объектов

Подключимся к базе данных и сделаем таблицу в которую добавим две строки:

postgres@s-pg13:~$ psql
Timing is on.
psql (13.3)
Type "help" for help.

postgres@postgres=# CREATE TABLE users_depts(login text, department text);
CREATE TABLE
Time: 3,448 ms

postgres@postgres=# INSERT INTO users_depts VALUES ('alice', 'PR'), ('bob', 'Sales');
INSERT 0 2
Time: 0,863 ms

Создадим ещё таблицу «revenue«, в которой будет информация о доходах и расходах PR отдела и Sales отдела:

postgres@postgres=# CREATE TABLE revenue(department text, amount numeric(10,2));
CREATE TABLE
Time: 1,329 ms

postgres@postgres=# INSERT INTO revenue SELECT 'PR', -random()* 100.00 FROM generate_series(1,100000);
INSERT 0 100000
Time: 214,237 ms

postgres@postgres=# INSERT INTO revenue SELECT 'Sales', random()*1000.00 FROM generate_series(1,10000);
INSERT 0 10000
Time: 28,831 ms

PR дают небольшие расходы (100.00) но количество их большое (100000). А отдел продаж приносит большие доходы (1000.00) но количество их меньше (10000).

Теперь создадим предикат:

postgres@postgres=# CREATE POLICY departments ON revenue USING (department = (SELECT department FROM users_depts WHERE login = current_user));
CREATE POLICY
Time: 1,101 ms

Само логическое выражение это — department = (SELECT department FROM users_depts WHERE login = current_user). То есть название отдела (department из таблицы revenue) должно совпадать с именем пользователя из таблички users_depts (SELECT department FROM users_depts WHERE login = current_user).

И включим эту политику с помощью команды ALTER:

postgres@postgres=# ALTER TABLE revenue ENABLE ROW LEVEL SECURITY;
ALTER TABLE
Time: 0,558 ms

Далее нужно выдать привилегии Алисе и Бобу, которых мы делали раньше:

postgres@postgres=# GRANT SELECT ON users_depts, revenue TO alice;
GRANT
Time: 0,705 ms

postgres@postgres=# GRANT SELECT ON users_depts, revenue TO bob;
GRANT
Time: 0,611 ms

Суперпользователь видит все строчки:

postgres@postgres=# SELECT department, SUM(amount) FROM revenue GROUP BY department;
 department |     sum
------------+-------------
 PR         | -5007687.16
 Sales      |  4997577.95
(2 rows)
Time: 22,451 ms

Теперь проверим что увидят Алиса и Боб:

postgres@postgres=# \c - alice
You are now connected to database "postgres" as user "alice".
alice@postgres=> SELECT department, SUM(amount) FROM revenue GROUP BY department;
 department |     sum
------------+-------------
 PR         | -5007687.16
(1 row)
Time: 18,413 ms

alice@postgres=> \c - bob
You are now connected to database "postgres" as user "bob".
bob@postgres=> SELECT department, SUM(amount) FROM revenue GROUP BY department;
 department |    sum
------------+------------
 Sales      | 4997577.95
(1 row)
Time: 8,707 ms

Создание предикатов для новых объектов

Разрешим теперь Бобу добавлять строки в таблицу, но только для своего отдела и только в пределах 100 рублей:

  • первое требование уже выполнено, так как первый предикат работает и для существующих и для новых строк;
  • для второго создадим новую ограничительную политику (AS RESTRICTIVE):
bob@postgres=> \c - postgres
You are now connected to database "postgres" as user "postgres".

postgres@postgres=# CREATE POLICY amount ON revenue AS RESTRICTIVE USING (true) WITH CHECK (abs(amount) <= 100.00);
CREATE POLICY
Time: 1,397 ms

В команде выше два условия:

  • USING (true) — все существующие строки видны в любом случае;
  • WITH CHECK (abs(amount) <= 100.00) — новые строки должны быть не более 100.000;

И дадим Бобу еще привилегию на вставку в эту таблицу:

postgres@postgres=# GRANT INSERT ON revenue TO bob;
GRANT
Time: 0,520 ms

Проверим работу для Боба:

postgres@postgres=# \c - bob
You are now connected to database "postgres" as user "bob".

bob@postgres=> INSERT INTO revenue VALUES ('Sales', 42.00);
INSERT 0 1
Time: 1,732 ms

bob@postgres=> INSERT INTO revenue VALUES ('PR', 42.00);
ERROR:  new row violates row-level security policy for table "revenue"
Time: 0,427 ms

bob@postgres=> INSERT INTO revenue VALUES ('Sales', 1000.00);
ERROR:  new row violates row-level security policy "amount" for table "revenue"
Time: 0,279 ms

В выводе выше первая ошибка фиксируется первой политикой, а вторая второй (запретительной).


Сводка
Политики защиты строк (RLS) в PostgreSQL
Имя статьи
Политики защиты строк (RLS) в PostgreSQL
Описание
Политики защиты строк в PostgreSQL (RLS) необходимы чтобы разграничить доступ к разным строкам. Применяется это редко, но знать про это нужно

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *