Changelog
Переименование проекта (2026-06-04): проект
pam_certauthпереименован в Tessera (CLI/пакетtessera, модульpam_tessera.so, пути/etc/tesseraи т.д.). Записи ниже относятся к релизам под прежним именемpam_certauthи сохранены как есть — упомянутые в них команды, бинари и пути соответствуют тому, что поставлялось на момент релиза.
[0.5.0] — unreleased
Заголовок раздела «[0.5.0] — unreleased»Breaking
Заголовок раздела «Breaking»-
Роль на логине — это имя учётной записи входа; суффикс
<user>+<role>и PAM-prompt удалены. Инженер входит в ролевую учётную запись, названную по роли (ssh serv@device), и запрошенная роль равнаPAM_USER. Других источников роли не осталось. Вместе с суффиксом ушло и переписываниеPAM_USER: пока модуль подменял имя, прочитанное стеком, разница между «до» и «после» подмены была доступна другим модулям стека — тот же класс, что вскрыл CVE-2021-3560 в polkit. Теперь подменять нечего. Имя учётной записи, не удовлетворяющее форматуrole_id(^[a-z][a-z0-9-]{0,15}$), отвергает вход до обращения к носителю. Личность инженера не теряется — она в удостоверении и журнале выпусков, а не в имени учётной записи. Миграция: входivanov+serv@deviceзаменяется наserv@deviceс удостоверением, чейpam_cert_allowed_rolesсодержит рольserv(см. запись про единственный список допуска ниже). Ролевые учётные записи должны быть заведены на устройствах до обновления (провижининг — Census) как обычные, а не системные: вход в учётную запись, чей uid вне диапазона обычных пользователей, продукт отвергает. Остальные пути входа в них (authorized_keys,su,sudo -u, парольный вход, PAM-стеки без модуля) закрывают провижининг и администратор — см. install.md §8.4. Руководство по установке (docs/{ru,en}/install.md) переработано на ролевую учётную запись: выдача тестового удостоверения ведётся на рольserv, и добавлен раздел о ролевом хранилище устройства — без заполненного хранилища вход не проходит. -
Допуск к учётной записи описывает один список —
pam_cert_allowed_roles; расширениеpam_cert_user_binding, аргументissuer issue-leaf --userи секция[[user_mapping]]удалены. Раз имя учётной записи входа и есть роль,user_binding(«в какие учётные записи пущен предъявитель») иallowed_roles(«какие роли вправе активировать») проверялись против одной и той же строки и утверждали одно и то же; различие, которое они сохраняли, описывало нереализуемое состояние «пущен вserv, но не вправе бытьserv». Секция[[user_mapping]]удалена по другой причине: она была утверждением устройства о допуске («пущу любого, чей CN совпал»), а не выпускающего, — параллельный путь, где рамки назначает ограничиваемая сторона. Удостоверения чужой корпоративной PKI не поддерживаются, и обслуживать этот откат нечего. Конфиг, всё ещё содержащий[[user_mapping]], отвергается при старте с диагностикой об удалении, а не общей ошибкой «неизвестное поле». Миграция: уберите секцию изconfig.toml; в командах выпуска замените--user <учётная запись>на--role <роль>— это одно и то же имя. Ранее выпущенные штатным путём удостоверения продолжают работать:pam_cert_user_bindingигнорируется как любое неизвестное non-critical расширение, аallowed_rolesони уже несут (без него вход не работал и раньше). Перевыпуск нужен только там, где вход держался на[[user_mapping]], — но такое удостоверение и до этого изменения не проходило проверку роли. OID2.25.215438916728501023845629178354627считается отозванным и не будет переиспользован под другую семантику. -
issuer issue-rootиissuer issue-caтребуют--allow-roleи не принимают--max-ttl 0. Список ролей в рамках делегирования — закрытый белый список, поэтому пустой список разрешал не «любую роль», а ни одной; нулевой потолок TTL требовал от дочернего звена нулевого срока действия. Прежние умолчания (--allow-roleнеобязателен,--max-ttl 0) выпускали CA, через который аутентификация не проходила никогда, — при этом сертификат выглядел валидным и попадал в журнал выдачи, а расхождение обнаруживалось только на устройстве, с диагностикой, указывающей на цепочку доверия, а не на аргументы команды выпуска.--max-ttlтеперь имеет ненулевые умолчания по тому, что рамка ограничивает:31536000(год, срок CA организации) уissue-rootи14400(4 часа, срок листа смены) уissue-ca. Миграция: добавьте--allow-roleс именами ролей вашего внедрения; документированные примеры выпуска уже задают оба аргумента явно. -
Ключ
[roles].enforceудалён; роль проверяется всегда. Прежде это был миграционный тумблер ("false"|"warn"|"require") с умолчанием"false"— «роли не проверяются». Умолчание оказалось несовместимо с собственным выпуском: удостоверения штатного issuer всегда несут рамки делегирования, а вход без выбранной роли отвергает любую цепочку с рамками, то есть парк на умолчаниях не пускал никого по своим же удостоверениям. Промежуточное"warn"(проверить, записать, пропустить) убрано вместе с остальным: положения «проверить и пропустить» в модели доверия быть не должно. Конфиг, содержащий ключ в любом значении, отвергается при старте с диагностикой об удалении — намеренно громко, а не тихим ужесточением. Миграция: удалить ключ и убедиться, что база ролей под[roles].dirзаполнена, иначе входы отклоняются fail-closed (это поведение не менялось). Парк, чьи удостоверения выпущены безpam_cert_allowed_roles, теряет возможность входа — альтернатива только перевыпуск. -
[trust.revocation].modeтеперь обязателен. Пропуск секции[trust.revocation]целиком или ключаmodeвнутри неё — ошибка валидации конфига на старте. Раньше отсутствие режима молча означалоmode = "none"(отзыв не проверяется), и отозванное удостоверение проходило аутентификацию. Миграция: чтобы сохранить прежнее поведение, добавьтеmode = "none"явно; лучше — включите реальную проверку (mode = "crl"/"ocsp"/"crl_then_ocsp"). -
ГОСТ через OpenSSL теперь требует явный
gost_engine_path. Если allow-list разрешает ГОСТ-подписи, неявный поиск native-модуля через унаследованныйOPENSSL_ENGINESотклоняется при загрузке конфига. -
Persistent state демона перенесён в
/var/lib/tessera/daemon/. Родитель/var/lib/tessera/теперь0750 root:tessera, потому что в нём лежат доверенные роли, теги и enrollment material. Старыйwallpaper.orig.jpgбезопасно мигрируется postinst-скриптом. -
pkcs11_locking_modeпо умолчанию —mutex, а неos. Раньше по умолчанию вызовы PKCS#11-модуля шли без сериализации, потому что модуль просят проCKF_OS_LOCKING_OK. Заявленная поддержка этого флага потокобезопасности не гарантирует:rtpkcs11ecp2.14.1 при одновременномC_Initializeиз двух нитей роняет процесс необработанным C++-исключением. Модуль — cdylib внутриsshd,loginиfly-dm, поэтому падает процесс аутентификации. Теперь по умолчанию каждый вызов сериализуется процесс-глобальным мьютексом; цена — около 20 нс на вызов, что несопоставимо со временемC_Signна устройстве. Миграция: инсталляциям, которым нужна параллельность и чей поставщик модуля её подтверждает, добавитьpkcs11_locking_mode = "os"явно; режим никуда не делся. Явно заданное значение и раньше, и теперь имеет приоритет. -
Команда
issuer serveудалена; открытыйissuer— инструмент выпуска в командной строке. Веб-кабинет и агент выпуска поставляются отдельно, и вместе с командой ушли её feature и зависимостьtiny_http. Модуль подтверждения оператором остался как библиотечный API за фичейnative— внешние фронтенды подписи продолжают им пользоваться. Релизные бинари собираются сcli,pkcs11,vault,file. Миграция: браузерный выпуск — через коммерческий инструментарий, выпуск из командной строки не изменился. -
Кабинет выпуска вынесен из репозитория; реестр подписывается на стороне агента. Для этого у агента появился выделенный ключ подписи реестра (P-256, проверяется при старте) и конечная точка
/sign-registry. Спецификация кабинета уехала вслед за кодом. -
Токен, не сообщивший
CKA_EXTRACTABLE, больше не считается подтвердившим неизвлекаемость ключа. Инвариант режима B проверялся чтением атрибута, ноcryptokiмолча выбрасывает из результата всё, что токен отказался отдать: записи сCK_UNAVAILABLE_INFORMATIONпропускаются, аCKR_ATTRIBUTE_SENSITIVEиCKR_ATTRIBUTE_TYPE_INVALIDошибкой не считаются. Парсер начинал с «неизвлекаем» и переписывал значение только при наличии атрибута, поэтому молчание провайдера было неотличимо от подтверждения, и ключ принимался. Теперь состояний три: подтверждённоеFALSE, подтверждённоеTRUEи «не сообщено»; последнее отклоняется отдельной ошибкойExtractableAttributeUnavailableс указанием причины отказа. Если уточняющий запрос сообщает, что атрибут читаем, выполняется одиночное перечитывание — провайдеры, ломающиеся на смешанных шаблонах, известны, а прямое чтениеFALSEи есть требуемое доказательство. Миграция: развёртывания, где токен атрибут не отдаёт, до обновления входили молча, после обновления получают отказ; вход восстанавливается ключомpkcs11_allow_unreported_extractable = true. Обходные ключи независимы:pkcs11_allow_extractable_keysразрешает только подтверждённоеTRUEи неотвеченный атрибут больше не покрывает, чтобы оператору, которому нужна совместимость с молчащим модулем, не приходилось заодно соглашаться на экспортируемые ключи. Измерено: на Рутокен ЭЦП 3.0 поведение не меняется — токен отдаётCKA_EXTRACTABLEштатно. От остального парка (SoftHSM2, JaCarta-2 GOST, Рутокен Lite) того же ожидаем, но по этому атрибуту не проверяли: если ваш модуль его не отдаёт, вход после обновления отклонится до установкиpkcs11_allow_unreported_extractable = true. -
Публичный API
tessera_core: полеFoundPrivateKey::extractableменяет тип сboolна перечислениеExtractableState— булево не может выразить «токен отказался отвечать», не соврав. Модульtoken::pkcs11экспортирует новые типыExtractableState,ExtractableProbeиExtractableKeyPolicy.
Security
Заголовок раздела «Security»- Фазы сессии и account management действуют только от имени, под которым
прошёл допуск.
PAM_USERпринадлежит приложению, а не модулю, и междуpam_sm_authenticateиpam_sm_open_sessionего значение никем не закреплено. Раньше сессионная фаза перечитывала имя и отдавала его как есть в MAC-метку сессии и в привилегированные хуки, а при ошибке чтения подставляла CN удостоверения — то есть личность инженера вместо имени ролевой учётной записи. Теперь прочитанное имя сверяется с ролью, зафиксированной в контексте аутентификации (имя ролевой учётной записи и есть роль), и расхождение отклоняется fail-closed:PAM_SESSION_ERRвopen_session,PAM_PERM_DENIEDвacct_mgmt, пропуск хуков вclose_session(выход из системы блокировать нельзя). Запасного имени нет ни при каком исходе. - PAM проверяет конфиг, trust anchors/intermediates, CRL, PKCS#11-модуль, GOST engine, standalone-роли и device tags по полной root-controlled цепочке пути. Symlink, non-root owner или group/world write приводят к fail-closed отказу.
- Битое присутствующее расширение
MAX_INTEGRITYбольше не считается отсутствующим: обе credential-схемы отклоняют его до применения optional fallback. - Unmaintained-цепочка
ab_glyph -> owned_ttf_parser -> ttf-parserудалена. Wallpaper writer переведён на maintainedskrifaиab_glyph_rasterizer; размеры шрифта, шаблона и raster allocation ограничены.
-
Вход по сертификату на Windows. Провайдер учётных данных на экране входа и служба движка под
LocalSystem; ядро проверки переиспользовано без изменений — всё, что понадобилось в нём поправить, это платформенные ограждения, логика проверки не двигалась. Инженер предъявляет съёмный носитель, выбирает роль, вводит PIN и попадает на рабочий стол. Роль пока не даёт прав: она выбирается, сверяется с тем, что допускает удостоверение, и попадает в журнал — но выдача прав остаётся за срезом, вместе с самым дорогим нерешённым вопросом дизайна. Доверие локальным файлам получило настоящую реализацию под Windows: конфигурация и ролевое хранилище читаются через него, а POSIX-политика вне Unix отказывает целиком. -
Один открытый host вместо сборок под feature-флаги: платформенные enforcement-бэкенды поставляются подписанными плагинами и выбираются в runtime секцией
[mac] backend. Подпись проверяется до загрузки.so; любой отказ — деградация в stub-бэкенд.
- PKCS#11-библиотека инициализируется один раз на процесс, а не на
каждую загрузку бэкенда.
Pkcs11Backend::loadзвалC_Initializeпри каждом вызове, хотя это процесс-глобальное состояние провайдера. Повторный вызов в лучшем случае возвращалCKR_CRYPTOKI_ALREADY_INITIALIZED, а одновременный — наrtpkcs11ecp2.14.1 убивал процесс. Теперь контекст живёт в процесс-глобальном реестре по канонизированному пути модуля: сколько бы ни было загрузок, инициализация одна. Разные модули в одном процессе по-прежнему получают независимые контексты. Создание контекста сериализовано независимо отpkcs11_locking_mode— два бэкенда могут быть настроены по-разному, а дефект провайдера процесс-глобален; сам режим теперь закрепляется за модулем при первой загрузке, расхождение логируется какpkcs11_locking_mode_conflict.C_Finalizeне вызывается вообще: это процесс-глобальная операция над общей загруженной библиотекой, и она деинициализировала бы провайдера у постороннего потребителя PKCS#11 в том же процессе (pam_pkcs11,sshdсPKCS11Provider), который не может об этом узнать. Цена — модуль остаётся загруженным до завершения процесса. Дефект не ловился три года, потому что весь PKCS#11-контур проверялся на SoftHSM2, который гонку переживает. - Выпущенный лист смены снова пригоден для аутентификации.
issuer issue-leafне клал в удостоверение ниkeyUsage(digitalSignature), ниextendedKeyUsage(clientAuth), а проверка листа в модуле требует оба. Любой выпуск штатным путём отвергался на первом же гейте с диагностикойtrust: key usage missing required bit— доступ не выдавался (fail-closed), но и выдаться не мог. Теперь лист несётkeyUsageсdigitalSignature(critical) иextendedKeyUsageсclientAuth; самопроверка после подписи отказывается отдать артефакт, в котором любого из них нет. Дефект пережил обе половины тестов, потому что выпуск сверялся со своими ожиданиями, а модуль — со своими фикстурами: добавлена перекрёстная проверка, прогоняющая выпущенный лист черезpre_validate_end_entity— ту же функцию, которой пользуется модуль. - Исчерпание попыток PIN возвращает
PAM_MAXTRIES(11), а не 8.FlowError::pam_codeдля классаMaxTries/PinLocked/MaxAttemptsExceededотдавал8под именемPAM_MAXTRIES, но в<security/_pam_types.h>8— этоPAM_CRED_INSUFFICIENT. Приложение вместо «попытки исчерпаны» получало «не удаётся получить данные аутентификации»:loginиsshdприPAM_MAXTRIESпрекращают дальнейшие запросы, приPAM_CRED_INSUFFICIENTтакого сигнала нет, а администратор по коду возврата искал проблему с носителем вместо заблокированного PIN. Доступ при этом не выдавался — fail-closed не нарушался. Значение в документации (architecture.md, таблица кодов возврата) исправлено там же. issuerпо умолчанию ставитprofile_version = 0, а не1. Три подкоманды выпуска (issue-root,issue-ca,issue-leaf) без явного--profile-versionзаписывали в удостоверение версию формата1, тогда как Engine без ключа[trust].max_supported_profile_versionпринимает максимум0. Любое удостоверение штатной выдачи отвергалось на устройстве с ошибкой «profile_version 1 exceeds supported maximum 0» — fail-closed сработал как задумано, но на паре «выпуск ↔ проверка» это означало неработоспособную конфигурацию по умолчанию. Дефолт выдачи опущен до0(текущая версия формата); потолок Engine не менялся. Кому нужен прежний вывод — передавайте--profile-version 1явно и поднимайтеmax_supported_profile_versionна устройствах.
Breaking
Заголовок раздела «Breaking»- OCSP-режимы отвергаются валидатором конфига.
[revocation].mode = "ocsp"/"crl_then_ocsp"и любыеocsp_*ключи теперь дают ошибку валидации на старте. Раньше ключи принимались, но OCSP-проверка не выполнялась — конфиг молча обещал то, чего нет. OCSP не реализован; план — вopenspec/changes/ocsp-support. crl_max_age_hours = 0невалиден. Допустимый диапазон —1..=8760. Ноль раньше означал «каждая CRL мгновенно stale» — бессмысленная конфигурация, которую принимали молча. Чтобы отключить bound по возрасту — опускайте ключ.- Extractable-ключ на PKCS#11-токене отклоняется по умолчанию.
Ключ с
CKA_EXTRACTABLE = TRUEпротиворечит смыслу аппаратного токена (приватный ключ можно выгрузить). Opt-out для legacy-токенов:pkcs11_allow_extractable_keys = true. - Пустой
[trust].anchors— ошибка валидации. Раньше пустой список принимался, и любая валидация цепочки гарантированно падала на первой auth-сессии вместо старта демона. - Malformed
pam_cert_user_binding— отказ аутентификации. Расширение, которое есть в сертификате, но не парсится, раньше приводило к fallback на[[user_mapping]]— повреждённое (или подделанное) binding-расширение тихо понижало источник авторизации. Теперь fail-closed. syslog_facility/journald_prioritydeprecated. Ключи игнорируются (WARN в лог при наличии в конфиге); удалите их изconfig.tomlна следующем переоформлении.- Подпись CRL верифицируется fail-closed. Подпись каждой применимой CRL проверяется ключом её issuer’а из verified-цепочки; CRL, подписанные не issuer’ом (или с невалидной подписью), перестанут приниматься — раньше revoked-список такой CRL применялся без проверки подписи.
[0.3.19] — 2026-05-27
Заголовок раздела «[0.3.19] — 2026-05-27»pam-certauth dump-host-idCLI subcommand. Пробует ВСЕ каноническиеhost_identity-источники (не только подмножество в[host_identity].sources) и пишет TSV-отчёт. Колонки:source,status,hash_hex,hash_prefix,raw,normalized,active_under_current_config,reason. Строка сactive_under_current_config=yesотмечает источник, который daemon сейчас реально использует. Запись атомарная через--output FILEили--usb(автоматический mount первой USB r/w, имяhost-ids-<hostname>-<UTC>.tsv). Stdout без флагов. Exit ≠ 0, если ни один источник не отдал непустое значение — сигнал «не выписывать сертификат, пока не починён вход»./usr/share/pam-certauth/finish-bootstrap.sh— single-pass переход с clone-image bootstrap state на production. Атомарный rewriteconfig.toml(sources = ["override"]→ реальные источники) с backup’ом.bak.<UTC>, валидация черезpam-certauth check, рестарт daemon’а,dump-host-id --usb. Флаги:--non-interactive(Ansible),--sources "A,B"(или envPOST_INSTALL_SOURCES),--no-restart,--no-dump. Идемпотент: повторный запуск безsources = ["override"]exit 0 без изменений.- fly-dm greeter pivot #2: wallpaper writer. GreetString.desktop
writer из 0.3.16 оказался no-op на production МКЦ-3 АРМ (fly-modern
hardcoded’но рендерит «Усиленный уровень защищенности» в headline
place). Замена: впечатать
host_idв JPG, на который смотрит[background].pathв/etc/X11/fly-dm/fly-modern/settings.ini. Opt-in:[fly_dm_greeter].update_wallpaper = true. Pure Rust (image+ab_glyph), без native deps. Templatestemplate_ru/template_en(по locale), one-time backup оригинала. Atomic save. - CA-инструменты (выпуск per-host / wildcard / bootstrap
сертификатов, настройка PKI, подготовка USB) вынесены из
.deb: не должны лежать на боевых АРМ, поставляются отдельно и хранятся на CA-машине.
- Новый документ
docs/clone-image.md— end-to-end runbook раскатки парка АРМ через клонированный образ (bootstrap-эталон,finish-bootstrap.sh, CA-сторона, troubleshooting, Ansible). docs/install.md§2.4¾,docs/operations.md§2.4,docs/cert-issuance.mdcross-linked на новый документ.
[0.3.18] — 2026-05-26
Заголовок раздела «[0.3.18] — 2026-05-26»Removed
Заголовок раздела «Removed»- Drop debconf prompt в
debian/postinst(введён в 0.3.16). Операторы раскатывают через готовыеconfig.toml(Ansible / centralized rollout) — debconf-шаг приdpkg installтолько переключал флаг вconfig.tomlи добавлял moving parts (templates, debconf dep, postinst branching) без изменения production-поведения. - Удалены
debian/templates,debian/config, debconf-branch вdebian/postinst,debconfизdebian/control Depends,confmodulesourcing.
Unchanged
Заголовок раздела «Unchanged»- Daemon-side feature не менялась:
[fly_dm_greeter].update_greet_string = trueвconfig.tomlпо-прежнему заставляет демон переписывать/etc/X11/fly-dm/override/GreetString.desktopна старте.
[0.3.17] — 2026-05-26
Заголовок раздела «[0.3.17] — 2026-05-26»CI-only
Заголовок раздела «CI-only»- Per-build pipeline time снижено:
astrajob теперьcargo nextest run --workspace --features astra-macв debug profile. Прежний release-run занимал 510s vs ubuntu 56s (9.1×) из-за workflow-levelCARGO_PROFILE_RELEASE_CODEGEN_UNITS=1LTO=thin(нужны для production.deb), которые делают release test compilation катастрофически медленным. Тесты по-прежнему проходятastra-macfeature-gate.
scripts/build-deb.shбольше не делаетcargo clean && cargo build --releaseпередdpkg-buildpackage.debian/rulesуже чиститtarget/черезoverride_dh_auto_cleanи пересобирает черезoverride_dh_auto_build— pre-build был чистым дублированием работы (~60s/pipeline).
- Source-code изменений vs 0.3.16 нет. Тот же
.deb, быстрее CI.
[0.3.16] — 2026-05-26
Заголовок раздела «[0.3.16] — 2026-05-26»Changed
Заголовок раздела «Changed»- fly-dm greeter pivot. Revert
fly-dmrc greeter-show-messages(cargo-cult из 0.3.15 — таргетил KDM/LightDM legacy key, который fly-qdm не парсит). Переключение на механизм, который реально работает на Astra fly-qdm 2.15+:/etc/X11/fly-dm/override/GreetString.desktop(freedesktop i18n.desktopфайл). Live-testing на Astra VM подтвердил рендерhost_idв headline login-form’ы. - Opt-in:
[fly_dm_greeter].update_greet_string = true. На старте daemon резолвит host identity и переписываетGreetString.desktopс short hash + source:Original сохраняется какУстройство astra184 · host_id=abc12345 (dmi_board_serial).origпри первом запуске. Atomic write (tmpfile + rename). Silent skip на хостах без/etc/X11/fly-dm/. Log-and-continue — auth никогда не блокируется ошибкой greeter writer. - Debconf prompt в
debian/postinst(en + ru) спрашивает оператора про включение banner’а; default off. Удалён в 0.3.18 (см. выше). pam-certauth checkvalidator:fly_dm_greeter_greet_string_customized,_default,_override_missing,_read_failed— Info-записи.integrate-pam.sh: убраны--enable-greeter-messages/--no-greeter-messagesфлаги иfly-dmrc patcherиз 0.3.15. Скрипт снова занимается только PAM-stack editing.
[0.3.15] — 2026-05-26
Заголовок раздела «[0.3.15] — 2026-05-26»Added (superseded by 0.3.16 → 0.3.19)
Заголовок раздела «Added (superseded by 0.3.16 → 0.3.19)»- Попытка №1 заставить fly-dm показать PAM_TEXT_INFO с
host_id: патчfly-dmrcчерезintegrate-pam.sh(greeter-show-messages = trueпод[greeter]). Идемпотент, backupfly-dmrc.bak.<UTC>. Флаги--enable-greeter-messages(default ON дляfly-dm*),--no-greeter-messages(CI).--unintegratefly-dmrcне трогает. - Startup-check
fly_dm_greeterчитаетfly-dmrcи эмитит Info-запись:messages_on/_off/_default/_read_failed. Silent skip при отсутствии файла (sshd-only хосты, серверы). - Не сработало на fly-qdm 2.15+ — таргетили KDM legacy key. Revert в 0.3.16, окончательный workaround через wallpaper writer в 0.3.19.
[0.3.14] — 2026-05-25
Заголовок раздела «[0.3.14] — 2026-05-25»- Build hotfix. Timestamp в
debian/changelogдля 0.3.13 (12:00 +0300) был раньше чем 0.3.12 (18:00 +0300) — Ubuntu lintianlatest-changelog-entry-without-new-dateвалил.debbuild. Astra.debв 0.3.13 release собрался (Astra pipeline пропускает lintian). Этот релиз бампает версию + timestamp, оба.debстроятся. - Функциональных изменений vs 0.3.13 нет.
[0.3.13] — 2026-05-25
Заголовок раздела «[0.3.13] — 2026-05-25»- Hotfix: ACTUALLY implement
XDG_SESSION_IDcapture inpam_sm_open_session. Версии 0.3.10/0.3.11/0.3.12 в changelog’е заявили, что капчурятXDG_SESSION_IDи отправляютUpdateSessionTargetв monitord, но в cdylib код этого не было —pam_sm_open_sessionне вызывалpam_getenv, не открывал IPC и не слалUpdateSessionTarget. На production-терминалах USB-removal Logout/Lock молча не работал три релиза подряд: action handler не имел logind id и падал наterminate_session. - Добавлены:
crate::pam_helpers::pam_get_env_string(thin обёртка надpam_getenv) и новый модульcrate::xdg_captureс чистой функциейcapture_xdg(unit-тесты на три ветки: XDG отсутствует / присутствует и IPC ok / IPC fail). Production cdylib зовётcapture_xdgпосле MAC pipeline и до session_open hooks; любая IPC-ошибка логируется WARN и НЕ ломает session-open (best-effort semantics). - Wire-протокол не меняется — переиспользуется
UpdateSessionTargetфрейм, отправленный с daemon-side ещё в 0.3.10.
[0.3.9] — 2026-05-25
Заголовок раздела «[0.3.9] — 2026-05-25»-
Startup validation pipeline. Daemon теперь на старте, сразу после
load_validated_config, прогоняетrun_startup_checks— единый sweep из шести проверок, который ловит мисконфиги, не видимые TOML-валидатором:- PAM stack ordering. Сканирует
/etc/pam.d/{login,fly-dm, fly-dm-np,sshd,sudo,su}. Если@include certauth-*стоит ПЕРЕДauth required pam_parsec_mac.so— ERROR с подсказкой проintegrate-pam.sh(регрессия фикса из 0.3.8: на Astra SE неправильный порядок убивает account-фазуpam_parsec_mac). [mac].runtimevs ядро.requiredбез активногоparsec_strict_mode()=1— ERROR + fail-fast (раньше demon стартовал, и фейл случался на каждой auth). Остальные комбинации — INFO/WARN с явным текстом.- Trust anchors / intermediates. Существование + не-ноль байт +
счётчик
BEGIN CERTIFICATEмаркеров. Defense-in-depth: TOML валидатор отвергает невалидные пути на загрузке, startup-check ловит сценарий «файл удалили/обрезали между провизионингом и рестартом». /etc/pam_certauth/ca/permissions. WARN, если каталог world-writable (mode & 0o002 != 0).PARSEC_CAP_CHMAC. WARN, если МКЦ-ядро активно и[mac].runtime ≠ disabled, но у процесса нет capability — метки наsessions.jsonне лягут.host_identityprobe. По одной INFO/WARN-строке на каждый настроенный источник — admin сразу видит резолв, не дожидаясь первой auth-сессии.
Каждая запись помечена стабильным
checkID (pam_stack_misorder,mac_runtime_required_missing_kernel,trust_anchor_missing, …) — grep’абельно черезjournalctl -u pam-certauth -g startup_check. При наличии хотя бы одного ERROR boot обрывается с явным сообщением — заметно вsystemctl status, а не в первой неудачной auth-сессии. - PAM stack ordering. Сканирует
-
pam-certauth checksubcommand. Standalone preflight: загружает config + прогоняет тот же pipeline без открытия socket’а. Печатает[INFO ]/[WARN ]/[ERROR]лог + summary. Exit 0 — чисто, exit 1 — есть ERROR. Можно навесить наExecStartPre=в systemd-unit, чтобы превратить preflight в hard gate. Документация вdocs/install.md§2.4½.
[0.3.8] — 2026-05-25
Заголовок раздела «[0.3.8] — 2026-05-25»Critical
Заголовок раздела «Critical»integrate-pam.shplacement fix для Astra SE. Скрипт теперь вставляет@include certauth*ПОСЛЕ существующей строкиauth ... pam_parsec_mac.so, если она есть, а не перед первойauth-строкой. Боевой кейс: на Astra SE 1.8.3/etc/pam.d/loginи/etc/pam.d/fly-dmштатно начинаются сauth required pam_parsec_mac.so; placement до неё приводил к тому, чтоsuccess=donejump изcertauth-onlysnippet’а обходил auth-инстанс pam_parsec_mac, и account/session-инстансы валились"Can't obtain required data"→ login deny несмотря на успешную cert-аутентификацию. Подтверждено на проде (Astra SE 1.8.3, kernel 6.1.141): после reordering login проходит до конца, fly-dm greeter banner и MAC integrity level 63 — всё работает.- Test harness:
tests/scripts/test_integrate_pam.shпроверяет инвариант «@include certauth-onlyстрого послеpam_parsec_mac.so».
Документация
Заголовок раздела «Документация»docs/install.md§8 — описана логика placement anchor.docs/install.md§10 «Что делать, если…» расширен на 8 новых troubleshooting-кейсов из боевой отладки:pam_parsec_mac: Can't obtain required data(три причины + фиксы)parsec.mac=0+ pam_parsec_mac в стеке (включить kernel МКЦ vs убрать из стека)- Legacy
[mac].enabled = true(TOML parse error → миграция на[mac].runtime) - WARN
mac_caps_missing/pdp_set_fd rc=-1— не блокеры, как выдатьPARSEC_CAP_CHMACесли нужна метка на session-файле - 14-секундная тишина после
trying USB candidateна 0.3.5 (фикс через апгрейд на 0.3.6+) dmi_board_serial = 0в виртуалках, drift host_id при пересборке- fly-dm не показывает greeter banner (
greeter-show-messages = true) - DIGSIG enforce без подписи (подписание или logging-only)
pam-certauthмодуль не загружается (ldd missing libparsec-mic.so.3)
[0.3.7] — 2026-05-25
Заголовок раздела «[0.3.7] — 2026-05-25»Critical
Заголовок раздела «Critical»[mac].runtimeruntime-переключатель Parsec backend. Новое поле[mac].runtime(required|auto|disabled, defaultauto) разводит compile-time featureastra-macот runtime-выбора backend’а. Боевой кейс: один.deb(собранный сastra-mac) ставится на терминалы с МКЦ и без — поведение управляется черезconfig.toml.disabled— гарантированныйStubBackend, никакихpdp_*вызовов даже на сборке сastra-mac(фиксирует событиеmac_runtime_disabledв syslog).required— fail-closed: еслиparsec_strict_mode()ядра вернул не «активно», аутентификация отклоняется с событиемmac_runtime_required(вместо тихой деградации).auto(default) — probe ядра на старте сессии; настоящийParsecBackendпри активном МКЦ, fallback наStubBackendс одноразовымmac_runtime_fallback(WARN) иначе.- Валидация:
disabled + cert_integrity=requiredиrequiredбезastra-macотвергаются на старте. - Снимает блокер на терминале МКЦ:
pam_parsec_mac: Can't obtain required dataтеперь решается выставлениемruntime = "disabled"- удалением
pam_parsec_macиз стека, а не пересборкой.deb.
- удалением
Диагностика
Заголовок раздела «Диагностика»HostIdentityResolver::probe_all()— публичный API, возвращающий одноProbeResultна каждый сконфигурированный источник ([host_identity].sources) без влияния на политику выбора (resolve()остаётся first-working-wins). cdylib теперь на старте каждой auth-сессии логирует по строке INFO на источник вpam_certauth.host_identity(probe ok/probe error+probe selected). Источник истины для регистрации терминала в реестре — этот лог;sha256sum /etc/machine-idвручную больше не нужен и даёт расхождение, если[host_identity].sourcesсодержит не толькоmachine_id.ResolvedHostId::hash_prefix()— первые 8 hex символов sha256 для on-screen диагностик. Сообщениеhost_bindingmismatch на лок-скрине терминала теперь показывает короткийhost_id=a1b2c3d4 (source=…)вместо нечитаемых 64 hex. Полный hash остаётся в syslog.- fly-dm greeter baseline: в начале
pam_sm_authenticateмодуль отправляетPAM_TEXT_INFOс короткой идентификацией машины («Это устройство: host_id=… (source=…)»).fly-dmпоказывает её в greeter UI приgreeter-show-messages = trueв/etc/X11/fly-dm/fly-dmrc— инженер у терминала мгновенно сверяет hash с реестром, не заходя в shell.
Документация
Заголовок раздела «Документация»configuration.md§«MAC integrity» — новая подсекция «Семантикаruntime» с матрицейruntime × cert_integrity × astra-mac, таблица полей дополненаruntimeиwarn_on_homedir_label_mismatch.install.md§8.5 переписан под runtime-переключатель: один и тот же.debдля трёх сценариев (МКЦ выключен / включён / смешанный парк). Подсекция §8.5.1 — baseline для fly-dm greeter.install.mdTroubleshooting — командаjournalctl … 'host_identity: probe'теперь источник истины для регистрации терминала.
Внутреннее
Заголовок раздела «Внутреннее»- Новые audit-события
mac_runtime_fallback(WARN) иmac_runtime_disabled(INFO) в targetmac.audit. MacRuntimeMode(validated layer) иRawMacRuntimeMode(raw config) — pub re-exports черезpam_certauth_core::config::validatedи::config::raw.build_backend(MacRuntimeMode)вpam_certauth::session— единственная точка решения «Parsec vs Stub», вместо двух compile-time ветвей.
[0.3.6] — 2026-05-25
Заголовок раздела «[0.3.6] — 2026-05-25»Диагностика
Заголовок раздела «Диагностика»host_idлогируется при каждомresolve()с указаниемsource,rawи полногоhost_id_hash(targetpam_certauth.host_identity). Fallback наunknownтоже логируется. Регистрация терминала в реестре теперь по факту resolved hash из syslog, не ручное вычислениеsha256(/etc/machine-id)— устраняет drift между скриптом выпуска cert’а и развёрнутыми[host_identity].sources.PAM_TEXT_INFOна экране приhost_bindingmismatch: показываетhost_id_hashэтой машины + тип источника + просьбу передать админу. Текст дублируется в syslog (warn).PAM_TEXT_INFOна экране при wrong .p12 PIN (MAC verify): если cert лежит в незашифрованном SafeBag (новый issuance-скрипт), модуль читает его без пароля и показывает host/user, для которых cert выпущен — инженер сразу видит «вставлена не та флешка». Для legacy-формата (cert тоже зашифрован) — обычное «пароль неверный».- Per-candidate USB-iteration логирование на уровне
info: mount succeeded → discovery → envelope parsed → chain validated → final outcome. Был «провал тишины» 14 секунд между «trying USB candidate» и concluding модулем; теперь каждый шаг видим вjournalctl -t pam_certauth.
Безопасность
Заголовок раздела «Безопасность»- Fail-closed на неверный PIN: не перебираем USB-партиции (lock-test
wrong_pin_does_not_fall_back_to_next_partition). Multi-partition fallback остаётся ограничен pre-password failures (ASN.1 envelope), иначе создаётся PIN-oracle / chain-probing по сменным носителям. - Multi-source matching по
[host_identity].sourcesнамеренно НЕ делается (weakest-link bypass: атакующий с root спуфит самый писабельный источник → байпасит host-binding). Это зафиксировано в threat-model.md §4.10.
Документация
Заголовок раздела «Документация»install.md— новая секция «Сертификат не принимается на терминале» (чек-лист: host_id из syslog → сверка с реестром → перевыпуск или чтение cert plaintext из .p12). Обновлён разделhost_binding mismatch(cert в новом формате читается без PIN).install.md§8.5 — два сценария PAM-стека (с/без МКЦ PARSEC MAC) с явной инструкцией гдеpam_parsec_mac.soнужен, а где он завалит account-фазу сCan't obtain required data.threat-model.md§4.10 — multi-source iteration по host_identity явно отмечена как НЕ выполняемая по причине weakest-link bypass.
Внутреннее
Заголовок раздела «Внутреннее»- Новый pub helper
pam_certauth_core::pkcs12::try_extract_cert_without_pin— best-effort чтение leaf-cert из PKCS#12 без пароля. ВозвращаетNoneдля legacy-формата. Используется wrong-PIN диагностикой. - Новый pub метод
FlowIo::show_info(&str)(default no-op) — путь доставкиPAM_TEXT_INFOна экран.RealFlowIo::with_pamh()привязывает live PAM-handle для cdylib; тест-фейки остаются без изменений.
Deferred (планируется к 0.3.7)
Заголовок раздела «Deferred (планируется к 0.3.7)»[mac].runtime = "auto" | "required" | "disabled"— runtime- переключатель Parsec backend без пересборки (сейчас compile-time featureastra-macрешает однозначно). Боевой кейс: бинарь собран сastra-mac, но на конкретной машине МКЦ-ядро выключено — нужен fallback на StubBackend без пересборки .deb.HostIdentityResolver::probe_all()— вернёт значения всех сконфигурированных источников (а не только первого работающего) для startup-логирования и admin-troubleshooting’а.host_id_hash_prefix(первые 8 hex) в PAM_TEXT_INFO — полный 64-char hash на экране нечитаем.- Baseline-строка
«Это устройство: source=… hash_prefix=…»для fly-dm greeter (до prompt’а PIN).
[0.3.5] — 2026-05-25
Заголовок раздела «[0.3.5] — 2026-05-25»- USB partition iteration теперь делает fallback на следующий раздел
при ASN.1-ошибке парсинга PKCS#12 (т.е. «файл по нашему пути есть,
но это не P12»). Раньше такая коллизия имён — типичная для
USB-устройств с несколькими разделами и Apple-форматированных
носителей — мгновенно роняла auth с
asn1_check_tlen: wrong tag, Type=PKCS12, не пробуя оставшиеся партиции.
Security
Заголовок раздела «Security»- Fallback срабатывает ТОЛЬКО на ASN.1-fail (pre-parse БЕЗ пароля). Ошибки MAC verify / decrypt / chain validation (всё, что требует пароля или валидации сертификата) остаются fail-closed без перебора — не создаёт PIN-oracle и не позволяет chain-probing по разделам.
pam_certauth_core::pkcs12::validate_p12_envelope(&[u8])— pure-функция, проверяющая ASN.1-конверт PKCS#12 без обращения к паролю. Используется вflow.rs::authenticate_pkcs12как граница между «файл на USB не P12 → пробуем следующий раздел» и «файл — валидный P12, но не расшифровывается → fail-closed».FlowError::P12Envelope(мапится наPAM_AUTHINFO_UNAVAIL(9)) для случая «ни одна партиция не дала валидного P12-конверта».
[0.3.3] — unreleased
Заголовок раздела «[0.3.3] — unreleased»pkcs12_path_patternтеперь реально применяется при discovery credentials с USB-носителя. До этого параметр декларировался в конфиге, но игнорировался — discovery всегда искал<mountpoint>/certs/user.p12. Default остался прежним (certs/user.p12) для backwards compat. Поддержан плейсхолдер${user}, добавлена защита от path-traversal в валидаторе (отклоняются абсолютные пути, пустая строка, сегменты..и.).
Changed
Заголовок раздела «Changed»- Снято требование
LABEL=PAMCERTна партиции USB-носителя.pam_certauthтеперь перебирает все партиции с FS из allowlist (vfat,exfat,ext4,ntfs) и останавливается на первой, где найден.p12. Реальная граница доверия — расшифровка.p12паролем пользователя и валидация цепочки сертификатов; label-фильтр ничего не добавлял к безопасности, только UX-friction. - Удалена ошибка
UsbError::AmbiguousPartition(несколько партиций с меткойPAMCERT) — она теряет смысл без обязательной метки.
- Новый конфиг-параметр
max_usb_partitions(default8, range1..=64) ограничивает число перебираемых партиций. Анти-DoS guard против атакующего с физическим доступом, который мог бы подсунуть устройство с огромным числом разделов и заставить модуль крутить бесконечный цикл mount/umount. - Новая ошибка
UsbError::TooManyPartitions { devnode, count, limit }(fail-closed при превышении лимита).
Migration
Заголовок раздела «Migration»- Конфиги 0.3.2 совместимы как есть:
max_usb_partitionsопционален, default8достаточен для всех реалистичных USB-носителей. - Раздел
LABEL=PAMCERTпродолжает работать, но метка больше не обязательна — можно оставить как есть или убрать на следующем переоформлении флешки.
[0.3.2] — unreleased
Заголовок раздела «[0.3.2] — unreleased»- Поддержка USB-флешек с partition table: если на whole-device нет FS, pam_certauth ищет среди разделов один с label=PAMCERT и FS из allowlist. Несколько подходящих разделов → отказ (fail-closed). Обратная совместимость: установки с FS на whole-device работают как раньше.
[0.3.0] — 2026-05-15
Заголовок раздела «[0.3.0] — 2026-05-15»- MAC integrity (МКЦ) integration for Astra SE strict-mode.
Сессия теперь получает метку
(level, categories), выбранную как пересечение расширенияMAX_INTEGRITYсертификата (OID2.25.273824307386008814506455310913083078403) с потолком рантайма от libpdp/libparsec. Новая секция[mac]вconfig.tomlc полямиcert_integrity(required/optional/ignore) иfallback_max_integrity. - Feature-флаг
astra-mac(включается на сборке для Astra SE); stub-бэкенд используется на не-Astra хостах и отвергаетcert_integrity = "required"на этапе загрузки конфига. - DER-кодек
IntegrityLabelсо строгим парсером и компонентнымstrictly_belowдля сравнения меток. - Метки
pdpl-file :::iinhнакладываются на/etc/pam_certauth/,/var/lib/pam_certauth/,/var/cache/pam_certauth/через postinst приastra-strictmode-control is-enabled.host_idполучаетchattr +iпосле первой записи. - Атомарная запись
sessions.jsonтеперь использует fd-based labeling черезpdp_set_fd(метка накладывается до публикации имени файла, закрывает TOCTOU-окно).irelaxчерез fd-API ядро не принимает (EINVAL) — relax-семантика дляsessions.jsonобеспечиваетсяiinh-наследованием от parent dir. - E2E-сценарии T1-T12 (
vagrant/scripts/test-mac.sh) и perf-bench (vagrant/scripts/bench-mac.sh) для Astra VM. - Документация:
docs/install.md,docs/cert-issuance.md,docs/configuration.md,docs/threat-model.mdпополнены секциями по МКЦ.
debian/control: добавленRecommends: libpdp3 (>= 3.11+ci97~)иlibparsec-base3 (>= 3.11+ci97~)(оба runtime-dep при сборке сastra-mac).- Linker fix:
parsec_capgetоказался экспортируемым изlibparsec-base.so, а неlibpdp.so— Astra CI build падал сundefined symbol: parsec_capget(verified run 25903325006, 2026-05-15).build.rsтеперь emits и-lpdp, и-lparsec-base; extern-блок сparsec_capgetпомечен#[link(name = "parsec-base")]. - Linker fix:
getmicnam/freemicent_rживут вlibparsec-mic.so.3, а не вlibpdp.so(комментарий вbuild.rs, утверждавший обратное, исправлен).build.rs/Dockerfileтеперь линкуют-lparsec-mic.
- libpdp text-codec grammar. Кодировщик
encode_label_textраньше формировал строку"0:0:cat:flags:level"(пять сегментов, пятый = линейный ilevel) — это была устаревшая интерпретация заголовков. Реальное strict-mode-ядро Astra 1.8.4 принимает четырёхсегментную грамматикуlevel:ilevel:cat[:flags]. Кодек переписан, e2e-применение метки наsessions.jsonтеперь отображаетсяpdpl-fileкакУровень_0:Сетевые_сервисы:Нет:0x0!. pdp_set_fd+irelaxнесовместимы. Ядро возвращает EINVAL, если irelax передан через fd-based API. Демон теперь вызываетset_fd_label(.., irelax=false). Path-basedpdp_set_pathirelax по-прежнему принимает (используется postinst черезpdpl-file).getmicnamвозвращает library-private static memory (perman getmicnamна Astra 1.8.4), а не heap-аллоцированную структуру. Прежний код звалfreemicent_rна результат и падал вpam_sm_open_sessionсfree(): invalid pointer→ SIGABRT. Указатель больше не освобождается.- Daemon под
User=pamcertauth(не root) при опциональной активации МКЦ. Шипованный drop-inmac-integrity.conf.exampleиспользуетPAMName=pam-certauth+ парный PAM-стекdist/pam.d/pam-certauth.example(pam_parsec_cap.so+pam_parsec_mac.so) для подъёма ilevel=63 иPARSEC_CAP_CHMACна процессе демона. Ранее обсуждавшийсяexecaps -c 0x8 -- ...-обход не используется —execapsсам требуетPARSEC_CAP_CAPу запускающего процесса, которой уpamcertauthнет. - Sessions registry на tmpfs. Переехал из
/var/lib/pam_certauth/sessions.json(persistent) в/run/pam_certauth/sessions.json(volatile,RuntimeDirectory=). Снимает stale-state-after-reboot foot-gun и MAC-labelling churn на каталоге.daemon.lockи кэши остаются в/var/lib/.
Removed
Заголовок раздела «Removed»- Откат 0.2.x-набора
pam_cert_scopes/ CMS M-of-N work-order / approver-EKU / external policy TOML /pam-certauth execute|policy|gc. Бинарь оставляет толькоpam-certauth daemon. IPC v2 retainsengineer_ski+engineer_cert_sha256(МКЦ-audit),scopesубран.
[0.1.1] — 2026-05-06
Заголовок раздела «[0.1.1] — 2026-05-06»- Cert-binding extensions take precedence over the legacy
[[user_mapping]]TOML list.pam_cert_user_binding/pam_cert_host_bindingare the sole source of authorisation when present;[[user_mapping]]is consulted only for certificates withoutpam_cert_user_binding. - PAM cdylib syslog backend wired into the
tracingsubscriber: everyerror!/warn!emitted fromlibpam_certauth.solands in/var/log/auth.log(LOG_AUTH facility, identpam_certauth,pam_certauth[<pid>]:prefix). Production diagnosis no longer blind. - Three PAM-stack snippets shipped alongside the module:
/etc/pam.d/certauth(2FA, default),/etc/pam.d/certauth-optional(phased rollout),/etc/pam.d/certauth-only(cert-only, lockout-strict).integrate-pam.sh --mode=2fa|optional|cert-onlyselects which one to wire in. The deprecated--strict/--optionalflags still work as aliases. - SysV init script (
/etc/init.d/pam-certauth) shipped for hosts without systemd; addslsb-basedependency to the.deb. - Manpage
pam-certauth(8)shipped. - Docs: USBGuard interop, Astra ЗПС (DIGSIG) caveat, USB-lockout
pre-deploy checklist, full
on_usb_removedmode reference.
[0.1.0] — 2026-05-05
Заголовок раздела «[0.1.0] — 2026-05-05»Initial public release.
- PAM module for X.509 certificate authentication on Astra Linux SE 1.7+.
- USB token support: PKCS#11 (Rutoken/JaCarta/ESMART), PKCS#12 file.
- GOST cryptography (Р 34.10-2012, Р 34.11-2012) via openssl + gost-engine.
- Cert-driven authorisation: per-cert host_binding and user_binding X.509 v3 extensions; no central ACL.
- Host-removal monitor daemon (pam-certauth) with udev + logind integration: lock/logout/shutdown on USB unplug.
- Configurable hook execution (pre_auth/post_auth_success/session_open/ session_close) via fork+execve with full sandboxing.
- Debian package for Astra Linux SE.