В дополнение к стандартной системе привилегий 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
или функций, содержащих SELECT
s, в выражениях политики. Однако имейте в виду, что такой доступ может создать условия гонки, которые могут привести к утечке информации, если не принять меры предосторожности. В качестве примера рассмотрим следующий дизайн таблицы:
-- 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 SHARE
in sub - SELECT
s in row. Однако для этого требуется предоставить UPDATE
привилегию на указанную таблицу (здесь users
) затронутым пользователям, что может быть нежелательно. (Но может быть применена другая политика безопасности строк, чтобы предотвратить фактическое использование ими этой привилегии, или SELECT
подпрограмма может быть встроена в функцию определения безопасности.) Кроме того, интенсивное одновременное использование блокировок общего доступа к строкам в таблице, на которую ссылаются, может создать проблему производительности. , особенно если его обновления происходят часто. Другое решение, практичное, если обновления таблицы, на которую ссылаются, происходят нечасто, состоит в том, чтобыACCESS EXCLUSIVE
блокировать ссылочную таблицу при ее обновлении, чтобы никакие параллельные транзакции не могли проверять старые значения строк. Или можно просто дождаться завершения всех одновременных транзакций после совершения обновления таблицы, на которую ссылаются, и перед внесением изменений, которые зависят от новой ситуации с безопасностью.
0 комментариев