Перейти к содержимому

Конфигурация

enodia читает до трёх файлов: enodia.yaml (обязателен — инвентарь ваших сервисов), опциональный отдельный credentials.yaml и опциональный settings.yaml (личные настройки отображения, никогда не обязателен). Все три — обычный YAML.

schemaVersion: 1
credentials_file: credentials.yaml # опционально, см. ниже
defaults: # опционально
timeout: 10s
concurrency: 5
retries: 2
backoff: 500ms
cve: # опционально, см. «Сопоставление с CVE»
bdu:
path: vulxml.zip
nvd:
path: nvd/
debian:
path: debian.json
oval:
path: oval/
alpine:
path: alpine/
mariadb:
path: mariadb.md
atlassian:
path: atlassian.json
postgresql:
path: postgresql/
nginx:
path: nginx.html
update: # параметры TLS для «enodia cve update»
ca_file: russian-trusted.pem
credentials: {} # опционально, см. «Credentials»
targets: [] # ваши сервисы

schemaVersion проверяется при чтении — будущая версия схемы будет отклонена с советом обновиться, а не разобрана «на авось».

Применяется к каждому таргету, если не переопределено на его уровне.

Поле Тип Значение
timeout duration Таймаут на запрос (по умолчанию 10s, если нигде не задано)
concurrency int Сколько таргетов опрашивается одновременно
retries int Число повторов — повторяется только ErrUnreachable; отклонённый credential не станет лучше со второй попытки
backoff duration Задержка между повторами

Длительности — в синтаксисе Go: 500ms, 10s, 2m, 1h30m.

Опционально. Указывает enodia на выгрузку БДУ ФСТЭК (cve.bdu.path), JSON-фиды NVD (cve.nvd.path) и, для сопоставления по пакетам на Linux-хостах, на выгрузку Debian Security Tracker (cve.debian.path), OVAL-файлы вендоров (cve.oval.path) и secdb Alpine (cve.alpine.path), а также собственные данные вендоров: таблицу исправленных CVE MariaDB (cve.mariadb.path), данные Atlassian (cve.atlassian.path), PostgreSQL (cve.postgresql.path) и nginx (cve.nginx.path) — любые из них, каждый работает и по отдельности. enodia cve update скачивает их по этим путям (её параметры TLS задаются в cve.update: ca_file, ca_dir, tls_skip_verify), либо вы скачиваете их сами; check, collect и serve никогда ничего не загружают. Относительные пути резолвятся относительно директории этого конфига, а указанный, но несуществующий путь — это ошибка. Что это даёт, как получить файлы и какие продукты сопоставляются — см. Сопоставление с CVE.

Одна запись на сервис:

targets:
- id: jira-main # обязательно, стабилен при переименованиях - на нём завязаны метрики и история
name: Jira (production) # опционально, по умолчанию = id
product: jira # обязательно - см. Поддерживаемые продукты
address: https://jira.example.com # обязательно
credentials: jira-token # опционально, имя записи в credentials:
timeout: 15s # опционально, переопределяет defaults.timeout
path: /rest/api/2/serverInfo # опционально, специфично для продукта - у большинства проб разумный дефолт уже есть
method: GET # опционально
headers: # опционально, доп. заголовки на каждый запрос
X-Custom: value
allow_insecure_transport: false # опционально - см. «Сначала HTTPS» в Концепциях
tls: # опционально, см. «TLS» ниже
ca_file: /etc/enodia/ca.pem
options: # опционально, специфичные для продукта настройки
key: value
parser: # только для product: generic - см. ниже
type: regex

address пишется ровно так, как вы бы его набрали — каждая проба разбирает его сама. Голый хост без префикса https:///http:// резолвится автоматически (см. Концепции), либо запустите enodia config resolve, чтобы увидеть, какую схему выберет каждый таргет, не отправляя ни одного credential.

options — произвольная карта для конкретного продукта; большинство проб её вообще игнорируют. p4d/p4p — первые, кто реально читает из неё значение: options.binary переопределяет путь к CLI p4, к которому они обращаются. freeradius читает options.container (и options.container_runtime), чтобы найти FreeRADIUS, запущенный в Docker или Podman, а postgresql читает options.database.

Раздел «Настройка продуктов» в боковом меню (или таблица Поддерживаемые продукты) содержит точный эндпоинт, требования к аутентификации и записываемые поля для каждой из 123 встроенных проб — path, credentials и options выше описывают общую форму; страница конкретного продукта говорит, что ему нужно на самом деле.

Три уровня, в порядке убывания корректности:

tls:
ca_file: /etc/enodia/corp-ca.pem # корпоративный CA-бандл - в большинстве закрытых контуров своя PKI
pin_sha256: # отпечаток(и) закреплённого leaf-сертификата
- "AB:CD:...:EF"
server_name: internal.example.com # переопределение SNI
min_version: "1.2" # минимальная версия TLS
insecure: true # последний резерв - см. ниже

insecure: true предупреждает при каждом запуске, а не только при валидации, потому что у этой настройки есть привычка добавляться «временно» и жить годами. Она же попадает в observation, так что отчёт заодно становится аудитом TLS по всему флоту — видно, какие сервисы проверяются без верификации.

Именованные записи, на которые ссылается поле credentials: таргета:

credentials:
jira-token:
kind: bearer
value: "${JIRA_TOKEN}"
gitlab-token:
kind: token-header
header: PRIVATE-TOKEN
value: "${GITLAB_TOKEN}"
vault-basic:
kind: basic
username: enodia
password: "${VAULT_PASSWORD}"
redis-auth:
kind: password
password: "${REDIS_PASSWORD}"
linux-host-ssh:
kind: ssh-key
username: enodia-ro
private_key_file: /etc/enodia/ssh/id_ed25519
passphrase: "${SSH_KEY_PASSPHRASE}" # опционально, только если ключ зашифрован
kind Используемые поля Что отправляется
none (дефолт, если не указан) — никакой credential
bearer value Authorization: Bearer <value>
token-header header, value произвольный заголовок, напр. PRIVATE-TOKEN, X-Vault-Token
basic username, password HTTP Basic auth
password password (плюс username — для протоколов, где он нужен: Redis ACL, PostgreSQL) нативная для протокола аутентификация (Redis AUTH, пароль SQL-подключения, …)
ssh-key username, private_key_file, passphrase (опционально) SSH-аутентификация по публичному ключу — для SSH-проб определения ОС (см. Поддерживаемые продукты)

Для SSH-таргетов также работает просто username вместе с kind: password и без private_key_file — SSH-пробы принимают либо пароль, либо приватный ключ, как и любой SSH-клиент (username плюс password под kind: password, либо username плюс private_key_file под kind: ssh-key).

kind у credential должен совпадать с тем, что читает его продукт: HTTP-продукты читают basic, bearer или token-header; SSH-пробы — ssh-key или password; Redis, PostgreSQL и другие пробы на wire-протоколах — password. Поэтому логин и пароль для веб-интерфейса или REST API (RouterOS, Harbor, Jenkins, BMC) — это kind: basic, а не kind: password. Какие виды принимает продукт, сказано на его собственной странице. Несовпадение — жёсткая ошибка и в enodia config validate, и при каждом запуске, с перечислением видов, которые продукт принимает; прежние версии такой credential молча отбрасывали и отправляли запрос вообще без аутентификации, а он потом падал с ошибкой авторизации, которая указывала куда угодно, только не на причину.

Каждая SSH-проба использует тот же блок tls:, что и HTTPS-пробы для проверки сертификата — pin_sha256 здесь хранит hex SHA-256 не TLS- сертификата, а самого ключа хоста в его wire-кодировке, но форма та же: «закрепите отпечаток, либо явно укажите insecure и получите предупреждение»:

targets:
- id: linux-host
product: debian
address: host.example.com
credentials: linux-host-ssh
tls:
pin_sha256:
- "AB:CD:...:EF" # sha256 ключа хоста, ssh-keyscan или аналог
# insecure: true # последний резерв — полностью отключает проверку ключа хоста

Если не указаны ни pin_sha256, ни insecure: true, соединение отклоняется ещё до отправки хоть одного credential.

Отдельный файл той же формы, что и инлайновая карта credentials::

credentials.yaml
jira-token:
kind: bearer
value: "${JIRA_TOKEN}"
enodia.yaml
schemaVersion: 1
credentials_file: credentials.yaml
targets:
- id: jira-main
product: jira
address: https://jira.example.com
credentials: jira-token

Именно это позволяет коммитить инвентарь сервисов в git, оставляя секреты полностью снаружи. Записи из credentials_file имеют приоритет над инлайновой записью с тем же именем. credentials_file резолвится относительно файла конфига, который его называет, а не относительно текущей директории.

Любое строковое значение в enodia.yaml или credentials.yaml может ссылаться на переменную окружения:

  • ${VAR} — заменяется значением $VAR; если переменная не задана — ошибка.
  • ${VAR:-default} — заменяется значением $VAR, либо default, если переменная не задана.

