Перейти к содержимому
ecdsa.com
← Все статьи

SSH-сертификаты или authorized_keys: CA и принципалы

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

Коротко. С authorized_keys на каждом сервере перечислены все открытые ключи, которым разрешён вход, и доступ действует, пока кто-нибудь не удалит строку. С сертификатами OpenSSH серверы доверяют одному центру сертификации (CA) — CA пользователей (TrustedUserCAKeys). Ключ каждого пользователя подписывается командой ssh-keygen -s, которая добавляет к нему идентификатор (-I), учётные записи, под которыми с этим ключом можно войти (принципалы, -n), и срок действия (-V). Клиенты доверяют одному CA хостов (@cert-authority в known_hosts), а не принимают отпечаток каждого хоста при первом подключении. С короткоживущими сертификатами доступ ушедшего из компании человека заканчивается сам, а остальное берут на себя списки отзыва ключей (RevokedKeys). Цена — теперь вы обслуживаете собственный CA и должны защищать его ключ.

Чего не умеет authorized_keys

~/.ssh/authorized_keys прост и не требует ничего обслуживать, поэтому он и используется повсюду. Его ограничения проявляются, когда людей и серверов становится больше:

  • Доступ бессрочный. Ключ, добавленный для подрядчика в 2023 году, работает и в 2026 году на каждом сервере, куда его скопировали, если никто его не удалил. (Для каждой строки можно задать expiry-time=, но кто-то должен это сделать.)
  • Нет идентификатора владельца. Строка — это ключ и комментарий. Кому он принадлежит (человеку, команде или заданию), записано в электронной таблице, если вообще где-то записано.
  • Распространение — ваша забота. Чтобы добавить или удалить человека, нужно изменить каждый сервер: через систему управления конфигурацией или вручную.
  • Ключам хостов доверяют при первом подключении (trust on first use). Каждый клиент принимает отпечаток каждого сервера при первом подключении, а после пересборки сервера все клиенты выдают REMOTE HOST IDENTIFICATION HAS CHANGED.

Сертификаты OpenSSH, доступные с OpenSSH 5.4 (2010), решают все четыре проблемы. Это не X.509: у OpenSSH собственный формат — открытый ключ с идентификационными данными и ограничениями, подписанный ключом CA, а ключ CA — обычный SSH-ключ. В 2025 году описание формата переехало из файла PROTOCOL.certkeys в исходном коде OpenSSH в Internet-Draft, который теперь называется draft-ietf-sshm-cert и разрабатывается в рабочей группе IETF SSHM.

Два CA — два направления

Нужны два отдельных ключа подписи, потому что они отвечают на разные вопросы:

  • CA пользователей отвечает на вопрос «может ли этот ключ войти под этой учётной записью?». Ему доверяют серверы.
  • CA хостов отвечает на вопрос «действительно ли это web01?». Ему доверяют клиенты.
Создание CA (OpenSSH 10.0p2)
$ ssh-keygen -q -t ed25519 -f user_ca -C "Example user CA" -N ""
$ ssh-keygen -q -t ed25519 -f host_ca -C "Example host CA" -N ""

-N "" здесь только для простоты стенда. Настоящий ключ CA должен храниться в HSM, на аппаратном токене или в службе подписи (так умеют механизм секретов SSH в HashiCorp Vault, smallstep, Teleport и аналоги у облачных провайдеров). Тот, у кого есть ключ CA пользователей, может войти от имени любого, кого допускают серверы.

Сертификаты пользователей

Подпишите существующий открытый ключ Алисы на 8 часов для учётных записей alice и deploy:

ssh-keygen -s: выпуск сертификата пользователя
$ ssh-keygen -s user_ca -I alice@example.test -n alice,deploy -V -5m:+8h -z 1001 id_ed25519.pub
Signed user key /home/alice-laptop/id_ed25519-cert.pub: id "alice@example.test" serial 1001 for alice,deploy valid from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
  • -I — это идентификатор ключа (key ID). sshd записывает его в журнал при каждом входе, поэтому указывайте в нём человека или задание, а не имя хоста.
  • -n перечисляет принципалы — имена, под которыми может действовать этот сертификат. В руководстве ssh-keygen сказано, что сертификаты без принципалов «действительны для всех пользователей или хостов», но sshd 10.0 отклоняет такой сертификат пользователя, если доверие задано через TrustedUserCAKeys (Certificate lacks principal list). Всегда задавайте -n.
  • -V -5m:+8h задаёт срок действия с момента 5 минут назад (запас на расхождение часов) до момента через 8 часов.
  • -z задаёт серийный номер, на который можно сослаться при отзыве.

ssh-keygen -L показывает, что было подписано:

ssh-keygen -L -f id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s
        Signing CA: ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60 (using ssh-ed25519)
        Key ID: "alice@example.test"
        Serial: 1001
        Valid: from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
        Principals: 
                alice
                deploy
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

По умолчанию сертификат пользователя даёт все пять расширений. -O clear убирает их, после чего можно вернуть только нужные, например -O clear -O permit-pty. Критические опции (critical options) ограничивают сертификат. force-command подменяет любую команду, которую запрашивает клиент, а source-address ограничивает адреса, с которых сертификатом можно пользоваться. С source-address=10.0.0.0/8 вход с 127.0.0.1 отклоняется. Если адрес источника разрешён, принудительная команда выполняется независимо от того, что запросил клиент:

Критические опции на практике (строки журнала sshd при LogLevel VERBOSE)
        Critical Options: 
                force-command echo backup only
                source-address 10.0.0.0/8
$ ssh deploy@web01.example.test
deploy@web01.example.test: Permission denied (publickey).
cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).
Refused by certificate options
...
$ ssh deploy@web01.example.test "rm -rf /tmp/anything"
backup only
Starting session: forced-command (key-option) 'echo backup only' for deploy from 127.0.0.1 port 48976 id 0

Клиент ssh находит сертификат автоматически, если тот лежит рядом с закрытым ключом под именем id_ed25519-cert.pub. Иначе укажите путь к нему в CertificateFile. ssh-add загружает в агент и ключ, и сертификат.

Настройка sshd

Вся настройка на стороне сервера занимает несколько строк. Этот подключаемый файл конфигурации прошёл проверку sshd -t, и с ним выполнены все тесты на этой странице:

/etc/ssh/sshd_config.d/ca.conf
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
RevokedKeys /etc/ssh/revoked_keys
PasswordAuthentication no
LogLevel VERBOSE
  • TrustedUserCAKeys содержит один или несколько открытых ключей CA. Во время ротации CA укажите оба.
  • AuthorizedPrincipalsFile сопоставляет учётные записи с принципалами. Здесь файл /etc/ssh/auth_principals/deploy содержит единственную строку deploy, поэтому войти как deploy могут только сертификаты, в которых указан принципал deploy. Без этого параметра sshd требует, чтобы среди принципалов было само имя учётной записи. Сопоставление гибче: принципал вроде oncall можно разрешить для многих учётных записей, а AuthorizedPrincipalsCommand может запрашивать сопоставление в службе каталогов.
  • RevokedKeys указывает на список отзыва (см. ниже).
  • LogLevel VERBOSE записывает в журнал идентификатор ключа и серийный номер сертификата при каждом входе. Такого журнала аудита у authorized_keys никогда не было.

Вход с сертификатом Алисы глазами клиента и сервера:

ssh -v (клиент) и журнал sshd (сервер), сокращено
Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:IuzvI4QsLmSx1+6p3yrvlRx2wdl+LUCY1zXqSzssLAc, serial 0 ID "web01" CA ssh-ed25519 SHA256:+iie/YHPulJReHN2GQ8UvrorBVrtayD2T1MGxeg/QCQ valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08
Host 'web01.example.test' is known and matches the ED25519-CERT host certificate.
Offering public key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Server accepts key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Authenticated to web01.example.test ([127.0.0.1]:22) using "publickey".

Accepted publickey for deploy from 127.0.0.1 port 57432 ssh2: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s ID alice@example.test (serial 1001) CA ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60

Если редактировать sshd_config нельзя, отдельная учётная запись может доверять CA через собственный authorized_keys. Параметр principals= работает так же, как файл принципалов:

~deploy/.ssh/authorized_keys: доверие CA для одной учётной записи (ключ сокращён)
cert-authority,principals="deploy-team" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK63...
$ ssh deploy@web01.example.test 'echo logged in as $(whoami)'
logged in as deploy

Сертификаты хостов

Подпишите ключ хоста каждого сервера с помощью CA хостов. С -h получается сертификат хоста, а принципалы в нём — это имена, по которым к серверу подключаются клиенты:

Подпись ключа хоста
$ ssh-keygen -s host_ca -I web01 -h -n web01.example.test,web01 -V -5m:+52w /etc/ssh/ssh_host_ed25519_key.pub
Signed host key /etc/ssh/ssh_host_ed25519_key-cert.pub: id "web01" serial 0 for web01.example.test,web01 valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08

Затем укажите полученный файл в HostCertificate (как в файле sshd выше) и вместо отпечатка для каждого хоста добавьте клиентам одну строку в known_hosts (или /etc/ssh/ssh_known_hosts):

known_hosts: доверие CA хостов для домена
@cert-authority *.example.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBtSsqoqxwcl31DTJg3kTPsv9eLuuw5gU1BcvYybODlu Example host CA

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

Подключение по имени db01.example.test к хосту, в сертификате которого указан только web01
$ ssh deploy@db01.example.test
Certificate invalid: name is not a listed principal
No ED25519 host key is known for db01.example.test and you have requested strict checking.
Host key verification failed.

Перечислите все имена, по которым подключаются клиенты, в том числе короткие имена, CNAME-записи, IP-адреса (если к серверу подключаются по IP) и имена балансировщиков нагрузки. ssh-keyscan -c получает сертификат сервера для анализа, а строки @revoked в known_hosts отклоняют конкретный ключ хоста или CA.

Как выглядят ошибки

При проблемах с сертификатом пользователя клиент всегда выдаёт только Permission denied (publickey). Причина записана в журнале сервера при LogLevel VERBOSE:

СитуацияЖурнал sshd (OpenSSH 10.0p2)
Учётной записи нет среди принципалов сертификата (с учётом сопоставления)Certificate does not contain an authorized principal
Сертификат выпущен без -n, AuthorizedPrincipalsFile не заданCertificate lacks principal list
Истёк срок действия, заданный в -VCertificate invalid: expired
Серийный номер или ключ есть в KRLAuthentication key ED25519-CERT SHA256:... revoked by file /etc/ssh/revoked_keys
source-address не совпадаетcert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).

Две ситуации похожи на проблемы с сертификатами, но к ним не относятся:

  • Расхождение часов. Срок действия проверяется по часам сервера. Выпускайте сертификаты, действительные с момента на несколько минут раньше текущего (-V -5m:...), и синхронизируйте часы серверов по NTP.
  • Штрафы за повторные неудачи. Начиная с OpenSSH 9.8, sshd штрафует адреса, с которых аутентификация раз за разом не проходит (PerSourcePenalties, включён по умолчанию). Когда мы проверяли сертификаты с одной машины, после неудачной попытки следующее подключение было сброшено ещё до аутентификации:
sshd 10.0p2 после неудачного входа с сертификатом
srclimit_penalise: 127.0.0.1/32: activating ipv4 penalty of 19 seconds for penalty: failed authentication
$ ssh deploy@web01.example.test
Connection closed by 127.0.0.1 port 22
drop connection #0 from [127.0.0.1]:55080 on [127.0.0.1]:22 penalty: failed authentication

Если ssh сообщает Connection closed сразу после серии неудач, дождитесь окончания штрафа, прежде чем снова менять настройки.

Отзыв через KRL

Главный механизм отзыва — короткий срок действия: сертификат на 8 часов истекает сам. Для остальных случаев в OpenSSH есть списки отзыва ключей (Key Revocation Lists, KRL; появились в OpenSSH 6.2) — компактный двоичный формат, который позволяет отзывать по серийному номеру, идентификатору ключа или самому ключу. Отзыв сертификата Алисы с серийным номером 1001:

ssh-keygen -k: создание KRL и проверка сертификата по нему
$ printf 'serial: 1001\n' > /tmp/krl-spec
$ ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca.pub /tmp/krl-spec
Revoking from /tmp/krl-spec
$ ssh-keygen -Q -f /etc/ssh/revoked_keys id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub (alice@laptop): REVOKED

sshd читает RevokedKeys при каждой аутентификации, поэтому перезапуск не нужен: следующий же вход будет отклонён. Добавьте -u, чтобы обновить существующий KRL, а не заменить его. Распространяйте KRL теми же инструментами, что и ключ CA. Сервер с устаревшим KRL примет и то, что уже отозвано, если не знает об этом. Это ещё одна причина делать сертификаты короткоживущими, а KRL считать страховкой на оставшиеся часы.

Сравнение

