Справочник конфигурации 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, паролей или клиентских хостов в этом документе нет.
Файл /etc/tessera/config.toml
Заголовок раздела «Файл /etc/tessera/config.toml»Полный поставочный пример лежит в
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 | целое | 3 | 1..=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 | целое | 10 | 0..=60 | Сколько секунд ждать вставки токена. | 0 — не ждать; UX vs. удобство. |
pkcs11_allow_extractable_keys | булево | false | true, false | Принимать ли ключи, о которых токен сообщил CKA_EXTRACTABLE = TRUE. | false (default) — отказ (fail-closed): extractable-ключ ломает инвариант режима B. true — только WARN pkcs11_extractable_key; включать осознанно. Случай, когда токен вообще не сообщил атрибут, этим ключом НЕ разрешается — для него есть pkcs11_allow_unreported_extractable. |
pkcs11_allow_unreported_extractable | булево | false | true, 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 | целое | 10 | 0..=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 | целое | 8 | 1..=64 | Максимум партиций, перебираемых при поиске .p12. | Защита от DoS: физический атакующий не сможет навязать огромное число mount/umount. |
on_usb_removed | строка | "lock" | "lock", "logout", "hook", "shutdown" | Действие при подтверждённом извлечении USB. | "shutdown" уместен для терминалов; "lock" — для рабочих станций. |
usb_removed_grace_seconds | целое | 0 | 0..=600 | Окно отмены: реинсерт того же серийника отменяет действие. | Защищает от ложных срабатываний; на терминалах ставить 0. |
suspend_grace_seconds | целое | 0 | 0..=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-модуля в процессе
Заголовок раздела «Жизненный цикл PKCS#11-модуля в процессе»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 на живой библиотеке провайдер в
лучшем случае отвергает, в худшем роняет процесс.
Значения on_usb_removed
Заголовок раздела «Значения on_usb_removed»| Значение | Действие при подтверждённом извлечении 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.
Секция [monitor]
Заголовок раздела «Секция [monitor]»Настройки IPC-канала PAM-модуля с демоном monitord, который следит
за извлечением носителя: таймауты, лимиты соединений, путь сокета и
пер-секционные переопределения действий из глобального блока.
| Поле | Тип | Default | Допустимые значения | Влияние | Влияние на безопасность |
|---|---|---|---|---|---|
on_usb_removed_hook_path | путь | None | абсолютный путь | Исполняемый файл для on_usb_removed = "hook". Валиден только при этом значении on_usb_removed. | Исполняется от root; путь проверяется на небезопасные права. |
idle_timeout_seconds | целое | 30 | 1..=3600 | Idle-таймаут IPC-соединения с monitord. | Анти-DoS: висящие соединения закрываются. |
max_concurrent_connections | целое | 64 | 1..=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). | Слишком большое окно ослабляет реакцию на извлечение. |
Секция [trust]
Заголовок раздела «Секция [trust]»Корни доверия и правила проверки X.509-цепочки: какие CA доверенные, предельная глубина цепи, допуск на рассинхрон часов и разрешённые алгоритмы подписи.
| Поле | Тип | Default | Допустимые значения | Влияние | Влияние на безопасность |
|---|---|---|---|---|---|
anchors | список путей | — | ≥ 1 PEM-файл | Корневые CA доверия. | Корень доверия. Должны быть 0640 root:root. |
intermediates | список путей | [] | PEM-файлы | Промежуточные CA (опционально). | Снимает нагрузку с поиска цепи. |
max_chain_depth | целое | 5 | 1..=16 | Максимальная глубина X.509-цепи. | Анти-DoS. |
clock_skew_seconds | целое | 0 | 0..=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"
Секция [trust.revocation]
Заголовок раздела «Секция [trust.revocation]»Как проверяется отзыв сертификатов — источники (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 | целое | None | 1..=8760 (часы) | Максимальный возраст CRL от thisUpdate до отказа. | Не задан — свежесть CRL не проверяется; не рекомендуется. |
ocsp_responder_url | строка URL | — | http://… / https://… | Адрес OCSP-responder’а. ОБЯЗАТЕЛЕН при mode ∈ {ocsp, crl_then_ocsp}. AIA из серта не извлекается. | Единственный источник адреса — конфиг (предсказуемость офлайн-аудита). |
ocsp_timeout_seconds | целое | 5 | 1..=30 | Общий deadline одного OCSP-обмена (connect+write+read). | Бюджет логина = (глубина цепи − 1) × таймаут. |
ocsp_cache_ttl_seconds | целое | 3600 | 0..=86400 | Верхний предел жизни кэш-записи (0 = кэш выключен). | Кэш ограничивает сетевые вызовы; запись валидна до min(nextUpdate, mtime+ttl). |
Семантика режимов отзыва:
mode | Поведение |
|---|---|
none | Отзыв не проверяется; компенсация — короткий TTL leaf-сертов (deployment-политика). |
crl | Strict 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 либо offlinecrl. OCSP — для сегментов с сетью (офисные АРМ, стенды заказчиков).ocsp_*-ключи приmode ∈ {none, crl}отвергаются валидацией (не могут молча игнорироваться). Кэш —/var/cache/tessera/ocsp/*.der, каталог создаёт postinst пакета.
Секция [trust.pinning]
Заголовок раздела «Секция [trust.pinning]»Пиннинг корневых CA по SPKI-хешу — защита на случай компрометации УЦ: корень не из списка отвергается, даже если цепочка формально валидна.
| Поле | Тип | Default | Допустимые значения | Влияние | Влияние на безопасность |
|---|---|---|---|---|---|
enabled | bool | false | true, false | Включает pinning по SPKI корневых CA. | Защита от компрометации УЦ. |
allowed_root_spki_sha256 | список строк | [] | 64-символьные lower-case hex | Список разрешённых SPKI-хешей корней. | Любой корень не из списка отвергается. |
Секция [host_identity]
Заголовок раздела «Секция [host_identity]»Как вычисляется 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 | строка | None | UTF-8, без перевода строк | Жёстко заданное значение host_id (для тестов). | НЕ использовать на production. |
custom_command | путь | None | абсолютный путь к скрипту | Скрипт, печатающий host_id в stdout. | Скрипт исполняется от root. Должен быть 0750 root:root. |
custom_command_timeout_seconds | целое | 5 | 1..=30 | Таймаут на исполнение custom_command. | Анти-DoS. |
Значения
custom_command_timeout_secondsвне диапазона1..=30приводятся к ближайшей границе (0→1,> 30→30), а не отклоняются при загрузке.
Реализация цепочки — в
crates/tessera_core/src/host_identity/chain.rs.
Поведение fallback = "deny" гарантирует fail-closed: если ни один
источник не дал значения, аутентификация не проходит.
Секция [logging]
Заголовок раздела «Секция [logging]»Детализация журнала демона. Два поля (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_priority | bool | опционален | true, false | Deprecated, игнорируется. При наличии ключа — WARN «deprecated and ignored». | Не влияет на поведение. |
PIN-коды и пароли никогда не логируются. Полные DN сертификатов логируются на уровне
debugи выше; наinfoи ниже — только CN.
Секция [roles]
Заголовок раздела «Секция [roles]»Управляет выбором роли на логине и базой ролей устройства (см.
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 | целое | 10 | 1–60 (секунды) | Сколько ждать ответа разрешения имён при проверке «не системная ли это учётная запись». | Не уложился — вход идёт по локальному файлу, а не отвергается; 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]] отвергается при валидации с диагностикой
об удалении секции.
Секция [tags]
Заголовок раздела «Секция [tags]»Теги устройства для делегационных ограничений (device-tags). Отсутствие
секции = у устройства нет тегов (fail-closed дефолт): делегационный
конверт с групповым ограничением на бестеговом устройстве отклоняется.
| Поле | Тип | Default | Допустимые значения | Влияние | Влияние на безопасность |
|---|---|---|---|---|---|
enforce | булево | false | true/false | Читать ли источник тегов. false — устройство без применённых тегов. | Групповые делегации на бестеговом устройстве всё равно отклоняются (fail-closed). |
mode | строка | standalone | standalone, managed | Доверенная модель источника: файл тегов или подписанный manifest.toml. | managed требует подписи манифеста. |
source | путь | /var/lib/tessera/tags.toml (standalone) / каталог role-store (managed) | абсолютный путь | Файл тегов либо каталог с манифестом. | Standalone-загрузка проверяет root-owned файл и весь путь без group/world write. |
Секция [[hooks]]
Заголовок раздела «Секция [[hooks]]»Массив таблиц. Каждый хук — внешняя команда, исполняемая в стадии
жизненного цикла. Полная реализация — в
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 | целое | 10 | 1..=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_*-переменные; кастомные ключи могут их переопределить. |
Подстановка ${...} работает только в значениях env — command
исполняется буквально (см.
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 = 5on_failure = "warn"env = { AUDIT_USER = "${pam_user}", AUDIT_SERIAL = "${cert_serial}" }Секция [fly_dm_greeter] (0.3.19+)
Заголовок раздела «Секция [fly_dm_greeter] (0.3.19+)»Опциональная. Контролирует 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_wallpaper | bool | false | Включить wallpaper writer. |
wallpaper_target | path | /usr/share/wallpapers/fly-default-light.jpg | JPG, который daemon перерисовывает. |
wallpaper_backup | path | /var/lib/tessera/daemon/wallpaper.orig.jpg | Куда сохраняется one-time оригинал источника. |
wallpaper_font | path | /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf | TrueType шрифт для рендера. |
wallpaper_font_size | int | 64 | Размер шрифта в пунктах (1..=512). |
wallpaper_text_color | string | "#000000" | Цвет в hex (#RRGGBB). |
wallpaper_gravity | enum | "south" | north / south / east / west / center — якорь позиционирования. |
wallpaper_offset_x | int | 0 | Горизонтальное смещение в пикселях от gravity-якоря. |
wallpaper_offset_y | int | 120 | Вертикальное смещение в пикселях от gravity-якоря (для south — вверх). |
template_ru | string | "Устройство %n host_id={host_id_short} ({source})" | Шаблон для ru locale. |
template_en | string | "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_override]]
Заголовок раздела «Секция [[trust_override]]»Массив таблиц. Каждая запись — переопределение [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 = 10on_usb_removed = "lock"usb_removed_grace_seconds = 5suspend_grace_seconds = 30monitor_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(OID2.25.183976554325829274683049824615098) —SEQUENCE OF UTF8String, каждая запись — либо*, либоsha256:<HEX>, либо «сырое» значениеmachine_id(тогда сравнение идёт через SHA-256 от строки).pam_cert_allowed_roles(OID2.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 = 3pkcs11_slot_wait_seconds = 5
usb_wait_seconds = 5on_usb_removed = "shutdown" # терминал — выключаемсяusb_removed_grace_seconds = 0 # без отменыsuspend_grace_seconds = 0monitor_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 = trueallowed_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 = 3pkcs11_slot_wait_seconds = 10
usb_wait_seconds = 10on_usb_removed = "lock"usb_removed_grace_seconds = 30suspend_grace_seconds = 60monitor_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 = 5on_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 USBpkcs12_pin_prompt = "PKCS#12 password: "
usb_wait_seconds = 5on_usb_removed = "lock"usb_removed_grace_seconds = 5suspend_grace_seconds = 0monitor_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 integrity (Astra МКЦ, 0.3.0+)
Заголовок раздела «MAC integrity (Astra МКЦ, 0.3.0+)»Секция [mac] опциональна. Один и тот же открытый бинарь работает на
Debian/Ubuntu/Astra. Реальный enforcement появляется только при установке
подписанного runtime-плагина и его явном выборе полем backend.
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
backend | string | — | Имя явно выбранного runtime-плагина, например "parsec". Нет поля — StubBackend. |
cert_integrity | enum | "optional" | Один из required / optional / ignore. См. ниже. |
fallback_max_integrity.level | int (-128..127) | — | Уровень fallback-метки, если расширение MAX_INTEGRITY отсутствует и cert_integrity = "optional". |
fallback_max_integrity.categories | string (hex или CSV) | — | Битовая маска категорий для fallback. Пустая строка = ''B. |
runtime | enum | "auto" | Один из required / auto / disabled. См. ниже (0.3.7+). |
warn_on_homedir_label_mismatch | bool | true | Логировать homedir_label_above_session_cap при расхождении. |
Семантика cert_integrity
Заголовок раздела «Семантика cert_integrity»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 МКЦ.
Семантика runtime (0.3.7+)
Заголовок раздела «Семантика runtime (0.3.7+)»Поле 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 = 0categories = ""См. docs/threat-model.md §«Privilege-escalation via MAC label» и
docs/cert-issuance.md §«MAX_INTEGRITY».
Дальнейшее чтение
Заголовок раздела «Дальнейшее чтение»- docs/install.md — пошаговая установка.
- docs/architecture.md — модель доверия и IPC-протокол.
- docs/threat-model.md — каждое поле через призму угроз.
- docs/operations.md — как менять конфиг на работающей машине без обрыва сессий.