SQL и Qt

Права доступа и роли в СУБД: GRANT/REVOKE, принцип наименьших привилегий

Введение

Статья 406 кратко упомянула использование представлений для ограничения доступа — в этой статье разберём систему прав доступа СУБД подробнее: роли, конкретные привилегии (SELECT, INSERT, UPDATE, DELETE отдельно для разных таблиц), и принцип наименьших привилегий как основную организующую идею безопасной конфигурации доступа.

Концепция

Принцип наименьших привилегий означает, что каждой роли/пользователю должны быть предоставлены минимально необходимые права, реально требуемые для выполнения её конкретных функций, а не максимально широкие права «на всякий случай» или из удобства настройки — приложение, которому реально нужен только SELECT/INSERT к определённым таблицам, не должно подключаться к БД через учётную запись с полными административными правами, даже если технически это «проще» настроить изначально. Роли (ROLE) группируют наборы привилегий, что упрощает управление доступом для множества похожих пользователей/приложений через назначение одной и той же роли, а не повторное, индивидуальное предоставление одинакового набора прав каждому отдельному пользователю.

Пример кода: Права доступа и роли в СУБД

-- Создание роли с ограниченными правами для приложения, которому нужно только чтение
CREATE ROLE app_readonly LOGIN PASSWORD 'secure_password_here';
GRANT CONNECT ON DATABASE mydb TO app_readonly;
GRANT USAGE ON SCHEMA public TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
-- ^ app_readonly НЕ может выполнять INSERT/UPDATE/DELETE вовсе, независимо от того,
-- что подразумевается в коде приложения — защита на уровне СУБД, а не только кода
-- Роль для приложения, которому нужно полноценное чтение/запись, но НЕ административные функции
CREATE ROLE app_readwrite LOGIN PASSWORD 'another_secure_password';
GRANT CONNECT ON DATABASE mydb TO app_readwrite;
GRANT USAGE ON SCHEMA public TO app_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_readwrite;
-- app_readwrite НЕ может создавать/удалять таблицы, изменять схему, управлять другими пользователями
-- Более тонкая, детальная гранулярность — доступ только к КОНКРЕТНЫМ таблицам/столбцам
CREATE ROLE analytics_role LOGIN PASSWORD 'analytics_password';
GRANT SELECT ON customer_order_summary TO analytics_role; -- доступ только к представлению (статья 406)
-- analytics_role НЕ имеет прямого доступа к исходным таблицам customers/orders вовсе,
-- только к специально подготовленному, ограниченному по составу данных представлению

-- Отзыв ранее предоставленных прав
REVOKE INSERT, UPDATE, DELETE ON orders FROM some_role;

Пояснения к коду

Три разные роли (app_readonly, app_readwrite, analytics_role) показывают практическое применение принципа наименьших привилегий — каждая роль получает ровно тот набор прав, который реально нужен для её конкретного назначения, и даже если код конкретного приложения, подключающегося через одну из этих ролей, содержит ошибку или уязвимость (например, успешная SQL-инъекция, статья 433, обходящая параметризацию из-за недосмотра разработчика), реальный ущерб от этого ограничен правами самой роли БД на уровне СУБД — приложение, подключённое через app_readonly, физически не может выполнить DELETE даже при успешной инъекции, поскольку сама СУБД отклонит такую попытку независимо от содержимого выполняемого SQL. analytics_role с доступом исключительно к представлению, а не к исходным таблицам, показывает дополнительный уровень защиты — даже зная структуру исходных таблиц, эта роль физически не может обратиться к ним напрямую, минуя ограничения, заложенные в определение представления (статья 406).

Подводные камни

  • Подключение приложения к БД через единственную, «суперпользовательскую» учётную запись (administrator/root/postgres) «для простоты», вместо создания специализированной роли с ограниченными правами, реально необходимыми именно этому конкретному приложению — это нарушает принцип наименьших привилегий самым существенным образом: любая уязвимость в приложении (включая успешную SQL-инъекцию, если параметризация где-то была упущена) при использовании суперпользовательской учётной записи даёт потенциальному атакующему полный, неограниченный контроль над всей БД, а не ограниченный доступ, соответствующий реальным, минимально необходимым потребностям конкретного приложения.
  • Чрезмерно детальная, избыточно сложная система ролей и прав без реальной практической потребности в такой детализации — хотя принцип наименьших привилегий важен, чрезмерное усложнение модели прав (десятки узкоспециализированных ролей для не сильно различающихся практических нужд) может создать значительные операционные сложности в управлении и понимании реальной модели доступа без соразмерной выгоды в реальной безопасности по сравнению с разумно, но не чрезмерно детализированной моделью.
  • Забытое обновление прав при изменении функциональности приложения — если приложение изначально было только для чтения (и подключалось через app_readonly), а затем была добавлена новая функция, требующая записи, забытое соответствующее изменение прав роли БД приведёт к понятной, но потенциально дезориентирующей при первой диагностике ошибке доступа, которую легко спутать с другими видами ошибок, если не сразу очевидна связь с правами доступа на уровне СУБД.
  • Хранение пароля учётной записи роли БД непосредственно в коде приложения или незащищённом конфигурационном файле — аналогично подводным камням криптографического цикла (статья 377), учётные данные для подключения к БД должны храниться через защищённый механизм (переменные окружения, специализированное хранилище секретов, системное хранилище учётных данных), а не быть зашиты непосредственно в исходный код или лежать в незащищённом текстовом файле конфигурации, доступном для случайного просмотра или попадания в систему контроля версий.