GrantForge

    Базы данных / Реляционные

    Агент PostgreSQL

    Запуск агента PostgreSQL рядом с серверами, чтобы политики GrantForge становились собственными ролями, привилегиями и политиками защиты строк PostgreSQL, вплоть до столбцов и строк, база за базой в одной транзакции на каждую, а доступы сообщались из журнала сервера.

    Агент 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 на схеме

    Развёртывание

    1. Добавьте сервис данных postgresql и настройте его политики. Откройте Права доступа к данным → Агенты, выберите сервис и выпустите токен агента. Он показывается только один раз; сохраните его в файл, доступный для чтения только учётной записи агента.

    2. Создайте пользователя PostgreSQL для агента как суперпользователя. Он выдаёт привилегии на таблицы, которыми не владеет, включает и выключает защиту строк, ведёт роли и читает журнал сервера:

      CREATE ROLE grantforge LOGIN SUPERUSER PASSWORD '…';

      Тесты запускают агента как суперпользователя. Суперпользователей не ограничивают ни привилегии, ни защита строк, поэтому агент не ведёт для них ролей.

    3. Чтобы сообщать о доступах, пусть 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 он ни о чём не сообщает и говорит об этом один раз.

    4. Напишите файл параметров, заменив адреса, пути и имена из примера:

      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 с обрезкой пробелов.

    5. Запустите агента, например как службу systemd:

      java -jar agents/postgresql/grantforge-agent-postgresql-<version>.jar /etc/grantforge/postgresql-agent.properties

      Замените <version> версией дистрибутива. Параметры, переданные как java -Dgrantforge.…=… -jar …, переопределяют файл. По SIGTERM агент корректно останавливается и отправляет или ставит в очередь ожидающие события; PostgreSQL сохраняет то, что получил.

    6. Перенесите существующие привилегии в политики: привилегии, выданные вне 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 нет маскирования столбцов, поэтому политики не могут маскировать значения.

    Связанные руководства

    Редактировать эту страницу на GitHub