Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
🧱
Данные
Опубликовано:
19.06.2026
Обновлено:
09.07.2026

PostgreSQL: полный чеклист безопасности для DBA и разработчиков

Данил Мануйлов

⚠️ Дисклеймер (YMYL): Безопасность СУБД - ответственная область. Перед применением команд на production-сервере тестируйте в staging-окружении. Для систем, обрабатывающих персональные данные граждан РФ, дополнительно изучите требования Федерального закона № 152-ФЗ и рекомендации ФСТЭК России.

Вы установили PostgreSQL, создали базу, подключили приложение - и считаете это «настройкой».

На самом деле дефолтный PostgreSQL открыт для атак: суперпользователь без пароля, md5-аутентификация, нет аудита, public-схема доступна всем. За 30–60 минут это можно исправить.

В этом руководстве - конкретные команды, примеры политик RLS и готовый чеклист из 30 пунктов.

Материал актуален для PostgreSQL 14–17 и Postgres Pro на 2026 год.

Почему безопасность PostgreSQL - это не опция

PostgreSQL популярен в России в том числе потому, что российская компания Postgres Professional развивает сертифицированный форк Postgres Pro и официальную документацию на русском языке.

Проблема в другом: при стандартной установке PostgreSQL оптимизирован под удобство разработки, а не под безопасность production-среды. Открытый порт 5432 на всех интерфейсах, метод аутентификации md5, суперпользователь postgres с пустым паролем - типичный набор «как после apt install».

Угрозы и последствия

Три сценария, которые чаще всего приводят к инцидентам:

  • SQL-инъекция через приложение - если роль приложения имеет избыточные права (UPDATE, DELETE, DROP), одна уязвимость в коде = компрометация всей БД.
  • Insider threat - сотрудник с прямым доступом к БД читает или модифицирует чужие записи. Без RLS и аудита это незаметно.
  • Privilege escalation через SECURITY DEFINER - функции с этим атрибутом выполняются с правами создателя, а не вызывающего. Злоумышленник может использовать их для повышения привилегий.

Требования 152-ФЗ и ФСТЭК

Если в вашей БД хранятся персональные данные граждан РФ, Федеральный закон № 152-ФЗ обязывает обеспечить их защиту от несанкционированного доступа. ФСТЭК России публикует методические документы по защите информационных систем персональных данных (ИСПДн) - в частности, Приказ № 21. Postgres Pro Enterprise имеет сертификат соответствия ФСТЭК - актуальный статус уточняйте на сайте регулятора.

Авторская ремарка: за три года работы с production PostgreSQL на нескольких SaaS-проектах я ни разу не видел, чтобы новый разработчик самостоятельно закрыл public-схему или заменил md5 на scram. Это не лень - просто документация об этом не кричит.

Роли и привилегии: архитектура доступа

Роль в PostgreSQL - универсальный объект: может быть пользователем (с LOGIN), группой (без LOGIN) или набором прав. Именно через роли реализуется принцип минимальных привилегий (PoLP - Principle of Least Privilege).

Иерархия ролей: четыре уровня

Рекомендуемая архитектура для production:

-- 1. Группы (без логина) - агрегируют права
CREATE ROLE readonly NOLOGIN;
CREATE ROLE app_user NOLOGIN;
CREATE ROLE migrator NOLOGIN;
CREATE ROLE dba_admin NOLOGIN;

-- 2. Конкретные пользователи - наследуют от групп
CREATE ROLE api_service LOGIN PASSWORD 'str0ngP@ss' CONNECTION LIMIT 50;
GRANT app_user TO api_service;

CREATE ROLE analyst_ivan LOGIN PASSWORD 'str0ngP@ss2';
GRANT readonly TO analyst_ivan;
Роль Права Когда использовать
readonly SELECT на нужные таблицы Аналитики, отчётные инструменты, BI
app_user SELECT, INSERT, UPDATE, DELETE - только на бизнес-таблицы Приложение в runtime
migrator DDL: CREATE, ALTER, DROP + DML CI/CD, Flyway, Liquibase
dba_admin Управление ролями, GRANT, конфиг Только для администратора

