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

Справочник конфигурации Tessera

Этот документ — справочник по основному конфигурационному файлу tessera:

  • /etc/tessera/config.toml — основная конфигурация модуля и демона tessera.

Авторизация «в какой роли и на каком устройстве» живёт в самом удостоверении — в X.509-расширениях pam_cert_host_binding и pam_cert_allowed_roles. Имя учётной записи входа и есть роль, поэтому список ролей отвечает и на вопрос допуска к учётной записи. Механизма, которым устройство разрешало бы вход по собственным правилам, в этом файле нет: рамки назначает выпускающий, а не ограничиваемая сторона. Рецепты выпуска — docs/cert-issuance.md.

Если нужно просто начать — возьмите за основу минимальный валидный пример (или один из типовых сценариев) и правьте под себя; таблицы ниже разбирают каждое поле. Каждое поле описано в формате «тип → значение по умолчанию → допустимые значения → влияние на поведение → влияние на безопасность». Все поля валидируются при загрузке через tessera_core::config::ValidatedConfig::try_from (см. crates/tessera_core/src/config/validated.rs и crates/tessera_core/src/config/raw.rs). Несуществующие поля или неверные типы — ошибка загрузки → fail-closed.

Все примеры используют тестовые данные ([email protected], TERMINAL-001, ca-test.example). Никаких реальных CA, паролей или клиентских хостов в этом документе нет.

Полный поставочный пример лежит в dist/config/config.toml.example.

Ключи верхнего уровня — вне всяких [секций]. Здесь выбирается крипто-бэкенд, где живёт ключ пользователя (mode), как ищется USB-носитель и что происходит при его извлечении.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
crypto_backendстрока"openssl", "pkcs11_native"Какой бэкенд считает подписи и хеши."openssl" обязателен для ГОСТ через gost-engine.
modeстрока"pkcs12", "pkcs11"Где живёт ключ пользователя."pkcs11" — non-extractable ключ; "pkcs12" — программная защита.
pkcs11_moduleпутьабсолютный путь к .soКакой PKCS#11-модуль используется.Обязателен в mode = "pkcs11"; файл и все предки должны контролироваться root.
pkcs11_token_labelстрокаNone≤ 64 байт без NULФильтр по CKA_LABEL токена.Защищает от случайного выбора чужого токена на машине.
pkcs11_object_labelстрокаNone≤ 64 байт без NULФильтр по CKA_LABEL объекта (cert/privkey).Аналогично, защита от выбора неправильного объекта.
pkcs11_max_pin_attemptsцелое31..=5Сколько раз модуль предложит ввести PIN.Слишком большой лимит ослабляет защиту от перебора PIN; слишком маленький — мешает работе.
pkcs11_locking_modeстрока"mutex""os", "mutex"Стратегия блокировок PKCS#11."mutex" — каждый вызов модуля сериализуется процесс-глобальным мьютексом; цена ≈ 20 нс на вызов. "os" снимает сериализацию и допустим только если поставщик модуля подтверждает потокобезопасность: заявленная поддержка CKF_OS_LOCKING_OK таковой не является. Режим закрепляется за модулем при первой загрузке в процессе; вторая загрузка с другим значением получает уже действующий режим и WARN pkcs11_locking_mode_conflict.
pkcs11_pin_promptстрока"Введите PIN токена: "UTF-8, непустая, ≤ 128 байтТекст приглашения PIN на PKCS#11-пути.Локализация UX, не безопасности.
pkcs11_slot_wait_secondsцелое100..=60Сколько секунд ждать вставки токена.0 — не ждать; UX vs. удобство.
pkcs11_allow_extractable_keysбулевоfalsetrue, falseПринимать ли ключи, о которых токен сообщил CKA_EXTRACTABLE = TRUE.false (default) — отказ (fail-closed): extractable-ключ ломает инвариант режима B. true — только WARN pkcs11_extractable_key; включать осознанно. Случай, когда токен вообще не сообщил атрибут, этим ключом НЕ разрешается — для него есть pkcs11_allow_unreported_extractable.
pkcs11_allow_unreported_extractableбулевоfalsetrue, falseПринимать ли ключ, для которого токен не сообщил CKA_EXTRACTABLE.false (default) — отказ (fail-closed): неизвлекаемость не доказана, а молчание провайдера не равно FALSE. true — только WARN pkcs11_extractable_attribute_unavailable с причиной отказа (sensitive, type_invalid, unavailable, available_but_not_returned — провайдер сказал, что значение читаемо, но так его и не отдал, probe_failed — сам уточняющий запрос не выполнился); включать, если поставщик токена подтвердил, что ключи неизвлекаемы, а атрибут не отдаётся по устройству модуля. Ключи, честно объявившие себя извлекаемыми, этим ключом НЕ разрешаются.
pkcs12_path_patternстрока"certs/user.p12"относительный путь от mountpoint USB, опц. ${user}Где искать .p12 на USB-носителе (поддерживает ${user}).Только относительный путь; ../. сегменты и абсолютные пути отклоняются валидатором.
pkcs12_pin_promptстрока"Smart-card PIN: "UTF-8, непустая, ≤ 128 байтТекст приглашения для пароля .p12.Локализация UX.
gost_engine_pathпутьNoneабсолютный путь к .soЯвный путь к gost-engine; обязателен, если OpenSSL разрешает ГОСТ-подписи.Неявный поиск через OPENSSL_ENGINES отклоняется; файл и все предки должны контролироваться root.
usb_wait_secondsцелое100..=300Сколько секунд ждать USB-носителя.UX. На 0 — fail-fast.
usb_allowed_devicesмассив строк[]строки "vid:pid", по 4 hex-цифры (формат lsusb), напр. ["0951:1666"]Allow-list USB-устройств, рассматриваемых как носитель .p12; пустой/отсутствующий = любое USB block-устройство.Гигиена против случайных/посторонних флешек, НЕ граница доверия: VID/PID подделываются, доверие даёт только расшифровка .p12 + валидация цепочки.
max_usb_partitionsцелое81..=64Максимум партиций, перебираемых при поиске .p12.Защита от DoS: физический атакующий не сможет навязать огромное число mount/umount.
on_usb_removedстрока"lock""lock", "logout", "hook", "shutdown"Действие при подтверждённом извлечении USB."shutdown" уместен для терминалов; "lock" — для рабочих станций.
usb_removed_grace_secondsцелое00..=600Окно отмены: реинсерт того же серийника отменяет действие.Защищает от ложных срабатываний; на терминалах ставить 0.
suspend_grace_secondsцелое00..=600Окно после resume, в котором USB-removal игнорируется.Хабы во время suspend часто шумят; 30 секунд — типовое значение.
monitor_fail_modeстрока"strict""strict", "permissive"Пробрасывать ли нефатальные ошибки IPC с monitord вызывающему коду (strict) или глотать с WARN (permissive).DeviceGone/Unauthorized фатальны всегда. Strict-режим пока отказывает PKCS#11-аутентификации: нативное наблюдение за извлечением токена не реализовано.

Авторизация (устройство + роль) описана в самом удостоверении (X.509 v3 расширения pam_cert_host_binding / pam_cert_allowed_roles), а этот файл задаёт только trust + identity + roles + monitor + hooks.

PAM-точка аутентификации проверяет конфиг и каждый настроенный trust anchor, intermediate, CRL, PKCS#11-модуль и GOST engine как обычный файл root:root под root-owned каталогами без group/world write. Небезопасный путь приводит к отказу до загрузки native-кода или доверенного материала.

PKCS#11-модуль загружается и инициализируется (C_Initialize) один раз на процесс на каждый путь модуля и не финализируется никогда: C_Finalize не вызывается, библиотека остаётся загруженной до завершения процесса.

Это важно, если в PAM-стеке того же сервиса есть второй потребитель PKCS#11 (pam_pkcs11, sshd, собранный с PKCS11Provider, p11-kit):

  • C_Initialize и C_Finalize — процесс-глобальные операции над общей загруженной библиотекой. Финализация с нашей стороны деинициализировала бы провайдера и у соседа, а узнать об этом ему нечем — поэтому мы её не делаем.
  • Состояние логина PKCS#11 тоже общее: оно привязано к «приложению», а приложение определяется вызовом C_Initialize. Пока сосед залогинен на том же токене, наши сессии видят приватные объекты без предъявления PIN, и наоборот. Свои сессии модуль закрывает C_Logout при завершении попытки входа — успешной или нет; на состояние, оставленное соседом, это не влияет.
  • Процесс-глобальный мьютекс режима mutex сериализует только наши вызовы. Вызовы соседа он не видит, поэтому потокобезопасность провайдера остаётся требованием, а не гарантией.

В sshd и login процесс живёт одну аутентификацию, поэтому «модуль загружен до конца процесса» ничего не стоит. В fly-dm slave-процесс дисплея обслуживает все попытки входа за время работы машины — там один контекст на процесс и есть цель: повторный C_Initialize на живой библиотеке провайдер в лучшем случае отвергает, в худшем роняет процесс.

ЗначениеДействие при подтверждённом извлечении USBТиповой сценарий
"lock"LockSession через D-Bus к logind для этой сессии. Хост продолжает работать.Рабочая станция оператора.
"logout"TerminateSession для этой сессии. Хост продолжает работать, остальные сессии целы.Киоски, терминалы (если хост не выключаем).
"hook"Запускается внешний исполняемый файл, заданный в monitor.on_usb_removed_hook_path.Сложные сценарии (audit + custom action).
"shutdown"PowerOff через D-Bus к logind — выключение хоста.Терминалы / выделенные АРМ.

При "hook" секция [monitor] должна содержать on_usb_removed_hook_path = "/абсолютный/путь". Валидатор отказывает в загрузке конфига при on_usb_removed = "hook" без hook_path, а также если исполняемый файл или любой родительский каталог не контролируется root. Путь повторно проверяется непосредственно перед запуском; хук получает минимальное окружение с фиксированным системным PATH.

Настройки IPC-канала PAM-модуля с демоном monitord, который следит за извлечением носителя: таймауты, лимиты соединений, путь сокета и пер-секционные переопределения действий из глобального блока.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
on_usb_removed_hook_pathпутьNoneабсолютный путьИсполняемый файл для on_usb_removed = "hook". Валиден только при этом значении on_usb_removed.Исполняется от root; путь проверяется на небезопасные права.
idle_timeout_secondsцелое301..=3600Idle-таймаут IPC-соединения с monitord.Анти-DoS: висящие соединения закрываются.
max_concurrent_connectionsцелое641..=4096Максимум одновременных IPC-соединений к monitord.Анти-DoS: ограничивает расход ресурсов демона.
socket_pathпуть/run/tessera/monitord.sockабсолютный путьUnix-сокет monitord.Права на сокет ограничивают доступ к IPC.
timeout_msцелое2000миллисекундыConnect+IO таймаут одного RPC.Отклик fail-режима при зависшем демоне.
fail_modeстрокакак monitor_fail_modeПер-секционный override верхнеуровневого monitor_fail_mode.Определяет поведение при недоступном monitord.
state_file_pathпуть/run/tessera/sessions.jsonабсолютный путьРеестр сессий (tmpfs; переживает рестарт демона, не boot).Смещение с tmpfs оставит устаревшие записи после ребута.
on_usb_removedстрокакак верхнеуровневыйПер-секционный override on_usb_removed.См. верхнеуровневый ключ.
usb_removed_grace_secondsцелоекак верхнеуровневыйПер-секционный override окна отмены.См. верхнеуровневый ключ.
suspend_grace_secondsцелоекак верхнеуровневый0..=600Окно после resume, в котором события извлечения игнорируются. Если ключ опущен — берётся значение верхнеуровневого suspend_grace_seconds (дефолт 0).Слишком большое окно ослабляет реакцию на извлечение.

Корни доверия и правила проверки X.509-цепочки: какие CA доверенные, предельная глубина цепи, допуск на рассинхрон часов и разрешённые алгоритмы подписи.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
anchorsсписок путей≥ 1 PEM-файлКорневые CA доверия.Корень доверия. Должны быть 0640 root:root.
intermediatesсписок путей[]PEM-файлыПромежуточные CA (опционально).Снимает нагрузку с поиска цепи.
max_chain_depthцелое51..=16Максимальная глубина X.509-цепи.Анти-DoS.
clock_skew_secondsцелое00..=600Допустимое отклонение часов при проверке notBefore/notAfter.Слишком много — атакующий с устаревшим сертификатом.
allowed_signature_algorithmsсписок строк[]OID или именаWhitelist подписей. Пустой/опущенный — подменяется безопасным дефолтом: sha256/384/512WithRSAEncryption, ecdsa-with-SHA256/384/512 (без SHA-1 и без ГОСТ).Запрет SHA-1/MD5/слабых RSA действует и без явной настройки; ГОСТ требует явного opt-in.
max_supported_profile_versionцелоекомпилируемый дефолтu32Максимальная понимаемая версия pam_cert_profile_version; серт с большей версией отклоняет всю цепь (fail-closed, version-gate).Защита от «тихого» игнорирования незнакомых семантик новых версий профиля.

Записи сравниваются точно (без подстрок) с OpenSSL display-формой алгоритма сертификата (см. pre_validate_end_entity в crates/tessera_core/src/x509/pre_validate.rs):

  • RSA: "sha256WithRSAEncryption", "sha384WithRSAEncryption", "sha512WithRSAEncryption"
  • ECDSA: "ecdsa-with-SHA256", "ecdsa-with-SHA384", "ecdsa-with-SHA512"
  • ГОСТ Р 34.10-2012-256: "id-tc26-signwithdigest-gost3410-12-256"
  • ГОСТ Р 34.10-2012-512: "id-tc26-signwithdigest-gost3410-12-512"

Как проверяется отзыв сертификатов — источники (CRL и/или OCSP) и их строгость. Ключ mode обязателен: без него конфиг не загрузится (молчаливого дефолта нет).

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
modeстрока— (обязателен)"none", "crl", "ocsp", "crl_then_ocsp"Какие источники отзыва используются.Обязателен: пропуск секции [trust.revocation] или ключа mode — ошибка валидации (нет молчаливого дефолта). "none" — отзыв не проверяется (НЕ для production), выбирается явно.
crl_pathsсписок путей[]PEM/DER-файлыЛокальные CRL.Обязательны при mode = "crl".
crl_max_age_hoursцелоеNone1..=8760 (часы)Максимальный возраст CRL от thisUpdate до отказа.Не задан — свежесть CRL не проверяется; не рекомендуется.
ocsp_responder_urlстрока URLhttp://… / https://…Адрес OCSP-responder’а. ОБЯЗАТЕЛЕН при mode ∈ {ocsp, crl_then_ocsp}. AIA из серта не извлекается.Единственный источник адреса — конфиг (предсказуемость офлайн-аудита).
ocsp_timeout_secondsцелое51..=30Общий deadline одного OCSP-обмена (connect+write+read).Бюджет логина = (глубина цепи − 1) × таймаут.
ocsp_cache_ttl_secondsцелое36000..=86400Верхний предел жизни кэш-записи (0 = кэш выключен).Кэш ограничивает сетевые вызовы; запись валидна до min(nextUpdate, mtime+ttl).

Семантика режимов отзыва:

modeПоведение
noneОтзыв не проверяется; компенсация — короткий TTL leaf-сертов (deployment-политика).
crlStrict offline CRL: просроченная/отсутствующая покрывающая CRL → отказ.
ocspКаждый non-anchor серт цепочки проверяется через OCSP; CRL-store не участвует.
crl_then_ocspСначала CRL: свежая CRL, чей issuer DN покрывает серт, даёт статус без сетевого вызова; иначе OCSP обязателен.

Fail-closed в OCSP-режимах. Недоступность responder’а, таймаут, статус unknown, непроверяемая подпись ответа, окно thisUpdate/nextUpdate вне допуска (с учётом clock_skew_seconds) → отказ аутентификации (PAM_AUTH_ERR). Деградации «WARN и пропустить» в OCSP-режимах нет — кто хочет мягкость, выбирает none или нестрогий CRL.

Zero-egress контурам (терминалы) OCSP не включать — там нет сети до responder’а; их режим none + короткий TTL либо offline crl. OCSP — для сегментов с сетью (офисные АРМ, стенды заказчиков). ocsp_*-ключи при mode ∈ {none, crl} отвергаются валидацией (не могут молча игнорироваться). Кэш — /var/cache/tessera/ocsp/*.der, каталог создаёт postinst пакета.

Пиннинг корневых CA по SPKI-хешу — защита на случай компрометации УЦ: корень не из списка отвергается, даже если цепочка формально валидна.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
enabledboolfalsetrue, falseВключает pinning по SPKI корневых CA.Защита от компрометации УЦ.
allowed_root_spki_sha256список строк[]64-символьные lower-case hexСписок разрешённых SPKI-хешей корней.Любой корень не из списка отвергается.

Как вычисляется host_id устройства — цепочка источников, первый непустой выигрывает. Именно это значение сверяется с расширением pam_cert_host_binding сертификата.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
sourcesсписок строк"machine_id", "dmi_board_serial", "dmi_system_uuid", "dmi_system_serial", "hostname", "custom_command", "override"Цепочка источников host_id. Первый непустой выигрывает.Чем стабильнее источник, тем сильнее host-binding.
fallbackстрока"deny""deny", "warn", "allow"Что делать, если все источники пустые.На production — только "deny".
overrideстрокаNoneUTF-8, без перевода строкЖёстко заданное значение host_id (для тестов).НЕ использовать на production.
custom_commandпутьNoneабсолютный путь к скриптуСкрипт, печатающий host_id в stdout.Скрипт исполняется от root. Должен быть 0750 root:root.
custom_command_timeout_secondsцелое51..=30Таймаут на исполнение custom_command.Анти-DoS.

Значения custom_command_timeout_seconds вне диапазона 1..=30 приводятся к ближайшей границе (01, > 3030), а не отклоняются при загрузке.

Реализация цепочки — в crates/tessera_core/src/host_identity/chain.rs. Поведение fallback = "deny" гарантирует fail-closed: если ни один источник не дал значения, аутентификация не проходит.

Детализация журнала демона. Два поля (syslog_facility, journald_priority) оставлены для обратной совместимости и на поведение не влияют.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
levelстрока"error", "warn", "info", "debug", "trace"Уровень детализации журнала демона. Переменная окружения TESSERA_LOG имеет приоритет над этим полем."trace" — отладка; не оставлять на production.
syslog_facilityстрокаопционален"auth", "authpriv", "user", "daemon"Deprecated, игнорируется. PAM-модуль пишет в syslog facility auth фиксированно. Поле валидируется (local0..7 не поддержаны — ошибка загрузки), но на runtime не влияет; при наличии ключа в журнал выдаётся WARN «deprecated and ignored».Не влияет на поведение.
journald_priorityboolопционаленtrue, falseDeprecated, игнорируется. При наличии ключа — WARN «deprecated and ignored».Не влияет на поведение.

PIN-коды и пароли никогда не логируются. Полные DN сертификатов логируются на уровне debug и выше; на info и ниже — только CN.

Управляет выбором роли на логине и базой ролей устройства (см. docs/cert-issuance.md — расширение pam_cert_allowed_roles).

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
dirпуть/var/lib/tessera/rolesабсолютный путь к каталогуКаталог базы ролей (срезы <role>.toml).Standalone-загрузка проверяет root:root для каталога, всех срезов и предков; group/world write запрещён.
default_session_ttl_secondsцелое43200 (12 ч)секундыTTL сессии, когда ни удостоверение, ни роль его не задают.Бессрочной сессии не возникает — потолок всегда конечен.
account_lookup_timeout_secondsцелое10160 (секунды)Сколько ждать ответа разрешения имён при проверке «не системная ли это учётная запись».Не уложился — вход идёт по локальному файлу, а не отвергается; 0 запрещён, чтобы проверку нельзя было выключить молча.

Проверка роли безусловна. Роль требуется на каждом входе, и параметра, отключающего проверку или ослабляющего её до предупреждения, нет. Конфиг, содержащий удалённый ключ [roles].enforce, отвергается при валидации с диагностикой об удалении ключа.

Fail-closed. Пустая или невалидная база ролей приводит к отказу входов с диагностикой «роли не настроены».

Выбор роли на логине. Дефолтной роли нет. Роль — это имя учётной записи входа: инженер входит в ролевую учётную запись, названную по роли (ssh serv@device), и запрошенная роль равна PAM_USER. Других источников роли нет — ни суффикса имени, ни PAM-prompt, ни переменной окружения.

Модуль никогда не переписывает PAM_USER: имя, прочитанное стеком, и есть имя, по которому принимается решение. Имя учётной записи, не удовлетворяющее формату role_id (^[a-z][a-z0-9-]{0,15}$), отвергается до обращения к носителю.

Личность инженера при этом не теряется — она в удостоверении и журнале выпусков, а не в имени учётной записи.

Ролевые учётные записи провижинятся отдельно (Census). Закрытие остальных путей входа в них — ~/.ssh/authorized_keys, su, sudo -u, парольный вход, PAM-стеки без модуля — задача провижининга и администратора устройства, а не продукта: он не управляет ни sshd_config, ни sudoers, ни чужими PAM-стеками. Явные команды и их проверка — install.md §8.4.

Сам продукт гарантирует здесь одно, и гарантирует безусловно: вход отвергается, если uid учётной записи, названной в PAM_USER, лежит вне диапазона обычных пользователей — ниже 1000 (там живут учётные записи дистрибутива и пакетов) или выше 61183 (там uid, раздаваемые systemd юнитам с DynamicUser=yes, а дальше nobody и nogroup). Обе границы и причина, по которой верхняя не совпадает с UID_MAX, — install.md §8.3. Отказ не зависит ни от содержимого хранилища ролей, ни от того, что разрешает удостоверение: ssh root@device не станет ролевым входом даже при наличии среза root и удостоверения, покрывающего эту роль. Тем же правилом срез, названный именем системной учётной записи, отвергается при загрузке хранилища и в tessera-cli role lint — чтобы ошибку провижининга увидел администратор, а не первый вошедший инженер.

Проверка не требует сети. uid берётся из локального /etc/passwd, и его одного достаточно, чтобы вход состоялся. Разрешение имён (NSS) спрашивается дополнительно и только затем, чтобы поймать учётные записи, которых в файле нет по устройству системы, — DynamicUser= systemd синтезирует nss-systemd. Этот источник может лишь ДОБАВИТЬ отказ: если каталог не отвечает, отвечает ошибкой или не укладывается в account_lookup_timeout_seconds, решение остаётся тем, которое вынес локальный файл. Недоступный LDAP не закрывает вход — в том числе аварийный консольный.

Допуск проверяется единственным расширением удостоверения — pam_cert_allowed_roles («предъявителю разрешено активировать эти роли»). Оно же отвечает и на вопрос «в какую учётную запись пущен предъявитель», потому что это одна и та же строка. Отдельного списка разрешённых учётных записей нет: два списка над одним именем описывали бы нереализуемое состояние «пущен в serv, но не вправе быть serv».

Иных источников допуска не существует. Конфигурация устройства не содержит механизма, разрешающего вход по признакам удостоверения (CN, SAN), которые выпускающий не предназначал для допуска: рамки несёт удостоверение, а путь, где их назначает ограничиваемая сторона, подрывает саму модель. Конфиг с удалённой секцией [[user_mapping]] отвергается при валидации с диагностикой об удалении секции.

Теги устройства для делегационных ограничений (device-tags). Отсутствие секции = у устройства нет тегов (fail-closed дефолт): делегационный конверт с групповым ограничением на бестеговом устройстве отклоняется.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
enforceбулевоfalsetrue/falseЧитать ли источник тегов. false — устройство без применённых тегов.Групповые делегации на бестеговом устройстве всё равно отклоняются (fail-closed).
modeстрокаstandalonestandalone, managedДоверенная модель источника: файл тегов или подписанный manifest.toml.managed требует подписи манифеста.
sourceпуть/var/lib/tessera/tags.toml (standalone) / каталог role-store (managed)абсолютный путьФайл тегов либо каталог с манифестом.Standalone-загрузка проверяет root-owned файл и весь путь без group/world write.

Массив таблиц. Каждый хук — внешняя команда, исполняемая в стадии жизненного цикла. Полная реализация — в crates/tessera_core/src/hooks/.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
stageстрока"pre_auth", "post_auth_success", "session_open", "session_close", "usb_removed"На какой стадии жизненного цикла вызывается хук.Хуки исполняются с sandbox-ограничениями (см. docs/threat-model.md).
commandсписок строк[ "/usr/local/sbin/foo", "arg" ], первый элемент — абсолютный путьArgv хука. Передаётся буквально, placeholder’ы в argv НЕ подставляются.Динамика передаётся только через env — argv injection невозможен.
timeout_secondsцелое101..=120Таймаут исполнения.Хук убивается через SIGKILL по истечении.
on_failureстрокаNone"warn", "ignore"; любое иное значение → abortЧто делать при ненулевом коде возврата хука.Default: abort (deny) для pre_auth (там "warn" тоже принудительно abort); "warn" для остальных стадий.
run_asстрокаNone"root", "user"Привилегия, под которой запускается хук: root или user (аутентифицированный PAM-пользователь).По умолчанию — root. Любое иное значение (опечатка, имя учётной записи) — ошибка конфигурации, а не молчаливый откат к root. Снижение привилегий (user) — лучшая практика.
envтаблица{}строки { KEY = "literal ${placeholder}" }Переменные окружения, передаваемые хуку.База: whitelist PATH/HOME/USER/LOGNAME/LANG + все TESSERA_*-переменные; кастомные ключи могут их переопределить.

Подстановка ${...} работает только в значениях envcommand исполняется буквально (см. crates/tessera_core/src/hooks/fork_exec.rs). Кроме того, хук всегда получает готовый набор переменных TESSERA_STAGE, TESSERA_USER, TESSERA_SERVICE, TESSERA_HOST_ID, TESSERA_HOST_ID_HASH, TESSERA_HOST_ID_SOURCE, TESSERA_CERT_CN, TESSERA_CERT_SERIAL, TESSERA_USB_SERIAL, TESSERA_USB_VID_PID, TESSERA_SESSION_ID (пустая строка, если значение недоступно).

Допустимые placeholder’ы для значений env (см. crates/tessera_core/src/hooks/placeholder.rs):

  • ${pam_user} — UNIX-пользователь.
  • ${pam_service} — PAM-сервис.
  • ${host_id} / ${host_id_hash} / ${host_id_source} — вычисленный host_id, его SHA-256 и имя источника.
  • ${cert_cn} — Common-Name сертификата.
  • ${cert_serial} — серийник сертификата (hex).
  • ${usb_serial} / ${usb_vid_pid} — данные USB-носителя.
  • ${session_id} — UUID PAM-сессии.

Пример: динамические данные — через env, не через argv:

[[hooks]]
stage = "post_auth_success"
command = ["/usr/local/sbin/audit-login"]
timeout_seconds = 5
on_failure = "warn"
env = { AUDIT_USER = "${pam_user}", AUDIT_SERIAL = "${cert_serial}" }

Опциональная. Контролирует wallpaper writer для fly-dm — впечатывает host_id в JPG-фон, на который указывает [background].path в /etc/X11/fly-dm/fly-modern/settings.ini. Workaround для МКЦ-3 fly-modern theme, где PAM_TEXT_INFO не пробрасывается в UI.

ПолеТипDefaultОписание
update_wallpaperboolfalseВключить wallpaper writer.
wallpaper_targetpath/usr/share/wallpapers/fly-default-light.jpgJPG, который daemon перерисовывает.
wallpaper_backuppath/var/lib/tessera/daemon/wallpaper.orig.jpgКуда сохраняется one-time оригинал источника.
wallpaper_fontpath/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttfTrueType шрифт для рендера.
wallpaper_font_sizeint64Размер шрифта в пунктах (1..=512).
wallpaper_text_colorstring"#000000"Цвет в hex (#RRGGBB).
wallpaper_gravityenum"south"north / south / east / west / center — якорь позиционирования.
wallpaper_offset_xint0Горизонтальное смещение в пикселях от gravity-якоря.
wallpaper_offset_yint120Вертикальное смещение в пикселях от gravity-якоря (для south — вверх).
template_rustring"Устройство %n host_id={host_id_short} ({source})"Шаблон для ru locale.
template_enstring"Device %n host_id={host_id_short} ({source})"Шаблон для en locale.

Подстановки в template’е: {host_id_short} (первые 8 hex sha256), {source} (имя источника — MachineId, DmiBoardSerial …), %n (hostname). Поведение, baseline для settings.ini, troubleshooting — см. fly-dm-greeter.md.

Legacy-поле update_greet_string (0.3.16–0.3.18) — переписывало /etc/X11/fly-dm/override/GreetString.desktop. На production МКЦ-3 fly-modern игнорируется (no-op). Сохранено для обратной совместимости, но НЕ работает на терминалах. Использовать update_wallpaper вместо.

Массив таблиц. Каждая запись — переопределение [trust] для ограниченного набора host_id.

ПолеТипDefaultДопустимые значенияВлияниеВлияние на безопасность
when_host_id_inсписок строксписок host_idНа каких машинах применять override.Должен быть непустым.
anchorsсписок путей≥ 1 PEM-файлКакие корни доверия использовать вместо основных.Обязателен и непуст; сужает доверие на конкретных машинах.
intermediatesсписок путей[]PEM-файлыКакие промежуточные использовать.Аналогично.

Пример: минимальная валидная конфигурация

Заголовок раздела «Пример: минимальная валидная конфигурация»

mode = "none" в блоке [trust.revocation] взят здесь только для того, чтобы пример был минимальным; для production выбор источника отзыва см. в разделе «Секция [trust.revocation]».

crypto_backend = "openssl"
mode = "pkcs12"
pkcs12_path_pattern = "certs/${user}.p12" # относительно mountpoint USB
usb_wait_seconds = 10
on_usb_removed = "lock"
usb_removed_grace_seconds = 5
suspend_grace_seconds = 30
monitor_fail_mode = "strict"
[trust]
anchors = ["/etc/tessera/ca/bundle.pem"]
[trust.revocation]
mode = "none"
[host_identity]
sources = ["machine_id", "hostname"]
fallback = "deny"
[roles]
dir = "/var/lib/tessera/roles"
[logging]
level = "info"

Привязка удостоверения к устройствам и ролевым учётным записям полностью описывается двумя X.509 v3 расширениями листа:

  • pam_cert_host_binding (OID 2.25.183976554325829274683049824615098) — SEQUENCE OF UTF8String, каждая запись — либо *, либо sha256:<HEX>, либо «сырое» значение machine_id (тогда сравнение идёт через SHA-256 от строки).
  • pam_cert_allowed_roles (OID 2.25.185305973969816596290730578528098241367) — SEQUENCE OF UTF8String, каждая запись — идентификатор роли (^[a-z][a-z0-9-]{0,15}$). Он же — имя учётной записи входа, поэтому список отвечает и на вопрос допуска к учётной записи.

Для авторизации удостоверения на конкретном host_id и в конкретной ролевой учётной записи требуется хотя бы одна совпавшая запись в каждом из расширений. Отсутствие любого из расширений, повреждённое DER-кодирование или полное отсутствие совпадений — отказ (PAM_AUTH_ERR), отката к какому-либо механизму устройства не существует.

Подробности и готовые рецепты openssl.cnf — в cert-issuance.md.

3.1 Терминал — оффлайн, CRL с TTL, PKCS#11 без continuous presence

Заголовок раздела «3.1 Терминал — оффлайн, CRL с TTL, PKCS#11 без continuous presence»

Свойства: машина в железной коробке, нет Интернета, ключ — на токене. Нативное наблюдение за извлечением PKCS#11 пока не реализовано, поэтому профиль допустим только там, где ограниченный TTL роли/сессии является достаточной компенсирующей мерой. Если извлечение токена должно немедленно завершать сессию, такой профиль разворачивать нельзя.

crypto_backend = "pkcs11_native"
mode = "pkcs11"
pkcs11_module = "/usr/lib/librtpkcs11ecp.so"
pkcs11_max_pin_attempts = 3
pkcs11_slot_wait_seconds = 5
usb_wait_seconds = 5
on_usb_removed = "shutdown" # терминал — выключаемся
usb_removed_grace_seconds = 0 # без отмены
suspend_grace_seconds = 0
monitor_fail_mode = "permissive" # обязательно для PKCS#11 до нативного мониторинга
[trust]
anchors = ["/etc/tessera/ca/terminal-ca.pem"]
allowed_signature_algorithms = [
"1.2.643.7.1.1.3.2", # ГОСТ-2012-256
]
[trust.revocation]
mode = "crl"
crl_paths = ["/etc/tessera/crl/terminal.crl"]
crl_max_age_hours = 72
[trust.pinning]
enabled = true
allowed_root_spki_sha256 = [
"ee0bd4f3a3c8e21d4a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f"
]
[host_identity]
sources = ["dmi_board_serial", "machine_id"]
fallback = "deny"
[roles]
dir = "/var/lib/tessera/roles"
[logging]
level = "warn"

Обоснование выбора:

  • mode = "pkcs11" + librtpkcs11ecp.so: ключ non-extractable.
  • monitor_fail_mode = "permissive": серийник PKCS#11-токена не относится к namespace серийников USB block-устройств. Strict-аутентификация отказывается до появления нативного token-event источника; поэтому on_usb_removed здесь не является рубежом enforcement.
  • usb_removed_grace_seconds = 0: на терминале не может быть «вынул и передумал».
  • mode = "crl" с crl_max_age_hours = 72: трое суток — компромисс между UX (CRL обновляется ежедневно) и безопасностью.
  • host_identity.sources = ["dmi_board_serial", ...]: материнская плата привязана к корпусу, замена → новый host_id → требуется перевыпустить сертификат с новым значением в pam_cert_host_binding.
  • pinning.enabled = true: компрометация УЦ не открывает все терминалы автоматически.

3.2 Рабочая станция в защищённом контуре — CRL, ГОСТ-токен

Заголовок раздела «3.2 Рабочая станция в защищённом контуре — CRL, ГОСТ-токен»
crypto_backend = "pkcs11_native"
mode = "pkcs11"
pkcs11_module = "/usr/lib/librtpkcs11ecp.so"
pkcs11_token_label = "STAFF"
pkcs11_max_pin_attempts = 3
pkcs11_slot_wait_seconds = 10
usb_wait_seconds = 10
on_usb_removed = "lock"
usb_removed_grace_seconds = 30
suspend_grace_seconds = 60
monitor_fail_mode = "permissive" # обязательно для PKCS#11 до нативного мониторинга
[trust]
anchors = ["/etc/tessera/ca/staff-ca.pem"]
intermediates = ["/etc/tessera/ca/staff-int.pem"]
allowed_signature_algorithms = [
"1.2.643.7.1.1.3.2", # ГОСТ-2012-256
"1.2.643.7.1.1.3.3", # ГОСТ-2012-512
]
[trust.revocation]
mode = "crl"
crl_paths = ["/etc/tessera/crl/staff.crl"]
crl_max_age_hours = 24
[host_identity]
sources = ["machine_id", "hostname"]
fallback = "deny"
[roles]
dir = "/var/lib/tessera/roles"
[logging]
level = "info"
[[hooks]]
stage = "post_auth_success"
command = ["/usr/local/sbin/audit-login"]
timeout_seconds = 5
on_failure = "warn"
run_as = "user"
env = { AUDIT_USER = "${pam_user}", AUDIT_SERIAL = "${cert_serial}" }

Обоснование:

  • usb_removed_grace_seconds = 30: пользователь может вытащить токен, чтобы что-то перевставить, и продолжить работу.
  • mode = "crl" + crl_max_age_hours = 24: единственный поддерживаемый источник отзыва; свежесть CRL контролируется TTL.
  • [[hooks]] для аудита: сторонняя система аудита получает событие «вход» (данные — через env, argv передаётся буквально).

3.3 Тестовое окружение — mode = "pkcs12", без revocation

Заголовок раздела «3.3 Тестовое окружение — mode = "pkcs12", без revocation»
crypto_backend = "openssl"
mode = "pkcs12"
pkcs12_path_pattern = "certs/${user}.p12" # относительно mountpoint USB
pkcs12_pin_prompt = "PKCS#12 password: "
usb_wait_seconds = 5
on_usb_removed = "lock"
usb_removed_grace_seconds = 5
suspend_grace_seconds = 0
monitor_fail_mode = "permissive"
[trust]
anchors = ["/etc/tessera/ca/test-ca.pem"]
[trust.revocation]
mode = "none"
[host_identity]
sources = ["hostname"]
fallback = "warn"
[roles]
dir = "/var/lib/tessera/roles"
[logging]
level = "debug"

Обоснование:

  • mode = "pkcs12": чтобы не возиться с реальным токеном на тестах.
  • monitor_fail_mode = "permissive": monitord падает на dev-машинах чаще, чем на production.
  • level = "debug": всё видно, для отладки.
  • revocation.mode = "none": тесты не должны зависеть от внешних сервисов.

Эту конфигурацию нельзя использовать на production. Маркер: в комментарии к файлу пишется # TEST CONFIG — DO NOT DEPLOY.

Секция [mac] опциональна. Один и тот же открытый бинарь работает на Debian/Ubuntu/Astra. Реальный enforcement появляется только при установке подписанного runtime-плагина и его явном выборе полем backend.

ПолеТипПо умолчаниюОписание
backendstringИмя явно выбранного runtime-плагина, например "parsec". Нет поля — StubBackend.
cert_integrityenum"optional"Один из required / optional / ignore. См. ниже.
fallback_max_integrity.levelint (-128..127)Уровень fallback-метки, если расширение MAX_INTEGRITY отсутствует и cert_integrity = "optional".
fallback_max_integrity.categoriesstring (hex или CSV)Битовая маска категорий для fallback. Пустая строка = ''B.
runtimeenum"auto"Один из required / auto / disabled. См. ниже (0.3.7+).
warn_on_homedir_label_mismatchbooltrueЛогировать homedir_label_above_session_cap при расхождении.
  • required — сертификат обязан содержать MAX_INTEGRITY. Если расширения нет или DER битый, аутентификация отклоняется (mac_required_no_label / mac_parse_failed).
  • optional — расширение применяется при наличии. Битое присутствующее расширение отклоняет аутентификацию; fallback допустим только при настоящем отсутствии:
    • есть [mac.fallback_max_integrity] → применяется fallback;
    • нет fallback → шаг применения метки пропускается (логируется mac_label_skipped).
  • ignore — валидное расширение распарсивается для диагностики (mac_label_parsed), но не применяется; битый DER всё равно отклоняет аутентификацию. Безопасно для миграции парка машин без runtime МКЦ.

Поле backend выбирает отдельную подписанную .so, а runtime задаёт политику её использования. Открытый host проверяет подпись и ABI до dlopen/init; лишние плагины не активируются автоматически.

  • required — выбранный plugin и его runtime обязательны. Если .so отсутствует, не проходит подпись/ABI/init либо runtime не активен, аутентификация отклоняется с plugin_rejected/mac_runtime_required.
  • auto (default) — если выбранный plugin загружен и активен, он используется; иначе fallback на StubBackend с событием mac_runtime_fallback (WARN). Подходит для дев-машин и смешанного парка.
  • disabled — всегда StubBackend, даже если plugin установлен. Гарантирует, что его callbacks не вызываются. Логируется событие mac_runtime_disabled (INFO).

Валидация конфига:

  • runtime = "disabled" + cert_integrity = "required" отвергается на старте (логически несовместимо: stub не может прочитать или выставить метку, которую требует cert-политика).
  • runtime = "required" или cert_integrity = "required" без поля backend отвергается на старте.

При open_session выбирается:

effective = intersect(cert_label, runtime_caps)

где runtime_caps — потолок, который libpdp возвращает из ipdp_get_caps(). Уровень эффективной метки — min(cert.level, caps.level); категории — cert.categories & caps.categories. Если после пересечения effective.level < cert.level — пишется событие mac_level_intersected; аналогично для категорий.

[mac]
backend = "parsec"
cert_integrity = "optional"
[mac.fallback_max_integrity]
level = 0
categories = ""

См. docs/threat-model.md §«Privilege-escalation via MAC label» и docs/cert-issuance.md §«MAX_INTEGRITY».