Читать 8 мин

Как чек-лист готовности SSH меняет безопасность агентов

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

Как чек-лист готовности SSH меняет безопасность агентов

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

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

Это не делает SSH непригодным для агентов. Но факт, что «HTTP-канал работает», почти ничего не говорит о готовности добавить SSH. Проверка допуска должна охватывать пять отдельных свойств: надёжный разбор результата, подтверждённую идентичность хоста, команды, безопасные при повторе, подтверждения с описанием последствий и явную обработку неизвестного состояния удалённой стороны. Если хотя бы одного нет, второй канал не включайте.

Считайте SSH другой моделью выполнения

Готовность к SSH начинается с признания: удалённая оболочка не HTTP-транспорт с другим URL. HTTP API публикуют операции, выбранные сервером. SSH открывает интерпретатор, выбранный удалённой учётной записью, и просит клиента передать в него неструктурированную строку команды.

При HTTP-вызове только для чтения проверяющий часто может понять эффект по GET, хосту и пути. У ответа со статусом 404 тоже есть обычное место в протоколе, даже если приложение придаёт ему предметный смысл. В SSH test -f /srv/app/release && cat /srv/app/release может вернуть 1, потому что файла нет, а cat /srv/app/release - потому что доступ запрещён. Если ваш парсер сводит оба случая к «команда не выполнена», агент не сможет безопасно решить, что делать дальше.

Удалённое окружение тоже меняет смысл. sed -i по-разному работает в распространённых операционных системах. Оболочка входа может загружать стартовые файлы, которые печатают баннеры в stdout. PATH может найти обёртку вместо ожидаемой программы. Настройки локали меняют читаемые человеком диагностические сообщения. Псевдотерминал может смешать поведение для человека с поведением для парсера. В типичной схеме HTTP-запроса этих переменных нет.

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

Первым фильтром используйте неинтерактивную проверку:

/usr/bin/ssh \
  -o BatchMode=yes \
  -o StrictHostKeyChecking=yes \
  -o ConnectTimeout=10 \
  -T [email protected] \
  'umask 077; printf "%s\n" "{\"probe\":\"ssh-ready\",\"version\":1}"'

В документации OpenSSH сказано, что BatchMode=yes отключает запросы, включая запросы пароля и подтверждения ключа хоста. -T отключает выделение псевдотерминала. StrictHostKeyChecking=yes отклоняет неизвестные и изменившиеся ключи. Ожидаемый stdout - один JSON-объект, stderr пуст, статус удалённого завершения равен нулю. Проверяйте каждое отклонение отдельно. Система, которая не отличает неизвестный хост от неправильного JSON, этот фильтр не прошла.

Сделайте формат результата однозначным

Разбор результатов готов только тогда, когда ошибки транспорта, завершение на удалённой стороне, stdout и stderr остаются раздельными. Сведение их в одно текстовое поле создаёт опасную ложную уверенность.

RFC 4254 определяет stdout как данные канала, а stderr как расширенные данные канала. Там же определены сообщения exit-status и exit-signal, но формулировка важна: отправлять статус завершения рекомендуется, но не требуется, а клиент может его проигнорировать. OpenSSH добавляет к этому протоколу полезное локальное соглашение. Его команда ssh завершается статусом удалённой команды или 255 при ошибке.

Не превращайте это соглашение в универсальную истину. Удалённая программа сама может завершиться с 255, и это столкнётся со значением ошибки клиента OpenSSH на границе процесса. Сервер или библиотека могут не отправить exit-status. Сигнал не равен обычному ненулевому завершению. Канал может закрыться после вывода, но до того, как клиент получит конечный статус.

Внутренний результат должен выглядеть как типизированная запись, а не расшифровка сеанса:

{
  "phase": "completed",
  "transport": "ok",
  "host": "host.example",
  "host_key_fingerprint": "SHA256:verified-value",
  "exit_status": 0,
  "exit_signal": null,
  "stdout": "{\"state\":\"present\",\"release\":\"2026.07\"}\n",
  "stderr": "",
  "truncated": false,
  "started_at": "request timestamp",
  "finished_at": "result timestamp"
}

