Читать 6 мин

Доступ через бастион для ИИ-агентов программирования: правильная настройка

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

Доступ через бастион для ИИ-агентов программирования: правильная настройка

Бастион-узел может дать ИИ-агентам для программирования более безопасный путь во внутреннюю инфраструктуру, но только если считать его частью границы доступа. Jump box, который лишь указан в SSH-конфигурации, не является контролем. Агент может обойти его, воспользоваться чужими учетными данными, открыть туннель или оставить запись аудита, доказывающую только то, что кто-то подключился к jump host.

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

Бастион меняет маршрут, но не полномочия агента

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

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

Но он не отвечает на четыре более важных для автономного клиента вопроса:

  • Может ли агент напрямую попасть к цели другим маршрутом?
  • Есть ли у учетной записи цели более широкие права, чем нужно для запрошенной задачи?
  • Может ли агент создать туннель и получить непроверенный путь?
  • Сможете ли вы позже определить конечную машину и удаленную команду?

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

Я видел, как команды создавали тщательно обслуживаемый jump box, а затем разрешали широкой подсети CI исходящие SSH-подключения «для устранения неполадок». Агенту в этой подсети не нужны ни эксплойт, ни хитрый прием. Он может напрямую обратиться к конечному адресу. Архитектура не сработала, потому что сеть позволяла обход, а не потому, что SSH вел себя неправильно.

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

ProxyJump не показывает конечную команду

Обычный SSH jump host, как правило, не может увидеть команду, которую агент запускает на конечном узле. Именно это упускают команды, обещающие «полные журналы команд с бастиона».

OpenSSH описывает ProxyJump в ssh_config(5) как сначала подключение к jump host с последующей настройкой пересылки к конечному назначению. В обычном OpenSSH клиент фактически просит jump host открыть TCP-соединение с целью, после чего проводит через этот поток сквозную SSH-сессию. Конечный SSH-транспорт остается зашифрованным между клиентом и целью.

Бастион часто может знать, что он подключился к 10.42.8.19:22. Он может записать использованную на бастионе учетную запись, источник, время и назначение запроса на пересылку. Но он не узнает автоматически, выполнялась ли внутри зашифрованной сессии команда systemctl restart api, cat /etc/shadow или интерактивная оболочка.

Здесь есть три разных наблюдения, которые часто ошибочно объединяют:

  1. Запрошенное назначение это то, что передал агент, например prod-api-01.
  2. Назначение подключения это адрес и порт, которые фактически открыл прокси.
  3. Назначение выполнения это идентификатор сервера и учетная запись, принявшие итоговую SSH-аутентификацию.

Они могут различаться. После изменения DNS псевдоним может разрешаться иначе. Скомпрометированная или устаревшая запись known hosts делает подключение подозрительным. Правило прокси может направить понятный псевдоним на неожиданный адрес. Запись только prod-api-01 не доказывает, какой сервер выполнил команду.

То же разделение относится к командам. Шлюз на стороне клиента может записать запрошенную команду до передачи данных. Оболочка на конечном узле может записать команду, которую OpenSSH передает для exec-запроса. Бастион может записать TCP-цель. Сопоставляйте эти наблюдения по идентификатору сессии и временному окну. Не делайте вид, что один слой видит все сразу.

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

Сетевые правила должны сделать одобренный путь неизбежным

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

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

Бастиону нужен короткий список разрешенных исходящих подключений. Если он администрирует узлы в двух закрытых диапазонах по TCP-порту 22, разрешите только эти диапазоны и этот порт. Не давайте ему произвольный исходящий доступ, называя такой узел бастионом. Точка входа, способная достичь любого сервиса, database и публичного endpoint, превращается в общий ретранслятор при злоупотреблении учетной записью агента.

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

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

nc -vz bastion.internal.example 22
nc -vz 10.42.8.19 22
ssh -o ConnectTimeout=5 prod-api-01 true

Первое подключение должно работать. Прямое подключение к конечному узлу должно завершаться ошибкой. Команда SSH должна работать только потому, что настроенный маршрут использует бастион. Если nc отсутствует, примените обычную для вашей среды команду проверки TCP. Смысл в проверке сетевого пути без возможности скрыть прямой маршрут настройками SSH.

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

Агенту нужна отзываемая идентичность

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

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

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

Сертификаты не делают широкую учетную запись узкой. Сертификат для root остается root-учетными данными до истечения срока. Сертификаты также не доказывают намерение команды. Считайте их механизмом выдачи и ограничения срока действия.

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

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

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

Записывайте заявление о выполнении, факт маршрута и результат

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

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

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

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

На конечном узле записывайте стабильный идентификатор узла, прошедшую аутентификацию учетную запись, отпечаток аутентификации или серийный номер сертификата, команду, если сервер получил exec-запрос, код завершения и важные локальные журналы сервиса. Это свидетельство выполнения. Конечный узел является единственным обычным SSH endpoint, который может видеть удаленную команду без нарушения сквозной модели SSH.

Используйте форму события, в которой отсутствие свидетельств заметно:

{
  "run_id": "run_7c31",
  "requested": {
    "host": "prod-api-01",
    "user": "deploy",
    "command": "sudo systemctl restart api",
    "tty": false
  },
  "route": {
    "bastion": "bastion.internal.example",
    "destination_ip": "10.42.8.19",
    "destination_port": 22
  },
  "final_host": {
    "host_fingerprint": "SHA256:example",
    "authenticated_user": "deploy",
    "command_observed": true,
    "exit_status": 0
  }
}

В примере намеренно разделены requested.command и наблюдение на конечном узле. Если сессия была интерактивной, установите command_observed в false и укажите причину. Пустое поле без объяснения заставляет предположить, что инструмент все записал.

Для обычного выполнения команд sshd_config(5) описывает ForceCommand, который принудительно задает команду для подходящей учетной записи или блока match. Оболочка на стороне цели может прочитать SSH_ORIGINAL_COMMAND для exec-запроса, проверить разрешенную операцию и записать событие аудита до запуска одобренной программы. Здесь нужна аккуратная работа с оболочкой. Не передавайте ненадежную строку команды через eval и не предполагайте, что SSH_ORIGINAL_COMMAND существует для интерактивной оболочки.

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

Сделайте конфигурацию SSH проверяемой

Сохраняйте псевдонимы узлов для людей, но перед передачей конфигурации агенту проверяйте, что именно будет делать OpenSSH. ssh -G выводит эффективную конфигурацию клиента после обработки OpenSSH совпадающих записей Host.

Этот пример направляет одну именованную цель через именованный бастион и отключает часто удивляющие команды настройки:

Host agent-bastion
    HostName bastion.internal.example
    User agent-gateway
    IdentityFile ~/.ssh/agent_gateway
    IdentitiesOnly yes

Host prod-api-01
    HostName 10.42.8.19
    User deploy
    ProxyJump agent-bastion
    StrictHostKeyChecking yes
    UserKnownHostsFile ~/.ssh/agent_known_hosts
    ForwardAgent no
    RequestTTY no

Проверьте отрендеренные параметры:

ssh -G prod-api-01 | grep -E '^(hostname|user|proxyjump|forwardagent|requesttty) '

Вывод должен иметь такую форму:

user deploy
hostname 10.42.8.19
requesttty no
forwardagent no
proxyjump agent-bastion

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

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

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

Перенаправление и оболочки создают пути в обход проверки

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

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

Локальное перенаправление позволяет клиенту открыть локальный порт, ведущий к внутреннему сервису через SSH. Удаленное перенаправление позволяет удаленной конечной точке открыть путь обратно к стороне клиента. Динамическое перенаправление создает SOCKS-прокси. Перенаправление агента делает учетные данные доступными на другом узле. Каждая возможность может быть оправдана для человека-администратора, но каждая ослабляет утверждение, что бастион остается единственным контролируемым маршрутом.

OpenSSH предоставляет серверные ограничения, которые нужно задавать на конечной цели или учетной записи бастиона, а не только в конфигурации клиента агента. sshd_config(5) описывает DisableForwarding, а authorized_keys поддерживает параметры no-port-forwarding, no-agent-forwarding, no-X11-forwarding и no-pty. Используйте возможности своей версии OpenSSH и проверяйте их настоящей попыткой подключения.

Ограниченная учетная запись может использовать запись авторизованного ключа такой формы:

no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... agent-run

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

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

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

Ограничения цели лучше фильтров строк команд

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

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

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

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

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

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

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

Реагирование на инцидент начинается с остановки действующего маршрута

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

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

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

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

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

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

Делает ли бастион-узел ИИ-агента безопасным сам по себе?

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

Может ли jump host видеть каждую команду, которую выполняет агент?

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

Что должно входить в запись аудита SSH-доступа агента?

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

Как не дать агенту обойти ProxyJump?

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

Должен ли ИИ-агент для программирования иметь закрытый SSH-ключ?

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

Лучше ли SSH-сертификаты для агентов, чем статические ключи?

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

Какие возможности SSH следует отключить для автономных агентов?

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

Почему агентам следует избегать интерактивных SSH-оболочек?

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

Где лучше всего фиксировать удаленную команду?

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

Что делать, если агент установил подозрительное SSH-соединение?

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

Sallyport

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

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