sqlpostgresqlgrantprivileges

GRANT в SQL: привилегии, роли и минимально достаточный доступ

GRANT задаёт реальную границу доступа: права на схемы, таблицы и колонки, членство в ролях и осторожное делегирование через grant option.

10 мин чтенияСправочникsql · postgresql · grant · privileges · roles · security

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

Аналитику обычно достаточно читать данные. Приложению нужно читать и записывать только свои таблицы. Администратору нужны более широкие права. Стажёру точно не стоит давать возможность случайно удалить боевые заказы.

Для управления доступом в SQL используют команду GRANT.

Она выдаёт право что-то делать:

  • читать таблицу;
  • вставлять строки;
  • обновлять данные;
  • удалять записи;
  • пользоваться схемой;
  • вызывать функции;
  • работать с последовательностями;
  • передавать права другим ролям.

В PostgreSQL GRANT — это основа нормальной модели доступа. Если настроить права слишком широко, человек или приложение сможет сделать больше, чем нужно. Иногда это заканчивается случайным изменением боевых данных.

Поэтому хорошее правило звучит так:

выдавайте роли только те права, которые действительно нужны для работы.

Этот подход называют принципом минимально достаточного доступа, или least privilege.

Разберёмся на понятных примерах.

Роли: кто получает права

В PostgreSQL права выдаются ролям.

Роль может быть пользователем, который умеет подключаться к базе:

CREATE ROLE alice LOGIN PASSWORD 'secret';

А может быть групповой ролью без входа:

CREATE ROLE analyst NOLOGIN;

NOLOGIN означает, что под этой ролью нельзя напрямую подключиться к базе. Такая роль обычно нужна как «папка с правами».

Например:

  • readonly — только чтение;
  • analyst — права аналитика;
  • app_read — чтение для приложения;
  • app_write — запись для приложения;
  • support — ограниченный доступ для поддержки.

Потом конкретного пользователя добавляют в такую роль:

GRANT analyst TO alice;

Теперь alice получает права, которые есть у роли analyst.

Это удобнее, чем выдавать права каждому человеку вручную.

Привилегии на таблицу

Самый частый вариант GRANT — выдать права на таблицу.

Например, есть таблицы users, orders и employees.

Создадим роль аналитика:

CREATE ROLE analyst NOLOGIN;

Разрешим читать пользователей:

GRANT SELECT ON users TO analyst;

Разрешим читать и добавлять заказы:

GRANT SELECT, INSERT ON orders TO analyst;

Теперь роль analyst может выполнять:

SELECT *
FROM users;

и:

INSERT INTO orders (user_id, amount, status)
VALUES (42, 1000, 'pending');

Но если вы не выдавали UPDATE, то такой запрос будет запрещён:

UPDATE orders
SET status = 'paid'
WHERE id = 1001;

PostgreSQL скажет, что прав недостаточно.

Основные права на таблицы

Для таблиц часто встречаются такие привилегии:

  • SELECT — читать данные.
  • INSERT — вставлять новые строки.
  • UPDATE — изменять существующие строки.
  • DELETE — удалять строки.
  • TRUNCATE — быстро очищать таблицу целиком.
  • REFERENCES — создавать внешние ключи на эту таблицу.
  • TRIGGER — создавать триггеры на таблице.

Например:

GRANT SELECT, INSERT, UPDATE ON orders TO app_write;

Так роль app_write сможет читать, вставлять и обновлять заказы.

Но не сможет удалять их, потому что DELETE мы не выдавали.

Почему ALL PRIVILEGES опасен

Можно выдать все права сразу:

GRANT ALL PRIVILEGES ON employees TO analyst;

Это удобно, но опасно.

ALL PRIVILEGES на таблицу означает:

«Разрешить всё, что применимо к этой таблице».

Для роли аналитика это почти всегда слишком широко. Аналитику обычно не нужно менять, удалять или очищать данные.

Плохая идея:

GRANT ALL PRIVILEGES ON orders TO analyst;

Лучше:

GRANT SELECT ON orders TO analyst;

Или, если действительно нужно добавлять данные:

GRANT SELECT, INSERT ON orders TO analyst;

Короткое правило:

не используйте ALL PRIVILEGES там, где можно явно перечислить нужные права.

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

Важная деталь: USAGE на схему

В PostgreSQL таблицы лежат внутри схем.

Например, в public.orders:

  • public — это схема;
  • orders — это таблица внутри схемы.

Чтобы роль могла обратиться к таблице, ей часто нужно два уровня прав:

  1. Право пользоваться схемой.
  2. Право на саму таблицу.

Например:

GRANT USAGE ON SCHEMA public TO analyst;
GRANT SELECT ON orders TO analyst;

Если вы выдали SELECT на таблицу, но не выдали доступ к схеме, запрос может упасть с ошибкой вида:

permission denied for schema public

То есть право на таблицу вроде бы есть, но добраться до неё роль не может.

Поэтому для чтения таблиц в схеме обычно нужен такой набор:

GRANT USAGE ON SCHEMA public TO analyst;
GRANT SELECT ON orders TO analyst;

Практически это можно читать так:

USAGE ON SCHEMA — можно зайти в коридор.
SELECT ON TABLE — можно открыть конкретную дверь.

Права на отдельные колонки

Иногда нельзя давать доступ ко всей таблице.

Например, есть таблица сотрудников employees:

id name dept salary
1 Анна QA 250000
2 Борис Backend 300000

Аналитику можно видеть:

  • id;
  • name;
  • dept.

Но нельзя видеть:

  • salary.

Тогда можно выдать SELECT только на отдельные колонки:

GRANT SELECT (id, name, dept)
ON employees
TO analyst;

Теперь такой запрос разрешён:

SELECT id, name, dept
FROM employees;

А такой будет запрещён:

SELECT id, name, salary
FROM employees;

Потому что salary не входит в список разрешённых колонок.

Это полезно для чувствительных данных:

  • зарплаты;
  • паспортные данные;
  • телефоны;
  • email;
  • персональные идентификаторы;
  • внутренние комментарии;
  • финансовая информация.

Поколоночный UPDATE

Ограничивать можно не только чтение, но и обновление.

Например, роль поддержки может менять только статус заказа, но не сумму и не пользователя.

GRANT UPDATE (status)
ON orders
TO support;

Теперь роль support сможет выполнить:

UPDATE orders
SET status = 'cancelled'
WHERE id = 1001;

Но не сможет выполнить:

UPDATE orders
SET amount = 1
WHERE id = 1001;

Такой подход хорошо защищает от случайных изменений важных полей.

Выдать права на все существующие таблицы в схеме

Если в схеме много таблиц, неудобно писать GRANT для каждой вручную.

Можно выдать право сразу на все таблицы в схеме:

GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst;

Это означает:

«Разрешить SELECT на всех таблицах, которые уже есть в схеме public».

Но здесь есть важная ловушка.

Команда действует только на таблицы, которые существуют сейчас.

Если завтра вы создадите новую таблицу:

CREATE TABLE payments (
    id bigint PRIMARY KEY,
    amount numeric NOT NULL
);

роль analyst автоматически не получит на неё SELECT.

Для будущих таблиц нужны default privileges.

ALTER DEFAULT PRIVILEGES: права для будущих таблиц

Чтобы новые таблицы автоматически получали нужные права, используют ALTER DEFAULT PRIVILEGES.

Например:

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO analyst;

Теперь таблицы, которые будут созданы позже, смогут автоматически получать SELECT для роли analyst.

Но важно понимать нюанс:

ALTER DEFAULT PRIVILEGES действует для объектов, которые в будущем создаст конкретная роль.

То есть если вы настроили default privileges от имени одного владельца, а новые таблицы создаёт другой владелец, права могут не примениться.

На практике в команде важно договориться:

  • какая роль создаёт таблицы;
  • кто владеет объектами;
  • где хранятся миграции;
  • какие default privileges настроены для этой роли.

Иначе может появиться странная ситуация: на одни новые таблицы доступ есть, а на другие нет.

Не забудьте про sequences

В PostgreSQL автоинкрементные значения часто работают через sequence.

