Агент PostgreSQL — сервис рядом с серверами. Он держит собственное управление доступом PostgreSQL в согласии с подписанными политиками сервиса данных postgresql: одна роль на пользователя, которая в каждой базе данных, которую ведёт агент, содержит ровно то, что политики дают пользователю, и одна политика защиты строк на каждый фильтр строк. Затем PostgreSQL сам проверяет каждую инструкцию, и агент никогда не стоит между клиентами и сервером. Дистрибутив поставляет его как исполняемый JAR, agents/postgresql/grantforge-agent-postgresql-<version>.jar, которому нужна Java 17 или новее.
Интеграционные тесты Testcontainers в репозитории запускают агента и поставляемый JAR на PostgreSQL 15, 16, 17 и 18 с pgaudit. Это не сертифицирует все остальные версии; серверы старше 15 агент отклоняет. Плагин сервиса описан в разделе PostgreSQL.
Как это работает
- Одна роль на пользователя содержит то, что даёт GrantForge. Для каждого пользователя, которому политики что-то дают, агент ведёт роль с его именем,
grantforge_aliceдляalice, и делает пользователя членом, который имеет привилегии роли безSET ROLE. Агент пишет комментарий к каждой своей роли и не принимает роль без него за свою, поэтому не меняет ни других ролей, ни привилегий, которые выдал не он. - Каждая база данных меняется в одной транзакции. Агент входит в каждую базу данных, которую ведёт, и вносит в ней изменения одной транзакцией, так что пользователи видят версию политик применённой к базе целиком или не применённой вовсе.
- Целые таблицы, где ниже ничего не решается иначе. Тип доступа, который политика разрешает на всю таблицу, если ни одна политика не может отнять его у столбца, выдаётся на таблицу, так что столбцы, добавленные позже, сразу его получают. Иначе чтение, вставка и изменение выдаются по столбцам.
CONNECTк базе данных,USAGEна схему и на последовательности столбцов serial, в которые пользователь может вставлять, выдаются вместе с ними. - Фильтры строк только сужают. Фильтр строк — ограничительная политика защиты строк роли пользователя на таблице, для
SELECT. Где защита строк выключена, агент включает её вместе с разрешающей политикой, которая показывает всем все строки и названа по его префиксу (grantforge), так что для остальных ничего не меняется; он снова выключает её, когда у таблицы не остаётся ни фильтров, ни собственных политик. Защита строк, которую включил владелец, остаётся за владельцем, а политики агента только сужают то, что она показывает. Фильтр, который PostgreSQL отклоняет, фильтр, который изменил бы, к кому относится политика, или текст из более чем одной инструкции заменяется политикой, которая не показывает пользователю ни одной строки, и записывается в журнал. - Политики текущего момента. Каждая синхронизация планирует по политикам в том виде, в каком они действуют в этот момент, так что сроки действия начинаются и заканчиваются вовремя, и по пользователям, схемам, таблицам и столбцам, которые есть в PostgreSQL, так что новые учитываются при следующей синхронизации.
- Роли — это группы. Политика для группы применяется к пользователям, которые являются членами роли PostgreSQL с этим именем напрямую или через другие роли, вместе с группами, в которые их помещает GrantForge.
- Изменения вручную отменяются. При каждой синхронизации агент сравнивает привилегии своих ролей с тем, что показывает PostgreSQL, а свои политики защиты строк — с тем, что он создал в последний раз; то, что отличается, восстанавливается при следующей синхронизации. Если ничего не изменилось, агент ничего не отправляет.
- Привилегии вне GrantForge сообщаются. PostgreSQL складывает привилегии, поэтому агент не может отнять то, что пользователь имеет сам, через другую роль или через PUBLIC, и то, что он может делать с таблицами, которыми владеет. Он один раз предупреждает о каждой такой привилегии, которая даёт то, что запрещает политика, называя пользователя, привилегию, то, как пользователь её имеет, и политику.
- Доступы берутся из журнала сервера. Агент читает журнал сервера PostgreSQL в формате JSON: журнал сеансов pgaudit для того, что PostgreSQL разрешил, — по событию на каждую таблицу, которую инструкция прочитала или записала, — и отказы PostgreSQL из-за отсутствующей привилегии — по одному запрещённому событию на то, что называет ошибка. Каждое событие сообщает, решил ли его GrantForge, то есть решают ли политики так же, как PostgreSQL, или его разрешила привилегия вне GrantForge.
| Инструкция | Событие доступа |
|---|---|
SELECT, COPY … TO, читающая часть других инструкций |
select на каждой прочитанной таблице |
INSERT, COPY … FROM |
insert на записанной таблице |
UPDATE, MERGE, SELECT … FOR UPDATE |
update на таблице |
DELETE, TRUNCATE |
delete, truncate на таблице |
CREATE TRIGGER |
trigger на таблице |
CREATE TABLE, CREATE VIEW и другие объекты |
create на схеме |
CREATE SCHEMA, CREATE TEMPORARY TABLE |
create_schema, temporary на базе данных |
Отклонённое подключение, схема, отклонённая из-за отсутствия USAGE |
connect на базе данных, usage на схеме |
Развёртывание
-
Добавьте сервис данных
postgresqlи настройте его политики. Откройте Права доступа к данным → Агенты, выберите сервис и выпустите токен агента. Он показывается только один раз; сохраните его в файл, доступный для чтения только учётной записи агента. -
Создайте пользователя PostgreSQL для агента как суперпользователя. Он выдаёт привилегии на таблицы, которыми не владеет, включает и выключает защиту строк, ведёт роли и читает журнал сервера:
CREATE ROLE grantforge LOGIN SUPERUSER PASSWORD '…';Тесты запускают агента как суперпользователя. Суперпользователей не ограничивают ни привилегии, ни защита строк, поэтому агент не ведёт для них ролей.
-
Чтобы сообщать о доступах, пусть PostgreSQL пишет журнал сервера в JSON и загружает pgaudit — из пакетов PGDG, например
postgresql-17-pgaudit, или платформы — вpostgresql.conf, после чего перезапустите сервер:logging_collector = on log_destination = 'stderr,jsonlog' shared_preload_libraries = 'pgaudit' pgaudit.log = 'read, write, ddl' pgaudit.log_relation = on pgaudit.log_catalog = offБез pgaudit агент сообщает о том, что PostgreSQL отклонил, но не о том, что он разрешил; без журнала JSON он ни о чём не сообщает и говорит об этом один раз.
-
Напишите файл параметров, заменив адреса, пути и имена из примера:
grantforge.server.url=https://grantforge.example.com grantforge.token.file=/etc/grantforge/postgresql-agent.token grantforge.instance=postgresql-agent-1 grantforge.cache.dir=/var/lib/grantforge/postgresql-agent grantforge.postgresql.hosts=postgres1.internal:5432,postgres2.internal:5432 grantforge.postgresql.user=grantforge grantforge.postgresql.password.file=/etc/grantforge/postgresql-agent.password grantforge.postgresql.ssl.mode=verify-full grantforge.postgresql.tls.ca=/etc/grantforge/postgresql-ca.pemЭти пути находятся на машине агента, а каталог кэша должен быть доступен для записи. Файлы паролей читаются в UTF-8 с обрезкой пробелов.
-
Запустите агента, например как службу systemd:
java -jar agents/postgresql/grantforge-agent-postgresql-<version>.jar /etc/grantforge/postgresql-agent.propertiesЗамените
<version>версией дистрибутива. Параметры, переданные какjava -Dgrantforge.…=… -jar …, переопределяют файл. По SIGTERM агент корректно останавливается и отправляет или ставит в очередь ожидающие события; PostgreSQL сохраняет то, что получил. -
Перенесите существующие привилегии в политики: привилегии, выданные вне GrantForge, продолжают действовать рядом с ролями агента. Создайте соответствующие политики, затем отзовите привилегии; предупреждения агента называют те, что дают запрещённое политикой.
| Параметр | Назначение |
|---|---|
grantforge.server.url, grantforge.token.file, grantforge.instance, grantforge.cache.dir |
Обязательные: сервер GrantForge, файл токена агента, имя этого агента и доступный для записи каталог для подписанного снимка, событий в очереди и позиции чтения журнала сервера |
grantforge.signing.key.file |
Необязательный файл, закрепляющий ключ подписи снимков; скопируйте открытый ключ Ed25519 X.509 в Base64 со страницы агентов. Если его нет, ключ берётся с сервера при первом обращении |
grantforge.postgresql.hosts, grantforge.postgresql.user |
Обязательные: серверы как host:port, перебираемые по очереди, пока доступный сервер не окажется основным, и пользователь PostgreSQL агента |
grantforge.postgresql.database |
База данных, в которую агент входит первой, чтобы читать роли, базы данных и журнал сервера, по умолчанию postgres |
grantforge.postgresql.password.file |
Пароль пользователя; если не задан, агент входит без пароля |
grantforge.postgresql.ssl.mode |
TLS так, как его называет sslmode из libpq, по умолчанию prefer |
grantforge.postgresql.tls.ca |
verify-ca и verify-full: сертификат УЦ в формате PEM, проверяющий серверы; пусто — доверие тому, чему доверяет Java |
grantforge.postgresql.tls.cert, grantforge.postgresql.tls.key |
Клиентский сертификат PEM и его закрытый ключ PKCS #8, вместе, чтобы входить по сертификату (аутентификация cert), а не по паролю; им нужен require или режим проверки, а режим проверки при этом требует grantforge.postgresql.tls.ca |
grantforge.postgresql.timeout.ms |
Сколько могут длиться подключение и инструкция, по умолчанию 30000, диапазон 1..600000 миллисекунд |
grantforge.postgresql.databases |
Базы данных, которые ведёт агент, через запятую; пусто — все базы, допускающие подключения |
grantforge.postgresql.role.prefix |
Начало имён ролей агента, по умолчанию grantforge_; буквы, цифры и подчёркивания, в конце подчёркивание, не более 32 символов и не начиная с pg_. Агенту принадлежат отмеченные им роли, имена которых так начинаются, и политики защиты строк, названные по ним или по префиксу без последнего подчёркивания |
grantforge.postgresql.unmanaged.users |
Пользователи, которых агент не трогает, через запятую, например администраторы; своего пользователя и суперпользователей он не трогает всегда |
grantforge.sync.interval.ms |
Как часто агент приводит PostgreSQL в согласие, по умолчанию 10000, диапазон 1000..3600000 миллисекунд |
grantforge.audit.server.log |
По умолчанию true; false — не читать журнал сервера и не сообщать о доступах |
grantforge.audit.server.log.interval.ms, grantforge.audit.server.log.max.bytes |
Как часто читается журнал, по умолчанию 5000 миллисекунд, и наибольшее число байтов за одно чтение, по умолчанию 16 МиБ, так что отставание читается за несколько проходов |
grantforge.connect.timeout.ms, grantforge.read.timeout.ms |
Тайм-ауты подключения и чтения к GrantForge, по умолчанию 5000 / 8000 миллисекунд |
grantforge.refresh.interval.ms |
Как часто агент запрашивает новые политики, по умолчанию 30000, не менее 1000 миллисекунд |
grantforge.audit.batch.size, grantforge.audit.queue.capacity, grantforge.audit.flush.interval.ms, grantforge.audit.spool.limit.bytes |
События доступа: размер пакета (по умолчанию 500), размер очереди (по умолчанию 10000), интервал отправки (по умолчанию 5000 миллисекунд) и предел очереди на диске (по умолчанию 64 МиБ) |
grantforge.audit.fold.window.ms |
Сколько времени одинаковые разрешённые доступы сворачиваются в одно событие, по умолчанию 10000, до 3600000 миллисекунд; 0 — отправлять каждое |
Пользователи, группы и роли
Политики называют пользователей PostgreSQL — роли, которые могут входить. Группы — это роли, членом которых пользователь является напрямую или через другие роли, вместе с группами, в которые GrantForge помещает пользователя; роли — это роли, которые GrantForge даёт пользователю.
Решения и ограничения
- Без снимка агент ничего не меняет. Сервис, который не используется, или токен, привязанный к сервису с типом не
postgresql, ничего не выдаёт: агент удаляет свои роли и политики защиты строк, и действуют собственные привилегии PostgreSQL. - Пока агент не работает, PostgreSQL сохраняет то, что получил последним; изменения политик ждут агента. Изменение доходит до PostgreSQL в пределах интервала обновления и интервала синхронизации.
- Таблицы, созданные после синхронизации, получают привилегии при следующей синхронизации.
- Суперпользователи и владельцы таблицы могут делать с ней всё; суперпользователей, роли с
BYPASSRLSи владельцев таблицы не ограничивает её защита строк, если владелец её не принудил. - Представление читает свои таблицы с привилегиями своего владельца, если оно не
security_invoker: журнал сервера сообщает о таблицах, прочитанных представлением, как о разрешённых привилегией вне GrantForge. - Прямое чтение секции не фильтруется защитой строк её секционированной таблицы.
- pgaudit записывает инструкцию до того, как PostgreSQL её выполнит, поэтому инструкция, которая потом завершается неудачей, например вставка, чья последовательность отклонена, сообщается как разрешённая, а её отказ — как запрещённый. Инструкции сеанса, который pgaudit не записывает, не сообщаются.
- Отказы PostgreSQL называют таблицу без схемы: событие называет таблицу, которую уточняет инструкция, единственную таблицу с таким именем или таблицу в
public. Объект, созданный без схемы, сообщается вpublic. - Агент читает журнал того сервера, в который входит, — основного; журналы реплик не читаются.
- В PostgreSQL нет маскирования столбцов, поэтому политики не могут маскировать значения.
