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

Бастион-узел может дать ИИ-агентам для программирования более безопасный путь во внутреннюю инфраструктуру, но только если считать его частью границы доступа. 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, где работает агент:

```sh
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-агента должна точно объяснять значение каждого поля. Так система аудита не будет делать утверждений, которые не может подтвердить.

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

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

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

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

```json
{
  "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`.

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

```sshconfig
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
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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