Например:

CREATE TABLE orders (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    amount numeric NOT NULL
);

Или старый стиль:

CREATE TABLE orders (
    id bigserial PRIMARY KEY,
    amount numeric NOT NULL
);

Если роль должна вставлять строки и база сама генерирует id, может понадобиться доступ к sequence.

Например:

GRANT USAGE ON SEQUENCE orders_id_seq TO app_write;

Или сразу на все существующие sequences в схеме:

GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app_write;

Для будущих sequences:

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE ON SEQUENCES TO app_write;

Это частая причина ошибки: таблицу вставлять разрешили, а sequence использовать забыли.

В итоге INSERT может падать не потому, что нет права на таблицу, а потому что нет права получить следующее значение id.

WITH GRANT OPTION: разрешить передавать права дальше

Обычно роль, которая получила право, не может передать его кому-то ещё.

Например:

GRANT SELECT ON orders TO analyst;

Роль analyst может читать orders, но не может сама выполнить:

GRANT SELECT ON orders TO someone_else;

Чтобы разрешить передавать право дальше, используют WITH GRANT OPTION.

Пример:

GRANT SELECT ON orders TO team_lead WITH GRANT OPTION;

Теперь team_lead может не только читать orders, но и выдавать SELECT на orders другим ролям.

Это удобно для делегирования, но опасно.

Почему?

Потому что вы теряете прямой контроль над тем, кто в итоге получил доступ.

Сегодня вы выдали право тимлиду. Завтра тимлид выдал его аналитику. Потом аналитик ушёл в другой проект, а доступ остался.

Поэтому WITH GRANT OPTION лучше использовать редко и осознанно.

Как отозвать права

Для отзыва прав используют REVOKE.

Например:

REVOKE SELECT ON orders FROM analyst;

Теперь роль analyst больше не имеет прямого права читать orders.

Если право было выдано с WITH GRANT OPTION, могут появиться зависимые права.

Например:

GRANT SELECT ON orders TO team_lead WITH GRANT OPTION;

Потом team_lead выдал право дальше:

GRANT SELECT ON orders TO analyst;

Если теперь попробовать отозвать право у team_lead, PostgreSQL может потребовать разобраться с зависимыми правами.

Можно использовать:

REVOKE SELECT ON orders FROM team_lead CASCADE;

CASCADE означает:

«Отозвать не только это право, но и зависящие от него права, которые были выданы дальше».

Это мощная команда. Используйте её внимательно.

Привилегии на объект и членство в роли — это разные вещи

Новички часто путают два вида GRANT.

1. Выдать право на объект

Например:

GRANT SELECT ON orders TO analyst;

Это значит:

роль analyst может читать таблицу orders.

Здесь право привязано к конкретному объекту — таблице orders.

2. Добавить одну роль в другую

Например:

GRANT readonly TO alice;

Это значит:

роль alice становится участником роли readonly и получает её права.

Здесь мы выдаём не право на таблицу, а членство в роли.

Почему групповые роли удобнее

Представим, у вас есть 10 аналитиков.

Плохой подход — каждому отдельно выдавать права:

GRANT SELECT ON users TO alice;
GRANT SELECT ON users TO bob;
GRANT SELECT ON users TO kate;

GRANT SELECT ON orders TO alice;
GRANT SELECT ON orders TO bob;
GRANT SELECT ON orders TO kate;

Так быстро появляется хаос.

Лучше создать групповую роль:

CREATE ROLE readonly NOLOGIN;

Выдать ей права:

GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;

А потом добавлять людей в эту роль:

GRANT readonly TO alice;
GRANT readonly TO bob;
GRANT readonly TO kate;

Теперь если нужно изменить права, вы меняете их у роли readonly, а не у каждого пользователя отдельно.

Например, добавить чтение новой схемы:

GRANT USAGE ON SCHEMA analytics TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO readonly;

Все участники роли получат доступ автоматически.

Пример нормальной схемы доступа

Допустим, у нас есть приложение и аналитики.

Создадим роли:

CREATE ROLE app_read NOLOGIN;
CREATE ROLE app_write NOLOGIN;
CREATE ROLE analyst NOLOGIN;

Права на схему:

GRANT USAGE ON SCHEMA public TO app_read;
GRANT USAGE ON SCHEMA public TO app_write;
GRANT USAGE ON SCHEMA public TO analyst;

Чтение для приложения:

GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_read;

Запись для приложения:

GRANT INSERT, UPDATE, DELETE ON orders TO app_write;
GRANT INSERT, UPDATE ON users TO app_write;

Чтение для аналитиков:

GRANT SELECT ON users TO analyst;
GRANT SELECT ON orders TO analyst;

Если есть чувствительная таблица employees, можно дать только часть колонок:

GRANT SELECT (id, name, dept)
ON employees
TO analyst;

Теперь создаём конкретного пользователя приложения:

CREATE ROLE app_user LOGIN PASSWORD 'strong_password';

И выдаём ему групповые роли:

GRANT app_read TO app_user;
GRANT app_write TO app_user;

Для аналитика:

CREATE ROLE alice LOGIN PASSWORD 'secret';
GRANT analyst TO alice;

Такая схема гораздо понятнее, чем десятки прямых GRANT на каждого пользователя.

Владелец объекта и суперпользователь

Важно понимать: GRANT не ограничивает всех на свете.

У каждого объекта есть владелец.

Например, таблицу создала роль app_owner. Эта роль обычно имеет полный контроль над своей таблицей.

Суперпользователь PostgreSQL тоже может делать почти всё.

Поэтому если вы выполните:

REVOKE SELECT ON orders FROM analyst;

это отзовёт доступ у analyst.

Но это не заберёт права у владельца таблицы и не ограничит суперпользователя.

GRANT и REVOKE нужны для управления обычными ролями, а не для ограничения владельца базы или суперпользователя.

Принцип минимально достаточных прав

Главная идея хорошей модели доступа:

роль должна иметь ровно те права, которые нужны для её задачи.

Не больше.

Если аналитик только строит отчёты, ему обычно нужен SELECT, но не нужны UPDATE, DELETE, TRUNCATE.

Если приложение должно создавать заказы, ему нужен INSERT на orders.

Но это не значит, что ему нужен DROP, TRUNCATE или полный доступ ко всем таблицам.

Плохой подход:

GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user;

Лучше:

GRANT SELECT, INSERT, UPDATE ON orders TO app_write;
GRANT SELECT ON users TO app_read;

Минимальные права помогают уменьшить ущерб от:

  • ошибки в коде;
  • SQL-инъекции;
  • человеческой ошибки;
  • неверной миграции;
  • случайного запуска запроса не в той базе.

Частые ошибки с GRANT

Ошибка 1. Выдать право на таблицу, но забыть USAGE на схему

Есть:

GRANT SELECT ON orders TO analyst;

Но нет:

GRANT USAGE ON SCHEMA public TO analyst;

В итоге роль может получить ошибку доступа к схеме.

Хороший набор:

GRANT USAGE ON SCHEMA public TO analyst;
GRANT SELECT ON orders TO analyst;

Ошибка 2. Выдать права на существующие таблицы и забыть будущие

Есть:

GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst;

Но это не касается таблиц, которые появятся позже.

Для будущих таблиц нужно:

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO analyst;

Ошибка 3. Дать ALL PRIVILEGES «чтобы точно работало»

Плохо:

GRANT ALL PRIVILEGES ON orders TO analyst;

Лучше явно:

GRANT SELECT ON orders TO analyst;

Или:

GRANT SELECT, INSERT ON orders TO analyst;

Если через месяц кто-то посмотрит на права, ему будет понятно, зачем они выданы.

Ошибка 4. Давать права напрямую каждому пользователю

Плохо:

GRANT SELECT ON orders TO alice;
GRANT SELECT ON orders TO bob;
GRANT SELECT ON orders TO kate;

Лучше:

CREATE ROLE readonly NOLOGIN;
GRANT SELECT ON orders TO readonly;

GRANT readonly TO alice;
GRANT readonly TO bob;
GRANT readonly TO kate;

Так проще сопровождать доступы.

