03 Занятие 03: Управление доступом / Доступ на уровне строк
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dbs_03_access_rows.html Archive: DBS-handouts-16.zip Страниц PDF: 32
Page 1: Управление доступом / Доступ на уровне строк
Авторские права
© Postgres Professional, 2017–2026 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Алексей Береснев Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Задача доступа на уровне строк
- Реализация с помощью функций
- Реализация с помощью представлений
- Политики защиты строк
Page 3: Доступ на уровне строк
Управление доступом на основе ролей
- разграничение на уровне отношений (столбцов)
- роли и привилегии, базовый механизм
- Управление доступом на уровне строк
- разграничение на уровне отдельных строк отношений
- требуется доверенный контекст
- различные способы реализации
Комментарии преподавателя
Роли и привилегии реализуют ролевую модель доступа (Role-Based Access Control, RBAC), базовый механизм управления доступом PostgreSQL. Эта модель регламентирует доступ к отношениям (таблицам, представлениям), возможно к отдельным их столбцам.
Более детальное разграничение информации на уровне отдельных строк как правило реализуется в приложениях, но может возникнуть потребность ограничить доступ и на уровне базы данных. Такое разграничение требует учета детальных условий (Fine-Grained Access Control, FGAC).
Критерии доступа должны быть основаны на информации, которой можно доверять. Очевидно, что нельзя полагаться ни на какие данные, передаваемые самим пользователем (параметры функций, условия в запросах и т. п.), поскольку злоумышленник может их подменить. По сути, единственное, чему можно доверять — это имя пользователя, поскольку СУБД проводит его проверку (аутентификацию). Если же аутентификация клиентов выполняется приложением, задача осложняется (см. задание Практики+).
Существуют разные подходы к решению задачи, основные из которых рассмотрены дальше в этой теме.
Page 4: Функции
Подход к решению
- Характеристика безопасности
Page 5: Подход к решению
{#source-figure-005 caption=“Source slide page 5: Подход к решению”}
Табличные функции для чтения данных
- Обычные функции или процедуры для изменения данных
Комментарии преподавателя
Одно из возможных решений состоит в предоставлении пользователю API, реализованного с помощью функций. Для чтения данных применяется табличная функция (возвращающая множество строк). К такой функции можно обращаться в предложении FROM как к таблице, а при необходимости можно и передавать параметры (которые функция может использовать, в том числе, для фильтрации данных).
https://postgrespro.ru/docs/postgresql/16/xfunc-sql#XFUNC-SQL-FUNCTIO NS-RETURNING-SET
https://postgrespro.ru/docs/postgresql/16/xfunc-sql#XFUNC-SQL-FUNCTIO NS-RETURNING-TABLE
Для изменения данных можно использовать обычные функции; перед собственно изменением в них можно выполнить все необходимые проверки.
Привилегий на базовую таблицу пользователь иметь не должен, ему необходима только привилегия EXECUTE на интерфейсные функции (такая привилегия есть у псевдороли PUBLIC по умолчанию).
Page 6: Функции
Создадим базу данных:
Для демонстрации нам потребуются традиционные роли:
Алиса будет работать в одном сеансе, Боб — в другом.
Таблица содержит информацию о доходах и расходах.
Заполним таблицу произвольными данными (Алиса зарабатывает, Боб тратит).
Задача состоит в том, чтобы Алиса видела только свои операции, а Боб — только свои. Создадим функцию, возвращающую множество строк. Запрос фильтрует данные, оставляя только строки, соответствующие пользователю сеанса:
Алиса пробует воспользоваться функцией:
Вызвать функцию можно, но при ее выполнении возникает ошибка, поскольку у Алисы нет права читать таблицу. Давать Алисе такое право, конечно, нельзя, поскольку тогда она сможет увидеть все строки.
Page 7: Права для подпрограмм
SECURITY INVOKER (по умолчанию)
- выполняется с правами вызывающего
- тело функции может подставляться в запрос
- SECURITY DEFINER
- выполняется с правами владельца
- тело функции никогда не подставляется в запрос
Комментарии преподавателя
Как мы уже знаем, для подпрограмм (функций и процедур) есть единственная привилегия EXECUTE, разрешающая выполнение этой подпрограммы.
При этом важно, от имени какого пользователя будет выполняться подпрограмма. Если подпрограмма объявлена как SECURITY INVOKER (по умолчанию), она выполняется с правами вызывающего пользователя. В этом случае операторы внутри подпрограммы смогут обращаться только к тем объектам, к которым у вызывающего пользователя есть доступ.
Если же указать фразу SECURITY DEFINER, подпрограмма работает с правами ее владельца. Так можно позволить другим пользователям выполнять определенные действия над объектами, к которым у них нет непосредственного доступа.
Тонкий момент состоит в том, что при определенных условиях тело SQL-функции с характеристикой безопасности SECURITY INVOKER может подставляться в основной запрос (рассматривается подробно в курсах QPT и DEV2) — обычно это хорошо, поскольку планировщик получает больше свободы и может найти более оптимальный план. Но тело функции с характеристикой SECURITY DEFINER никогда не подставляется. Таким образом, сначала функция полностью вычисляется, и только затем к полученному результату применяются дополнительные условия. Это может быть менее эффективно.
https://postgrespro.ru/docs/postgresql/16/sql-createfunction#SQL-CREATEF UNCTION-SECURITY
Page 8: Характеристики безопасности
{#source-figure-008 caption=“Source slide page 8: Характеристики безопасности”}
Поменяем характеристику безопасности функции:
Обратите внимание, что в условии мы использовали функцию session_user, а не current_user. Поскольку функция с характеристикой SECURITY DEFINER выполняется от имени владельца, то и current_user в ней принимает значение имени роли-владельца. Теперь Алиса видит свои строки, а Боб — свои, как и задумано:
Изменение данных
Для примера создадим функцию для вставки строки:
У Алисы все работает:
Но Боб решил посмотреть, получится ли у него вставить строку от имени Алисы:
Строка добавилась, но Боб ее не видит:
Page 9: Page 9
Зато Алиса удивилась:
Восстановим справедливость и решим проблему тем, что уберем первый параметр функции. Теперь Боб в принципе не сможет передать в функцию неправильное значение. В более сложном случае мы могли бы добавить необходимые проверки в саму функцию.
Page 10: Представления
Подход к решению
- Барьер безопасности и герметичные функции
Page 11: Подход к решению
{#source-figure-011 caption=“Source slide page 11: Подход к решению”}
Представления для чтения
- Обновляемые представления или триггеры для изменения
Комментарии преподавателя
Другое решение состоит в том, чтобы предоставить пользователю доступ к представлению, фильтрующему данные базовой таблицы, но не к самой таблице.
В простом случае, когда представление строится на одной таблице, не использует группировку и т. д., представление будет обновляемым и для него будут работать команды INSERT, UPDATE, DELETE, MERGE. В более сложном случае для обновления придется создать триггеры INSTEAD OF (триггеры подробно обсуждаются в одноименной теме курса DEV1).
https://postgrespro.ru/docs/postgresql/16/sql-createview
Page 12: Представления
{#source-figure-012 caption=“Source slide page 12: Представления”}
Создадим представление с тем же условием фильтрации, что и раньше в функции:
Предоставим Алисе и Бобу право читать данные из представления:
Все работает, как задумано:
Обратите внимание, что представление работает как функция SECURITY DEFINER: права на базовые таблицы не требуются. Если требуется аналог функции SECURITY INVOKER, нужно установить соответствующий параметр представления:
Теперь представление требует привилегии на базовые таблицы. Вернем поведение по умолчанию:
Боб подумал, что может использовать в условии WHERE функцию, выводящую на терминал все строки, которые попадают ей на вход. Если при выполнении запроса эта функция будет вызываться для каждой строки таблицы, он сможет увидеть секретные сведения. У Боба нет привилегий ни на одну схему, но через псевдороль PUBLIC он получил на базу данных привилегию TEMPORARY, позволяющую создавать объекты во временной схеме.
Page 13: Page 13
{#source-figure-013 caption=“Source slide page 13: Page 13”}
Не получилось, но Боб не отчаивается и смотрит план выполнения запроса:
Очевидно, условие на пользователя сработало раньше его функции. Если бы он мог убедить планировщик в том, что функцию надо выполнить первой… и такой способ действительно есть: можно задать функции очень низкую стоимость (это подробно рассматривается в теме “Функции” курса QPT):
Вуаля! Боб увидел строки, которые от него скрывают.
Page 14: Барьер безопасности
Защита от потенциально опасных (негерметичных) функций
- не содержит
- негерметичных
- SELECT …
- FROM view
- WHERE условие1 AND условие2
- функций
- барьер
- безопасности
- SELECT …
- FROM (
- SELECT … FROM … WHERE фильтр AND условие1 AND условие2
- SELECT …
- FROM …
- WHERE фильтр
- AND условие2
- )
- WHERE условие1
Комментарии преподавателя
Как мы только что видели, фильтрация строк в представлении ненадежна, если пользователь может создавать функции. Проблема в том, что представление прозрачно для планировщика: по сути, текст представления подставляется в запрос пользователя и получившийся запрос оптимизируется целиком. Поэтому дополнительное условие может быть вычислено раньше, чем условие из представления.
С функциями такой проблемы не возникает, они непрозрачны: сначала вычисляется результат, потом к получившемуся набору строк применяются дополнительные условия. Исключение составляет подстановка тела SQL-функций в запрос (inlining), но для функций с характеристикой SECURITY DEFINER подстановка никогда не выполняется.
Для борьбы с потенциально опасными функциями представление надо объявить с барьером безопасности. Барьер гарантирует безопасный порядок вычислений: условия самого представления будут применены до любых условий, добавленных пользователем. При этом через барьер проникают условия, содержащие простые вычисления, функции, не получающие данные из представления, а также функции, которые специально помечены как герметичные (через которые секретная информация гарантированно не утечет). Это дает больше свободы планировщику и увеличивает шансы найти оптимальный план выполнения.
https://postgrespro.ru/docs/postgresql/16/sql-createview
https://postgrespro.ru/docs/postgresql/16/sql-createfunction
Page 15: Барьер безопасности
{#source-figure-015 caption=“Source slide page 15: Барьер безопасности”}
Включим барьер безопасности:
Теперь уловка Боба не работает:
В плане запроса видно, что сначала вычисляется условие из представления, а затем уже функция Боба:
Боб хотел бы разрешить своей функции преодолевать барьер, пометив ее как герметичную, но у него недостаточно прав:
Пометить функцию как герметичную может только суперпользователь, это действие недоступно даже ее владельцу.
Изменение данных
Наше представление обновляемое. Проверим, например, как работает вставка:
У Алисы все работает. Но Боб снова пытается вставить чужую строку, и у него снова получается:
Page 16: Page 16
{#source-figure-016 caption=“Source slide page 16: Page 16”}
Можно пойти тем же путем, что и в подходе с функциями: разрешить вставку только для столбца amount.
Теперь Боб не может указать пользователя:
Но с указанием только суммы вставка работает:
Для обновляемых представлений есть и другой способ. Можно запретить любое изменение данных, результат которого пользователь не сможет увидеть в представлении. Вернем привилегии на представление и зададим его параметр:
Боб проверяет и терпит неудачу:
Page 17: Политики защиты строк
Подход к решению
- Условия применения политик
- Комбинирование политик
Page 18: Подход к решению
{#source-figure-018 caption=“Source slide page 18: Подход к решению”}
Доступ к базовой таблице со включенной защитой строк
Комментарии преподавателя
Третий подход заключается в использовании специально созданного механизма — политик защиты строк (row-level security, RLS). В этом случае пользователю предоставляется доступ к самой таблице, а проверка условий, заданных в политике, происходит автоматически.
По сути, политика работает так же, как представление с барьером безопасности: гарантируется, что дополнительные условия на выборку из таблицы будут вычислены после того, как будут отфильтрованы строки, не удовлетворяющие политике (за исключением безопасных выражений с герметичными функциями). Как и в представлении, условия, описанные в политике, вычисляются с правами вызывающего.
Однако то, что у пользователя есть доступ к самой таблице, добавляет определенные нюансы.
https://postgrespro.ru/docs/postgresql/16/ddl-rowsecurity
Page 19: Условия применения
Политика применяется
- к таблице, для которой включена защита
- для указанных ролей и для указанных операторов
- (SELECT, INSERT, UPDATE, DELETE)
- Политика не применяется
- при проверке ограничений целостности
- для суперпользователей и ролей с атрибутом BYPASSRLS
- для владельца (если не включить принудительно)
- Применение приводит к ошибке
- при создании логической копии утилитой pg_dump
- row_security = off
Комментарии преподавателя
Чтобы политики защиты строк начали работать, нужно явно включить этот механизм для каждой таблицы.
При создании политики можно также задать, для каких ролей она будет работать (по умолчанию — для всех) и для каких операторов (по умолчанию — также для всех).
Политики не применяются при проверке ограничений целостности — независимо от настроенных политик СУБД гарантирует целостность данных.
Политики не применяются для суперпользователей (для них, как обычно, никакие проверки безопасности не выполняются) и для ролей с атрибутом BYPASSRLS.
Для владельца таблицы политики не работают по умолчанию, но специальной командой можно включить защиту и для владельца.
При выполнении логической резервной копии утилита pg_dump устанавливает параметр row_security в значение off. Это не отключает политику защиты строк, но фиксирует ошибку, если хотя бы одна строка была отфильтрована. Таким образом гарантируется, что созданная копия будет полной.
Page 20: Политики защиты строк
Определим политику с тем же условием, что раньше, и включим защиту строк на таблице:
Политики, созданные для таблицы, показывают команды psql \d (описание объекта) и \dp (описание привилегий), например:
Эту информацию можно получить и из представления pg_policies системного каталога. Теперь можно выдать Алисе и Бобу привилегии на таблицу:
Проверяем:
Изменение данных
Наша политика работает так же, как представление с параметром check_option — не допускает добавления строк, не удовлетворяющих условию.
У Алисы все работает:
Page 21: Page 21
{#source-figure-021 caption=“Source slide page 21: Page 21”}
Боб пробует свои наработки, но безуспешно:
Для новых строк можно задать другие условия в предложении WITH CHECK. Например, создадим аналог представления с выключенным параметром check_option, допускающий вставку любых строк.
Теперь Боб может развернуться:
Обратите внимание, что команда COPY FROM не работает при включенной защите строк:
Page 22: Комбинирование политик
Разрешительные политики
- видимость должна предоставить хотя бы одна разрешительная политика
- если не определена ни одна политика, строка не видима
- Ограничительные политики
- видимость должны предоставить все ограничительные политики,
- если они определены
Комментарии преподавателя
На одной таблице можно определить несколько политик. В этом случае будут учитываться условия всех политик.
По умолчанию создаются разрешительные (permissive) политики. Чтобы строка была доступна, достаточно, чтобы хотя бы одно из условий таких политик оказалось истинным.
Но если на таблице включена защита строк, и при этом не определено ни одной разрешительной политики, не будет доступна ни одна строка.
Дополнительно можно создать и ограничительные (restrictive) политики. Если они заданы, то все их предикаты должны быть истинны.
Иными словами, если определены только разрешительные политики с условиями P1,…, PN, то для каждой строки вычисляется выражение
P1 OR … OR PN.
А если к тому же определены ограничительные политики с предикатами R1,…, RM, то для каждой строки будет вычисляться выражение (P1 OR … OR PN) AND R1 AND … AND RM.
То есть чтобы строка была видна, доступ к ней должны предоставить хотя бы одна разрешительная и все ограничительные политики.
Page 23: Комбинирование политик
{#source-figure-023 caption=“Source slide page 23: Комбинирование политик”}
Сначала убедимся, что при включенной защите строк отсутствие политик приводит к тому, что не видна ни одна строка:
Теперь создадим две политики — разрешительные по умолчанию. Первая, как и раньше, разрешает пользователю видеть свои строки:
Вторая разрешает видеть строки любого пользователя, если сумма не превышает пятисот рублей:
Проверим.
Теперь добавим ограничительную политику: строки с суммой больше тысячи не должны быть видны никому.
Таким образом можно комбинировать несколько условий.
Page 24: Итоги
Ограничения доступа на уровне строк реализуют с помощью
- табличных функций, вызываемых с правами владельца
- представлений с барьером безопасности
- политик защиты строк
- Каждый способ реализации доступа по строкам имеет свои особенности и ограничения
Page 25: Практика
- Дополните пример из демонстрации таблицей staff с распределением сотрудников по отделам. Настройте разграничение доступа на уровне строк с помощью функции так, чтобы пользователи видели суммы только сотрудников своего отдела.
- Решите ту же задачу с помощью представления.
- Решите ту же задачу с помощью политик защиты строк.
Page 26: 1. Разграничение с помощью функции
Создадим необходимые роли и базу данных.
Создадим таблицу распределения сотрудников по отделам.
Таблица доходов и расходов из демонстрации теперь ссылается на staff.
Функция для разграничения доступа по строкам:
Проверяем в сеансе Алисы:
Page 27: Page 27
{#source-figure-027 caption=“Source slide page 27: Page 27”}
Прямого доступа к таблице нет.
Функция работает. Проверяем в сеансе Боба:
2. Разграничение с помощью представления
Представление для разграничения доступа:
Проверяем.
3. Разграничение с помощью политик
Политика защиты строк:
Page 28: Page 28
{#source-figure-028 caption=“Source slide page 28: Page 28”}
Проверяем.
Уборка
Page 29: Практика+
- Выполните настройку политик защиты строк, как описано в задании 3 Практики.
- Создайте представление, показывающее итоговые суммы по отделам, и выдайте доступ к нему пользователям.
- Убедитесь, что запрос к представлению не учитывает политики: любой сотрудник может видеть итоги по всем отделам.
- Измените представление так, чтобы любой сотрудник видел итоги только своего отдела.
Комментарии преподавателя
- Установите у представления параметр security_invoker.
Page 30: 1. Настройка политик
Создаем роли, база данных и таблицы как в Практике.
Политика защиты строк:
Page 31: Page 31
Проверяем работу политик, сначала в сеансе Алисы:
Затем в сеансе Боба:
2. Представление
Владелец по умолчанию видит все:
3. Проверка представления
Обратимся к представлению в сеансе Алисы:
Несмотря на наличие политики, Алиса видит все строки исходной таблицы, так как запрос, определяющий представление, по умолчанию выполняется от имени его владельца student. То же и для Боба:
Page 32: 4. Свойство представления security_invoker
{#source-figure-032 caption=“Source slide page 32: 4. Свойство представления security_invoker”}
Чтобы запрос представления выполнялся от имени текущего пользователя, нужно задать параметр security_invoker:
Теперь и Алиса, и Боб видят строки только своих отделов: