Перейти к содержанию

SSH Key Registry и Keyholder API

Если вам нужно настроить SSH-доступ, а не разобраться в устройстве

Эта страница описывает механизм: модель данных, эндпоинты, форматы ответов. Пошаговая инструкция — что нажать и что положить на ваш сервер — в «Настройке»; обзор всего раздела — здесь.

Не путать с сертификатным SSH (/enroll/ssh)

Alatyr поддерживает два независимых механизма SSH-доступа. Первый — описанный в «Архитектура и PKI» выпуск короткоживущих OpenSSH-сертификатов через Vault SSH secrets engine (/enroll/ssh, purpose ssh, отзыв через KRL). Второй — SSH Key Registry, тема этой страницы: реестр «сырых» hardware-backed публичных ключей без какого-либо сертификата и без CA. Это две разные, параллельные подсистемы: у них независимое хранение данных, разные эндпоинты регистрации и разный жизненный цикл — сертификат истекает и переиздаётся, а зарегистрированный сырой ключ живёт, пока его явно не отзовут.

Модель: raw-ключ вместо сертификата

На устройстве в TPM 2.0 или Secure Enclave (в зависимости от платформы) уже существует аппаратный ключ пользовательского присутствия — тот же самый, которым агент пользуется для аутентификации по mTLS. Реестр не создаёт для него отдельный ключ — он просто регистрирует публичную половину этого ключа на сервере как самостоятельную SSH-идентичность:

  • Приватный ключ никогда не покидает TPM/Secure Enclave.
  • Сервер хранит только публичный ключ в формате authorized_keys и его SHA-256-отпечаток — никакого сертификата, CA или срока действия.
  • Один и тот же принципал (логин пользователя) может иметь несколько зарегистрированных ключей — с разных устройств, а при необходимости и несколько с одного.

Регистрация

Агент отправляет POST /api/v1/enroll/ssh-key:

{
  "device_serial": "...",
  "enrollment_token": "...",
  "principal": "ivan.petrov",
  "ssh_public_key": "ecdsa-sha2-nistp256 AAAAE2VjZHNh... alatyr-agent",
  "proof": { "...": "TPM2 AK-to-EK proof-of-possession, если применимо" }
}

Учётной записью эндпоинт не аутентифицируется. Как и остальные /enroll/*, он проверяет enrollment_token устройства (тот же общий секрет, что и у других видов заявок) и ограничен по частоте запросов. Перед созданием строки эндпоинт последовательно проверяет:

  1. Allowlist принципала — principal должен точно совпадать с одним из значений в администрируемом списке разрешённых принципалов для purpose ssh. Это тот же список, которым пользуется сертификатный /enroll/ssh: второй администрируемый список ради одного и того же смысла («кому вообще разрешён SSH-доступ») не потребовался.
  2. Существование устройства + enrollment_token — устройство должно уже быть заведено (сама регистрация ключа новое устройство не создаёт), токен сверяется constant-time-сравнением.
  3. Доказательство обладания ключом (если платформа это поддерживает) — для TPM-хранилищ проверяется криптографическое доказательство владения ключом; для Secure Enclave отдельное доказательство не требуется — у него нет внешне проверяемой цепочки аттестации в принципе.

Публичный ключ, приложенный к регистрации, не превращается в X.509-CSR и никуда не подписывается — сервер лишь сохраняет его как есть и вычисляет отпечаток для дедупликации по паре (device_id, fingerprint). Повторная отправка уже известного (device, ключ) — идемпотентна: существующая строка возвращается без изменений, в том числе если она уже revoked/rejected — повторная регистрация никогда не «оживляет» отозванный или отклонённый ключ молча.

Жизненный цикл ключа

Статус Значение
pending Зарегистрирован, ждёт решения администратора
active Одобрен (вручную или автоматически) — ключ реально обслуживается Keyholder API
rejected Отклонён администратором — можно повторно одобрить (approve) позже
revoked Отозван — терминальное состояние, approve больше не проходит (409, не 404)

По умолчанию каждая новая регистрация уходит в pending и требует ручного подтверждения POST /api/v1/admin/ssh-keys/{id}/approve (роль cert-admin, и только она — в отличие от обычных заявок на сертификат, где approve доступен ещё и cert-approver). Отдельная настройка автоодобрения (по умолчанию выключена) переводит прошедшую проверку регистрацию сразу в active, без участия человека — сама криптографическая проверка при этом не ослабляется, настройка убирает только шаг человеческого ревью.

Автоодобрение доверяет принципалу, который называет сам агент: principal — это поле, которое присылает клиент, а сервер не связывает устройство с конкретным человеком — такой привязки в модели данных нет. Единственная проверка — allowlist принципалов. При включённом автоодобрении любое устройство с валидным enrollment_token получит рабочий SSH-доступ под любым принципалом из allowlist — без единого человеческого решения. Поэтому ручное одобрение — поведение по умолчанию, а не автоодобрение.

Одобрение (approve) при каждом вызове заново проверяет, не заблокировано ли владеющее устройство, и отказывает (403), если это так — даже если на момент регистрации оно заблокировано не было. Отклонённая (rejected) регистрация ключа может быть повторно одобрена тем же эндпоинтом — это единственный предусмотренный путь «реабилитации»; отозванную (revoked) регистрацию ключа одобрить нельзя никогда.

Отзыв устройства отзывает и его SSH-ключи

Отдельно ходить по ключам не нужно: отзыв устройства снимает их каскадом. Каскад отрабатывает дважды — до отзыва сертификатов и ещё раз после того, как устройство помечено заблокированным. Второй проход закрывает окно, в котором одобрение ключа могло прийти, пока шли обращения к УЦ.

Поверх этого Keyholder не отдаёт ключи заблокированного устройства, даже если такой ключ каким-то образом остался активным. Две защиты закрывают разное: каскад делает отзыв прочным и видимым в истории, а проверка при выдаче держит свойство независимо от порядка событий.

Отозвать ключи по одному всё ещё можно — список устройства отдаёт GET /api/v1/devices/{serial}/ssh-keys, отзыв — POST /api/v1/admin/ssh-keys/{id}/revoke. Это нужно, когда забирают доступ у человека, а не у машины.

Admin UI

Зарегистрированные ключи видны в двух местах:

  • На карточке устройства — при разворачивании строки устройства в списке появляется вторая панель (рядом с панелью сертификатов) с ключами именно этого устройства: принципал, отпечаток, статус, дата.
  • На отдельной странице «SSH Keys» (только для ролей с доступом к Admin UI) — общий список по всему флоту: устройство (ссылка на карточку), принципал, отпечаток, статус, уровень аттестации, даты создания/одобрения/отзыва. Здесь же выполняются approve/reject/revoke.

Интерфейс показывает фактический уровень аттестации: для ключа без аппаратного доказательства обладания в списке так и указано, что ключ не аттестован.

Keyholder API

Keyholder API — не самостоятельный сервис, а группа маршрутов того же сервера, вынесенная из-под админской авторизации: доступ к ней определяется allowlist'ом по IP, а не ролями. Эндпоинт GET /api/v1/keyholder/keys?principal=<login> определяет по login список активных публичных ключей — предназначен для использования в AuthorizedKeysCommand целевых серверов (sshd на каждой машине, куда пускают по этим ключам, вызывает внешнюю команду, которая обращается сюда).

GET /api/v1/keyholder/keys?principal=ivan.petrov
→ {"keys": ["ecdsa-sha2-nistp256 AAAAE2VjZHNh...", "ecdsa-sha2-nistp256 AAAAE2VjZHNi..."]}

Тип ключа — не ssh-…

Ключи, которые заводит агент в TPM и Secure Enclave, начинаются с ecdsa-sha2-nistp256. Реестр принимает и другие типы (например sk-ssh-ed25519@openssh.com у аппаратных токенов FIDO2), поэтому разбирать ответ поиском подстроки ssh- нельзя: самодельный фильтр такого вида не найдёт ни одного ключа и закроет вход, не объяснив причины. Разбирайте JSON целиком — пример в настройке.

Возвращаются только ключи в статусе active. Для неизвестного принципала эндпоинт отвечает 200 и пустым массивом, никогда не 404 — иначе сам факт ответа позволил бы перебором узнавать, какие логины вообще существуют во флоте (перебор принципалов), даже через фильтр по IP.

Доступность

AuthorizedKeysCommand вызывается при каждой попытке SSH-логина, поэтому доступность Keyholder API напрямую влияет на доступность SSH во флоте. Рекомендуемая эксплуатационная конфигурация: обёртка вокруг AuthorizedKeysCommand, которая кеширует последний успешный ответ на диск и использует кеш, если сервер недоступен, плюс сохранённый локально break-glass-ключ администратора в обычном authorized_keys.

Доступ: IP-allowlist + rate limit + опциональный токен

В отличие от остального Admin API, /keyholder/keys не защищён обычным Bearer-токеном администратора — это service-to-service эндпоинт для инфраструктуры (bastion-хостов, целевых серверов), а не для людей. Порядок проверок:

  1. IP/CIDR allowlist (обязателен, «fail closed» по умолчанию). Список CIDR редактируется администратором (вкладка «SSH-ключи» в настройках). Пустой список = эндпоинт полностью закрыт — оператор обязан явно перечислить хотя бы одну подсеть, прежде чем API станет доступен вообще. Список читается заново на каждый запрос — правка в настройках применяется без перезапуска сервера. Некорректная запись CIDR пропускается с предупреждением в лог, а не роняет всю проверку. Запись обязана быть узкой: сервер отвергает подсети шире /24 (IPv4) и /64 (IPv6) — это служебный интерфейс на считанные хосты, и широкая сеть впустила бы к нему всех, кто в неё попадает.
  2. Rate limit, привязанный к паре (IP клиента, principal), а не просто к IP — потому что один легитимный bastion-хост обслуживает логины множества разных пользователей, и лимит по одному IP душил бы легитимный трафик.
  3. Опциональный bearer-токен сервера (по умолчанию выключен, включается в настройках). Когда включён, каждый запрос дополнительно должен нести Authorization: Bearer <token>, сверяемый по SHA-256-хешу с активными токенами. Хранится только хеш; сырое значение токена показывается администратору один раз — в момент создания — и больше никогда не восстанавливается.

IP-allowlist — не барьер между разными принципалами

Даже с allowlist один и тот же хост внутри разрешённой подсети может запросить ключи любого принципала, а не только «своего» — allowlist ограничивает, откуда можно спрашивать, но не что именно спрашивать. Это задокументированный остаточный риск: опциональный bearer-токен существует именно для тех, кому этого мало.

Администрирование токенов Keyholder

Управление серверными токенами — обычные админ-эндпоинты (роль cert-admin), доступные в той же вкладке настроек, где включается опциональный bearer-токен сервера:

Метод и путь Назначение
GET /api/v1/admin/keyholder-tokens Список токенов (label, даты — без значения/хеша)
POST /api/v1/admin/keyholder-tokens Создать токен по метке; сырое значение возвращается один раз в ответе
DELETE /api/v1/admin/keyholder-tokens/{id} Отозвать токен — сразу исключается из проверки

Что дальше