Ошибка 5. Без необходимости использовать WITH GRANT OPTION

Опасно:

GRANT SELECT ON orders TO analyst WITH GRANT OPTION;

Если роль не должна раздавать права другим, WITH GRANT OPTION не нужен.

Обычно достаточно:

GRANT SELECT ON orders TO analyst;

Ошибка 6. Забыть про sequences

Есть право на вставку:

GRANT INSERT ON orders TO app_write;

Но при вставке с автоинкрементным id может понадобиться право на sequence:

GRANT USAGE ON SEQUENCE orders_id_seq TO app_write;

Или шире:

GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app_write;

Как посмотреть выданные права

В psql можно использовать команду:

\dp orders

Она покажет права на таблицу orders.

Для всех таблиц:

\dp

Также можно смотреть системные представления, например:

SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name = 'orders';

Это удобно, когда нужно разобраться, почему роль что-то может или не может сделать.

MySQL: похожая идея, другой синтаксис

В MySQL тоже есть GRANT.

Например, дать чтение на все таблицы базы:

GRANT SELECT ON mydb.* TO 'analyst'@'%';

Дать права на конкретную таблицу:

GRANT SELECT, INSERT ON mydb.orders TO 'app_user'@'%';

Права на отдельные колонки тоже возможны:

GRANT SELECT (id, name, dept)
ON mydb.employees
TO 'analyst'@'%';

Но в MySQL нет такой же конструкции GRANT SELECT ON ALL TABLES IN SCHEMA public, как в PostgreSQL.

Там чаще работают через уровень базы, например mydb.*.

Идея остаётся той же: выдавать минимально необходимые права, а не всё подряд.

ClickHouse: роли и GRANT тоже есть

В ClickHouse тоже есть пользователи, роли и GRANT.

Например:

GRANT SELECT ON analytics.orders TO analyst;

Или выдать роль пользователю:

GRANT analyst TO alice;

Также поддерживается передача прав дальше через WITH GRANT OPTION.

Но модель отличается от PostgreSQL. Например, в ClickHouse нет такой же необходимости выдавать USAGE ON SCHEMA public, потому что там другая организация баз и таблиц.

Поэтому общее правило переносится, а детали синтаксиса нужно смотреть под конкретную СУБД.

Короткая шпаргалка

Создать групповую роль:

CREATE ROLE analyst NOLOGIN;

Создать пользователя:

CREATE ROLE alice LOGIN PASSWORD 'secret';

Выдать пользователю роль:

GRANT analyst TO alice;

Дать доступ к схеме:

GRANT USAGE ON SCHEMA public TO analyst;

Дать чтение таблицы:

GRANT SELECT ON orders TO analyst;

Дать чтение и вставку:

GRANT SELECT, INSERT ON orders TO app_write;

Дать доступ только к отдельным колонкам:

GRANT SELECT (id, name, dept)
ON employees
TO analyst;

Дать права на все существующие таблицы схемы:

GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst;

Настроить права для будущих таблиц:

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO analyst;

Дать право на sequences:

GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app_write;

Выдать право с возможностью передавать дальше:

GRANT SELECT ON orders TO team_lead WITH GRANT OPTION;

Отозвать право:

REVOKE SELECT ON orders FROM analyst;

Главное правило

GRANT — это не просто техническая команда. Это способ описать, кто и что может делать с данными.

Хорошая модель доступа обычно строится так:

  1. Создаём групповые роли под задачи: readonly, analyst, app_write, support.
  2. Выдаём этим ролям минимально необходимые права.
  3. Пользователей добавляем в роли, а не раздаём им всё напрямую.
  4. Не забываем про USAGE ON SCHEMA.
  5. Для новых таблиц настраиваем ALTER DEFAULT PRIVILEGES.
  6. Чувствительные данные закрываем через поколоночные права или отдельные представления.
  7. WITH GRANT OPTION используем только там, где передача прав действительно нужна.

Запомните коротко:

сначала роль, потом минимальные права, потом пользователи в эту роль.

Так доступы остаются понятными, безопасными и управляемыми даже тогда, когда проект растёт.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр