# SSH-сертификаты вместо authorized_keys

Индекс LLMS: [llms.txt](/llms.txt)

---

authorized_keys — это классика, но когда серверов больше десятка, начинается хаос. Добавление нового разработчика превращается в ручное скачивание ключей и раскладку по десяткам машин. SSH-сертификаты решают проблему: один CA-ключ подписывает все публичные ключи, и ни один authorized_keys не нужен.

## Зачем authorized_keys — это боль

Схема с authorized_keys требует, чтобы публичный ключ пользователя физически присутствовал на каждом сервере. При масштабировании это означает:

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

SSH-сертификат подписывает публичный ключ пользователя или хоста центральным CA-ключом. Серверу достаточно доверять этому CA — сам ключ подкладывать не нужно.

## Как работают SSH-сертификаты

Два участника: CA (Certificate Authority) и подписываемый ключ. CA — это обычная SSH-пара ed25519 или rsa. Подпись создаётся командой `ssh-keygen -s <ca_private> -I <identifier> <key.pub>` и порождает файл `<key-cert.pub>`.

Серверу достаточно двух вещей: публичный ключ CA в `TrustedUserCAKeys` (для пользователей) или `HostCertificate` (для хостов). Аутентификация проходит, если подпись валидна и сертификат не просрочен.

> [!NOTE]
> Сертификат не заменяет ключ. Ключ по-прежнему необходим — он подписывается CA. Сертификат добавляет метаданные: время жизни, principals, расширения.

## Генерация CA-ключей

```bash
ssh-keygen -t ed25519 -f /etc/ssh/ca_user -C "CA for user certificates"
ssh-keygen -t ed25519 -f /etc/ssh/ca_host -C "CA for host certificates"
```

| Флаг | Значение |
|------|----------|
| `-t ed25519` | Тип ключа, ed25519 рекомендован RFC 8709 |
| `-f` | Путь к файлу ключа |
| `-C` | Комментарий, удобен для идентификации CA |

CA-ключи хранятся в защищённом месте — ideally на отдельной машине или в HSM. Приватный ключ CA никогда не должен попадать на целевые серверы.

## Подпись user-сертификата: one-liner

```bash
ssh-keygen -s /etc/ssh/ca_user \
  -I "john@devops" \
  -n ubuntu,deploy \
  -V +52w \
  -z 1 \
  ~/.ssh/id_ed25519.pub
```

| Флаг | Значение |
|------|----------|
| `-s ca_private` | Приватный ключ CA |
| `-I identifier` | Строка-идентификатор в логах |
| `-n principals` | Список principals через запятую |
| `-V +52w` | Срок действия: 52 недели от now |
| `-z serial` | Серийный номер, полезен для аудита |

Результат — файл `id_ed25519-cert.pub` рядом с ключом. Пользователь подкладывает оба файла на машину, с которой работает. Подпись для другого сотрудника — та же команда с другим ключом и CA.

> [!TIP]
> Формат `+52w` поддерживает суффиксы `h` (часы), `d` (дни), `w` (недели). `+1d` — сутки, `-1d` — вчера (сертификат уже истёк).

## Подпись host-сертификатов

На каждом сервере создаётся пара host-ключей (если ещё нет):

```bash
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
```

Подпись сертификата — на машине с CA:

```bash
ssh-keygen -s /etc/ssh/ca_host \
  -I "prod-web-01" \
  -h \
  -n prod-web-01,10.0.1.5 \
  -V +52w \
  /etc/ssh/ssh_host_ed25519_key.pub
```

Флаг `-h` превращает подпись в host-сертификат. `-n` содержит hostname и IP, которые клиент будет сверять при подключении.

Сертификат кладётся рядом с host-ключом:

```bash
cp /tmp/ssh_host_ed25519_key-cert.pub /etc/ssh/ssh_host_ed25519_key-cert.pub
```

## sshd_config: CertFile, TrustedUserCAKeys, HostCertificate

Конфигурация sshd на целевом сервере:

