Выделение прав на доступ к объектам базы данных будем рассматривать на практическом примере из этой статьи.
Задача и требования к правам
Имеется база данных parser, включающая следующие схемы
- config - схема хранящая конфигурацию
- logs - схема хранения логов
- [project_data1, project_data2,...] - одна или более схем с данными различных проектов
Нужно создать пользователя external_user, наделенного следующими правами
- Пользователь может иметь доступ только к базе данных parser. Доступ к информации других баз данных в данном кластере пользователю запрещен.
- Пользователь имеет доступ только к схемам config и logs. Доступ к схемам с данными проектов пользователю запрещен.
- В схеме config определена функция getQuery() и материализованное представление _. Пользователь имеет доступ только к этим объектам. Доступ к другим объектам схемы (таблицам, представлениям, последовательностям и т.д.) пользователю запрещен.
- В схеме logs от имени пользователя может выполняться создание дочерних таблиц логов и добавление в них записей. Остальные операции с таблицами запрещены.
- Доступ к публичным схемам information_schema и pg_catalog максимально ограничен. Имеется доступ только к объектам, доступным для выполнения функции config.getQuery()
Реализация правил
- Добавляем пользователя и устанавливаем пароль для него.
create role external_user with LOGIN;
ALTER USER external_user WITH PASSWORD 'qweasd';
Начиная с версии 8.1 в postgresql группы пользователей и пользователи объединены в одну концепцию ролей. Это значит, что команда create role может создавать как групповые роли (группы пользователей), так и роли входа (пользователей). Выражение with LOGIN указывает на то, что мы создаем роль входа (пользователя). Второй командой мы задаем пароль для пользователя.
Примечание. Для групповой роль также может быть назначен пароль.
На данном шаге следует запомнить два момента.
- пользователь создается на уровне кластера баз данных, а не отдельной базы
- каждый созданный пользователь автоматически наделяется правами PUBLIC - эта групповая роль, которая существует в любом кластере. Для таблиц, столбцов, схем и табличных пространств роль PUBLIC не имеет никаких прав. Для баз данных роль имеет права CONNECT (подключение) и CREATE_TEMP_TABLE (создание временных таблиц). Для функций - EXECUTE (выполнение), для языков - USAGE (использование).
Из этого следует важный вывод - созданные пользователь может подключиться к любой базе данных кластера. Правда при этом не иметь доступа к таблицам ее схем, поскольку у него нет доступа к самим схемам.
Важность этого вывода в следующем. В любой базе данных postgresql существует информационная схема с именем information_schema, которая содержит набор представлений, содержащих информацию об объектах текущей базы данных. И еще есть схема pg_catalog, также содержащая объекты (таблицы, функции, представления) о текущей базе данных. Поэтому созданный пользователь не имея еще фактически никаких прав, уже может узнать много полезной информации о базе данных, к которой он подключился и о кластере баз. Например
-- посмотреть пользователей зарегистрированных в кластере баз данных и их права
select * from pg_catalog.pg_user;
-- посмотреть список баз данных кластера
select datname from pg_catalog.pg_stat_database;
-- посмотреть существующие схемы и таблицы кластера баз данных
select * from pg_catalog.pg_tables;
и так далее. Данная информация потенциально может быть опасной. Поэтому наш следующий шаг - закрываем для созданного пользователя доступ к объектам схем
Отступление
Здесь важно понять механизм исключения прав. Создаваемый пользователь наследует права роли PUBLIC. Поэтому, чтобы лишить его какого-либо права нужно лишить этого права PUBLIC. Или создать другую групповую роль с нужными правами и затем предоставлять в ней членство создаваемым пользователям (в данной публикации этот вопрос не рассматривается)
- Закрываем созданному пользователю доступ к схемам information_schema, pg_catalog.
На данном этапе существуют определенные тонкости со схемой pg_catalog. Во-первых, схема хранит много служебной информации, необходимой для работы пользовательских функций. Так, если закрыть для пользователя external_user доструп к pg_catalog, то вызов даже такой простой функции как getQuery() (описанной в этой статье) будет генерировать ошибку. В частности потому, что в каталоге pg_catalog хранится информация о всеъ типах данных postgresql.
А во-вторых существует особенность доступа к объектам схемы pg_catalog. А именно при разборе sql-запроса postgresql автоматически добавляет схему pg_catalog в пути поиска. Поэтому, мы можем закрыть доступ к схеме pg_catalog и запрос типа
SELECT * FROM pg_catalog.pg_roles;
подскажет нам об этом. Но автоматическое добавление pg_catalog в путь поиска позволяет нам выполнить запрос
SELECT * FROM pg_roles;
который вернет нам зарегистрированных в кластере пользователей.
Поэтому, если мы хотим максимально ограничить доступ пользователя к схеме pg_catalog, воспользуемся следующими командами
Забираем у групповой роли PUBLIC права на доступ к таблицам, представлениям и выполнению функций
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA pg_catalog FROM PUBLIC;
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA pg_catalog from public;
Предоставляем права пользователю external_user только для нужных объектов (в нашем примере - таблицы pg_class, pg_type)
grant select on pg_class, pg_type to external_user
- Закрываем доступ к подключению других баз данных (на примере бд other_db)
SET SESSION AUTHORIZATION postgres;
REVOKE connect ON database other_db FROM PUBLIC;
- Добавляем доступ к объектам схемам config и logs
GRANT USAGE ON SCHEMA config TO external_user
GRANT USAGE ON SCHEMA logs TO external_user
При этом, после после получения доступа к схеме, наследуя права PUBLIC стали доступны следующие операции
- запуск всех функций, хранящихся в схеме,
- создание временных таблиц,
- возможность использования языков
- Ограничиваем доступные для выполнения функции.
Если нужно запретить доступ к отдельным функциям, можно сделать так (на примере функции a())
REVOKE execute ON function config.a() FROM public;
Внимание! Если функция предусматривает входные параметры, при ее указании в скобках нужно перечислить типы этих параметров.
Если же функций много, а разрешить выполнение нужно только некоторым, делаем так (на примере функции a())
-- запрещаем для роли public выполнение всех функций
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA config from public;
--даем разрешение на выполнение только тем пользователям, которым нужно
GRANT execute ON function config.a() TO external_user;
- Напоследок проверяем какими правами обладает наш пользовател external_user в полкюченной базе. Для этого выполняем запрос.
select * from (
SELECT
use.usename as subject,
nsp.nspname as schem,
c.relname as obj,
c.relkind as type,
use2.usename as owner,
c.relacl,
(use2.usename != use.usename and c.relacl::text !~ ('({|,)' || use.usename || '=')) as public
FROM
pg_user use
cross join pg_class c
left join pg_namespace nsp on (c.relnamespace = nsp.oid)
left join pg_user use2 on (c.relowner = use2.usesysid)
WHERE
c.relowner = use.usesysid or
c.relacl::text ~ ('({|,)(|' || use.usename || ')=')
ORDER BY
subject,
schem,
obj
) as res
where
subject = 'external_user'
and public=false;
Запрос работает в пределах текущей подключенной базы данных. И не информативен для роли с правами суперпользователя
Запрос содержит следующие поля
- subject - имя пользователя
- schem - схема хранения объекта
- obj - название конечного объекта на который у пользователя есть права
- type - метка типа объекта
- owner - владелец объекта
- relacl - права пользователя на объект
- public - метка принадлежности к публичной схеме information_schema или pg_catalog
Метка типа объекта
- r = обычная таблица,
- i = индекс (index),
- S = последовательность (sequence),
- v = представление (view),
- m = материализованное представление(materialized view),
- c = составной тип (composite),
- t = таблица TOAST,
- f = сторонняя таблица (foreign)
Права пользоватя на объект
Записи, выводимые запросом интерпретируются так: имя_роли=xxxx -- права, назначенные роли
=xxxx -- права, назначенные PUBLIC
- r - SELECT ("read", чтение)
- w - UPDATE ("write", запись)
- a - INSERT ("append", добавление)
- d - DELETE
- D - TRUNCATE
- x - REFERENCES
- t - TRIGGER
- X - EXECUTE
- U - USAGE
- C - CREATE
- c - CONNECT
- T - TEMPORARY
- arwdDxt - ALL PRIVILEGES (все права для таблиц; для других объектов другие)
- * - право передачи заданного права
- /yyyy -- роль, назначившая это право
Запрос возвращает только те конечные объекты (таблицы, функции,... но не базы данных и схемы) у которых пользователю заданы какие-либо права. При этом, из результатов исключаются объекты, принадлежащие общим схемам information_schema и pg_catalog. Если мы хотим включить и их - нужно закомментировать условие public=false
Как было сказано ранее, запрос возвращает права доступа пользователя к объектам текущей подключенной бд. Чтобы узнать его права для объектов другой бд кластера postgresql, сначала к этой бд нужно подключиться. Для этого будем использовать расширение postgresql dblink.
sudo apt-get install postgresql-contrib
CREATE EXTENSION dblink;
(помним, что расширение создается для текущей бд)
Теперь запрос на получение прав пользователя будет выглядеть так
SELECT * FROM dblink('dbname={{database_name}}', '
select * from (
SELECT
use.usename as subject,
nsp.nspname as schem,
c.relname as obj,
c.relkind as type,
use2.usename as owner,
c.relacl,
(use2.usename != use.usename and c.relacl::text !~ (''({|,)'' || use.usename || ''='')) as public
FROM
pg_user use
cross join pg_class c
left join pg_namespace nsp on (c.relnamespace = nsp.oid)
left join pg_user use2 on (c.relowner = use2.usesysid)
WHERE
c.relowner = use.usesysid or
c.relacl::text ~ (''({|,)(|'' || use.usename || '')='')
ORDER BY
subject,
schem,
obj
) as res
where
subject = ''{{user_name}}'';
')
AS r(subject name, schem name, obj name, type char, owner name, relacl aclitem[], public boolean)
запрос должен выполняться от имени суперпользователя
Узнать имена всех баз данных текущего класера можно так
select datname from pg_database