# Разрешения инфраструктуры для AI-агентов и безопасный apply

AI-агентам не следует выдавать постоянное разрешение, которое означает «применять изменения инфраструктуры». Им нужна узкая авторизация с ограниченным сроком действия: применить одно проверенное изменение к одному подтверждённому целевому аккаунту, имея процедуру восстановления, которую оператор прочитал и принял.

Я видел, как команды принимали Terraform-план за средство безопасности, а кнопку согласования за решение. По отдельности ни то ни другое недостаточно. План можно заново создать для другого аккаунта. Согласование может пережить изменившийся коммит. В заметке об откате может стоять слово «вернуть», хотя изменение уничтожает данные или меняет зависимость, которой уже не существует.

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

## Разрешение должно быть связано с действием, а не с должностью

Разрешения AI-агента на работу с инфраструктурой должны санкционировать конкретное изменение, а не категорию вроде «развёртывание», «Terraform» или «операции в production». Широкие категории кажутся удобными, потому что люди мыслят ролями. Но облачные API выполняют запросы, а у запросов есть цели, параметры, идентификаторы и последствия.

Полезное разрешение на apply как минимум связывает следующие факты:

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

Важно различать разрешение *рассчитать* изменение и разрешение *выполнить* его. Разрешите агенту изучать репозитории, обращаться к API только для чтения, запускать проверки и предлагать план. Не считайте молча эти действия доказательством того, что агент может менять production. Доступ для изучения обычно раскрывает рабочие сведения. Доступ на изменение влияет на счёт, доступность и иногда на объём данных, которые останутся после инцидента.

Роль с названием `InfrastructureDeployer` не является объектом согласования. Это деталь реализации. Если она позволяет применять любые изменения в любом аккаунте всякий раз, когда процесс её получает, то ваша процедура согласования зависит от того, вспомнят ли люди о правильном использовании роли. Агенты работают достаточно быстро, чтобы превратить такую проверку памяти в инцидент.

## Проверенный план должен быть тем же артефактом, который применяется

Проверенный план имеет смысл только тогда, когда команда apply использует именно его. Повторный запуск `terraform plan` после согласования создаёт новый артефакт, даже если файлы выглядят неизменными.

Документация команд Terraform ясно показывает это различие. `terraform plan -out=FILE` записывает файл плана, предназначенный для `terraform apply FILE`; `terraform apply` без сохранённого плана сначала создаёт новый план и только потом запрашивает подтверждение. Второй вариант подходит для интерактивной сессии человека. Для рабочего процесса агента, который заявляет о проверке изменений человеком, он неверен.

Используйте сохранённый план и преобразуйте его в JSON для проверки. Минимальная последовательность команд в shell выглядит так:

```sh
set -euo pipefail

git rev-parse HEAD \u003e evidence/source-revision.txt
terraform init -lockfile=readonly
terraform plan -out=evidence/change.tfplan
terraform show -json evidence/change.tfplan \u003e evidence/change.json
shasum -a 256 evidence/change.tfplan \u003e evidence/change.tfplan.sha256
terraform providers lock -platform=darwin_arm64
```

Дайджест имеет простой вид:

```text
f1f5f2...a94c  evidence/change.tfplan
```

В записи согласования нужно указать полный дайджест, а не просто прикрепить снимок экрана терминала. Затем runner apply проверяет его перед выполнением:

```sh
shasum -a 256 -c evidence/change.tfplan.sha256
terraform apply -input=false evidence/change.tfplan
```

Это предотвращает распространённую ошибку: агент открывает pull request, создаёт план, получает комментарий с согласованием, затем забирает последнюю ветку main и запускает новый план. Между этими действиями коллега мог объединить другое изменение. Поздний план может добавить удаление, сменить версию образа или указать на другой alias провайдера. Проверяющий согласовал вчерашнюю графовую структуру изменений, а не то, что runner видит сейчас.

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

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

## Именованная цель должна исходить из учётных данных, а не из метки

Имя цели `prod` почти ничего не доказывает. Репозитории копируют названия каталогов. Workspace расходятся. Переменные окружения остаются в shell. Агент может точно следовать конфигурации с названием production, будучи авторизованным в sandbox, или, что ещё хуже, в production-аккаунте, выбранном унаследованным профилем.

Пусть исполняющая сущность запросит у облачной управляющей плоскости сведения о себе перед планированием и ещё раз непосредственно перед apply. Сохраните результат в пакете доказательств и сравните его с одобренной целью.

Для действия на базе AWS проверка может быть такой простой:

```sh
aws sts get-caller-identity --output json \u003e evidence/caller-identity.json
cat evidence/caller-identity.json
```

Результат содержит аккаунт и principal:

```json
{
  \"UserId\": \"AROAXXXXX:apply-run\",
  \"Account\": \"123456789012\",
  \"Arn\": \"arn:aws:sts::123456789012:assumed-role/InfraApply/apply-run\"
}
```

Для Google Cloud отдельно запишите активный аккаунт и проект. Для Azure укажите ID подписки и tenant ID из контекста учётных данных. Не полагайтесь на отображаемое имя облачного аккаунта, поскольку люди могут создавать одинаковые имена. Числовые или глобально уникальные идентификаторы читать менее удобно, зато выполнять такую проверку безопаснее.

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

Добавьте в runner утверждение о цели, которое можно проверить автоматически. Этот пример откажется работать с неожиданным AWS-аккаунтом:

```sh
expected_account=\"123456789012\"
actual_account=\"$(aws sts get-caller-identity --query Account --output text)\"

if [ \"$actual_account\" != \"$expected_account\" ]; then
  printf 'refusing apply: expected account %s, got %s\\n' \\\n    \"$expected_account\" \"$actual_account\" \u003e\u00262
  exit 1
fi
```

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

## Расхождение состояния делает старые согласования небезопасными

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

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

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

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

- Изменилась исходная ревизия, набор переменных, ссылка на модуль или файл блокировки провайдеров.
- Workspace, backend, аккаунт, подписка, tenant, проект или регион отличаются от одобренных данных.
- Дайджест плана не совпадает с записью согласования.
- Блокировка состояния, обновление или предварительное условие сообщают о конфликте, меняющем предлагаемое действие.

Не заменяйте это правило утверждением «в pull request ничего не изменилось». Внешние источники данных, значения провайдера по умолчанию, текущее состояние и учётные данные находятся за пределами diff. У инфраструктурной работы слишком много входных данных, чтобы проверка только исходников несла всю ответственность за решение.

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

## Откат должен описывать состояние, которое можно восстановить

Понятный путь отката объясняет, как вернуть сервис в приемлемое состояние, кто может это сделать, какие данные окажутся под угрозой и когда нужно остановиться. «Запустите Terraform destroy» и «отмените коммит» редко соответствуют этому стандарту.

Инфраструктурные изменения относятся к разным классам восстановления. Если обращаться с ними одинаково, появляется ложная уверенность.

Обратимое изменение конфигурации, например правило security group или вес load balancer, может поддерживать прямое обратное применение из известной рабочей ревизии. Для замены группы инстансов перед откатом могут потребоваться проверки ёмкости. Миграция базы данных после записи данных может стать необратимой, поэтому восстановление может потребовать миграции вперёд, восстановления из проверенной резервной копии или feature flag, который остановит новые записи.

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

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

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

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

## Runner apply должен отказываться от неоднозначности

Runner, который выполняет изменение, должен самостоятельно проверять все факты согласования. Бот, способный получить сообщение в чате «можно», не умеет надёжно отличать подтверждённый план от случайной команды.

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

```json
{
  \"change_id\": \"infra-2025-041\",
  \"source_revision\": \"4ad7d2f\",
  \"plan_sha256\": \"f1f5f2...a94c\",
  \"target\": {
    \"cloud\": \"aws\",
    \"account_id\": \"123456789012\",
    \"region\": \"us-east-1\",
    \"workspace\": \"production\"
  },
  \"approved_by\": \"operator-id\",
  \"expires_at\": \"2025-04-18T15:30:00Z\",
  \"rollback_ref\": \"runbook: payments-api capacity revert\"
}
```

Агент может собрать этот запрос, но не должен записывать `approved_by` или продлевать `expires_at`. Сервис согласования добавляет эти факты после того, как человек увидит визуализированное изменение. Runner apply читает запись, заново вычисляет дайджест плана, проверяет исходную ревизию и целевую идентичность, затем помечает авторизацию использованной до отправки первого запроса на запись.

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

Ограничьте поверхность записи агента. Ему могут понадобиться HTTP-вызовы для API развёртывания или SSH-доступ к контролируемому runner, но не передавайте ему многоразовый облачный секрет в контексте. Sallyport хранит API- и SSH-учётные данные в зашифрованном хранилище, выполняет запрошенное действие и возвращает результат агенту. Это помогает предотвратить копирование учётных данных, но не делает расплывчатый запрос на apply безопасным.

## Согласование каждого вызова должно быть на опасной границе

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

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

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

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

## Неудачный apply требует другого решения, чем успешный

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

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

Используйте такую последовательность решений:

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

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

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

## Сделайте первый production-релиз намеренно скучным

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

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

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

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