```bash
# /etc/ssh/sshd_config.d/certs.conf

# Аутентификация по сертификатам
PubkeyAuthentication yes

# CA для пользовательских сертификатов
TrustedUserCAKeys /etc/ssh/ca_user.pub

# Путь к host-сертификату
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

# Необязательно: файл principals для конкретного пользователя
# AuthorizedPrincipalsFile /etc/ssh/%u.principals
```

После изменения конфига — проверка и перезагрузка:

```bash
sshd -t && systemctl reload sshd
```

> [!WARNING]
> `TrustedUserCAKeys` принимает именно публичный ключ CA, не сертификат. Сертификат нужен только для host-ключей.

## Ограничение principals и from

При подписи можно задать `from` — ограничение по IP-адресу источника. Либо в момент подписи:

```bash
ssh-keygen -s /etc/ssh/ca_user \
  -I "jenkins@ci" \
  -n deploy \
  -O source-address=10.8.0.0/16 \
  ~/.ssh/id_ed25519.pub
```

Либо через `from` в `AuthorizedPrincipalsFile` на сервере:

```
# /etc/ssh/deploy.principals
deploy from="10.8.0.0/16"
```

В сертификате может быть несколько principals — sshd проверит, есть ли хотя бы один совпадающий с `AuthorizedPrincipalsFile`. Это позволяет выдавать сертификат с `ubuntu,deploy,admin` и раздавать доступ через разные принципалы на разных хостах.

## Время жизни и ротация

TTL задаётся при подписи. Рекомендации из практики:

| Роль | Срок | Причина |
|------|------|---------|
| CI/CD, автоматика | 24–72 часа | Ключи в pipelines живут недолго |
| Разработчики | 1–6 месяцев | Баланс между безопасностью и удобством |
| Хосты | 12 месяцев | Host-сертификат привязан к hostname/IP |

Ротация — новый сертификат с новым `-z serial`. Старый автоматически перестаёт работать после истечения. Единая точка отзыва не нужна: истёкший сертификат невалиден, CA самодостаточен.

## Fingerprints: проблема host-key

Классический known_hosts хранит fingerprint host-ключа. Host-сертификат ломает эту схему: fingerprint в known_hosts не совпадает с сертификатом. Есть два пути:

Первый — перейти на `ssh_known_hosts` с `ssh-keyscan`:

```bash
ssh-keyscan -t ed25519 prod-web-01 >> /etc/ssh/ssh_known_hosts
```

Второй — включить `HostKeyAlgorithms` с сертификатами, тогда fingerprint игнорируется:

```
Host prod-web-01
    HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com
    UpdateHostKeys ask
```

При первом подключении ssh предложит принять сертификат, добавит его в known_hosts и больше спрашивать не будет.

## Troubleshooting

Ошибки разбираются по шагам:

```bash
# Проверить, что sshd принял конфиг
sshd -t

# Посмотреть логи аутентификации
journalctl -u sshd -f

# На клиенте —verbose
ssh -vvv user@host
```

В выводе `-vvv` смотрите строки `Certificateهو` и `Authentications that can continue`. Если видите `no matching identity` — сертификат не найден рядом с ключом. `certificate refused` — CA не доверен или principal не совпал.

Проверить содержимое сертификата:

```bash
ssh-keygen -Lf ~/.ssh/id_ed25519-cert.pub
```

Вывод покажет `Valid: from ... to ...`, `Principals:`, `Serial:`. Это первое, что стоит глянуть, если что-то не работает.

> [!NOTE]
> Ошибка `too many authentication failures` при наличии сертификата обычно означает, что ssh перебирает все ключи до сертификата. Добавьте `-o PubkeyAuthentication=no` перед явным указанием ключа, или уберите лишние ключи из `.ssh`.

SSH-сертификаты убирают необходимость раскладки ключей по хостам. Один CA, подпись с TTL, principals для разграничения доступа. Если в инфраструктуре больше 5–10 серверов — это уже не luxury, а необходимость.