Допускайте null в exit_status. Пусть phase различает как минимум rejected, not-started, started, completed и unknown. Записывайте усечение как данные, никогда не обрезайте вывод молча и не передавайте агенту остаток как полный. Прикрепляйте проверенный отпечаток хоста, использованный именно в этом подключении, а не только имя хоста, которое запросил агент.

Затем проверьте матрицу исходов. Выполните команду, которая успешно завершается без вывода, другую, которая пишет в оба потока и завершается с 7, команду, остановленную сигналом, команду, превысившую срок, команду с недопустимым UTF-8, если ваш стек допускает байты, и команду, которая превышает каждый лимит вывода. Оборвите клиентское подключение, пока удалённая команда спит, затем изучите результат. Адаптер не должен придумывать exit_status: 0, выводить успех из stdout или называть тайм-аут «ошибкой», если не может доказать, выполнялась ли команда.

Композиции оболочки нужны отдельные проверки. POSIX говорит, что конвейер обычно возвращает статус последней команды, если не включён pipefail. Значит, generate | upload может сообщить об успехе, потому что upload принял пустой ввод после сбоя generate. Не полагайтесь на настройки интерактивной оболочки. Помещайте многошаговые операции в проверенные скрипты с явной обработкой ошибок и версией, затем вызывайте одну точку входа скрипта.

Проверяйте хосты до того, как агент сможет до них достучаться

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

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

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

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

Полезная проверка допуска сравнивает обнаруженные и утверждённые ключи, не меняя доверие:

ssh-keyscan -T 5 -t ed25519 host.example > observed.keys
ssh-keygen -lf observed.keys

Вывод отпечатка содержит длину ключа, отпечаток, метку хоста и тип ключа. Человек или доверенная служба учёта сравнивает отпечаток с независимо полученным значением. Только после совпадения автоматизация должна установить запись known-hosts. Само сканирование - это свидетельство для сравнения, а не основание для доверия.

Спланируйте ротацию до закрепления ключа. UpdateHostKeys в OpenSSH может узнавать дополнительные ключи после аутентификации сервера уже доверенным ключом, что помогает проводить постепенную ротацию. Будете ли вы использовать это расширение или распространять новый набор known-hosts, определите период перекрытия и аварийный путь. Изменившийся ключ должен остановить выполнение и дать отдельную ошибку идентичности. Он не должен запускать обычный повтор, автоматическое удаление старой записи или карточку подтверждения с просьбой в спешке принять необъяснённый отпечаток.

Требуйте идемпотентность на границе эффекта

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

Некоторые команды естественно повторяемы: чтение фиксированного файла, проверка состояния службы или создание каталога через mkdir -p с контролируемыми правами. Другим нужны ограничения. Добавление строки с echo ... >> file, отправка уведомления, создание пользователя со сгенерированным идентификатором, списание средств локальным инструментом и перезапуск службы небезопасны лишь потому, что команда оболочки короткая.

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

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

Небольшой скрипт развёртывания делает контракт наглядным:

#!/bin/sh
set -eu

op_id=$1
release=$2
state_dir=/var/lib/agent-ops
record="$state_dir/$op_id"

test -d "$state_dir" || exit 70
if test -f "$record"; then
  cat "$record"
  exit 0
fi

current=$(/usr/bin/readlink /srv/app/current || true)
if test "$current" = "/srv/app/releases/$release"; then
  /usr/bin/printf '{"operation":"%s","state":"already-current"}\n' "$op_id"
  exit 0
fi

test -d "/srv/app/releases/$release" || exit 66
/usr/bin/ln -sfn "/srv/app/releases/$release" /srv/app/current.new
/usr/bin/mv -f /srv/app/current.new /srv/app/current
/usr/bin/printf '{"operation":"%s","state":"changed","release":"%s"}\n' \
  "$op_id" "$release" > "$record.tmp"
/usr/bin/mv -f "$record.tmp" "$record"
cat "$record"

Этот пример не атомарен во всех случаях. Смена символьной ссылки и запись операции - два изменения файловой системы, поэтому сбой между ними оставляет пробел. Явная проверка current сверяет именно этот пробел. Для вашей операции нужна защита, связанная с её собственным эффектом, а не универсальная метка, скопированная из этого скрипта.

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

Показывайте утверждающему эффект, а не текст оболочки

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

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

Сравните systemctl restart api с подтверждением, где написано: production-хост api-03, удалённый пользователь deploy, перезапуск службы api, активные подключения могут оборваться, идентификатор операции rel-2026-07-24-04, версия команды restart-service/v2. Второе описание даёт проверяющему факты, которые можно сверить с изменением. Оно также выявляет недостающий контекст. Если агент не может сказать, какой хост или службу затронет, он не должен получать подтверждение.

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

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

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

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

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

Считайте неизвестное удалённое состояние полноценным результатом

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

Рассмотрим частую последовательность. Агент подключается, запускает скрипт, а скрипт заменяет конфигурационный файл. Начинается перезагрузка службы. В этот момент пропадает сеть. Клиент не получает ни статуса завершения, ни финального stdout. Локальный тайм-аут помечает вызов как ошибочный. Агент повторяет его. Второй запуск видит новый файл, отправляет ещё одну перезагрузку и, возможно, перезаписывает диагностическую запись первого запуска. Первый вызов выполнил реальную работу, хотя клиент так и не увидел завершения.

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

Определите конечный автомат до выпуска:

  1. not_started: соединение, идентичность, аутентификация или подтверждение не прошли до отправки.
  2. started: удалённая сторона приняла команду, но конечного результата ещё нет.
  3. completed: пришли конечный статус и весь ограниченный вывод.
  4. unknown: отправка могла произойти, но клиент потерял доказательство завершения.
  5. reconciled: независимый запрос позже установил итоговое состояние.

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

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

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

Ограничьте удалённую учётную запись до расширения набора команд

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

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

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

Ограничения authorized_keys OpenSSH могут уменьшить риск для учётных данных. В зависимости от схемы принудительная команда может направлять каждое соединение через диспетчер, а параметры могут отключать псевдотерминалы, перенаправление агента, X11 и портов. Конфигурация сервера тоже может ограничивать перенаправление. Используйте руководство для вашей версии сервера и проверяйте фактическую конфигурацию: один разрешающий include или блок match может отменить ваше предположение.

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

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

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

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

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

Знайте, какой агент отправил запрос
В первом запросе на подтверждение для процесса сразу указывается его полномочие подписи кода.

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

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

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

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

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

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

Допускайте SSH только после прохождения проверок

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

Запись о допуске должна содержать:

  1. Контракт канала с указанием оболочки, учётной записи, каталога, окружения, тайм-аутов, лимитов вывода и схемы результата.
  2. Проверенный учёт хостов с независимым источником отпечатков, покрытием промежуточных хостов и проверенным процессом ротации.
  3. Каталог команд с классификацией повторов и запросом сверки для каждого изменения.
  4. Спецификацию подтверждения, привязанную к точному хосту, учётной записи, версии команды, аргументам, привилегиям, тайм-ауту и идентификатору операции.
  5. Результаты внедрения сбоев, доказывающие неизвестные состояния, отказы, усечения, отзыв доступа и восстановление аудита.

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

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

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

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

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

Когда ИИ-агент готов к SSH-доступу?

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

Достаточно ли сначала включить SSH только для чтения?

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

Стоит ли автоматизированному SSH использовать StrictHostKeyChecking no?

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

Можно ли безопасно создать known_hosts с помощью ssh-keyscan?

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

Доказывает ли код выхода SSH 0, что изменение удалось?

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

Что означает код выхода SSH 255?

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

Когда SSH-команда идемпотентна?

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

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

Только если система может доказать, что команда не запускалась. Если отправка могла состояться, пометьте результат как неизвестный и выполните запрос сверки только для чтения, прежде чем рассматривать новое изменение.

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

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

Как командам тестировать SSH-доступ для агентов?

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

Sallyport

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

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