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

Лицензирование

Лицензия ограничивает, сколько устройств Alatyr обслуживает одновременно. Эта страница описывает, как определяется лимит, как посмотреть текущий статус, как применить приобретённую лицензию, что происходит при приближении к лимиту или его превышении и как освобождать места при выводе устройств из эксплуатации. Список эндпоинтов — в REST API; про роли — в Администрирование.

Как определяется лимит

Действующая лицензия — это подписанный вендором файл, применённый к конкретному развёртыванию. Если ни одна лицензия не была загружена, сервер использует лицензию по умолчанию с лимитом 50 устройств — дополнительных действий для работы в этом режиме не требуется. Загруженная платная лицензия задаёт собственный лимит (max_devices) и, как правило, срок действия.

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

  • Слоты устройств (max_devices) — сколько машин вообще обслуживается. Слот занимает машина целиком, независимо от того, сколько целей она держит.
  • Места по назначениям — сколько машин может держать каждую отдельную цель (wifi, user_mtls, ad_logon, ssh, k8s, vpn). Их несёт не всякая лицензия; см. «Места по назначениям» ниже.

Проверка текущего статуса

GET /api/v1/license/status

Доступен любой admin-роли (cert-admin/cert-approver/cert-viewer). Пример ответа:

{
  "mode": "free",
  "tier": "free",
  "issued_to": "bundled-free",
  "active_count": 12,
  "max_devices": 50,
  "high_water": 14,
  "expires_at": "2125-01-01T00:00:00Z",
  "tamper": false,
  "tamper_reason": "",
  "deployment_instance_key": "base64-ed25519-pubkey...",
  "purpose_seats_limited": false,
  "purpose_seats": [
    {"purpose": "ad_logon", "bought": 0, "used": 0,  "free": 0},
    {"purpose": "k8s",      "bought": 0, "used": 3,  "free": 0},
    {"purpose": "ssh",      "bought": 0, "used": 5,  "free": 0},
    {"purpose": "user_mtls","bought": 0, "used": 7,  "free": 0},
    {"purpose": "vpn",      "bought": 0, "used": 0,  "free": 0},
    {"purpose": "wifi",     "bought": 0, "used": 12, "free": 0}
  ]
}
Поле Значение
tier Тариф действующей лицензии: free или paid
mode Состояние: licensed (действует платная), free (лицензия не загружалась), expired (платная загружена, но срок истёк — мягкий откат к базовому лимиту)
active_count Сколько устройств сейчас учтено лицензией
max_devices Текущий лимит
high_water Наибольшее значение, которое даёт пересчёт журнала (используется при восстановлении журнала, см. ниже; после восстановления журнала может уменьшиться)
expires_at Срок действия применённой лицензии
tamper Флаг деградированного режима из-за нарушения целостности журнала учёта (см. ниже)
tamper_reason Что именно не сошлось в журнале. Пустая строка, когда tamper равен false
deployment_instance_key Публичный Ed25519-ключ этого развёртывания, сгенерированный при первом запуске; не секрет, используется только для привязки лицензии к конкретному развёртыванию
purpose_seats_limited Несёт ли лицензия места по назначениям. false — не несёт, и bought в таблице ниже ничего не значит
purpose_seats Строка на каждое известное назначение: purpose, bought (куплено), used (занято устройствами сейчас), free (осталось). Строки выводятся и для назначений, которые не куплены — именно они и интересны перед покупкой

Тот же статус отображается на странице Настройки → Лицензия в админке в виде баннера, карточки статуса и карточки «Места по назначениям» — раздел «Настройки» есть в навигации только у роли cert-admin; применение лицензии и decommission на ней тоже доступны только cert-admin. Предупреждающий баннер при этом показывается всем ролям на любой странице (см. Администрирование).

Применение приобретённой лицензии

  1. Получите deployment_instance_key этого развёртывания — из ответа GET /api/v1/license/status или со страницы «Лицензия» в админке (поле с копированием в буфер).
  2. Передайте этот ключ вместе с нужным количеством устройств при оформлении покупки. Лицензия выпускается вендором привязанной к этому ключу.
  3. Полученный файл лицензии (.lic) примените одним из способов:
  4. в админке: страница «Лицензия» → вставить или загрузить файл → Активировать;
  5. через API:
    POST /api/v1/license/activate
    {"license_b64": "<base64-содержимое .lic-файла>"}
    
    (cert-admin).
  6. Сервер проверяет подпись вендора и соответствие поля instance_public_key внутри файла лицензии ключу этого развёртывания — тому самому, что отдаётся эндпоинтом статуса под именем deployment_instance_key (это один и тот же Ed25519-ключ, только под разными именами: deployment_instance_key — как его возвращает API этого развёртывания, instance_public_key — как он записан внутри выпущенного файла лицензии). При несовпадении или повреждённой подписи запрос отклоняется (400 {"code":"license_invalid"}), а ранее действовавшая лицензия (платная или базовая) остаётся в силе без изменений.

После успешного применения GET /api/v1/license/status отражает новый tier и max_devices.

Аттестация лицензии агентом