authorized_keysСертификаты
Выдача доступаСкопировать ключ на каждый серверОдин раз подписать ключ
Отзыв доступаУдалить ключ на каждом сервереДождаться истечения срока действия или добавить в KRL
Срок действияНеобязателен, задаётся для строки (expiry-time=)Есть у каждого сертификата
Идентификатор в журналеОтпечаток ключаИдентификатор ключа и серийный номер
Ограниченияfrom=, command=, restrict для каждой строкиsource-address, force-command, расширения для каждого сертификата
Доверие хостамОтпечаток для каждого хоста (TOFU)Одна строка @cert-authority
Главный секретЕдиного нетЗакрытый ключ CA
Что нужно обслуживатьНичегоВыпуск, защита CA, распространение KRL

Ограничения для отдельных строк в authorized_keys действительно работают, это проверено на том же сервере:

Параметры authorized_keys, OpenSSH 10.0p2 (ключи сокращены)
restrict,from="10.0.0.0/8",command="echo backup only" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).
/home/deploy/.ssh/authorized_keys:1: Authentication tried for deploy with correct key but not from a permitted host (host=127.0.0.1, ip=127.0.0.1, required=10.0.0.0/8).

expiry-time="20261001" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).

Для нескольких серверов и людей authorized_keys с restrict и expiry-time вполне подходит. Сертификаты окупаются, когда приход и уход людей и пересборка хостов случаются каждую неделю, или когда нужно знать, кто вошёл, а не только каким ключом.

Переход с authorized_keys

Переключать всё в один день не нужно. sshd проверяет authorized_keys и доверенные CA параллельно, поэтому во время перехода работают оба способа входа:

  1. Сначала настройте доверие к CA. Разверните на всех серверах TrustedUserCAKeys, файлы принципалов и пустой KRL, а authorized_keys не трогайте.
  2. Выпустите сертификаты первой команде и дайте ей поработать с ними несколько недель. Теперь каждый вход появляется в журнале с ID ... (serial ...).
  3. Найдите тех, кто остался на старых ключах. Вход через authorized_keys записывается в журнал без идентификатора сертификата, например Accepted publickey for deploy from 127.0.0.1 port 58770 ssh2: ED25519 SHA256:tT5X32Wds5InYaLSh7mV+QRqAtxA4KXwlKV3hkf0YkI. Обычно это автоматизация: раннеры CI, задания резервного копирования, мониторинг. Выдайте каждому из них собственный короткоживущий или строго ограниченный сертификат.
  4. Отключите файлы ключей параметром AuthorizedKeysFile none, когда легитимных входов через них не останется. Сохраните аварийный путь доступа, например доступ через консоль или отдельный аварийный CA, который хранится офлайн, на тот день, когда служба подписи не работает.
  5. Сторону хостов переводите отдельно. Подпишите ключи хостов и добавьте строку @cert-authority в глобальный файл клиентов /etc/ssh/ssh_known_hosts. Старые записи known_hosts для отдельных хостов можно оставить. Они работают, пока не сменятся ключи хостов.

Эксплуатация на практике

  • Выпускайте сертификаты через поставщика удостоверений (identity provider, IdP). Служба подписи проверяет вход пользователя через SSO (OIDC) и возвращает сертификат с его принципалами на несколько часов. С ключом CA никто не работает напрямую, а после ухода человека из компании его доступ по SSH прекращается к следующему дню без каких-либо действий на серверах.
  • По возможности храните ключ пользователя на аппаратном устройстве. ssh-keygen -t ed25519-sk создаёт ключ, привязанный к аутентификатору FIDO2. Сертификаты работают с ним так же, как с любым другим ключом.
  • Проводите ротацию CA с перекрытием. Добавьте новый CA пользователей второй строкой в TrustedUserCAKeys или новый CA хостов второй строкой @cert-authority. Переключите выпуск сертификатов на новый CA, затем удалите старый.
  • Следите и за стороной хостов. Переподписывайте ключи хостов до истечения срока действия их сертификатов. Если сертификат хоста истёк, клиенты переходят к обычным ключам хостов, которых у них обычно нет:
ssh к хосту, сертификат которого истёк вчера
Certificate invalid: expired
No ED25519 host key is known for web01.example.test and you have requested strict checking.
Host key verification failed.

Чтобы разобрать файл ключа или сертификата, ничего не запуская, откройте инспектор SSH-ключей: он декодирует ключи OpenSSH и показывает их тип, размер и отпечаток. Аудит SSH-сервера проверяет, что предлагает сервер, включая алгоритмы ключей хоста и сертификатов и постквантовый обмен ключами.

Источники

Весь вывод на этой странице получен в OpenSSH 10.0p2 (пакеты Debian 13) в debian:trixie-20261005-slim 20 октября 2026 года с тестовыми ключами, сгенерированными в контейнере.