Роли, привилегии, Row Level Security, аудит действий
Безопасность базы данных критична для защиты данных пользователей. В этой теме вы изучите управление ролями, привилегиями, Row Level Security и аудит действий.
-- Роль без права входа (группа)
CREATE ROLE developers;
-- Роль с правом входа (пользователь)
CREATE ROLE alice WITH LOGIN PASSWORD 'secure_password';
-- Роль с дополнительными атрибутами
CREATE ROLE bob WITH
LOGIN
PASSWORD 'secure_password'
CREATEDB -- может создавать базы данных
CREATEROLE -- может создавать роли
VALID UNTIL '2026-12-31'; -- срок действия
-- Суперпользователь (осторожно!)
CREATE ROLE admin WITH
LOGIN
PASSWORD 'admin_password'
SUPERUSER;| Атрибут | Описание |
|---|---|
LOGIN / NOLOGIN | Может подключаться к БД |
SUPERUSER | Полные права (осторожно!) |
CREATEDB | Может создавать базы данных |
CREATEROLE | Может создавать роли |
REPLICATION | Может подключаться для репликации |
VALID UNTIL | Дата истечения срока действия |
-- Добавить пользователя в роль
GRANT developers TO alice, bob;
-- Удалить пользователя из роли
REVOKE developers FROM alice;
-- Просмотр членства
SELECT
r.rolname as role,
m.rolname as member
FROM pg_auth_members am
JOIN pg_roles r ON am.roleid = r.oid
JOIN pg_roles m ON am.member = m.oid;| Привилегия | Объекты | Описание |
|---|---|---|
SELECT | Таблицы, представления | Чтение данных |
INSERT | Таблицы | Вставка данных |
UPDATE | Таблицы | Обновление данных |
DELETE | Таблицы | Удаление данных |
TRUNCATE | Таблицы | Очистка таблицы |
REFERENCES | Таблицы | Создание FK |
TRIGGER | Таблицы | Создание триггеров |
CREATE | Схемы, БД | Создание объектов |
CONNECT | Базы данных | Подключение к БД |
EXECUTE | Функции | Вызов функций |
USAGE | Схемы, последовательности | Использование |
-- Предоставить SELECT на таблицу
GRANT SELECT ON users TO alice;
-- Предоставить несколько привилегий
GRANT SELECT, INSERT, UPDATE ON orders TO bob;
-- Предоставить все привилегии
GRANT ALL PRIVILEGES ON TABLE products TO developers;
-- Предоставить на все таблицы схемы
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analysts;
-- Предоставить на будущие таблицы (default privileges)
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO analysts;
-- Предоставить на схему
GRANT USAGE ON SCHEMA analytics TO analysts;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO analysts;
-- Предоставить на функцию
GRANT EXECUTE ON FUNCTION get_user_data TO analysts;
-- Предоставить на последовательность
GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO developers;-- Отозвать привилегию
REVOKE INSERT ON orders FROM bob;
-- Отозвать все привилегии
REVOKE ALL PRIVILEGES ON TABLE users FROM alice;
-- Отозвать на все таблицы схемы
REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM analysts;-- Привилегии на таблицы
SELECT
table_schema,
table_name,
privilege_type,
grantee
FROM information_schema.role_table_grants
WHERE grantee = 'alice';
-- Привилегии роли
\du alice -- в psql
-- Просмотр привилегий таблицы
\d+ table_name -- в psqlRLS ограничивает доступ к строкам таблицы на основе политик.
-- Включить RLS для таблицы
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Включить RLS для владельца тоже (опционально)
ALTER TABLE documents FORCE ROW LEVEL SECURITY;-- Политика: пользователи видят только свои документы
CREATE POLICY user_documents ON documents
FOR SELECT
USING (owner_id = current_user_id());
-- Политика: пользователи могут вставлять только свои документы
CREATE POLICY user_insert ON documents
FOR INSERT
WITH CHECK (owner_id = current_user_id());
-- Политика: пользователи могут обновлять только свои документы
CREATE POLICY user_update ON documents
FOR UPDATE
USING (owner_id = current_user_id())
WITH CHECK (owner_id = current_user_id());
-- Политика: пользователи могут удалять только свои документы
CREATE POLICY user_delete ON documents
FOR DELETE
USING (owner_id = current_user_id());| Тип | Описание |
|---|---|
FOR SELECT | Применяется к SELECT |
FOR INSERT | Применяется к INSERT |
FOR UPDATE | Применяется к UPDATE |
FOR DELETE | Применяется к DELETE |
FOR ALL | Применяется ко всем операциям |
USING — условие для фильтрации существующих строкWITH CHECK — условие для проверки новых/изменённых строк-- Пример: модераторы видят все документы
CREATE POLICY moderator_all ON documents
FOR ALL
TO moderators
USING (true) -- видят всё
WITH CHECK (true); -- могут делать всё
-- Пример: обычные пользователи ограничены
CREATE POLICY user_limited ON documents
FOR ALL
TO users
USING (owner_id = current_user_id())
WITH CHECK (owner_id = current_user_id());-- Функция для получения ID текущего пользователя
CREATE OR REPLACE FUNCTION current_user_id()
RETURNS INTEGER AS $$
BEGIN
-- Вариант 1: из JWT токена (если используется)
-- RETURN NULLIF(current_setting('app.current_user_id', true), '')::INTEGER;
-- Вариант 2: из роли
RETURN NULLIF(current_setting('app.user_id', true), '')::INTEGER;
-- Вариант 3: маппинг роли на ID
-- RETURN (SELECT id FROM users WHERE username = current_user);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Установка переменной сессии
SET app.user_id = '123';-- Таблица задач
CREATE TABLE tasks (
id SERIAL PRIMARY KEY,
title TEXT,
status TEXT,
priority INTEGER,
assignee_id INTEGER,
project_id INTEGER,
created_by INTEGER
);
-- Включить RLS
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
-- Политика: исполнители видят свои задачи
CREATE POLICY assignee_tasks ON tasks
FOR SELECT
USING (assignee_id = current_user_id());
-- Политика: создатели видят свои задачи
CREATE POLICY creator_tasks ON tasks
FOR SELECT
USING (created_by = current_user_id());
-- Политика: менеджеры проекта видят все задачи проекта
CREATE POLICY manager_project_tasks ON tasks
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM project_members pm
WHERE pm.project_id = tasks.project_id
AND pm.user_id = current_user_id()
AND pm.role = 'manager'
)
);
-- Политика: админы видят всё
CREATE POLICY admin_all ON tasks
FOR ALL
TO admins
USING (true)
WITH CHECK (true);-- Просмотр политик
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd,
qual,
with_check
FROM pg_policies
WHERE tablename = 'documents';
-- Тестирование политик
SET app.user_id = '1';
SELECT * FROM documents; -- видит только документы пользователя 1
SET app.user_id = '2';
SELECT * FROM documents; -- видит только документы пользователя 2CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
schema_name TEXT NOT NULL,
table_name TEXT NOT NULL,
operation TEXT NOT NULL, -- INSERT, UPDATE, DELETE
user_name TEXT NOT NULL DEFAULT current_user,
user_id INTEGER,
client_ip INET DEFAULT inet_client_addr(),
old_data JSONB,
new_data JSONB,
query TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_audit_table ON audit_log(schema_name, table_name);
CREATE INDEX idx_audit_created ON audit_log(created_at);
CREATE INDEX idx_audit_user ON audit_log(user_name);CREATE OR REPLACE FUNCTION audit_trigger_function()
RETURNS TRIGGER AS $$
DECLARE
user_id INTEGER;
BEGIN
-- Получение ID пользователя из переменной сессии
user_id := NULLIF(current_setting('app.user_id', true), '')::INTEGER;
IF TG_OP = 'INSERT' THEN
INSERT INTO audit_log (
schema_name, table_name, operation,
user_id, new_data, query
) VALUES (
TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP,
user_id, to_jsonb(NEW), current_query()
);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO audit_log (
schema_name, table_name, operation,
user_id, old_data, new_data, query
) VALUES (
TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP,
user_id, to_jsonb(OLD), to_jsonb(NEW), current_query()
);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO audit_log (
schema_name, table_name, operation,
user_id, old_data, query
) VALUES (
TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP,
user_id, to_jsonb(OLD), current_query()
);
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Создание триггеров для важных таблиц
CREATE TRIGGER audit_users
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
CREATE TRIGGER audit_orders
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();-- Включение логирования в postgresql.conf
-- log_connections = on
-- log_disconnections = on
-- log_statement = 'all' -- или 'mod', 'ddl'
-- Просмотр активных подключений
SELECT
pid,
usename,
application_name,
client_addr,
backend_start,
state,
query
FROM pg_stat_activity;
-- Завершение подключения
SELECT pg_terminate_backend(pid);-- Функция выполняется с правами создателя
CREATE OR REPLACE FUNCTION get_sensitive_data(user_id INTEGER)
RETURNS SETOF sensitive_table AS $$
SELECT * FROM sensitive_table WHERE owner_id = user_id;
$$ LANGUAGE sql SECURITY DEFINER
SET search_path = public, pg_temp; -- защита от подмены объектовВажно: Всегда указывайте SET search_path для SECURITY DEFINER функций, чтобы предотвратить атаки через подмену объектов в других схемах.
-- Предоставить доступ только определённой роли
REVOKE ALL ON FUNCTION get_sensitive_data FROM PUBLIC;
GRANT EXECUTE ON FUNCTION get_sensitive_data TO authorized_users;-- Включить расширение
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Хэширование паролей
INSERT INTO users (username, password_hash)
VALUES ('alice', crypt('password123', gen_salt('bf')));
-- Проверка пароля
SELECT * FROM users
WHERE username = 'alice'
AND password_hash = crypt('password123', password_hash);
-- Шифрование данных
INSERT INTO secrets (data)
VALUES (pgp_sym_encrypt('secret data', 'encryption_key'));
-- Расшифровка
SELECT pgp_sym_decrypt(data, 'encryption_key') FROM secrets;CREATE TABLE encrypted_data (
id SERIAL PRIMARY KEY,
data BYTEA -- зашифрованные данные
);
-- Вставка зашифрованных данных
INSERT INTO encrypted_data (data)
VALUES (encrypt('sensitive data', 'key', 'aes'));
-- Расшифровка
SELECT decrypt(data, 'key', 'aes') FROM encrypted_data;-- Плохо: дать все права
GRANT ALL ON TABLE users TO app_user;
-- Хорошо: только необходимое
GRANT SELECT, INSERT, UPDATE ON TABLE users TO app_user;
GRANT USAGE ON SEQUENCE users_id_seq TO app_user;-- Создать роли для разных уровней доступа
CREATE ROLE read_only;
CREATE ROLE read_write;
CREATE ROLE admin;
-- Назначить привилегии ролям
GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO read_write;
-- Добавить пользователей в роли
GRANT read_only TO alice;
GRANT read_write TO bob;-- Найти таблицы без RLS
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT IN (
SELECT tablename FROM pg_policies
);
-- Найти пользователей с избыточными правами
SELECT grantee, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('postgres', 'pg_monitor');-- Плохо: конкатенация
EXECUTE 'SELECT * FROM ' || table_name || ' WHERE id = ' || user_input;
-- Хорошо: параметризация
EXECUTE format('SELECT * FROM %I WHERE id = $1', table_name) USING user_input;Теперь вы умеете управлять доступом и аудитом. В следующей (последней) теме вы изучите практические паттерны и миграции — версионирование схемы, миграции данных и паттерны разработки.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.