linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-production-09-oidc

lesson ── terraform-production ── ~15 мин ── 5 шагов

OIDC, IAM-роль для CI без access-keys

На реальном AWS GitHub Actions ходит через OIDC: workflow получает JWT, STS обменивает на временные credentials, никаких долгих access-keys. LocalStack Community OIDC не поддерживает, поэтому эмулируем, создаём IAM-роль с trust policy на «федерати», assume'им её через AssumeRole, и смотрим, как это выглядит. Концепция та же, что и с реальным AWS.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Создай IAM OIDC-провайдера (эмуляция)

    В реальном AWS это команда:

    aws iam create-open-id-connect-provider \
      --url https://token.actions.githubusercontent.com \
      --client-id-list sts.amazonaws.com

    На LocalStack, то же самое:

    bash
    cd /home/student/tf-oidc
    aws --endpoint-url=http://localstack:4566 \
      iam create-open-id-connect-provider \
      --url https://token.actions.githubusercontent.com \
      --client-id-list sts.amazonaws.com \
      --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 2>&1 || true
    aws --endpoint-url=http://localstack:4566 \
      iam list-open-id-connect-providers

    LocalStack может вернуть ошибку «not implemented», это нормально, Community не поддерживает OIDC-provider. Мы продолжаем, нам важна сама модель, не endpoint.

    Создадим простую IAM-роль с trust на AssumeRole (это LocalStack поддерживает):

    bash
    cat > trust.json <<'EOF'
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "ec2.amazonaws.com"},
        "Action": "sts:AssumeRole"
      }]
    }
    EOF
    aws --endpoint-url=http://localstack:4566 \
      iam create-role \
      --role-name tf-runner-demo \
      --assume-role-policy-document file://trust.json
    aws --endpoint-url=http://localstack:4566 \
      iam attach-role-policy \
      --role-name tf-runner-demo \
      --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

    Роль создана. У неё доверие к ec2-сервису (эмуляция; в реальном OIDC trust был бы к federated identity GitHub).

    ✓ Роль создана. Сейчас её assume'нем.

  2. 02

    Assume роль, получим временные credentials

    bash
    CREDS=$(aws --endpoint-url=http://localstack:4566 \
      sts assume-role \
      --role-arn arn:aws:iam::000000000000:role/tf-runner-demo \
      --role-session-name local-demo)
    echo "$CREDS" | jq '.Credentials.AccessKeyId, .Credentials.SecretAccessKey | .[0:10]'

    Получили временные creds. Они короткоживущие (1 час по дефолту), не ротируются как обычные access keys.

    В реальном GitHub Actions это делает aws-actions/configure-aws-credentials:

    yaml
    - uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::123:role/tf-runner-demo
        aws-region: us-east-1

    Под капотом, ровно та же команда sts assume-role-with-web-identity (с OIDC JWT) или sts assume-role (для self-assumed).

    ✓ Временные creds получены. Это и есть модель CI без секретов.

  3. 03

    Используем временные creds для terraform apply

    bash
    eval "$(aws --endpoint-url=http://localstack:4566 \
      sts assume-role \
      --role-arn arn:aws:iam::000000000000:role/tf-runner-demo \
      --role-session-name terraform-run | jq -r '
        .Credentials |
        "export AWS_ACCESS_KEY_ID=" + .AccessKeyId,
        "export AWS_SECRET_ACCESS_KEY=" + .SecretAccessKey,
        "export AWS_SESSION_TOKEN=" + .SessionToken
      ')"
    cat > main.tf <<'EOF'
    resource "aws_s3_bucket" "oidc_demo" {
      bucket = "linuxlab-oidc-demo"
    }
    EOF
    terraform init -no-color > /dev/null
    terraform apply -auto-approve -no-color
    terraform state list

    Terraform отработал на временных credentials, не на долгоживущих. Это то, что делает реальный CI с OIDC.

    ✓ Apply прошёл на временных creds. Принцип OIDC реализован.

  4. 04

    Trust policy в реальном OIDC

    На реальном AWS trust выглядит иначе:

    bash
    cat > trust-real-oidc.json <<'EOF'
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringEquals": {
            "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
          },
          "StringLike": {
            "token.actions.githubusercontent.com:sub": "repo:linuxlab/terraform-infra:ref:refs/heads/main"
          }
        }
      }]
    }
    EOF
    cat trust-real-oidc.json | jq

    Ключевые поля:

    • Federated, ARN OIDC-провайдера GitHub в твоём AWS-аккаунте.
    • Action: sts:AssumeRoleWithWebIdentity, не AssumeRole, а federated.
    • Condition.StringEquals.aud, gh выдаёт audience = sts.amazonaws.com.
    • Condition.StringLike.sub, главный фильтр; разрешает assume только из конкретного repo + конкретной ref/environment.

    Sub-claim, это то, что ограничивает blast-radius. repo:org/*:* = vulnerable. repo:org/specific-repo:ref:refs/heads/main = tight.

    ✓ Trust policy для реального OIDC написана. На реальном AWS, copy-paste.

    То же самое на OpenTofu

    OpenTofu держит CLI и state совместимыми с Terraform по командам этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе сделай backup state и прогон на feature-branch - расхождения концентрируются в новых фичах (variables в backend, state-encryption, OCI registry-backed модули). См. tf-opentofu-parity для полной матрицы.

    • → OpenTofu parity
  5. 05

    Раздельные роли по средам

    Antipattern, одна роль gh-tf-runner для dev+stage+prod. Compromise dev-CI = compromise prod.

    Правильно, три роли, разные trust:

    bash
    cat > roles-design.md <<'EOF'
    | Role | Trust sub | Permissions |
    |---|---|---|
    | gh-tf-runner-dev | repo:linuxlab/infra:ref:refs/heads/* | dev-resources |
    | gh-tf-runner-stage | repo:linuxlab/infra:environment:stage | stage-resources |
    | gh-tf-runner-prod | repo:linuxlab/infra:environment:prod | prod-resources |
    EOF
    cat roles-design.md

    Workflow указывает свою role-to-assume в зависимости от target-env. Environment-gating через GitHub UI (required reviewers, branch filter) ограничивает кто может assume prod-role.

    На LocalStack создадим аналогичную модель, три отдельных role:

    bash
    for env in dev stage prod; do
      aws --endpoint-url=http://localstack:4566 \
        iam create-role \
        --role-name "tf-runner-${env}" \
        --assume-role-policy-document file://trust.json
    done
    aws --endpoint-url=http://localstack:4566 iam list-roles \
      --query 'Roles[*].RoleName' --output text

    Видишь три tf-runner-* роли. В реальной системе каждая привязана к разным environment'ам в GitHub.

    ✓ Multi-role pattern реализован. В prod это даёт изоляцию blast-radius.

    Read-only роль для plan-job

    Бонусный паттерн: plan-job не нужны permissions писать.

    gh-tf-runner-plan  , read-only (ReadOnlyAccess)
        trust: pull_request
    gh-tf-runner-apply , full (Custom managed policy)
        trust: push на main

    Plan на PR гоняется под read-only, даже compromised PR с злонамеренным HCL не может ничего создать. Apply возможен только после merge в main (другой trust).

    В trust policy:

    json
    "Condition": {
      "StringLike": {
        "token.actions.githubusercontent.com:sub": "repo:linuxlab/infra:pull_request"
      }
    }

    pull_request, keyword в sub-claim для PR-context'а. Любой PR в этом repo получает доступ к read-only роли. Apply-role требует :ref:refs/heads/main, недоступен из PR-context'а.

    Это снижает blast-radius при compromised contributor: он может сделать PR с злонамеренным HCL → plan-job его прогонит → reviewer должен ещё одобрить merge → apply-job уже на main → Сразу видно что было apply'нуто. Audit-trail сохранён.

    • → OIDC + IAM полностью
    • → Секреты в state и CI

Что ты узнал

LocalStack Community поддерживает AssumeRole, но не реальный OIDC. Что делаем: IAM-роль с минимальными правами для S3, ассамируем её через sts assume-role, временные credentials получаем, это модель того, как реальный workflow получает creds через aws-actions/configure-aws-credentials.

команды

  • aws iam create-role ...создать роль с trust policy.
  • aws iam attach-role-policy ...привязать permissions к роли.
  • aws sts assume-role --role-arn ...получить временные credentials.
  • AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... terraform applyиспользовать временные creds для terraform.

концепции

  • · trust policy определяет КТО может assume; permissions policy, ЧТО он может потом делать
  • · В реальном GitHub OIDC trust policy указывает на token.actions.githubusercontent.com
  • · Sub-claim в реальной OIDC, repo:org/repo:ref:refs/heads/main

← предыдущий

moved блок: рефакторинг без пересоздания

следующий →

locals и функции: убираем дублирование в HCL

Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки