Как Terraform вообще логинится в облако
Сам по себе terraform - это просто движок, который читает HCL и дергает API. Чтобы дернуть API Yandex Cloud, ему нужен IAM-токен или эквивалент. Провайдер yandex умеет получать креды из нескольких источников, и порядок тут важен. Грубо говоря, провайдер смотрит:
- аргументы в блоке provider (token, service_account_key_file) - хардкодить туда секреты НЕ надо;
- переменные окружения - вот это рабочий вариант;
- метаданные ВМ (если Terraform крутится на инстансе с привязанным сервисным аккаунтом).
Код: Выделить всё
export YC_TOKEN="t1.9eud...."
export YC_CLOUD_ID="b1gxxxxxxxxxxxxxxxxx"
export YC_FOLDER_ID="b1gyyyyyyyyyyyyyyyyy"
terraform plan
Код: Выделить всё
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
version = ">= 0.130"
}
}
}
provider "yandex" {
# token, cloud_id, folder_id берутся из YC_TOKEN / YC_CLOUD_ID / YC_FOLDER_ID
}

Сервисный аккаунт: робот, который ходит вместо тебя
OAuth-токен человека - это нормально для ручного запуска, но для автоматики (cron, CI, другая инфраструктура) нужен не человек, а робот. В Yandex Cloud такой робот называется сервисный аккаунт. Создается он тем же Terraform:
Код: Выделить всё
resource "yandex_iam_service_account" "tf_runner" {
name = "tf-runner"
description = "service account for terraform automation"
folder_id = var.folder_id
}
Код: Выделить всё
resource "yandex_resourcemanager_folder_iam_member" "tf_runner_compute" {
folder_id = var.folder_id
role = "compute.editor"
member = "serviceAccount:${yandex_iam_service_account.tf_runner.id}"
}
Дальше классика - аккаунту нужен ключ, чтобы Terraform мог под ним аутентифицироваться. Авторизованный ключ (тот самый JSON) обычно создают через yc CLI или ресурс yandex_iam_service_account_key, а Terraform указывают на файл через переменную окружения:
Код: Выделить всё
# создаем ключ один раз через CLI
yc iam key create --service-account-name tf-runner --output key.json
export YC_SERVICE_ACCOUNT_KEY_FILE="/secure/path/key.json"
terraform apply
Статический ключ для Object Storage - отдельная история
Тут есть тонкость, на которой спотыкаются почти все. Object Storage в Yandex Cloud - это S3-совместимое хранилище, и оно ходит не по IAM-токену, а по паре access_key/secret_key в стиле AWS. Для этого есть отдельный ресурс:
Код: Выделить всё
resource "yandex_iam_service_account_static_access_key" "storage_key" {
service_account_id = yandex_iam_service_account.tf_runner.id
description = "static access key for object storage"
}
output "access_key" {
value = yandex_iam_service_account_static_access_key.storage_key.access_key
}
output "secret_key" {
value = yandex_iam_service_account_static_access_key.storage_key.secret_key
sensitive = true
}
Для совсем взрослого подхода у ресурса есть блок output_to_lockbox - тогда secret_key не светится в state, а сразу уезжает в секрет Lockbox с полями entry_for_access_key и entry_for_secret_key.
2026: OIDC-федерация - убиваем долгоживущие ключи в CI
Теперь главный апгрейд. Любой ключ-файл - это бомба замедленного действия: он не протухает сам, его надо ротировать, его могут украсть. Для CI вроде GitHub Actions или GitLab правильный путь в 2026 году - workload identity federation по протоколу OIDC. Идея гениальна в своей простоте: пайплайн получает короткоживущий OIDC-токен от своего провайдера (GitHub, GitLab), Yandex Cloud этому токену доверяет и обменивает его на временный IAM-токен. Никаких файлов ключей в секретах CI вообще.
Настраивается это двумя ресурсами. Сначала федерация - доверие к внешнему IdP:
Код: Выделить всё
resource "yandex_iam_workload_identity_oidc_federation" "github" {
name = "github-actions-federation"
folder_id = var.folder_id
disabled = false
audiences = ["https://github.com/my-org"]
issuer = "https://token.actions.githubusercontent.com"
jwks_url = "https://token.actions.githubusercontent.com/.well-known/jwks"
}
Дальше привязываем конкретный внешний субъект (репозиторий, ветку, окружение) к сервисному аккаунту через федеративную привязку:
Код: Выделить всё
resource "yandex_iam_workload_identity_federated_credential" "github_main" {
service_account_id = yandex_iam_service_account.tf_runner.id
federation_id = yandex_iam_workload_identity_oidc_federation.github.id
external_subject_id = "repo:my-org/my-repo:ref:refs/heads/main"
}
Кстати, все ровно это работает и в OpenTofu - открытом форке Terraform под лицензией MPL 2.0 (на середину 2026 актуальна ветка 1.x). Провайдер yandex один и тот же, синтаксис HCL идентичен, так что выбор движка на тему секретов не влияет.
Типичные грабли
- Секрет в коде. token = "t1...." или путь к key.json прямо в provider. Закоммитил - считай, утек. Чисти историю git и отзывай ключ.
- Слишком жирная роль. admin на folder "чтобы работало". Работает, да. И весь твой CI теперь может удалить вообще все. Дай compute.editor, vpc.admin - что реально нужно.
- Забыл sensitive у secret_key. Terraform печатает секрет в plan/apply, а лог CI хранится публично. Прощай, секрет.
- Незащищенный terraform state. Статические ключи и пароли лежат в state открытым текстом. Локальный state в репозитории - это утечка.
- Слишком широкий external_subject_id в OIDC. Если разрешишь любой репозиторий org - чужой форк сможет угнать твой сервисный аккаунт. Привязывай к конкретной ветке/окружению.
Повтори руками, без этого в голове не уляжется:
- Подними YC_TOKEN/YC_CLOUD_ID/YC_FOLDER_ID через export и сделай terraform plan с пустым блоком provider. Убедись, что секретов в коде ноль.
- Опиши yandex_iam_service_account и выдай ему ОДНУ узкую роль через yandex_resourcemanager_folder_iam_member.
- Создай yandex_iam_service_account_static_access_key, выведи secret_key с sensitive = true и убедись, что в логе apply его не видно, а в state - видно (открой state и ужаснись).
- Опиши федерацию yandex_iam_workload_identity_oidc_federation для GitHub Actions и привяжи к ней federated_credential на ветку main. Сравни, насколько это спокойнее, чем хранить key.json в секретах репозитория.
- Какие переменные окружения подхватывает провайдер yandex для аутентификации и зачем держать секреты именно там, а не в HCL?
- Чем статический ключ для Object Storage отличается от авторизованного ключа сервисного аккаунта и почему secret_key надо помечать sensitive?
- Какие два ресурса нужны, чтобы настроить OIDC-федерацию для GitHub Actions, и что задает external_subject_id?
- Почему долгоживущий key.json в секретах CI хуже, чем workload identity federation?
Аутентификация - это фундамент безопасности всей твоей инфраструктуры как код. Секреты провайдера живут в переменных окружения и секрет-хранилищах, а не в git. Для автоматики заводи сервисный аккаунт и давай ему минимум прав. Object Storage ходит по отдельному статическому ключу, который утекает в terraform state, поэтому state шифруй. А для CI в 2026 году забудь про файлы ключей: OIDC-федерация (yandex_iam_workload_identity_oidc_federation плюс federated_credential) дает короткоживущие токены без единого долгоживущего секрета. Меньше ключей - меньше поводов для бессонницы.