Конфигурация¶
agate-server (точка входа в Docker) настраивается одним файлом agate.toml
со слоем переопределений из окружения. Смонтируйте файл в контейнер и укажите на
него AGATE_CONFIG. Готовый шаблон —
agate.example.toml.
Источники и приоритет¶
От низшего к высшему — каждый следующий слой переопределяет предыдущий:
- Встроенные значения по умолчанию.
agate.toml— путь изAGATE_CONFIG(по умолчанию/etc/agate/agate.toml). Отсутствие файла допустимо — применяются значения по умолчанию.- Окружение —
AGATE__SECTION__KEY(в верхнем регистре,__между уровнями). Секреты предпочтительно задавать через окружение.
То есть [audit].database_url переопределяется через AGATE__AUDIT__DATABASE_URL.
docker run --rm \
-v "$PWD/agate.toml:/etc/agate/agate.toml:ro" \
-e AGATE_CONFIG=/etc/agate/agate.toml \
-e AGATE__AUDIT__DATABASE_URL='postgres://agate:secret@db:5432/agate' \
ghcr.io/c3equalzz/agate
Отсутствие обязательного значения (proxy.agent_endpoint,
audit.database_url) прерывает запуск — отказ при неверной конфигурации вместо
работы в деградированном режиме.
[proxy]¶
| Ключ | Обязателен | По умолчанию | Значение |
|---|---|---|---|
agent_endpoint |
да | — | URL вышестоящего AG-UI-агента, которому Agate пересылает проинспектированный трафик. |
bind |
нет | 0.0.0.0:8080 |
Адрес/порт, который Agate слушает для входящего AG-UI-трафика. |
connect_timeout_secs |
нет | 5 |
Быстрый таймаут подключения к вышестоящему агенту. |
read_timeout_secs |
нет | 60 |
Таймаут простоя между чанками SSE-ответа. Не общий дедлайн — здоровый поток работает дальше. |
max_body_bytes |
нет | 1048576 |
Максимальный размер тела запроса (1 МиБ). Слишком большие запросы получают 413. |
max_concurrent_requests |
нет | 256 |
Максимум одновременно обрабатываемых прогонов. Каждый держит соединение с агентом на всё время потока; запросы сверх лимита отбрасываются с 503 (не ставятся в очередь), чтобы наплыв не исчерпал память/соединения. |
max_response_events |
нет | 100000 |
Лимит на число событий ответа, отдаваемых клиенту за прогон. Агент, превысивший его, отрезается с RUN_ERROR. 0 = без лимита — это потоковый счётчик, а не буфер, поэтому 0 отключает guard против флуда клиента (availability), а не ограничение памяти (память на run ограничена max_frame_bytes). |
max_frame_bytes |
нет | 1048576 |
Максимум байт, буферизуемых для одного ещё-не-завершённого SSE-события. Учитывается, пока кадр ещё принимается (лимиты на run выше считают только завершённые события), поэтому upstream, отправляющий кадр без терминатора, не может неограниченно растить память. Превышение завершает run через RUN_ERROR. Должно быть > 0 — 0 вернул бы неограниченное поведение. Корректное AG-UI событие — несколько КиБ. |
max_response_bytes |
нет | 67108864 |
Лимит на число байт ответа, отдаваемых клиенту за прогон (64 МиБ). 0 = без лимита. Как и max_response_events, это потоковый счётчик, а не буфер — 0 это availability-trade-off, а не ограничение памяти. |
rate_limit_per_second |
нет | 0 |
Постоянная частота запросов на один IP клиента. Превышение отбрасывается с 429 Too Many Requests и подсказкой Retry-After. 0 = выключено (по умолчанию). IP берётся из пира соединения, поэтому включайте только там, где Agate видит реального клиента. За обратным прокси (nginx) или балансировщиком все запросы идут с одного IP прокси — тогда лимит зарежет всех клиентов как одного и начнёт отклонять легитимный трафик, поэтому оставьте 0 и ограничивайте частоту на самом прокси. |
rate_limit_burst |
нет | 0 |
Глубина всплеска для лимита на IP — наибольший мгновенный всплеск до выхода на постоянную частоту. 0 означает откат к rate_limit_per_second. |
api_key |
нет | — | Одиночный API-ключ в заголовке X-API-Key (сокращение, объединяется с api_keys). Секрет лучше через AGATE__PROXY__API_KEY. |
api_keys |
нет | [] |
Принятые API-ключи; запрос аутентифицирован, если совпал с любым (иначе 401). Несколько ключей одновременно дают ротацию без даунтайма: добавить новый, перевести клиентов, убрать старый. Если api_key и api_keys пусты — прокси открыт. |
Пробы liveness и readiness
/healthz (liveness) возвращает 200, пока процесс жив. /readyz
(readiness) возвращает 200 только когда хранилище журнала прозрачности
доступно, иначе 503 — направьте на него readiness-пробу оркестратора,
чтобы трафик удерживался, пока Agate не сможет записывать. Проверка
доступности скрыта за портом HealthCheck, поэтому остаётся корректной при
смене бэкенда хранилища. Обе пробы обходят проверки API-ключа и размера тела.
[audit]¶
| Ключ | Обязателен | По умолчанию | Значение |
|---|---|---|---|
backend |
нет | postgres |
Какой бэкенд хранилища собирается при старте. Сейчас postgres; другие бэкенды подключаются через Cargo-фичи. |
database_url |
да | — | Строка подключения PostgreSQL для журнала прозрачности (для backend = "postgres"). Миграции выполняются при старте. Пароль лучше задавать через AGATE__AUDIT__DATABASE_URL. |
max_connections |
нет | 10 |
Максимум подключений в пуле к базе. |
acquire_timeout_secs |
нет | 30 |
Сколько ждать свободного подключения из пула до ошибки. |
connect_max_retries |
нет | 10 |
Сколько раз повторить начальное подключение перед отказом старта (0 = одна попытка). Позволяет переждать БД, которая ещё поднимается рядом с Agate (compose/Kubernetes), вместо падения на первой неудаче. |
connect_backoff_secs |
нет | 1 |
Базовый бэкофф между попытками подключения (удваивается каждый повтор, с потолком). |
checkpoint_interval_secs |
нет | 0 |
Как часто фоновая задача выпускает подписанный чекпоинт (STH) для журнала, в секундах (0 = выключено). Собственная каденция доказуемости журнала (как частота STH в CT-логе); неизменное между тиками дерево подписывается, но повторно не якорится. Требует ключ подписи (ниже). |
checkpoint_key_id |
нет | checkpoint-ed25519 |
Идентификатор ключа подписи, который запрашивает периодический издатель; должен совпадать с загруженным в хранилище (AUDIT_CHECKPOINT_KEY_ID). |
outbox_capacity |
нет | 1024 |
Сколько инспектированных записей может стоять в очереди в журнал прозрачности до заполнения outbox. Ограничено, чтобы медленная БД не растила память без предела. |
outbox_on_full |
нет | block |
Что делает прокси при полном outbox: block (backpressure — медленная БД замедляет прокси, но запись не теряется; по умолчанию) или shed (отбросить запись, с громким логом и счётчиком, чтобы прокси продолжал работать ценой пробела в журнале). |
Ключ подписи чекпоинтов¶
Ed25519-ключ задаётся только через окружение, никогда не в файле конфигурации:
AUDIT_CHECKPOINT_SEED— 32-байтовый seed как 64 hex-символа. Без него подпись чекпоинтов выключена (периодическому интервалу нечего подписывать).AUDIT_CHECKPOINT_KEY_ID— необязательный id ключа (по умолчаниюcheckpoint-ed25519); держите его равным[audit].checkpoint_key_id.
Журнал, в который добавляются записи, закрепляется переменной окружения
AUDIT_LOG_ID (UUID). Если она не задана, при старте создаётся новый журнал и
его id печатается, чтобы закрепить его при следующем запуске.
[policy.tools] и [policy]¶
Все ключи политики необязательны. Если ничего не задано, все инструменты разрешены и ничего не редактируется.
| Ключ | Формат | Значение |
|---|---|---|
[policy.tools].mode |
allow-all | allowlist | denylist |
Как авторизуются вызовы инструментов. По умолчанию allow-all. |
[policy.tools].names |
массив сопоставителей инструментов | Инструменты, управляемые mode (игнорируются при allow-all). Каждая запись сопоставляется с полным именем инструмента, с учётом регистра, по виду: голое имя — точное (search); glob: — shell-стиль */? (glob:fs.*); regex: — регулярное выражение, заякоренное на всё имя (regex:db_.*). Поэтому search никогда не совпадёт с research, а glob:fs.* покрывает все инструменты fs.. Неверный glob/regex прерывает запуск. |
[[policy.tools.deny_arguments]] |
таблицы { tool?, path?, contains \| matches } |
Правила запрета по аргументам: разрешённый вызов инструмента блокируется, если его аргументы совпадают. Каждое правило задаёт ровно одно из contains (литерал без учёта регистра) или matches (regex). tool ограничивает правило одним инструментом; опустите его — правило применится к любому. path (точечный путь вроде url или config.endpoint) ограничивает совпадение одним полем разобранных аргументов, чтобы оно не срабатывало на другом поле с тем же текстом; опустите — совпадение по всей сырой строке аргументов. Правило с path не срабатывает, если аргументы не валидный JSON или путь отсутствует. |
[[policy.tools.deny_results]] |
таблицы { tool?, path?, contains \| matches } |
Правила запрета по результатам: результат инструмента блокируется (отбрасывается до клиента), если его содержимое совпадает — зеркало deny_arguments для возвращаемого. Та же форма tool / path / contains / matches. tool срабатывает только когда имя инструмента результата известно (сопоставлено по началу вызова) и совпадает. |
[policy].redact |
массив литеральных маркеров | Подстроки, маскируемые (без учёта регистра) в исходящем тексте и результатах инструментов до их доставки клиенту. |
[policy].redact_regex |
массив regex-паттернов | Regex-маркеры, маскируемые в исходящем тексте и результатах инструментов (полный синтаксис regex; префикс (?i) для регистронезависимости). Невалидное выражение прерывает запуск. |
[policy].fail_mode |
open | closed |
Что делать при таймауте решения политики: переслать (open) или заблокировать (closed). По умолчанию closed (безопасность важнее доступности). |
[policy].decision_timeout_ms |
целое (мс) | Дедлайн одного решения политики. По умолчанию 5000; должен быть > 0. |
[policy].on_malformed_event |
forward | drop | terminate |
Что делать с распознанным, но битым событием ответа (известный тип без обязательного поля), которое нельзя инспектировать. forward пересылает сырой фрейм, drop отбрасывает его, terminate завершает прогон. По умолчанию terminate (оно не должно обходить политику). |
[policy.session_memory].enabled |
bool | Память реплея между прогонами: если инструмент запрещён в одном прогоне, отказывать ему (по имени) до конца сессии, чтобы агент не повторил его с другими аргументами в следующем прогоне. Эшелонированная защита поверх stateless-политики. По умолчанию false. |
[policy.session_memory].ttl_secs |
целое (с) | Сколько карантин сессии живёт без активности. Сессия, простаивающая дольше, забывается. По умолчанию 3600; должно быть > 0 при включении. |
[policy.session_memory].backend |
memory | redis |
Где хранится ledger. memory — в пределах процесса (теряется при рестарте, не шарится между репликами); redis — общий для реплик и переживает рестарт. По умолчанию memory. Redis-backend fail-open: если Redis недоступен, деградирует до «нет памяти», никогда не wrong-allow. |
[policy.session_memory].redis_url |
строка | URL подключения к Redis (например, redis://127.0.0.1:6379). Обязателен при backend = "redis", иначе игнорируется. |
[policy].backend |
ruleset | cel | rego |
Какой движок принимает вердикты. ruleset (по умолчанию) — встроенная статическая политика, описанная выше. cel и rego передают каждое решение политике оператора (из [policy.cel] / [policy.rego]) — статические правила выше тогда не учитываются. Выбор cel / rego требует сборки с Cargo-feature policy-cel / policy-rego. |
[policy.cel].policy_path |
строка | Путь к файлу CEL-политики (TOML-список записей [[rule]]; см. ниже). Обязателен при backend = "cel". Каждое правило компилируется при старте, поэтому ошибка разбора прерывает процесс. |
[policy.cel].watch |
bool | Авто-перезагрузка файла политики при его изменении на диске, поверх всегда-включённого SIGHUP. По умолчанию false (opt-in — слежение опирается на inotify/FSEvents платформы и может не срабатывать на некоторых сетевых ФС). Перезагрузка по watch — та же fail-safe перезагрузка, что и по SIGHUP. |
[policy.cel].max_rules |
целое (> 0) | Максимум записей [[rule]] в CEL-политике. По умолчанию 1000. Вычисление синхронно и линейно по числу правил, поэтому это ограничивает стоимость одного решения; политика с бо́льшим числом правил отклоняется при загрузке и при перезагрузке (работающая политика сохраняется). По умолчанию щедро — у легитимных политик десятки–сотни правил. |
[policy.rego].policy_path |
строка | Путь к файлу Rego (OPA) политики (см. ниже). Обязателен при backend = "rego". Компилируется при старте, поэтому ошибка разбора прерывает процесс. |
[policy.rego].watch |
bool | Авто-перезагрузка Rego-политики при изменении файла, поверх SIGHUP. По умолчанию false; та же fail-safe семантика, что и у CEL watch. |
Неверная политика прерывает запуск
Пустое или некорректное имя инструмента, либо пустой паттерн редактирования, прерывает запуск — опечатка не должна молча ослаблять контроль.
Движок политик CEL (backend = "cel")¶
Статический набор правил выше покрывает частые случаи декларативно. Для политик,
которым нужны выражения — сравнения, булева логика, адресация вложенных полей, —
Agate поставляет альтернативный движок, вычисляющий правила на CEL (Common
Expression Language). CEL не полон по Тьюрингу (нет циклов и рекурсии), поэтому
любое выражение завершается. Вычисление синхронно и выполняется inline на
каждое событие, поэтому ограничитель decision_timeout_ms не прерывает
CEL-решение в процессе (он ограничивает лишь backend'ы, ждущие чего-то внешнего);
стоимость одного решения ограничивается вместо этого через max_rules. Это
отдельный backend PolicyPort, выбираемый через
[policy].backend = "cel", доступный только в сборке с Cargo-feature policy-cel
(cargo build -p agate-server --features policy-cel).
Файл политики — TOML-список таблиц [[rule]], вычисляемых по порядку;
побеждает первое правило, чьё when истинно. Если ни одно правило не
совпало, событие разрешается — правила перечисляют, что блокируется или
редактируется, поэтому для режима default-deny добавьте замыкающее правило-запрет
when = "true".
| Поле | Обязательно | Значение |
|---|---|---|
when |
да | CEL-булево выражение над action и context (ниже). |
effect |
да | deny (блок с reason), redact (заменить текст события) или allow (пропустить и прекратить вычисление). |
reason |
нет | Сообщение запрета (для effect = "deny"). По умолчанию — общая причина. |
replacement |
нет | CEL-строковое выражение, дающее текст замены (для effect = "redact"). Применяется только к сообщениям и результатам инструментов; redact-правило, совпавшее с любым другим видом события, падает в закрытую сторону (событие запрещается, а не пропускается). Выражение видит весь action, поэтому не возвращайте найденный секрет назад (replacement = 'action.text' замаскировал бы его сам в себя). Если оно падает или даёт не-строку, используется "[REDACTED]" (логируется). По умолчанию "[REDACTED]". |
Каждое правило видит две переменные. action — плоская карта, описывающая
событие; каждый ключ всегда присутствует (null, если неприменим), поэтому
правило может называть любое поле, не падая на отсутствующем ключе:
Поле action |
Присутствует для | Значение |
|---|---|---|
kind |
каждого события | "tool_call", "message", "tool_result", "state" или "other". |
name |
вызовов и результатов инструментов | Имя инструмента. |
arguments |
вызовов инструментов | Сырая строка аргументов. |
arguments_json |
вызовов инструментов | Аргументы, разобранные как JSON (адресация полей: action.arguments_json.url), или null, если это не валидный JSON. |
text |
сообщений | Текст исходящего сообщения. |
content |
результатов инструментов | Сырое содержимое результата. |
content_json |
результатов инструментов | Результат, разобранный как JSON, или null. |
state_json |
мутаций состояния | Полезная нагрузка состояния, разобранная как JSON, или null. |
context несёт идентичность прогона: context.session_id и context.run_id
(обе строки).
# cel-policy.toml — на него ссылается [policy.cel].policy_path
# Блокировать инструмент по имени.
[[rule]]
when = 'action.kind == "tool_call" && action.name == "delete_file"'
effect = "deny"
reason = "destructive tool is not permitted"
# Блокировать SSRF-подобный аргумент, адресуя разобранное поле. Сначала охраните
# nullable-поле: без `!= null` не-JSON аргумент заставит правило упасть и быть
# пропущенным (см. примечание про null-guard ниже), а не заблокировать.
[[rule]]
when = 'action.arguments_json != null && action.arguments_json.url.startsWith("http://169.254.169.254")'
effect = "deny"
reason = "link-local metadata endpoint"
# Заредактировать форму API-ключа в исходящем тексте сообщения.
[[rule]]
when = 'action.kind == "message" && action.text.contains("sk-")'
effect = "redact"
replacement = '"[REDACTED]"'
# Default-deny: всё, что явно не разрешено выше, блокируется.
[[rule]]
when = "true"
effect = "deny"
reason = "not permitted by policy"
Охраняйте nullable-поля — упавшее правило не блокирует
Правило, которое падает при вычислении (например, лезет в
action.arguments_json.url, когда аргументы не JSON и arguments_json
равно null), считается не совпавшим — логируется и пропускается — но
никогда не приводит к жёсткому отказу. Поэтому само по себе оно не
блокирует: если ни одно последующее правило не совпало, событие разрешается.
Охраняйте nullable-поля (action.arguments_json != null &&
action.arguments_json.url...) и, где нужен default-deny, завершайте файл
правилом-запретом when = "true".
Горячая перезагрузка (SIGHUP)¶
Пошлите процессу SIGHUP, чтобы перечитать и перекомпилировать файл политики
без рестарта (kill -HUP <pid> или docker kill --signal=HUP <container>). Новый
набор правил подменяется атомарно — решение, уже выполняющееся, сохраняет правила,
с которыми началось, и на пути запроса нет блокировок.
Перезагрузка fail-safe: если файл отсутствует, не парсится или какое-то
правило не компилируется, работающая политика сохраняется (ошибка
перезагрузки логируется, остаются прежние известно-хорошие правила) — плохая
правка никогда не оставит шлюз без политики. Перезагрузка, дающая ноль правил
(пустой или обрезанный файл — например, неатомарная запись, пойманная на лету),
тоже отклоняется, ведь отсутствие правил означает allow-all; при правке вживую
предпочитайте атомарную запись (во временный файл, затем rename). Перезагрузка по
SIGHUP только под Unix; на других платформах политика фиксируется при старте.
Задайте [policy.cel].watch = true, чтобы также перезагружать автоматически
при любом изменении файла на диске (кросс-платформенно, opt-in). Слежение идёт за
директорией файла — поэтому переживает атомарную запись temp-затем-rename, которой
пользуются редакторы, — и схлопывает пачку событий одного сохранения в одну
перезагрузку. Сама перезагрузка — тот же fail-safe путь, поэтому временный
пустой/недописанный файл отклоняется, а работающая политика сохраняется.
Движок политик Rego (backend = "rego")¶
Командам, уже вложившимся в Open Policy Agent,
Agate может принимать решения на Rego — языке политик OPA, — вычисляемом
pure-Rust интерпретатором regorus (без
sidecar, без WASM-рантайма). Как и CEL, он не полон по Тьюрингу, поэтому
вычисление всегда завершается; это отдельный backend PolicyPort, выбираемый
через [policy].backend = "rego", доступный только в сборке с Cargo-feature
policy-rego (cargo build -p agate-server --features policy-rego).
Файл политики — Rego-исходник в пакете agate.policy, определяющий правило
decision. Для каждого события Agate задаёт input = { "action": …,
"context": … } (та же проекция, что видит CEL-движок — input.action.kind,
input.action.name, input.action.arguments_json.…, input.context.run_id и
т.д.) и вычисляет data.agate.policy.decision, ожидая объект с effect из
allow / deny / redact (плюс опциональные reason / replacement):
# rego-policy.rego — на него ссылается [policy.rego].policy_path
package agate.policy
import rego.v1
# Блокировать инструмент по имени.
decision := {"effect": "deny", "reason": "destructive tool"} if {
input.action.kind == "tool_call"
input.action.name == "delete_file"
}
# Блокировать SSRF-подобный аргумент (сначала охраните nullable разобранное поле).
decision := {"effect": "deny", "reason": "metadata endpoint"} if {
input.action.arguments_json.url
startswith(input.action.arguments_json.url, "http://169.254.169.254")
}
# Заредактировать форму API-ключа в исходящем тексте сообщения.
decision := {"effect": "redact", "replacement": "[REDACTED]"} if {
input.action.kind == "message"
contains(input.action.text, "sk-")
}
Если decision не определён (ни одно правило не совпало), событие
разрешается — правила оператора перечисляют, что блокируется; для default-deny
добавьте замыкающий decision. Ошибка вычисления или некорректное решение
(не объект, либо effect отсутствует/неизвестен) падает в закрытую сторону
(deny) и логируется — сломанная политика никогда не разрешает молча. Редакция
следует тому же правилу, что и в CEL: применяется к сообщениям и результатам
инструментов, а redact-решение на любом другом виде события падает в закрытую
сторону. Горячая перезагрузка (SIGHUP) и [policy.rego].watch работают точно
как у CEL выше.
[observability.logging]¶
| Ключ | По умолчанию | Значение |
|---|---|---|
enabled |
true |
Устанавливать ли подписчик логов вообще; false отключает логи. |
format |
pretty |
pretty (консоль) или json (по объекту на строку, для сборщиков логов). |
level |
info |
Директива фильтра (например, agate_proxy=debug,info). RUST_LOG переопределяет её, если задана. |
На info видны события жизненного цикла: старт, каждый проксированный прогон,
запреты и редакции политики, создание журнала прозрачности. Поднимите до debug
(например, level = "agate_proxy=debug,info") для детализации по каждому событию
(каждое переданное/буферизованное событие, каждая добавленная запись аудита).
[observability.metrics]¶
Эндпоинт для Prometheus на отдельном порту, не на публичном data-plane порту (скрейпится из внутренней сети).
| Ключ | По умолчанию | Значение |
|---|---|---|
enabled |
false |
Устанавливать ли recorder + экспортёр метрик. Если выключено — метрики no-op. |
exporter |
prometheus |
prometheus (эндпоинт /metrics) или none. |
bind |
0.0.0.0:9090 |
Адрес, который слушает эндпоинт /metrics. |
Экспортируемые метрики:
agate_runs_total— проксированных прогонов.agate_events_inspected_total{outcome="forward|buffer|transform|deny|terminate"}— проинспектированные события по исходу (разбивка по вердиктам).agate_upstream_errors_total{kind="connect|timeout|status|stream"}— ошибки запроса/потока к вышестоящему агенту по видам.agate_audit_records_appended_total/agate_audit_records_dropped_total— записи в журнал прозрачности против дропов (ненулевой drop-rate = аудит не успевает, ставьте алерт).agate_audit_outbox_depth/agate_audit_outbox_capacity— gauge'и заполненности outbox; depth у capacity = растёт backpressure (прокси вот-вот замедлится приblockили начнёт шедить приshed). Алерт до насыщения.
Готовый стек Prometheus + Grafana с преднастроенным дашбордом — в
deploy/observability/.
[observability.tracing]¶
Экспорт трейсов по OTLP — третий столп наблюдаемости наряду с логами и метриками.
Когда трейсинг выключен, спаны всё равно создаются, но не экспортируются; в логах
они видны, только если [observability.logging].enabled = true (подписчик,
который и рендерит спаны в логи, и экспортирует их, поднимается лишь тогда).
| Ключ | По умолчанию | Значение |
|---|---|---|
enabled |
false |
Экспортировать спаны в OTLP-коллектор по gRPC. |
endpoint |
http://localhost:4317 |
OTLP gRPC-эндпоинт коллектора. |
service_name |
agate-server |
service.name в экспортируемых спанах. |
Спаны покрывают путь запроса от края до края:
proxy_run— по одному на каждый проксированный прогон на data-plane.audit.request— по одному на каждую отправленную команду/запрос аудита.TracingBehaviorоборачивает весь конвейер медиатора (самым внешним звеном, поверх behaviour'ов метрик и транзакции), поэтому каждый use case трассируется единообразно — новые use case'ы получают спан автоматически.db.log.load/db.log.save/db.proof.inclusion/db.proof.consistency— по одному на каждый SQL-оператор, вложенному в спанaudit.request, который его инициировал.
Когда трейсинг включён (и [observability.logging].enabled = true, чтобы
подписчик был установлен), спаны сбрасываются при graceful shutdown. Укажите
endpoint на OpenTelemetry Collector (или любой OTLP/gRPC-бэкенд), чтобы собирать
трейсы по каждому прогону.
[tls]¶
Терминация TLS на собственном слушателе Agate. По умолчанию выключено — тогда Agate отдаёт обычный HTTP, что разумно только за TLS-терминирующим шлюзом (ingress, балансировщик, service mesh). Включите, чтобы отдавать HTTPS напрямую (например, в zero-trust-развёртывании без отдельного терминатора).
| Ключ | По умолчанию | Значение |
|---|---|---|
enabled |
false |
Отдавать HTTPS вместо обычного HTTP. При false cert/key игнорируются. |
cert |
— | Путь к PEM-цепочке сертификатов (листовой сертификат первым). Обязателен при включении. |
key |
— | Путь к PEM-приватному ключу для cert. Обязателен при включении. |
При включении отсутствие или некорректность cert/key прерывает запуск
(отказ при неверно настроенном слушателе). Тогда и пробы, и data-plane отдаются
по одному TLS-слушателю на proxy.bind.
Полный пример¶
[proxy]
agent_endpoint = "http://agent:8000/run"
bind = "0.0.0.0:8080"
connect_timeout_secs = 5
read_timeout_secs = 60
max_body_bytes = 1048576
max_concurrent_requests = 256
max_response_events = 100000
max_response_bytes = 67108864
max_frame_bytes = 1048576 # макс. байт на одно незавершённое SSE-событие
rate_limit_per_second = 0 # лимит запросов на IP клиента; 0 = выключено
rate_limit_burst = 0 # глубина всплеска; 0 = откат к частоте
# api_key = "change-me" # один ключ; секрет лучше через AGATE__PROXY__API_KEY
# api_keys = ["current", "next"] # несколько ключей для ротации без даунтайма
[audit]
backend = "postgres"
# Пароль лучше задавать через AGATE__AUDIT__DATABASE_URL.
database_url = "postgres://agate@postgres:5432/agate"
max_connections = 10
acquire_timeout_secs = 30
connect_max_retries = 10
connect_backoff_secs = 1
checkpoint_interval_secs = 0 # 0 = выключено; включить вместе с AUDIT_CHECKPOINT_SEED
checkpoint_key_id = "checkpoint-ed25519"
outbox_capacity = 1024
outbox_on_full = "block" # block (backpressure) | shed (drop + alert)
[policy.tools]
mode = "allowlist"
names = ["search", "fetch", "glob:fs.*", "regex:db_.*"]
[[policy.tools.deny_arguments]]
tool = "search"
contains = "rm -rf"
[[policy.tools.deny_arguments]]
tool = "fetch"
path = "url"
matches = "^https?://169\\.254\\.169\\.254"
[[policy.tools.deny_results]]
contains = "BEGIN RSA PRIVATE KEY"
[policy]
backend = "ruleset" # или "cel" / "rego" (нужна соответствующая сборка); см. ниже
redact = ["sk-", "AKIA"]
redact_regex = ["sk-[A-Za-z0-9]{20,}", "AKIA[0-9A-Z]{16}"]
fail_mode = "closed"
decision_timeout_ms = 5000
on_malformed_event = "terminate"
# [policy.cel]
# policy_path = "/etc/agate/cel-policy.toml" # обязателен при backend = "cel"
# watch = false # авто-reload при изменении файла
# [policy.rego]
# policy_path = "/etc/agate/rego-policy.rego" # обязателен при backend = "rego"
# watch = false # авто-reload при изменении файла
[policy.session_memory]
enabled = false
ttl_secs = 3600
backend = "memory"
# redis_url = "redis://127.0.0.1:6379" # обязателен при backend = "redis"
[observability.logging]
enabled = true
format = "pretty"
level = "info"
[observability.metrics]
enabled = true
exporter = "prometheus"
bind = "0.0.0.0:9090"
[observability.tracing]
enabled = false
endpoint = "http://localhost:4317"
service_name = "agate-server"
[tls]
enabled = false
cert = "/etc/agate/tls/cert.pem"
key = "/etc/agate/tls/key.pem"