POST /api/v1/license/attest — публичный, не требующий авторизации эндпоинт (ограничен тем же rate-limit, что и /enroll). Им пользуется не администратор, а сам агент: перед тем как сгенерировать ключевой материал и CSR, агент запрашивает у сервера действующую лицензию вместе с подписью, подтверждающей — над присланным агентом нонсом, — что ответ пришёл именно от этого развёртывания, и проверяет её встроенным вендорским ключом. Если проверка не проходит, агент прерывает enrollment ещё до обращения к TPM/Secure Enclave. Для администрирования лицензии этот эндпоинт не нужен — он описан здесь для полноты картины REST API.

Поведение лицензии по ситуациям

Ситуация Действующая лицензия
Лицензия ни разу не загружалась Базовая, лимит 50 устройств
Загружена валидная платная лицензия, срок не истёк Эта лицензия, лимит max_devices
Платная лицензия загружена, но expires_at в прошлом Мягкий откат к базовой (лимит 50) — не блокировка. На UI — баннер «лицензия истекла, требуется продление». Устройства, уже превышающие 50, сохраняют выданные сертификаты; новая выдача ограничена лимитом 50
Загруженный файл не проходит проверку подписи/привязки Отклоняется целиком (400 license_invalid), действующая лицензия не меняется

Уже выданные сертификаты и их продление изменением лицензии никогда не затрагиваются — лимит влияет только на выдачу сертификата новому устройству.

Что засчитывается в лимит

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

  1. серийный номер EK-сертификата (TPM на Windows и Linux, Secure Enclave на macOS) — единственный вариант, который нельзя разделить между разными машинами;
  2. серийный номер устройства — если аппаратной аттестации нет;
  3. хеш публичного ключа из CSR — резервный вариант, если серийный номер пуст или является известным заглушечным значением.

Продление сертификата уже учтённого устройства нового слота не расходует.

Места по назначениям

Кроме общего лимита устройств лицензия может нести места по назначениям: сколько машин вправе держать каждую отдельную цель. Смысл коммерческий — парк «только Wi-Fi» и парк «все цели сразу» стоят по-разному, поэтому цели продаются отдельно, и обязательного wifi под остальными нет.

Считается устройство, а не пользователь: ноутбук с двумя учётными записями и двумя SSH-ключами занимает одно место ssh. Продление уже занятого места ничего не расходует и никогда не отклоняется.

Лицензия без мест ничего не ограничивает

Места несёт не всякая лицензия. Базовая и любая выписанная до появления этой возможности мест не несут — тогда purpose_seats_limited равен false, и все цели разрешены в пределах лимита устройств. Это не «куплено ноль», это «лимита по целям нет вовсе»: различать их надо по purpose_seats_limited, а не по нулю в колонке «Куплено».

Что делать, когда места кончились — отдельная настройка на странице «Лицензия», три ступени:

Ступень в интерфейсе Что происходит
Ничего — только считать (по умолчанию) Занятость записывается, выдача не ограничивается
Записывать в лог и подсвечивать, выдавать Превышение фиксируется в журнале сервера и видно в интерфейсе, выдача продолжается
Отказывать в выдаче Одобрение заявки на цель, по которой мест не осталось, отклоняется: license_purpose_limit

Ступень «записывать» существует не ради мягкости: она даёт увидеть настоящую картину парка до того, как отказ остановит выдачу. Поднимать до «Отказывать» имеет смысл после того, как в карточке «Места по назначениям» сошлись «Куплено» и «Занято».

Проверок мест на самом деле две, и ступень управляет только одной:

  • При назначении цели машине (карточка устройства в разделе «Устройства», а также одобрение самозаявки устройства) — если мест не осталось, запрос отклоняется сразу: 402, code=license_purpose_limit. Эта проверка ступеней не знает и действует всегда. Цели, которые машина уже держит, при пересохранении не перепроверяются — иначе заполнившийся парк ломал бы правку карточек, ничего не расходующую.
  • При одобрении заявки на выпуск — вот здесь и работает ступень выше: «Ничего» и «Записывать» пропускают выдачу, «Отказывать» её отклоняет.

Обе проверки молчат, если лицензия мест не несёт.

Отзыв, удаление записи и decommission — в чём разница

Три разных действия часто путают, потому что визуально они происходят на соседних страницах админки:

  • Отзыв сертификата (страница «Сертификаты», POST /api/v1/certificates/{serial}/revoke, а также POST /api/v1/users/{identity}/revoke-certs) отзывает конкретный сертификат через CA. Сам по себе слот лицензии не освобождает — устройство остаётся учтённым в лимите, пока за ним не выполнен decommission.
  • Удаление записи устройства или сертификата из данных системы слот тоже не освобождает: журнал учёта хранит идентичность устройства независимо от того, существует ли ещё соответствующая строка в таблицах устройств/заявок.
  • Decommission — единственное действие, которое освобождает слот (снижает active_count). Оно бывает в двух формах:
    • явной — cert-admin вызывает его напрямую для конкретной идентичности устройства (см. следующий раздел);
    • автоматической, как часть полного отзыва устройства («убить устройство», POST /api/v1/devices/{serial}/revoke, cert-admin): эндпоинт отзывает все активные сертификаты устройства и, только если каждый из них отозвался успешно, сам выполняет decommission этого устройства. Частичный сбой отзыва хотя бы одного сертификата (HTTP 207) слот не освобождает — повторный вызов того же эндпоинта безопасен и либо дозавершит отзыв оставшихся сертификатов, либо, если все они уже отозваны, доведёт до конца сам decommission.

Блокировка и отзыв SSH-ключей решения об освобождении места не ждут

Полный отзыв устройства делает три разные вещи, и на успех отзыва сертификатов завязана только одна из них — освобождение места. Блокировка от повторной регистрации ставится и при частичном сбое (HTTP 207), и отзыв SSH-ключей устройства тоже выполняется независимо. Направления разные намеренно: отдать место за устройство, чей сертификат ещё жив, — ошибка в сторону «слишком много», а отложить защитное действие из-за неудавшегося обращения к CA — ошибка в сторону «слишком мало». Подробнее про блокировку — Администрирование.

Освобождение слота при выводе устройства из эксплуатации

Когда оборудование выведено из эксплуатации, cert-admin может освободить его слот явным decommission-действием, не дожидаясь отзыва сертификатов (например, если устройство физически утеряно и сертификат уже истёк сам):

POST /api/v1/license/decommission
{"identity_key": "ek:... | sn:... | pk:..."}

Действие снижает счётчик активных устройств на единицу, освобождая слот. Обратить его можно только повторным enroll этого устройства. Отдельной записи в журнале аудита админки это действие не создаёт. Прямое удаление записей устройства или сертификата на это не влияет.

Ключ идентичности устройства не выводится ни в админке, ни через API — и в штатном сценарии он не нужен: вывод устройства из эксплуатации делается через POST /api/v1/devices/{serial}/revoke, который выполняет decommission сам (см. «Отзыв, удаление записи и decommission» выше). Явный decommission по ключу — на случай, когда ключ известен заранее.

Коды ошибок при выдаче

Проверка лимита выполняется в момент одобрения заявки (approve), а не при её создании — заявка, ожидающая одобрения, слот не занимает.

  • 402, code=license_limit — новое устройство превысило бы max_devices. В UI это ведёт к предложению приобрести дополнительный лимит.
  • 402, code=license_tamper — не пройдена проверка целостности журнала учёта устройств (см. ниже).
  • license_purpose_limit — по этой цели не осталось мест. При назначении цели машине это 402; при одобрении заявки на выпуск отказ наступает, только если ступень «Что делать, когда места кончились» поднята до «Отказывать в выдаче» (см. «Места по назначениям»).
  • license_limit и license_tamper возвращаются как HTTP-статус, когда одобряется одна заявка. При массовом одобрении ответ остаётся 200, а отказ виден в соответствующей строке массива результатов (status: "error", error: "license_limit" / "license_tamper" / "license_purpose_limit"). При одобрении заявок отказ по местам приходит только этой строкой — отдельного HTTP-статуса у него там нет.
  • Продление и переиздание сертификата для уже учтённого устройства ни одним из этих кодов не блокируется.

Нарушение целостности журнала учёта и восстановление

Учёт устройств ведётся во внутреннем журнале с проверкой целостности. Сервер проверяет его при старте и перед каждой новой засчитываемой выдачей сертификата. Если журнал не проходит проверку — например, его записи были изменены в обход штатных операций, — сервер переходит в режим tamper:

  • новая выдача/одобрение сертификатов блокируется: 402 {"code":"license_tamper"};
  • в интерфейсе появляется предупреждающий баннер; отдельной записи в журнале аудита это не создаёт — факт фиксируется в журналах сервера, в том числе строкой при старте;
  • уже выданные сертификаты и их продление не затрагиваются — режим tamper блокирует только новую засчитываемую выдачу.

Это деградированное, но не постоянное состояние. cert-admin может восстановить журнал:

POST /api/v1/license/reanchor

Операция восстанавливает журнал по сохранившимся данным и фиксирует новое состояние. Восстановление не может занизить учтённое количество устройств: итоговое значение — максимум из (а) того, что даёт пересчёт уцелевших записей журнала, (б) числа устройств с реально выпущенными и неотозванными сертификатами и (в) счётчика из последней криптографически подтверждённой контрольной точки журнала. Ранее достигнутый пик (high_water) в этот расчёт не входит — легитимно списанные через decommission устройства после восстановления не «воскресают». Единственный штатный способ понизить active_count — decommission (явный вызов или как часть полного отзыва устройства, см. «Отзыв, удаление записи и decommission» выше) на исправном журнале.

Практическое следствие

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

Что мониторить

  • active_count относительно max_devices — приближение к лимиту стоит планировать заранее, чтобы выдача новых сертификатов не остановилась в неподходящий момент.
  • expires_at — истечение платной лицензии не блокирует систему, но откатывает лимит к 50 устройствам.
  • tamper — появление true требует внимания администратора и, как правило, восстановления журнала через reanchor.

Все три поля доступны через GET /api/v1/license/status и подходят для периодического опроса без похода в UI.