# Жизненный цикл клиентских сертификатов

## Репозиторий и ветка

- Репозиторий: https://github.com/ochenstarik-ui/server-monitor-manager
- Локальный клон: `C:\Users\Ochenstarik\projects\server-monitor-manager`
- База: `main` @ `b11c277`
- Ветка: `hermes/cert-lifecycle`

Задание не пересекается по файлам с `b3r-fact-reconciliation` и выполняется параллельно в отдельном worktree. Затрагиваемые файлы (`CertificateAuthority.cs`, `CertificateLifecycleService.cs`, `AgentClient.cs`) веткой B-3R не изменяются.

## Зачем

`CertificateAuthority.cs`, выдача клиентского сертификата:

```csharp
var requestedNotAfter = DateTimeOffset.UtcNow.AddYears(1);
```

Срок — один год, автопродления нет ни у Agent, ни у Operator. Через год после развёртывания весь парк перестаёт проходить mTLS одновременно, и восстановление требует ручной перерегистрации каждого Node через одноразовый enrollment token. Процедуры ротации Control CA в проекте нет вообще.

Отзыв работает через таблицу identity в Control (`ResolveIdentityAsync`), CRL не используется — это осознанное решение и менять его не нужно.

## Работы

### 1. Срок жизни

- сократить срок клиентского сертификата до 30–90 дней;
- вынести значение в `ControlOptions` с валидацией в общей цепочке `.Validate(...)` в `Program.cs`, по образцу `LinkReconciliationSeconds`;
- значение по умолчанию в `appsettings.json` и в `control.env`, который пишет bootstrap;
- срок сертификата не может превышать остаточный срок Control CA — существующая логика ограничения сохраняется.

### 2. Автопродление Agent

- Agent проверяет остаточный срок собственного сертификата при старте и далее периодически;
- при остатке меньше трети срока формирует новый CSR и запрашивает выдачу **по действующему mTLS-каналу**, без enrollment token;
- новый эндпоинт выдачи требует действующий сертификат того же `node_id`, idempotency key и запись в audit;
- замена `agent.pfx` атомарная: запись во временный файл с правами `0600`, затем rename; при любой ошибке остаётся действующий сертификат;
- отозванный сертификат продлению не подлежит — запрос отклоняется, Node требует повторной регистрации.

### 3. Продление Operator-сертификата Desktop

- Desktop проверяет остаточный срок при подключении к Control;
- продление по тому же принципу, с явным уведомлением пользователя в интерфейсе;
- защищённое хранение DPAPI сохраняется, приватный ключ не покидает устройство.

### 4. Уведомление об истечении

- событие `certificate.expiring` в поток событий Control при остатке меньше трети срока;
- поле остаточного срока в ответе `/api/v1/control/agents`;
- отображение в Desktop на странице Servers.

### 5. Ротация Control CA

Документ `docs/certificate-rotation.md`:

- когда ротация требуется: компрометация, истечение, смена алгоритма;
- порядок: выпуск нового CA, период двойного доверия, перевыпуск клиентских сертификатов, вывод старого CA;
- что делает оператор руками и что делает bootstrap;
- как проверить, что ни один Node не остался на старом CA;
- почему ротация CA требует режима подтверждения `two_person` — см. `docs/approval-policies.md`.

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

## Тесты

1. Сертификат с остатком меньше трети срока продлевается; `agent.pfx` заменён, старый сертификат больше не используется.
2. Сертификат с достаточным остатком не продлевается — лишних запросов нет.
3. Hub недоступен в момент продления: Agent продолжает работать на действующем сертификате, повторяет попытку позже, метрики не теряются.
4. Отозванный сертификат продлить нельзя — запрос отклонён.
5. Запрос продления сертификатом другого `node_id` отклонён.
6. Замена `agent.pfx` прервана на середине: остаётся действующий сертификат, Agent поднимается.
7. Срок вне диапазона в конфигурации: Control не стартует, ошибка валидации внятная.

## Критерий приёмки

- Node, проработавший дольше срока сертификата, продолжает работать без вмешательства оператора;
- отзыв по-прежнему немедленно закрывает доступ;
- существующие тесты `CertificateAuthorityTests` и сценарии перерегистрации зелёные без правок логики;
- `docs/certificate-rotation.md` описывает выполнимую процедуру, а не намерение;
- Control suite прогнан на Linux;
- PR создан, CI зелёный;
- независимый review получил полный текст этого задания.

## Границы

- не трогать `LinkService.cs`, `LinkPolicyApplier.cs`, `LinkReconciliationBackgroundService.cs`, `ControlStore.cs` в части Links, `Provisioning.Helper/*` и `deploy/ochenstarik-smm-policy-apply` — они заняты заданием B-3R;
- CRL не вводить;
- автоматическую ротацию CA не реализовывать;
- приватный ключ Control CA остаётся на Hub и в enrollment-коды не попадает.

## Отчётность

Раздельно перечислить: что запущено локально, что в CI, что не запускалось и почему. Статус после merge — `merged / verified`.
