Читать 6 мин

Проверка хоста SSH для агентов, работающих с рабочей средой

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

Проверка хоста SSH для агентов, работающих с рабочей средой

Автономный агент программирования не должен сам решать, что новый SSH-сервер заслуживает доверия. Он может изучить репозиторий, подготовить развёртывание и запросить доступ, но должен остановиться, если удалённая идентичность не совпадает с записью, созданной человеком или доверенным процессом подготовки инфраструктуры.

SSH ставит два разных вопроса безопасности: «Подключился ли я к нужному серверу?» и «Что может делать эта учётная запись на сервере?» Команды часто смешивают эти вопросы, потому что в обоих случаях используются открытые ключи. Такая ошибка превращает соединение не с тем хостом сначала в утечку учётных данных, а затем чрезмерно привилегированный аккаунт в инцидент на рабочей среде.

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

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

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

Ключ хоста принадлежит серверу, а не администратору и не агенту. Отпечаток - это короткое представление открытого ключа хоста, обычно в формате SHA256. Если deploy.example.internal обычно предъявляет один отпечаток, а затем внезапно предъявляет другой, у клиента есть свидетельство изменения. Причиной может быть законная пересборка. Но это также может быть ошибка DNS, повторно использованный адрес, ошибка в bastion-хосте или активная попытка перехвата.

Представьте, что агенту программирования поручили выполнить миграцию на db-prod.internal. Конфигурация SSH разрешает это имя в адрес. Злоумышленник, способный повлиять на DNS, маршрут через прокси или устаревшую запись инвентаризации, может направить соединение на подконтрольный сервер. Если клиент примет незнакомый ключ хоста, этот сервер сможет запросить аутентификацию пользователя. Закрытый ключ SSH на стороне клиента может не покинуть клиент, но доступ агента часто включает пароли, операции подписи сертификатов, перенаправление или команды, раскрывающие полезную информацию после входа. И главное, агент теперь может выполнить запланированную команду не на той машине.

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

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

Доверие при первом использовании плохо подходит для работы без присмотра

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

OpenSSH описывает этот выбор в руководстве ssh_config, в разделе StrictHostKeyChecking. При значении yes клиент никогда не добавляет неизвестные ключи хостов автоматически и отклоняет изменившийся ключ. При accept-new он автоматически записывает неизвестный ключ, но всё равно отклоняет изменившийся. При no или off он принимает больше ситуаций, требующих внимания оператора.

accept-new часто называют разумным компромиссом. Он действительно уменьшает количество остановок, когда хосты создаются часто. Но он также даёт сетевому маршруту право создать исходную запись идентичности. Для агента, способного менять инфраструктуру, это неправильная сторона, принимающая решение.

Для рабочих и тестовых конечных точек, которыми управляет агент, используйте StrictHostKeyChecking=yes. Если соединение не удалось из-за неизвестного хоста, передайте запрос агента человеку, который сравнит показанный отпечаток с источником вне этого SSH-соединения. Подойдут консоль облачного провайдера, подписанная запись инвентаризации, физическая консоль или существующий канал управления.

Не устраняйте паузу, добавляя StrictHostKeyChecking=no в глобальный файл конфигурации. Такая строка обычно переживает временный инцидент, ради которого её добавили, а затем незаметно применяется к хостам, для которых никто не хотел ослаблять проверку.

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

Отпечаток полезен только при независимом источнике

Отпечаток, скопированный с конечной точки, которую вы пытаетесь проверить, почти ничего не доказывает. ssh-keyscan удобен для сбора открытых ключей хостов, и именно это удобство создаёт знакомую ловушку: оператор запускает команду для имени хоста, вставляет результат в known_hosts и считает хост проверенным. Если DNS или маршрутизация уже указывают на злоумышленника, оператор закрепил ключ злоумышленника.

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

ssh-keyscan -t ed25519 app-prod.internal > /tmp/app-prod.hostkey
ssh-keygen -lf /tmp/app-prod.hostkey -E sha256

Вторая команда выводит результат примерно в таком виде:

256 SHA256:exampleFingerprintMaterial app-prod.internal (ED25519)

Сравните значение SHA256: посимвольно с независимо полученным значением. Также проверьте алгоритм. Если в записи указан ED25519, а собранный результат относится к RSA, остановитесь и разберитесь, а не считайте результаты взаимозаменяемыми.

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

app-prod.internal ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicHostKeyMaterial
[192.0.2.44]:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicHostKeyMaterial

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