GRANT и REVOKE: выдавать точечно

Не давайте права на всю БД - выдавайте только то, что нужно:

-- Закрываем public-схему (критично!)
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

-- Права app_user только на бизнес-схему
GRANT USAGE ON SCHEMA app TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA app TO app_user;

-- Права readonly
GRANT USAGE ON SCHEMA app TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO readonly;

-- Чтобы новые таблицы тоже получали права:
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT ON TABLES TO readonly;

⚠️ REVOKE ALL ON SCHEMA public FROM PUBLIC - это не опечатка. По умолчанию каждый пользователь PostgreSQL может создавать объекты в public-схеме. Это открывает атаку через подмену функций.

Опасности: superuser и SECURITY DEFINER

Два правила, которые нарушают почти все:

  1. Никогда не подключайте приложение под postgres (суперпользователь обходит RLS, GRANT, все ограничения).
  2. Функции с SECURITY DEFINER - потенциальный вектор privilege escalation. Если без них не обойтись, помещайте их в отдельную схему и закрывайте search_path:
CREATE OR REPLACE FUNCTION secure.get_user_data(uid int)
RETURNS TABLE(name text)
SECURITY DEFINER
SET search_path = secure, pg_catalog -- Фиксируем search_path!
AS $$
SELECT name FROM secure.users WHERE id = uid;
$$ LANGUAGE sql;

Проверить текущие права - запрос к системным таблицам:

SELECT grantee, table_schema, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('postgres', 'PUBLIC')
ORDER BY grantee, table_name;

Row-Level Security: фильтрация на уровне строк

RLS появился в PostgreSQL 9.5 и работает как невидимый WHERE-фильтр на каждой таблице. Даже если приложение выполняет SELECT * FROM orders, БД возвращает только те строки, для которых политика разрешает доступ текущему пользователю.

Как включить RLS

-- 1. Включаем RLS на таблице
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- 2. Для владельца таблицы тоже (иначе он обходит политики)
ALTER TABLE orders FORCE ROW LEVEL SECURITY;

После ENABLE ROW LEVEL SECURITY без определённых политик таблица недоступна никому (Default Deny). Это задокументированное поведение PostgreSQL, не баг.

USING vs WITH CHECK

В CREATE POLICY два ключевых клауза:

  • USING - фильтрует строки при SELECT, UPDATE, DELETE (что пользователь видит)
  • WITH CHECK - проверяет строки при INSERT и UPDATE (что пользователь пишет)
-- Пользователь видит только свои заказы
CREATE POLICY user_sees_own_orders ON orders
FOR SELECT
USING (user_id = current_user::int);

-- Пользователь может создавать заказы только от своего имени
CREATE POLICY user_inserts_own_orders ON orders
FOR INSERT
WITH CHECK (user_id = current_user::int);

RLS для мультитенантного SaaS

Классический паттерн для SaaS с несколькими арендаторами - столбец tenant_id + RLS:

-- Таблица с данными арендаторов
CREATE TABLE documents (
  id bigserial PRIMARY KEY,
  tenant_id int NOT NULL,
  content text
);

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents FORCE ROW LEVEL SECURITY;

-- Роль получает tenant_id через настройку сессии
CREATE POLICY tenant_isolation ON documents
USING (tenant_id = current_setting('app.current_tenant')::int);

-- Приложение устанавливает перед запросами:
-- SET app.current_tenant = '42';

Авторская ремарка: в реальном проекте я использовал именно этот паттерн - current_setting() в USING-клаузе. Главное - устанавливать переменную сессии до любого запроса к таблице, иначе получите ошибку или пустой результат.

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

  • Функции в USING-условии лучше делать STABLE или IMMUTABLE - PostgreSQL может кешировать их результат.
  • BYPASSRLS - атрибут роли, который полностью отключает RLS. Его нельзя давать прикладным пользователям.
  • Суперпользователь (SUPERUSER) обходит RLS автоматически - ещё один аргумент не подключать приложение под postgres.

