# Готова ли ротация ключей хоста SSH?

Ключ хоста SSH нужно менять по вашему графику, когда клиент уже доверяет ключу на замену. Если разработчики впервые узнают о ротации из красного предупреждения в терминале, план уже провалился. Предупреждение не объяснит, штатно ли изменился сервер или кто-то перехватил соединение. Срочная просьба удалить строку из `known_hosts` уничтожает данные, по которым нужно принять решение.

Безопасная ротация состоит из четырех отдельных действий: опубликовать старый и новый отпечатки через доверенный канал, достаточно долго предлагать оба ключа, чтобы клиенты узнали новый, удалить старый в заданное окно и доказать, что клиенты отвергают выведенный из обращения идентификатор. Я видел, как команды правильно выполняли работу на сервере и одновременно приучали разработчиков к худшей привычке: считать проверку хоста помехой. Задача эксплуатации - сделать безопасный путь обычным до появления предупреждения.

## Идентификация хоста и аутентификация пользователя различаются

Ключ хоста подтверждает, какой сервер SSH ответил; пользовательский ключ подтверждает, кто пытается войти. Ротация одного не меняет другой. Различие кажется простым, но инструкции по инцидентам часто смешивают `authorized_keys`, личные ключи SSH, сертификаты хостов и файлы `/etc/ssh/ssh_host_*` в расплывчатое указание «заменить ключи SSH».

RFC 4253 помещает аутентификацию сервера в транспортное рукопожатие. Сервер подписывает хеш обмена закрытым ключом хоста, а клиент сверяет открытый ключ с доверенным источником: `known_hosts`, центром сертификации хостов или аутентифицированными записями SSHFP. Пароли, пользовательские сертификаты и открытые ключи пользователей идут позже. Если клиент перестал проверять сервер, разработчик может передать самозванцу полностью действительные учетные данные.

Поэтому предупреждение о смене хоста намеренно выглядит серьезно. Оно сообщает о разрыве непрерывности: имя, которое ввел разработчик, теперь предъявляет другой идентификатор. Плановая замена, пересозданная виртуальная машина, балансировщик с неверным пулом, подмена DNS и активный перехват в этот момент выглядят одинаково. Клиент не видит вашу заявку на изменение.

До генерации ключей запишите область работ. Учтите все имена и псевдонимы, нестандартные порты, адреса в скриптах, маршруты через бастионы, исполнители CI, агенты развертывания и общие `GlobalKnownHostsFile`. `[host]:port` и `host` - разные имена в known-hosts, а псевдоним может скрывать канонический адрес. Если ротация охватила `git.example.net`, но пропустила `git.internal`, сбой будет казаться случайным, хотя оба имени ведут на одну машину.

Составьте список всех предлагаемых алгоритмов ключей хоста. На одном сервере могут одновременно находиться ключи Ed25519, ECDSA и RSA. OpenSSH согласует алгоритм с клиентом, поэтому два разработчика способны закрепить разные открытые ключи одного daemon. Замены ключа, замеченного на вашем ноутбуке, недостаточно для полного набора идентификаторов сервера. Считайте настроенные директивы `HostKey` и проверенные файлы открытых ключей достоверным списком, затем сопоставьте его с результатом согласования у каждой поддерживаемой группы клиентов.

## Опубликуйте оба отпечатка до изменения сервера

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

Вычисляйте отпечатки из файлов открытых ключей на доверенной административной машине. Не спрашивайте у рабочей сети, что она сейчас показывает. OpenSSH по умолчанию выводит отпечатки SHA-256. Команда и форма результата:

```text
$ ssh-keygen -lf ssh_host_ed25519_key.pub -E sha256
256 SHA256:<base64-fingerprint> host.example.net (ED25519)
```

Публикуйте не один короткий хеш. Для каждого идентификатора укажите имя и порт, алгоритм, отпечаток SHA-256, состояние (`current`, `new` или `retired`), начало использования, время вывода и того, кто одобрил запись. Обозначьте часовой пояс. Перечислите псевдонимы с общим ключом. Если узлы за одним именем намеренно предъявляют разные ключи, опубликуйте весь разрешенный набор и объясните причину.

Полезная запись выглядит так:

```text
Host: build.example.net:22
Algorithm: ssh-ed25519
Current: SHA256:<old-fingerprint>
New: SHA256:<new-fingerprint>
Overlap begins: 2026-08-10 15:00 UTC
Old key removed: 2026-08-17 15:00 UTC
Old key marked revoked: 2026-08-17 15:30 UTC
Owner: Platform operations
```

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

Отпечатки - публичные идентификаторы, а не секреты. Закрытый ключ хоста остается защищенным на сервере, но открытый ключ и отпечаток нужно распространить так, чтобы проверка не зависела от доступности одного администратора. Рассматривайте утверждение публикации как изменение безопасности: два исходных файла с одинаковым отпечатком надежнее снимка экрана в чате.

## Перекрытие безопасно передает новый ключ клиентам

Предлагайте старый и новый ключи вместе до удаления старого. Во время перекрытия известный клиент аутентифицирует сервер старым доверенным ключом, а затем узнает дополнительный через расширение OpenSSH `hostkeys@openssh.com`. Непрерывность обеспечивает старый ключ, поэтому новый не приходит как неподтвержденное заявление.

Руководство OpenSSH `ssh_config` прямо называет `UpdateHostKeys` средством плавной ротации и указывает важные ограничения. Клиент принимает дополнительные ключи лишь после аутентификации уже доверенным или явно принятым обычным ключом через пользовательский файл known-hosts. Механизм не действует после аутентификации сертификатом хоста или только глобальным файлом. Значение по умолчанию также может отключиться, если пользователь переопределил `UserKnownHostsFile` или включил `VerifyHostKeyDNS`.

Не полагайтесь на значение по умолчанию. Посмотрите эффективную конфигурацию:

```text
$ ssh -G build.example.net | grep -E '^(updatehostkeys|userknownhostsfile|stricthostkeychecking) '
stricthostkeychecking ask
updatehostkeys true
userknownhostsfile ~/.ssh/known_hosts ~/.ssh/known_hosts2
```

На сервере храните закрытые ключи в разных файлах и объявите оба. Пути ниже даны как пример:

```text
HostKey /etc/ssh/ssh_host_ed25519_key_old
HostKey /etc/ssh/ssh_host_ed25519_key_new
```

Перед перезагрузкой конфигурации выполните `sshd -t`. По руководству этот режим проверяет конфигурацию и исправность ключей, поэтому заранее находит нечитаемый файл или неверную настройку. Перечитайте конфигурацию, не завершая действующие сеансы, если менеджер службы не требует другого. Затем подключитесь чистым тестовым клиентом, который доверяет только старому ключу, и после успешного сеанса изучите его изолированный файл.

Несколько настроенных ключей не гарантируют, что все клиенты согласуют один и тот же. Влияют предпочтения алгоритмов, возраст клиента, измененный `HostKeyAlgorithms`, сертификаты и глобальные хранилища. Держите перекрытие не менее одного обычного цикла подключения каждой категории. Ноутбукам с еженедельным подключением и исполнителям CI с минутным интервалом нужны разные окна.

Измеряйте распространение, не притворяясь, что видите все личные `known_hosts`. Управляемые клиенты могут сообщить о наличии нового ключа в развернутом файле. Для остальных отслеживайте успешные соединения по версии, если это разрешает политика конфиденциальности, опубликуйте команду проверки и сохраните перекрытие на срок обычного использования. Отсутствие жалоб не доказывает распространение.

## Окну нужны состояния и условия остановки

Окно изменения должно задавать наблюдаемые состояния, ответственных и условия отката. «Заменить в 15:00» не описывает перекрытие, готовность клиентов и момент запрета старого идентификатора. Поместите переход доверия на одну временную шкалу с развертыванием.

Используйте пять рубежей:

1. Публикация завершена: независимые проверяющие воспроизвели все отпечатки из одобренных файлов.
2. Перекрытие работает: сервер предлагает оба идентификатора, проходит `sshd -t`, а чистые клиенты аутентифицируются старым и узнают новый.
3. Готовность достигнута: управляемые хранилища содержат замену, тесты охватывают системы и маршруты, служба поддержки имеет точные отпечатки.
4. Переключение завершено: сервер больше не предлагает старый закрытый ключ, новые сеансы проходят строгую проверку, неожиданных узлов со старым ключом нет.
5. Отзыв доказан: тестовый адрес со старым идентификатором отклоняется, а выведенный отпечаток остается опубликованным как отозванный.

Назначьте человека с правом остановить переключение. Остановитесь, если узел показывает неопубликованный ключ, маршрут приводит к узлу вне списка, поддерживаемый клиент не получает замену или откат требует ключа, который группа реагирования считает скомпрометированным. Обычная ротация допускает возврат к старому ключу в период перекрытия. При компрометации такой ключ не может быть безопасной точкой возврата.

Разделяйте откат службы и доверия. Можно вернуть прежний пакет сервера, не возвращая выведенный идентификатор. Держите доступными проверенную конфигурацию, новый закрытый ключ и консоль или панель провайдера. Если SSH - единственный способ починить SSH, в окне скрыта единая точка отказа.

Учтите долгие мультиплексированные сеансы. Основное соединение OpenSSH может жить, пока новые оболочки используют существующий транспорт. Они не выполняют новый обмен ключами и не подтверждают идентификатор после переключения. При необходимости закройте тестовые master-соединения командой `ssh -O exit host` и заставьте пробы создавать новые TCP-соединения.

Точно назначьте вывод и оставьте связь открытой после него. Разработчик после отпуска принесет устаревшее закрепление. Ожидаемый результат - описанный отказ и проверенный путь обновления, а не исключение. Поддержка сначала сравнивает отпечатки, находит запись через `ssh-keygen -F` и заменяет ее данными из одобренной публикации.

## Отрепетируйте предупреждение с изолированным файлом

Проверяйте ротацию, не затрагивая настоящий `~/.ssh/known_hosts`. Изолированный файл делает каждое состояние воспроизводимым и не позволяет ключам, изученным месяцы назад, создать ложный успех. Используйте стенд, который воспроизводит последовательность рабочих ключей, или временный `sshd` на другом порту с теми же ограничениями.

Сначала создайте `known_hosts.test` из одобренного старого открытого ключа, а не из сетевого сканирования. Подключитесь, закрепив логическое рабочее имя через `HostKeyAlias`:

```text
$ ssh -F /dev/null \
  -o HostKeyAlias=build.example.net \
  -o UserKnownHostsFile=./known_hosts.test \
  -o GlobalKnownHostsFile=/dev/null \
  -o StrictHostKeyChecking=yes \
  -o UpdateHostKeys=yes \
  -p 2222 test-host.example.net true
```

Первый запуск должен пройти, пока адрес предлагает оба ключа. Проверьте файл через `ssh-keygen -F build.example.net -f ./known_hosts.test`, подтвердите наличие одобренной замены и сравните отпечатки всех полей с публикацией. Нулевой статус доказывает связь, но не набор идентификаторов.

Затем оставьте на тестовом адресе только новый ключ и откройте новое соединение. Оно должно пройти благодаря аутентифицированному обучению. Восстановите копию только со старым закреплением и повторите: при `StrictHostKeyChecking=yes` попытка обязана завершиться отказом. Этот отрицательный тест показывает, что пропустившие перекрытие клиенты останавливаются, а не принимают изменение молча.

Наконец, предъявите посторонний ключ. Сохраните текст ошибки и статус для инструкции. Не ослабляйте тест ради успеха: вы защищаете именно отказ. Убедитесь, что скрипты передают ненулевой статус, а не поглощают его повторными попытками.

Никогда не исправляйте репетицию через `StrictHostKeyChecking=no`. Текущее руководство сообщает, что настройка разрешает некоторые измененные ключи, тогда как `accept-new` их отвергает. Но и `accept-new` не решает ротацию известного имени. Передайте аутентифицированные данные доверия или используйте перекрытие.

## Отзыв должен приводить к отказу, а не к очистке

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

Файлы OpenSSH поддерживают маркер `@revoked`. Совпавший ключ с таким маркером никогда не принимается и вызывает предупреждение. Управляемый парк может раздать глобальную запись, небольшой - отдельный файл. Используйте точный одобренный открытый ключ и осознанно задайте область имени:

```text
@revoked build.example.net ssh-ed25519 <old-public-key-data>
```

Key Revocation List удобен для большого набора и сертификатов. Создайте тестовый KRL из старого ключа и запросите его:

```text
$ ssh-keygen -k -f revoked-hostkeys.krl ssh_host_ed25519_key_old.pub
$ ssh-keygen -Q -f revoked-hostkeys.krl ssh_host_ed25519_key_old.pub
ssh_host_ed25519_key_old.pub (<comment>): REVOKED
```

Для отозванного ключа запрос возвращает ненулевой статус; автоматизация должна трактовать его намеренно. Укажите KRL в `RevokedHostKeys`, потребуйте отказ для старого адреса и успех для нового. Обе половины нужны: поврежденный или нечитаемый файл способен запретить все соединения.

`ssh-keygen -R host` помогает править файлы с хешированными именами, но ничего не отзывает. Команда удаляет все записи имени, включая действительную замену, и снова превращает следующее соединение в решение о доверии. Применяйте ее только после сравнения, а затем добавьте одобренный новый ключ. Цепочка из `-R` и неаутентифицированного `ssh-keyscan` автоматизирует отказ от доверия.

Сохраняйте отозванные открытые материалы. Нескомпрометированный старый закрытый ключ можно уничтожить по политике, но его открытый ключ, отпечаток, одобрение и результат теста нужно оставить. Они помогут узнать старый образ, вернувшийся через месяцы.

## Автоматизация должна безопасно останавливаться без хрупкости

Неинтерактивным задачам нужен обслуживаемый источник доверия, а не отключенная проверка. Исполнители CI, агенты развертывания и автономные агенты программирования часто первыми встречают ротацию, потому что весь день открывают короткие соединения. Если образ содержит один ключ и никто не отвечает за обновление, обычной срочной заплатой становится `StrictHostKeyChecking=no`. Она превращает ошибку доступности в провал аутентификации.

Выберите модель для каждого класса: положите оба одобренных ключа в образ на время перекрытия, подключите управляемый глобальный файл, возвращайте одобренные строки через `KnownHostsCommand`, используйте SSHFP с проверкой DNSSEC или доверяйте центру сертификации хостов. Руководство говорит, что команда дополняет обычные файлы и прекращает соединение при сбое. Это верное поведение, но сервис доверия должен быть доступен.

Задайте пакетный режим явно:

```text
Host build.example.net
    BatchMode yes
    StrictHostKeyChecking yes
    UserKnownHostsFile /etc/company/ssh_known_hosts
    UpdateHostKeys no
```

`UpdateHostKeys no` здесь выбран намеренно. Файлом владеет управление конфигурацией; разрешение каждому временному исполнителю менять его создает несохраняемое и неаудируемое состояние. В постоянном пользовательском файле `yes` может быть уместнее. Ответ зависит от владельца распространения доверия.

Тестируйте псевдонимы, переходные хосты, прямые IP и порты как отдельные идентификаторы. `ProxyJump` все равно аутентифицирует назначение, а переходный хост имеет свое закрепление. Контейнер может подключить файл только для чтения; изолированный агент может использовать другой двоичный файл или игнорировать каталог пользователя. Выполняйте `ssh -G target` в настоящем окружении.

Для SSH под управлением агента разделяйте учетные данные и доверие хосту. Sallyport выполняет действия через `sp-ssh`, а ключи SSH остаются в зашифрованном хранилище, поэтому агент не получает секреты. Это защищает хранение учетных данных; плану ротации все равно нужен аутентифицированный источник идентификатора и проверенный путь отказа.

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

Повторите тот же тест на одноразовом исполнителе без старого состояния домашнего каталога. Дайте ему точный управляемый пакет, запустите реальную команду и сохраните `ssh -G`. Так обнаруживается ложный успех из-за прежнего интерактивного сеанса, заполнившего необъявленный файл.

После перехода на новый ключ удалите замену из копии пакета и проверьте отказ. Задача должна остановиться до удаленной команды и сохранить статус SSH. Затем верните новый ключ и повторите. Если система превращает любую ошибку SSH в тайм-аут, исправьте наблюдаемость до окна, чтобы различать отказ идентификатора, потерю сети и ошибку пользователя.

Ищите чрезмерно широкое доверие. Шаблон в known-hosts может разрешить ключ для многих имен, а общий ключ делает машины неразличимыми. Решение может быть намеренным, но запись ротации должна указывать область. Для разных идентификаторов предпочитайте отдельные строки и тестируйте точное имя из задачи.

Зафиксируйте двоичный файл SSH и договор конфигурации автономных задач. Обновление базового образа может поменять значения по умолчанию вместе с идентификатором. Снимите версию, эффективные алгоритмы, хеш файла доверия и логическое имя до и после. Так можно отличить чужой сервер, отсутствие ключа и невозможность согласования.

## DNS и сертификаты меняют способ распространения

SSHFP и сертификаты уменьшают число закреплений на хост, но не отменяют планирование ротации. Они переносят стабильный якорь доверия. Используйте их, если умеете обслуживать этот якорь лучше тысяч отдельных записей.

RFC 4255 требует аутентифицированных данных DNS до доверия SSHFP. На практике DNSSEC должен проверяться от клиента до записи. Неподписанная DNS-запись помогает сравнивать, но не устанавливает идентификатор против меняющего ответы злоумышленника. `VerifyHostKeyDNS yes` безусловно доверяет только безопасным совпадениям.

Опубликуйте обе записи SSHFP на время перекрытия, дождитесь кешей и резолверов, затем удалите старую вместе с закрытым ключом. Генерируйте их из одобренных файлов командой `ssh-keygen -r hostname -f public_key_file`. Проверяйте запись и статус по реальному пути клиента. Малый TTL не исправит сломанный DNSSEC или неверное значение.

Сертификаты позволяют доверять записи вроде `@cert-authority *.example.net`. Новый сертифицированный ключ с правильными принципалами и сроком принимается под существующим центром. Это удобно для динамического парка, но центр получает большие полномочия. Защитите его закрытый ключ, ограничьте выдачу, регистрируйте серийные номера и принципалы, отдельно репетируйте отзыв.

Не внедряйте центр только ради одной неудобной ротации. Он добавляет выдачу, окончание срока, именование и собственную ротацию. Небольшой стабильный парк хорошо работает с явными закреплениями; большому временному парку сертификаты часто подходят лучше.

Документируйте начало доверия. `UpdateHostKeys` не применяется после сертификата, а глобальный файл не обучается через пользовательский. Смешение моделей без приоритета приводит к успеху на ноутбуке и сбою в CI.

## Завершите ротацию доказательствами, которые переживут окно

Завершенная ротация показывает, что изменилось, кто одобрил, что приняли клиенты и был ли отвергнут старый идентификатор. Сохраните открытые ключи, воспроизведенные отпечатки, результат `sshd -t`, эффективную конфигурацию, версию публикации, матрицу клиентов, время переключения, материал отзыва и отрицательный тест. Не кладите закрытый ключ в заявку.

После окна запишите фактическое состояние. Опросите каждый узел по каждому рабочему маршруту и сравните с одобренным набором, помня, что сетевой сбор - наблюдение, а не источник доверия. Сопоставьте его с аутентифицированной публикацией. Проверьте образы автомасштабирования и выключенные резервные узлы: старые ключи часто возвращаются с новой емкостью.

Activity journal и Sessions journal в Sallyport могут сохранить защищенный от незаметного изменения след действий SSH агента, построенный из зашифрованного журнала с цепочкой хешей. `sp audit verify` проверяет цепочку по шифротексту автономно и без ключа хранилища, добавляя доказательства действий, но не заменяя тесты идентификатора.

Назначьте последующую проверку по поведению парка. Ищите клиентов, которые еще отказывают из-за старого отпечатка, узлы с неожиданной идентичностью и скрипты, получившие параметры обхода. Уберите временное перекрытие. Сохраняйте отзыв, пока закрытый ключ может жить в резервной копии, образе или посторонней копии.

Предупреждение о ключе хоста должно оставаться редким и тревожным. Хорошая ротация не подавляет его. Она заранее организует переход доверия, чтобы ожидаемым клиентам не пришлось его игнорировать, а затем доказывает, что при возвращении старого идентификатора соединение все еще остановится.