Начиная с 2.2.1 подстановка выполняется после разбора YAML, внутри каждого строкового значения, так что значение берётся дословно: пароль с обратным слешем, #, : или ведущим *, & или [ больше не ломает файл или enodia config validate, а значение, похожее на число или true, остаётся строкой. До 2.2.0 включительно переменная сначала вставлялась в сырой текст файла, поэтому такое значение приходилось вручную брать в кавычки в YAML.

Ни встроенную карту credentials: в enodia.yaml, ни отдельный credentials.yaml не обязательно писать вручную. Оба — это просто файлы, которые enodia читает заново при каждом запуске — подтверждено по исходному коду: enodia check перечитывает конфиг и credentials с нуля при каждом вызове, а enodia serve --interval делает то же самое на каждом цикле обновления (Config.Build вызывает LoadCredentials каждый раз, когда выполняется collectObservations — ничего не кешируется на время жизни процесса, поэтому изменения в любом из этих файлов вступают в силу без перезапуска). Это ровно та форма, под которую заточен собственный рендеринг шаблонов у Vault Agent. У enodia нет собственной интеграции с Vault — она и не нужна, оба механизма ниже уже сочетаются с ним напрямую.

Направьте блок template (или env_template) Vault Agent на секреты, которые нужны таргету, и ссылайтесь на них обычным способом — через подстановку переменных окружения выше:

credentials.yaml
jira-token:
kind: bearer
value: "${JIRA_TOKEN}"

Режим exec у Vault Agent запускает саму enodia (или обёрточный скрипт, вызывающий enodia check) как свой управляемый дочерний процесс, вставляя отрендеренные переменные прямо в окружение этого процесса — секрет вообще не попадает на диск как файл, который enodia должна прочитать. Блок exec у Vault Agent также поддерживает перезапуск дочернего процесса при изменении отрендеренного секрета, если вы хотите, чтобы долгоживущий enodia serve подхватил обновлённый токен сразу, а не дожидался, пока он просто останется валидным до следующего тика --interval — точную настройку для этого смотрите в собственной документации Vault Agent, это целиком на его стороне.

Укажите credentials_file: на путь, по которому блок template Vault Agent записывает файл, и отрендерите ровно ту форму, которую ожидает credentials_file:

enodia.yaml
schemaVersion: 1
credentials_file: /run/enodia/credentials.yaml
targets:
- id: jira-main
product: jira
address: https://jira.example.com
credentials: jira-token
Блок шаблона Vault Agent — иллюстративно; точный синтаксис смотрите в документации Vault Agent
template {
destination = "/run/enodia/credentials.yaml"
perms = "0600"
contents = <<EOT
jira-token:
kind: bearer
value: "{{ with secret "secret/data/enodia/jira" }}{{ .Data.data.token }}{{ end }}"
EOT
}

Этот путь вообще не требует настройки exec/перезапуска: enodia check перечитывает credentials_file с нуля при каждом вызове, а enodia serve — на каждом цикле обновления, независимо от того, как именно файл изменился на диске. И cron-задача с enodia check, и долгоживущий enodia serve просто подхватывают то, что Vault Agent записал последним, по собственному расписанию — ничего специфичного для enodia настраивать не нужно.

В любом случае — по правилам обработки credentials у enodia

Заголовок раздела «В любом случае — по правилам обработки credentials у enodia»

Оба варианта всё равно попадают под всё, что уже описано в Безопасности — credentials никогда не попадают в инвентарь, экспортированные отчёты или логи, а проверка TLS остаётся включённой, если вы не откажетесь от неё для конкретного таргета. Именно настройки perms и выбор директории назначения у самого Vault Agent защищают отрендеренный файл от доступа со стороны — у самой enodia нет никакого мнения о том, где лежит credentials_file, кроме того, что относительный путь резолвится относительно конфига, который на него ссылается.

product: generic — аварийный выход для таргета, который никогда не получит отдельную пробу. Его словарь намеренно небольшой и заморожен — ни условий, ни циклов, ни цепочек запросов, ни шаблонизации. Таргету, которому нужно что-то из этого, нужна настоящая проба на Go, а не расширение словаря generic-пробы.

targets:
- id: in-house-api
product: generic
address: https://internal.example.com
parser:
type: regex # json | xml | header | plaintext | regex
key: version # dotted path (json), путь вроде XPath (xml), или имя заголовка
regex: 'v(\d+\.\d+\.\d+)'
clean_regex: '^v' # побеждает первая захватывающая группа - snake_case, см. ниже
line: 1 # только для plaintext - какую строку читать