Аудит: что, кто и когда делал в БД

Аудит - это не паранойя, а доказательная база. Без него невозможно ни расследовать инцидент, ни отчитаться перед регулятором.

Встроенное логирование через postgresql.conf

Минимальный набор параметров для безопасного логирования:

# postgresql.conf
log_connections = on                 # Логировать все подключения
log_disconnections = on              # Логировать отключения
log_failed_auth = on                 # Неудачные попытки аутентификации
log_line_prefix = '%m [%p] %q%u@%d ' # Время, PID, пользователь, БД
log_statement = 'ddl'                # Логировать DDL-операции (CREATE, ALTER, DROP)
log_min_duration_statement = 1000    # Запросы дольше 1 сек - в лог (аномалии)

После изменения параметров - SELECT pg_reload_conf(); (без рестарта).

pg_audit: полный аудит DDL и DML

Для детальной записи всех операций - расширение pg_audit:

# postgresql.conf - добавить pg_audit в preload
shared_preload_libraries = 'pg_audit'

# После рестарта кластера:
pgaudit.log = 'read, write, ddl, role'   # Что аудировать
pgaudit.log_catalog = off                 # Не спамить системными запросами
pgaudit.log_parameter = on                # Логировать параметры запросов

Установка расширения:

CREATE EXTENSION pgaudit;

Лог pg_audit пишется в стандартный PostgreSQL-лог. Формат строки содержит: тип операции, объект, команду, пользователя - это позволяет парсить лог и строить отчёты.

Мониторинг активных сессий

-- Кто сейчас подключён и что делает
SELECT pid, usename, application_name, client_addr,
state, query_start, query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

-- Убить подвисший процесс (осторожно!)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'active' AND query_start < NOW() - INTERVAL '1 hour';

Шифрование: данные в пути и в покое

SSL/TLS для соединений

Включение SSL - минимальное требование для production:

# postgresql.conf
ssl = on
ssl_cert_file = '/etc/postgresql/ssl/server.crt'
ssl_key_file = '/etc/postgresql/ssl/server.key'
ssl_ca_file = '/etc/postgresql/ssl/ca.crt'   # Для взаимной аутентификации mTLS

В pg_hba.conf - принудительно требовать SSL:

# pg_hba.conf - только hostssl, не host
hostssl all all 0.0.0.0/0 scram-sha-256

На стороне клиента - sslmode=verify-full (проверяет сертификат сервера + CN):

postgresql://user:pass@db.example.com/mydb?sslmode=verify-full

⚠️ sslmode=require (без verify-full) не защищает от атак «человек посередине» - он шифрует трафик, но не проверяет подлинность сервера.

pgcrypto: шифрование чувствительных полей

Для шифрования конкретных столбцов (пароли, токены, номера карт) - расширение pgcrypto:

CREATE EXTENSION pgcrypto;

-- Шифруем значение перед вставкой
INSERT INTO users (email, secret_token) VALUES (
  'user@example.com',
  pgp_sym_encrypt('my-secret-token', 'encryption-key-here')
);

-- Расшифровываем при чтении
SELECT email, pgp_sym_decrypt(secret_token::bytea, 'encryption-key-here')
FROM users WHERE id = 1;

-- Хэшируем пароли (bcrypt)
SELECT crypt('user-password', gen_salt('bf', 10));

-- Проверка:
SELECT crypt('user-password', stored_hash) = stored_hash;

⚠️ Точных данных о производительности pgcrypto на высоких нагрузках нет - тестируйте в вашем конкретном окружении.

Шифрование резервных копий

Бэкап без шифрования - это незашифрованная копия всех ваших данных:

# pg_dump + GPG шифрование
pg_dump -h localhost -U backup_user mydb | \
gpg --symmetric --cipher-algo AES256 -o backup_$(date +%Y%m%d).dump.gpg