Записи DNS SSHFP могут помочь, если проверка DNSSEC правильно настроена от начала до конца. Но они не спасут конфигурацию агента, принимающую обычные неподписанные ответы DNS как доказательство. Считайте SSHFP дополнительным проверенным каналом публикации, а не декоративной записью, которая делает слепое первое подключение безопасным.

Закрепите маршрут и запись хоста в конфигурации SSH

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

Host app-production
    HostName app-prod.internal
    User agent_release
    Port 22
    UserKnownHostsFile ~/.ssh/agent_known_hosts
    GlobalKnownHostsFile /dev/null
    StrictHostKeyChecking yes
    UpdateHostKeys no
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    ForwardAgent no
    PermitLocalCommand no

Этот фрагмент предотвращает несколько обычных ошибок. UserKnownHostsFile не даёт неожиданно зависеть от записей хостов, накопленных человеком. GlobalKnownHostsFile /dev/null не позволяет неуправляемому общесистемному файлу незаметно расширить доверие. UpdateHostKeys no запрещает автоматическим обновлениям ключей менять набор закреплённых ключей во время работы агента. ForwardAgent no не даёт удалённой машине использовать перенаправленный агент аутентификации для доступа куда-либо ещё.

Руководство OpenSSH ssh_config объясняет, что UpdateHostKeys поддерживает ротацию ключей хоста, когда хост доказывает владение доверенным ключом. Это может быть полезно для интерактивных парков машин с дисциплинированным управлением хостами. Для автономного агента автоматическое изменение конфигурации усложняет разбор инцидента. Человек должен одобрить изменение идентичности рабочей системы и намеренно обновить управляемый файл хостов.

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

Проверьте действующую конфигурацию до того, как агент получит доступ к любым учётным данным:

ssh -G app-production | grep -E '^(hostname|user|stricthostkeychecking|userknownhostsfile|forwardagent|updatehostkeys) '

Ожидайте значения примерно такого вида:

hostname app-prod.internal
user agent_release
stricthostkeychecking yes
userknownhostsfile /Users/operator/.ssh/agent_known_hosts
forwardagent no
updatehostkeys no

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

Ограничения аккаунта сдерживают ущерб после правильного подключения

Избегайте разрастания правил SSH
Фиксированная цепочка решений охватывает блокировку хранилища, авторизацию сессии и ключи, для которых нужно подтверждение каждого использования.

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

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

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

Если задача узкая, ограничьте авторизованный открытый ключ SSH принудительной командой. В authorized_keys запись на сервере может привязать эти учётные данные к оболочке:

command="/usr/local/sbin/agent-release-wrapper",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleAgentPublicKey agent-release

Оболочка должна разбирать небольшой набор аргументов, отклонять метасимволы оболочки, записывать запрошенную операцию и запускать фиксированные программы по фиксированным путям. Не пишите оболочку, которая принимает произвольную строку из SSH_ORIGINAL_COMMAND и передаёт её в sh -c. Это лишь прячет неограниченный удалённый доступ к оболочке за именем функции.

Принудительные команды подходят не для каждой задачи обслуживания. Когда агенту действительно нужна оболочка для расследования, используйте отдельный аккаунт расследования с правами только на чтение и без повышения привилегий. Для изменений создайте отдельный явно проверяемый маршрут. Смешивание диагностики и изменения в одном широком аккаунте даёт агенту слишком много возможностей превратить неточный вывод в необратимое действие.

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

При проверке команды нужно видеть её эффект, а не расплывчатое намерение

Подтверждение команды полезно только тогда, когда человек видит достаточно контекста для оценки последствий. «Развернуть релиз» - это намерение. sudo systemctl restart payments-api на app-production от имени agent_release - действие, которое можно оценить.

Проверяйте точный псевдоним назначения, удалённый аккаунт, буквальную команду, аргументы и рабочий каталог. Также выясняйте, запускает ли команда оболочку, раскрывает ли переменные, читает ли удалённый скрипт, скачивает ли содержимое или использует sudo. Именно эти детали определяют, может ли внешне безобидный запрос выйти далеко за пределы своего описания.

Разумная запись подтверждения удалённого действия выглядит так:

Target: app-production (app-prod.internal)
Account: agent_release
Command: /usr/local/sbin/agent-release-wrapper activate 2025.04.17-rc2
Reason: activate the approved release after smoke tests

Сравните её с таким запросом:

ssh app-production "curl $URL | sudo sh"

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

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

Sallyport может хранить SSH-учётные данные в зашифрованном хранилище и требовать подтверждения каждого использования выбранной учётной записи, а SSH-канал при этом выполняется через sp-ssh, не раскрывая учётные данные агенту. Но закрепить хост и решить, заслуживает ли видимая команда подтверждения, всё равно должны вы.

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

Отзывайте опасную сессию агента
Журнал сессий позволяет сразу отозвать подтверждённый запуск агента, если его запросы начинают выглядеть подозрительно.

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

Представьте агента выпуска, настроенного с StrictHostKeyChecking=accept-new, общей учётной записью развёртывания и запросом подтверждения, в котором написано только «выполнить deploy». Запись DNS ненадолго указывает на заменённую машину, пока инвентаризация ещё не обновлена. Агент видит незнакомый хост, записывает его ключ, входит на заменённую машину через общий аккаунт и запускает оболочку развёртывания. У оболочки широкие права на запись, потому что общий аккаунт также используется для аварийного ремонта.

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

Каждый контроль прерывает свой участок цепочки:

  1. Предварительно закреплённая запись хоста отклоняет заменённую машину, пока оператор не проверит её.
  2. Отдельный аккаунт ограничивает ущерб, если утверждённая цель всё же неверна по причине, которую идентичность хоста не позволяет обнаружить.
  3. Запись подтверждения с полной командой даёт проверяющему возможность заметить неожиданную цель или действие с повышенными правами.
  4. Журнал сессии и вызова позволяет после сбоя восстановить запрос, подтверждение, цель и результат.

Не заменяйте первый контроль последующими. Проверяющий может пропустить несоответствие цели. В ограничениях аккаунта может остаться незамеченная привилегия. Журнал объясняет ущерб задним числом. Закрепление идентичности хоста предотвращает класс ошибок подключения ещё до того, как в дело вступит удалённая учётная запись.

Для смены ключа хоста нужна процедура, а не кнопка исключения

Подтвердите запуск агента один раз
Для нового процесса агента требуется одно подтверждение, после чего его SSH-вызовы могут выполняться в рамках сессии.

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

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

Запись о ротации должна быть небольшой, но конкретной: имя хоста, адрес, если он важен, прежний отпечаток, новый отпечаток, причина, проверяющий и срок окончания периода с двумя ключами. Тикета с текстом «SSH изменился» недостаточно, чтобы отличить плановую работу от попытки научить клиентов доверять самозванцу.

Никогда не поручайте агенту автоматически запускать ssh-keygen -R hostname и подключаться снова. Удаление старой записи стирает сигнал о несоответствии до того, как кто-либо установит причину. Оператор может использовать эту команду после проверки как часть согласованного обновления, но она не должна быть логикой восстановления внутри рабочего процесса агента.

Записи аудита должны позволять восстановить решение

После сбоя или неожиданного результата SSH-действия полезная запись должна отвечать на четыре вопроса: какой процесс агента его запросил, какая идентичность его одобрила, какие точные соединение и команда использовались и какой результат был получен. Фраза «агент развернул сервис» не отвечает ни на один из них.

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

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

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

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

Вопросы и ответы

Что такое отпечаток хоста SSH?

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

Доказывает ли SSH-ключ, что удалённый сервер безопасен?

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

Какое значение StrictHostKeyChecking должен использовать автономный агент?

Для автономного агента самым безопасным практическим значением по умолчанию будет StrictHostKeyChecking=yes. Оно отклоняет неизвестные хосты и изменившиеся ключи, поэтому развёртывание останавливается, пока человек не проверит ситуацию. Такая пауза дешевле, чем незаметно отправить учётные данные не тому узлу.

Можно ли использовать ssh-keyscan для проверки рабочего хоста?

Нет. ssh-keyscan запрашивает ключ хоста у сетевой конечной точки, но не доказывает, кто ею управляет. Используйте команду только после сравнения результата с отпечатком, полученным по отдельному доверенному каналу, например через консоль или подписанную запись инфраструктуры.

Что делать, если отпечаток SSH-сервера изменился?

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

Зачем каждому агенту программирования нужна отдельная SSH-учётная запись?

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

Что проверить перед подтверждением SSH-команды агента?

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

Защищает ли проверка хоста от разрушительных SSH-команд?

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

Как добавлять новые SSH-хосты без доверия при первом использовании?

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

Может ли шлюз действий заменить ограничения SSH-аккаунта?

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

Sallyport

Sallyport выполняет API-вызовы и SSH-команды за вашего ИИ-агента. Ключи остаются в локальном хранилище на вашем Mac; вы подтверждаете каждый запуск, и каждое действие попадает в запечатанный журнал.

© 2026 Sallyport · Открытый код по лицензии Apache-2.0 · Oleg Sotnikov