И enodia.yaml, и settings.yaml ищутся одинаково: явный путь (--config/--settings, либо $ENODIA_CONFIG/$ENODIA_SETTINGS для точного файла) всегда побеждает и обязан существовать — опечатка должна быть ошибкой, а не тихим переключением на какой-то другой файл. Если явного пути нет, запускается поиск в порядке ниже; первое совпадение побеждает безоговорочно, ничего не сливается из нескольких найденных файлов. Расположение важнее имени: совпадение в текущей директории всегда побеждает совпадение в $XDG_CONFIG_HOME, которое всегда побеждает совпадение в /etc/enodia/, независимо от того, какое имя где совпало.

enodia.yaml:

  1. ./enodia.yaml
  2. ./enodia.yml
  3. ./config.yaml
  4. ./config.yml
  5. ./.enodia.yaml
  6. ./.enodia.yml
  7. ./.config.yaml
  8. ./.config.yml
  9. $XDG_CONFIG_HOME/enodia/enodia.yaml (~/.config/enodia/enodia.yaml, если $XDG_CONFIG_HOME не задан)
  10. $XDG_CONFIG_HOME/enodia/enodia.yml
  11. $XDG_CONFIG_HOME/enodia/config.yaml
  12. $XDG_CONFIG_HOME/enodia/config.yml
  13. /etc/enodia/enodia.yaml
  14. /etc/enodia/enodia.yml
  15. /etc/enodia/config.yaml
  16. /etc/enodia/config.yml

Если не найдено ничего — это ошибка: конфиг, который не удалось найти, стоит того, чтобы упасть громко, потому что это обычно значит, что вот-вот будет использован не тот файл (или вообще никакой). Запустите enodia config path, чтобы увидеть, какой файл реально будет использован.

settings.yaml — та же идея, с несколькими отличиями: дополнительно проверяется голое имя settings. (не только enodia.settings.), дополнительно проверяется директория, где лежит сам исполняемый файл (не только текущая директория — см. ниже), и отсутствие файла вообще — не ошибка: каждое поле просто откатывается к встроенному значению по умолчанию, поскольку этот файл полностью опционален:

  1. ./enodia.settings.yaml
  2. ./enodia.settings.yml
  3. ./settings.yaml
  4. ./settings.yml
  5. ./.enodia.settings.yaml
  6. ./.enodia.settings.yml
  7. ./.settings.yaml
  8. ./.settings.yml
  9. <директория, где лежит исполняемый файл>/settings.yaml
  10. <та же>/settings.yml
  11. $XDG_CONFIG_HOME/enodia/settings.yaml (~/.config/enodia/settings.yaml, если $XDG_CONFIG_HOME не задан)
  12. $XDG_CONFIG_HOME/enodia/settings.yml
  13. /etc/enodia/settings.yaml
  14. /etc/enodia/settings.yml

Шаг 9-10 отличается от текущей директории (шаги 1-8): портативная установка (распаковал куда угодно, без пакетного менеджера) запускается из той директории, где в данный момент стоит оператор, а это на Windows, в частности, практически никогда не сама директория установки (install.ps1 по умолчанию ставит в %LOCALAPPDATA%\enodia, добавляя в PATH — весь смысл PATH в том, что текущая директория перестаёт иметь значение). Этот шаг намеренно ограничен только settings.yaml — это опциональные настройки отображения, так что неверный или подменённый файл в общей директории установки — в худшем случае косметическая проблема. enodia.yaml несёт credentials и такого шага не получает.

Личные, персональные для оператора настройки отображения — никогда не таргеты, никогда не credentials, никогда не то, чем обычно делятся так же, как enodia.yaml.

settings.yaml
schemaVersion: 1
render:
# compact (по умолчанию) | lifecycle | drift | fleet
default_view: fleet
export:
# json (по умолчанию) | prometheus | html - используется, когда сам
# export запущен без --format
default_format: html
html:
# inline (по умолчанию, полностью офлайн) | cdn (грузит Bootstrap/Bootswatch)
assets: cdn
# none (вообще без стилей) | default (чистый Bootstrap) | любая из
# 26 настоящих тем Bootswatch: brite, cerulean, cosmo, cyborg, darkly,
# flatly, journal, litera, lumen, lux, materia, minty, morph, pulse,
# quartz, sandstone, simplex, sketchy, slate, solar, spacelab,
# superhero, united, vapor, yeti, zephyr
theme: lumen
# auto (по умолчанию: гонка между jsdelivr и cdnjs, побеждает тот, кто
# первым ответит) | jsdelivr | cdnjs
cdn: auto
# опционально: ограничить экспорт одним представлением вместо всех четырёх
# view: fleet

render.default_view применяется к --view команды check, когда сам флаг не передан. export.default_format делает то же самое для --format команды export. html.* имеет значение только для export --format html — см. Отчёты, что именно меняет каждое поле.