# Восстановление
gpg -d backup_20260601.dump.gpg | psql -U postgres mydb

Ключ шифрования храните отдельно от бэкапа - в секрет-менеджере (HashiCorp Vault, Yandex Lockbox, AWS Secrets Manager).

Сетевая изоляция и конфигурация сервера

listen_addresses: не слушать лишнее

# postgresql.conf
# Только localhost или конкретный внутренний IP
listen_addresses = '127.0.0.1'

# Для сети: listen_addresses = '10.0.1.5'
# Никогда для production: listen_addresses = '*'

Порт 5432 - закройте на уровне firewall для всех, кроме разрешённых IP:

# iptables: разрешить только приложению (10.0.1.10)
iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.10 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP

pg_hba.conf: правила подключений

# Локальные соединения через Unix-сокет - peer (доверяем ОС)
local all postgres peer
local all all peer

# TCP/IP - только scram-sha-256, только SSL, только нужные подсети
hostssl mydb api_service 10.0.1.0/24 scram-sha-256
hostssl mydb analyst_ivan 10.0.2.5/32 scram-sha-256

# Всё остальное - запрет
host all all 0.0.0.0/0 reject

scram-sha-256 вместо md5

md5 в PostgreSQL - устаревший метод, уязвимый к rainbow-table атакам. Перевод на scram-sha-256 (доступен с PG 10):

# postgresql.conf
password_encryption = scram-sha-256

После смены параметра - обновить пароли всех ролей (иначе старые хэши остаются в pg_authid):

-- Для каждой роли:
ALTER ROLE api_service PASSWORD 'newStr0ngP@ss';

Чеклист: 30 пунктов за 30 минут

🔐 Блок 1. Роли и доступ (10 пунктов)

  • R1. Смените пароль суперпользователя postgres: ALTER USER postgres PASSWORD 'newPass';
  • R2. Никогда не подключайте приложение под postgres - создайте отдельную роль.
  • R3. Создайте иерархию ролей: readonly, app_user, migrator, dba_admin.
  • R4. Закройте public-схему: REVOKE ALL ON SCHEMA public FROM PUBLIC;
  • R5. Установите CONNECTION LIMIT для всех прикладных ролей.
  • R6. Проверьте роли с SUPERUSER: SELECT rolname FROM pg_roles WHERE rolsuper = true;
  • R7. Удалите или заблокируйте неиспользуемые роли: ALTER ROLE old_user NOLOGIN;
  • R8. Все функции с SECURITY DEFINER - зафиксируйте SET search_path внутри.
  • R9. Выдавайте права через DEFAULT PRIVILEGES - чтобы новые объекты наследовали политику.
  • R10. Ежеквартально аудируйте права: information_schema.role_table_grants.

🛡️ Блок 2. RLS и политики (5 пунктов)

  • L1. Включите RLS на всех таблицах с чувствительными данными: ENABLE ROW LEVEL SECURITY.
  • L2. Добавьте FORCE ROW LEVEL SECURITY для защиты даже от владельца таблицы.
  • L3. Создайте политику Default Deny (без политик = полный запрет).
  • L4. Для мультитенантных таблиц - используйте tenant_id + current_setting().
  • L5. Никогда не давайте прикладным ролям атрибут BYPASSRLS.

📋 Блок 3. Аудит и мониторинг (6 пунктов)

  • A1. Включите log_connections, log_disconnections, log_failed_auth.
  • A2. Установите log_line_prefix = '%m [%p] %q%u@%d '.
  • A3. Установите расширение pg_audit, добавьте в shared_preload_libraries.
  • A4. Настройте pgaudit.log = 'read, write, ddl, role'.
  • A5. Настройте log_min_duration_statement для выявления аномалий.
  • A6. Регулярно проверяйте pg_stat_activity на подозрительные соединения.

