Как соединить агентов Google, AWS и Azure через A2A без единого долгоживущего ключа. Практический гайд с кодом и разбором подводных камней. Читайте и внедряйте!
Представьте: у вас есть агент на Google Cloud, и его нужно связать с агентом на AWS или Azure. Первая мысль — создать сервисный ключ, положить в секретницу и забыть. Но так вы получаете вечный долгоживущий секрет, который нужно ротировать, ограничивать, аудировать и в итоге объяснять, почему в проде лежит статический ключ. Есть другой путь — федерация удостоверений через OIDC. Он не сложнее, просто требует решений на раннем этапе. В этой статье разберём, как соединить три облака без единого долгоживущего секрета, используя протокол A2A и временные токены.
Вся архитектура сводится к одному вопросу: какой рантайм может выпускать OIDC-токены для произвольной аудитории? Если может — он может федеративно доверять другим облакам. Если нет — придётся хранить секрет. Сравним три облака:
Cloud Run выигрывает, потому что его метаданные позволяют получить токен для любой аудитории — именно то, что нужно для доверия других облаков. Выбор координатора на Cloud Run даёт два следствия:
gcloud auth print-identity-token --audiences=... откажется, требуя сервисный аккаунт.Каждый лега выглядит по-разному:
roles/run.invoker.AssumeRoleWithWebIdentity, получаем временные ключи, подписываем запрос SigV4.Разные формы: два bearer-токена и подпись запроса. Чтобы унифицировать, используем интерфейс httpx.Auth. Для httpx bearer-заголовок и подпись тела — одинаковые объекты. Все SDK принимают httpx.AsyncClient, поэтому credential присоединяется один раз и передаётся через клиент:
auth = credentials_for(peer, endpoint) # httpx.Auth или None
client = load_client(stack, endpoint, auth=auth)Стройте этот шов до второго облака, а не после третьего. Иначе получите три стиля обработки ошибок и три места кэширования токенов.
Аудиторию выбирает вызывающий. Если проверять только audience, вы докажете, что кто-то в IdP выпустил токен, но не что это ваш сервис. Привязывайте subject — и привязывайте к числовому ID, а не к email, потому что email можно освободить и перепривязать к другому пользователю.
AWS федеративно работает с accounts.google.com из коробки. Если создать явный OIDC-провайдер для него — сломаете с InvalidIdentityToken. В Azure нужно явно создавать Federated Credential. Одна и та же задача, противоположные требования, и ни одна ошибка не подскажет, на каком вы правиле.
accounts.google.com:oaud — это audience токена, а accounts.google.com:aud — azp, то есть число. Если в :aud подставить строку audience — условие никогда не совпадёт, и отказ не будет об этом упоминать.
Метаданные GCP требуют format=full. Без него Google обрезает claims, включая email, и условия, читающие этот claim, молча перестают работать.
От STS: InvalidIdentityToken — токен не прошёл валидацию, проблема с провайдером. AccessDenied — токен валиден, но условия не совпали, проблема с политикой. Это разные проблемы, и их нужно различать.
Клонируйте репозиторий и установите зависимости:
git clone https://github.com/xbill9/multicloud-adk-a2a-currency
cd multicloud-adk-a2a-currency
uv pip install --system "a2a-sdk[http-server]" google-adk agent-framework-a2a agent-framework-core pydantic httpx uvicorn pytest pytest-asyncio
uv pip install --system -e .Запустите три агента и задайте вопрос:
./infra/run_mesh.sh start
python3 -m coordinator.cli 100 USD EUR JPYВывод:
participants: gcp, aws, azure
100 USD = 92 EUR @ 0.92 [3/3 clouds, agreed]
gcp 92 (164ms) aws 92 (25ms) azure 92 (12ms)Демо с ошибками:
./infra/demo.shЧетыре акта: три облака отвечают, матрица совместимости 3×3, облако уходит офлайн, облако врёт. Последние два — суть: что угодно может показать три зелёные галочки.
Развёртывание — это команды, а не runbook. Каждый идентификатор живёт в одном месте — в скрипте, который его создал. Остальные скрипты читают его оттуда. Это проверено: я полностью снёс и пересоздал инфраструктуру с нуля.
AWS вернул другой ARN, Azure — другой client ID, Container App — другой FQDN. Ничего не правилось вручную. wire прочитал всё заново, и меш снова ответил: 100 USD = 92 EUR @ 0.92 [3/3 clouds, agreed].
Полная проверка прошла на инфраструктуре, которой час назад не существовало: три консенсуса и восемь проверок авторизации — каждая лега отвечает со своим credential, каждая лега отклонена без него, неаутентифицированный запрос отклонён, запрос с правильной идентичностью, но неправильной аудиторией отклонён.
Пересоздание также выявило два бага, которые не видны при обычном деплое:
None, и первый деплой умирал молча.FlagMustBeSetForRestore.Вывод: пересоздавайте с нуля хотя бы раз, прежде чем называть систему воспроизводимой.
Вся система простаивает с нулём реплик. Но первый вызов в легу платит холодный старт: холодная Azure-лега — 27.8 секунд против 0.5 секунд в тёплом состоянии. Если смешать эти режимы в одной таблице, все выводы будут ложными. Указывайте, какой режим измеряли.
httpx.Auth) — вызывающие не знают, какой из трёх механизмов используется.convert()) — облако это реализация, а не ветка.Последний пункт — самый важный. Меш берёт медиану по трём облакам и деградирует намеренно. Потеря одного облака — остальные два достигают кворума, и запуск завершается с кодом 0. Если тестировать авторизацию, удалив credential одной леги из трёхоблачного запуска, он всё равно выйдет с 0. Это читается как «отказа не было», а на самом деле «отказ был поглощён». Поэтому каждую легу проверяем отдельно: восемь проверок — каждая лега отвечает со своим credential, каждая отклонена без него, неаутентифицированный запрос отклонён, и запрос с правильной идентичностью, но неправильной аудиторией отклонён.
Прямо сейчас: склонируйте репозиторий, запустите локальный меш и посмотрите, как три облака отвечают согласованно. Затем разверните хотя бы одну легу в облаке и проверьте, как работает федерация. И главное — пересоздайте всё с нуля, чтобы убедиться, что ваша система действительно воспроизводима. Это займёт меньше часа, но сэкономит дни отладки в будущем.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →