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

Когда для таблицы включена защита строк (с помощью ALTER TABLE ... ENABLE ROW LEVEL SECURITY ), весь нормальный доступ к таблице для выбора строк или изменения строк должен быть разрешен политикой безопасности строк. (Однако на владельца таблицы обычно не распространяются политики безопасности строк.) Если для таблицы не существует политики, используется политика запрета по умолчанию, означающая, что строки не видны и не могут быть изменены. Операции, которые применяются ко всей таблице, такие как TRUNCATEи REFERENCES, не подлежат безопасности строк.

Политики безопасности строк могут относиться к командам, ролям или и тем, и другим. Политику можно указать для применения к ALLкомандам или к SELECT, INSERT, UPDATEили DELETE. Одной политике может быть назначено несколько ролей, и применяются обычные правила членства и наследования ролей.

Чтобы указать, какие строки являются видимыми или изменяемыми в соответствии с политикой, требуется выражение, возвращающее логический результат. Это выражение будет оцениваться для каждой строки перед любыми условиями или функциями, поступающими из пользовательского запроса. (Единственными исключениями из этого правила являются leakproofфункции, которые гарантированно не пропускают информацию; оптимизатор может применить такие функции перед проверкой безопасности строк.) Строки, для которых выражение не возвращает значение.trueне будет обработано. Можно указать отдельные выражения, чтобы обеспечить независимый контроль над строками, которые видны, и строками, которые разрешено изменять. Выражения политики запускаются как часть запроса и с привилегиями пользователя, выполняющего запрос, хотя функции определения безопасности могут использоваться для доступа к данным, недоступным для вызывающего пользователя.

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

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

Политики создаются с помощью команды CREATE POLICY , изменяются с помощью команды ALTER POLICY и удаляются с помощью команды DROP POLICY . Чтобы включить или отключить защиту строк для данной таблицы, используйте команду ALTER TABLE .

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

Когда к заданному запросу применяется несколько политик, они объединяются с помощью либо OR(для разрешительных политик, которые используются по умолчанию), либо с помощью AND(для ограничительных политик). Это похоже на правило, согласно которому данная роль имеет привилегии всех ролей, членом которых она является. Разрешительные и ограничительные политики обсуждаются ниже.

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

CREATE TABLE accounts (manager text, company text, contact_email text);

ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;

CREATE POLICY account_managers ON accounts TO managers
    USING (manager = current_user);

Приведенная выше политика неявно предоставляет WITH CHECKпредложение, идентичное его USINGпредложению, так что ограничение применяется как к строкам, выбранным командой (поэтому менеджер не может SELECT, UPDATE, или DELETEсуществующие строки, принадлежащие другому менеджеру), так и к строкам, измененным командой (поэтому строки принадлежащие другому менеджеру, не могут быть созданы с помощью INSERTили UPDATE).

Если роль не указана или используется специальное имя PUBLICпользователя, политика применяется ко всем пользователям в системе. Чтобы разрешить всем пользователям доступ только к своей строке в usersтаблице, можно использовать простую политику:

CREATE POLICY user_policy ON users
    USING (user_name = current_user);

Это работает аналогично предыдущему примеру.

Чтобы использовать другую политику для строк, которые добавляются в таблицу, по сравнению с видимыми строками, можно объединить несколько политик. Эта пара политик позволит всем пользователям просматривать все строки в usersтаблице, но изменять только свои собственные:

CREATE POLICY user_sel_policy ON users
    FOR SELECT
    USING (true);
CREATE POLICY user_mod_policy ON users
    USING (user_name = current_user);

В SELECTкоманде эти две политики объединяются с помощью OR, в результате чего можно выбрать все строки. В других типах команд применяется только вторая политика, так что эффекты такие же, как и раньше.

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

Ниже приведен более крупный пример того, как эту функцию можно использовать в производственных средах. Таблица passwdэмулирует файл паролей Unix:

-- Simple passwd-file based example
CREATE TABLE passwd (
  user_name             text UNIQUE NOT NULL,
  pwhash                text,
  uid                   int  PRIMARY KEY,
  gid                   int  NOT NULL,
  real_name             text NOT NULL,
  home_phone            text,
  extra_info            text,
  home_dir              text NOT NULL,
  shell                 text NOT NULL
);

CREATE ROLE admin;  -- Administrator
CREATE ROLE bob;    -- Normal user
CREATE ROLE alice;  -- Normal user

-- Populate the table
INSERT INTO passwd VALUES
  ('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash');
INSERT INTO passwd VALUES
  ('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh');
INSERT INTO passwd VALUES
  ('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh');

-- Be sure to enable row-level security on the table
ALTER TABLE passwd ENABLE ROW LEVEL SECURITY;

-- Create policies
-- Administrator can see all rows and add any rows
CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true);
-- Normal users can view all rows
CREATE POLICY all_view ON passwd FOR SELECT USING (true);
-- Normal users can update their own records, but
-- limit which shells a normal user is allowed to set
CREATE POLICY user_mod ON passwd FOR UPDATE
  USING (current_user = user_name)
  WITH CHECK (
    current_user = user_name AND
    shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh')
  );

-- Allow admin all normal rights
GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin;
-- Users only get select access on public columns
GRANT SELECT
  (user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell)
  ON passwd TO public;
-- Allow users to update certain columns
GRANT UPDATE
  (pwhash, real_name, home_phone, extra_info, shell)
  ON passwd TO public;

Как и в случае с любыми параметрами безопасности, важно протестировать и убедиться, что система работает должным образом. На приведенном выше примере показано, что система разрешений работает правильно.

-- admin can view all rows and fields
postgres=> set role admin;
SET
postgres=> table passwd;
 user_name | pwhash | uid | gid | real_name |  home_phone  | extra_info | home_dir    |   shell
-----------+--------+-----+-----+-----------+--------------+------------+-------------+-----------
 admin     | xxx    |   0 |   0 | Admin     | 111-222-3333 |            | /root       | /bin/dash
 bob       | xxx    |   1 |   1 | Bob       | 123-456-7890 |            | /home/bob   | /bin/zsh
 alice     | xxx    |   2 |   1 | Alice     | 098-765-4321 |            | /home/alice | /bin/zsh
(3 rows)

-- Test what Alice is able to do
postgres=> set role alice;
SET
postgres=> table passwd;
ERROR:  permission denied for relation passwd
postgres=> select user_name,real_name,home_phone,extra_info,home_dir,shell from passwd;
 user_name | real_name |  home_phone  | extra_info | home_dir    |   shell
-----------+-----------+--------------+------------+-------------+-----------
 admin     | Admin     | 111-222-3333 |            | /root       | /bin/dash
 bob       | Bob       | 123-456-7890 |            | /home/bob   | /bin/zsh
 alice     | Alice     | 098-765-4321 |            | /home/alice | /bin/zsh
(3 rows)

postgres=> update passwd set user_name = 'joe';
ERROR:  permission denied for relation passwd
-- Alice is allowed to change her own real_name, but no others
postgres=> update passwd set real_name = 'Alice Doe';
UPDATE 1
postgres=> update passwd set real_name = 'John Doe' where user_name = 'admin';
UPDATE 0
postgres=> update passwd set shell = '/bin/xx';
ERROR:  new row violates WITH CHECK OPTION for "passwd"
postgres=> delete from passwd;
ERROR:  permission denied for relation passwd
postgres=> insert into passwd (user_name) values ('xxx');
ERROR:  permission denied for relation passwd
-- Alice can change her own password; RLS silently prevents updating other rows
postgres=> update passwd set pwhash = 'abc';
UPDATE 1

Все разработанные до сих пор политики были разрешающими политиками, что означает, что при применении нескольких политик они объединяются с помощью логического оператора « ИЛИ » . Хотя разрешающие политики могут быть построены так, чтобы разрешать доступ к строкам только в предусмотренных случаях, может быть проще объединить разрешающие политики с ограничительными политиками (которые должны проходить записи и которые объединяются с помощью логического оператора « И » ). Опираясь на приведенный выше пример, мы добавляем ограничительную политику, требующую от администратора подключения через локальный сокет Unix для доступа к записям passwdтаблицы:

CREATE POLICY admin_local_only ON passwd AS RESTRICTIVE TO admin
    USING (pg_catalog.inet_client_addr() IS NULL);

Затем мы видим, что администратор, подключающийся по сети, не увидит никаких записей из-за ограничительной политики:

=> SELECT current_user;
 current_user 
--------------
 admin
(1 row)

=> select inet_client_addr();
 inet_client_addr 
------------------
 127.0.0.1
(1 row)

=> TABLE passwd;
 user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | shell
-----------+--------+-----+-----+-----------+------------+------------+----------+-------
(0 rows)

=> UPDATE passwd set pwhash = NULL;
UPDATE 0

Проверки ссылочной целостности, такие как ограничения уникальности или первичных ключей и ссылки на внешние ключи, всегда обходят защиту строк, чтобы обеспечить целостность данных. Необходимо соблюдать осторожность при разработке схем и политик на уровне строк, чтобы избежать утечки информации по « скрытым каналам » через такие проверки ссылочной целостности.

В некоторых контекстах важно убедиться, что безопасность строк не применяется. Например, при резервном копировании может быть катастрофа, если защита строк автоматически приведет к тому, что некоторые строки будут исключены из резервной копии. В такой ситуации вы можете установить для параметра конфигурации row_securityoff значение . Это само по себе не обходит защиту строк; что он делает, так это выдает ошибку, если результаты любого запроса будут отфильтрованы политикой. Затем можно выяснить причину ошибки и устранить ее.

В приведенных выше примерах выражения политики учитывают только текущие значения в строке для доступа или обновления. Это самый простой и эффективный случай; когда это возможно, лучше проектировать приложения для защиты строк таким образом. Если для принятия решения о политике необходимо обратиться к другим строкам или другим таблицам, это можно сделать с помощью подпрограмм SELECTили функций, содержащих SELECTs, в выражениях политики. Однако имейте в виду, что такой доступ может создать условия гонки, которые могут привести к утечке информации, если не принять меры предосторожности. В качестве примера рассмотрим следующий дизайн таблицы:

-- definition of privilege groups
CREATE TABLE groups (group_id int PRIMARY KEY,
                     group_name text NOT NULL);

INSERT INTO groups VALUES
  (1, 'low'),
  (2, 'medium'),
  (5, 'high');

GRANT ALL ON groups TO alice;  -- alice is the administrator
GRANT SELECT ON groups TO public;

-- definition of users' privilege levels
CREATE TABLE users (user_name text PRIMARY KEY,
                    group_id int NOT NULL REFERENCES groups);

INSERT INTO users VALUES
  ('alice', 5),
  ('bob', 2),
  ('mallory', 2);

GRANT ALL ON users TO alice;
GRANT SELECT ON users TO public;

-- table holding the information to be protected
CREATE TABLE information (info text,
                          group_id int NOT NULL REFERENCES groups);

INSERT INTO information VALUES
  ('barely secret', 1),
  ('slightly secret', 2),
  ('very secret', 5);

ALTER TABLE information ENABLE ROW LEVEL SECURITY;

-- a row should be visible to/updatable by users whose security group_id is
-- greater than or equal to the row's group_id
CREATE POLICY fp_s ON information FOR SELECT
  USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));
CREATE POLICY fp_u ON information FOR UPDATE
  USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));

-- we rely only on RLS to protect the information table
GRANT ALL ON information TO public;

Теперь предположим, что она aliceхочет изменить « слегка секретную » информацию, но решает, что malloryей нельзя доверять новое содержимое этой строки, поэтому она делает:

BEGIN;
UPDATE users SET group_id = 1 WHERE user_name = 'mallory';
UPDATE information SET info = 'secret from mallory' WHERE group_id = 2;
COMMIT;

Это выглядит безопасно; нет окна, в котором malloryможно было бы увидеть строку « secret from mallory » . Однако здесь есть условие гонки. Если malloryодновременно делает, скажем,

SELECT * FROM information WHERE group_id = 2 FOR UPDATE;

и ее транзакция находится в READ COMMITTEDрежиме, она может увидеть « секрет от мэллори » . Это происходит, если ее транзакция достигает informationстроки сразу после того alice, как это делает . Он блокирует ожидание aliceфиксации транзакции, а затем извлекает обновленное содержимое строки благодаря FOR UPDATEпредложению. Однако он не извлекает обновленную строку для неявного SELECTиз users, потому что у этого подпункта SELECTне было FOR UPDATE; вместо этого usersстрока считывается со снимком, сделанным в начале запроса. Поэтому выражение политики проверяет старое значение malloryуровня привилегий и позволяет ей увидеть обновленную строку.

Есть несколько способов обойти эту проблему. Один простой ответ — использовать политики безопасности SELECT ... FOR SHAREin sub - SELECTs in row. Однако для этого требуется предоставить UPDATEпривилегию на указанную таблицу (здесь users) затронутым пользователям, что может быть нежелательно. (Но может быть применена другая политика безопасности строк, чтобы предотвратить фактическое использование ими этой привилегии, или SELECTподпрограмма может быть встроена в функцию определения безопасности.) Кроме того, интенсивное одновременное использование блокировок общего доступа к строкам в таблице, на которую ссылаются, может создать проблему производительности. , особенно если его обновления происходят часто. Другое решение, практичное, если обновления таблицы, на которую ссылаются, происходят нечасто, состоит в том, чтобыACCESS EXCLUSIVEблокировать ссылочную таблицу при ее обновлении, чтобы никакие параллельные транзакции не могли проверять старые значения строк. Или можно просто дождаться завершения всех одновременных транзакций после совершения обновления таблицы, на которую ссылаются, и перед внесением изменений, которые зависят от новой ситуации с безопасностью.