🔒 Блок 4. Шифрование (5 пунктов)

  • E1. Включите ssl = on, настройте пути к сертификатам.
  • E2. В pg_hba.conf замените host на hostssl.
  • E3. На клиентах установите sslmode=verify-full.
  • E4. Установите pgcrypto для шифрования чувствительных полей.
  • E5. Бэкапы шифруйте GPG или встроенным шифрованием облачного хранилища.

🌐 Блок 5. Сеть и обновления (4 пункта)

  • N1. Установите listen_addresses - только нужные IP, не *.
  • N2. Закройте порт 5432 на firewall для всех, кроме разрешённых хостов.
  • N3. Переключитесь на scram-sha-256 в pg_hba.conf и postgresql.conf.
  • N4. Подпишитесь на рассылку безопасности PostgreSQL и своевременно применяйте патчи.

Альтернативный взгляд

Некоторые архитекторы считают RLS «костылём», которым компенсируют слабую логику разграничения в приложении. Их аргумент: политики строк усложняют отладку, замедляют сложные JOIN-запросы и создают неочевидные зависимости. Для небольших монолитов с надёжным application-layer auth это может быть оправдано. Но в мультитенантных SaaS и системах с несколькими клиентскими приложениями RLS - последний рубеж обороны, когда в одном из слоёв возникает уязвимость.

Нетривиальный факт

Мало кто знает, что PostgreSQL по умолчанию не логирует успешные SELECT-запросы даже при log_statement = 'all' - если они выполняются через extended query protocol (prepared statements). Для полного аудита SELECT-запросов нужен pg_audit с флагом pgaudit.log = 'read'. Без этого целый класс операций чтения остаётся невидимым для аудитора.

FAQ

Что будет, если включить RLS без создания политик?

Таблица полностью закрывается для всех, кроме суперпользователя. Это не баг - это поведение Default Deny, задокументированное в официальной документации PostgreSQL. Сначала создайте политику, затем включайте RLS на production-таблицах.

Можно ли использовать md5 в 2026 году?

Технически работает, но md5 - устаревший метод аутентификации, уязвимый к rainbow-table атакам. С PostgreSQL 10 доступен scram-sha-256 - переходите на него. После смены password_encryption в postgresql.conf обязательно обновите пароли всех ролей через ALTER ROLE.

Влияет ли RLS на производительность?

Да, RLS добавляет overhead: к каждому запросу PostgreSQL автоматически подклеивает условие из политики. Минимизируйте влияние: используйте STABLE/IMMUTABLE функции в USING-условиях (PostgreSQL кеширует их результат), создавайте индексы по полям, которые участвуют в политике (например, tenant_id), проверяйте план через EXPLAIN ANALYZE.

Зачем pg_audit, если есть log_statement?

log_statement = 'all' не фиксирует SELECT-запросы, выполняемые через extended query protocol (prepared statements в JDBC, psycopg2, pgx). pg_audit закрывает этот пробел и пишет структурированный лог: тип операции, схема, объект, команда, параметры - в формате, удобном для парсинга и SIEM-систем.

Что такое BYPASSRLS и кому нельзя его давать?

BYPASSRLS - атрибут роли, полностью отключающий проверку Row-Level Security для этой роли. Нельзя выдавать прикладным пользователям, сервисным аккаунтам и ролям приложений. Допустимо только для DBA при отладке - и только временно, с обязательным отзывом после.

Нужен ли pg_audit для соответствия 152-ФЗ?

152-ФЗ обязывает фиксировать факты доступа к персональным данным. pg_audit с pgaudit.log = 'read, write' обеспечивает нужный уровень детализации. Точные требования к составу журнала аудита зависят от класса ИСПДн - уточняйте в приказах ФСТЭК и документах РКН.

Как проверить, что RLS реально работает?

Подключитесь под прикладной ролью (не postgres) и выполните SELECT * FROM protected_table. Затем проверьте EXPLAIN (ANALYZE, VERBOSE) SELECT * FROM protected_table - в плане должен быть виден фильтр из политики. Суперпользователь RLS не видит по определению - для тестирования используйте SET ROLE app_user.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также