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-06-opa-rego

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

OPA + Rego, гейтим plan.json

OPA + Rego, для правил, которых нет в Checkov. Бизнес-инвариант (все ресурсы с тегом CostCenter), cross-resource (Lambda и RDS в одной VPC), conditional (prod-ресурс, только из main-ветки). На этом уроке напишешь Rego-правила, прогонишь conftest на plan.json.

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

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

запустить sandbox →

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

Шаги

  1. 01

    Положи HCL с нарушением policy

    Бизнес-правило: все S3-бакеты должны иметь теги CostCenter и Environment. Один из бакетов нарушит.

    bash
    cd /home/student/tf-opa
    cat > main.tf <<'EOF'
    resource "aws_s3_bucket" "good" {
      bucket = "linuxlab-opa-good"
      tags = {
        CostCenter  = "ml-team"
        Environment = "dev"
      }
    }
    resource "aws_s3_bucket" "missing_tags" {
      bucket = "linuxlab-opa-missing"
      tags = {
        Owner = "student"
      }
    }
    EOF
    terraform init -no-color > /dev/null
    terraform plan -no-color -out=plan.tfplan
    terraform show -json plan.tfplan > plan.json

    plan.json готов. Сейчас напишем правило.

    ✓ Plan сгенерирован. Бакет missing_tags нарушает политику.

  2. 02

    Напиши Rego-правило

    bash
    cat > policies/tags.rego <<'EOF'
    package terraform.tags
    import future.keywords.contains
    import future.keywords.if
    import future.keywords.in
    mandatory_tags := {"CostCenter", "Environment"}
    deny contains msg if {
        some resource in input.resource_changes
        resource.type == "aws_s3_bucket"
        resource.change.actions[_] == "create"
        tags := object.get(resource.change.after, "tags", {})
        some tag in mandatory_tags
        not tags[tag]
        msg := sprintf("%s missing mandatory tag %q", [resource.address, tag])
    }
    EOF

    Что делает: для каждого aws_s3_bucket в plan'е, который будет создан, проверяет наличие каждого обязательного тега. Если нет добавляет deny-сообщение.

    Сразу прогоним:

    bash
    opa eval -d policies/ -i plan.json 'data.terraform.tags.deny'

    Должно вернуть массив с двумя сообщениями (CostCenter и Environment отсутствуют у missing_tags).

    ✓ Rego видит нарушение. OPA опознала bucket без тегов.

  3. 03

    Тесты на сам Rego

    Тесты на правила, критичны: правило с багом игнорирует нарушения.

    bash
    cat > policies/tags_test.rego <<'EOF'
    package terraform.tags
    import future.keywords.if
    test_deny_when_missing_tags if {
        mock := {"resource_changes": [{
            "address": "aws_s3_bucket.bad",
            "type": "aws_s3_bucket",
            "change": {
                "actions": ["create"],
                "after": {"tags": {"Owner": "x"}},
            },
        }]}
        count(deny) == 2 with input as mock
    }
    test_no_deny_with_all_tags if {
        mock := {"resource_changes": [{
            "address": "aws_s3_bucket.good",
            "type": "aws_s3_bucket",
            "change": {
                "actions": ["create"],
                "after": {"tags": {
                    "CostCenter": "team",
                    "Environment": "dev",
                }},
            },
        }]}
        count(deny) == 0 with input as mock
    }
    test_ignores_delete_actions if {
        mock := {"resource_changes": [{
            "address": "aws_s3_bucket.gone",
            "type": "aws_s3_bucket",
            "change": {"actions": ["delete"], "after": null},
        }]}
        count(deny) == 0 with input as mock
    }
    EOF
    opa test policies/

    Должно показать «PASS: 3/3». Если бы правило игнорировало missing tags, test_deny_when_missing_tags упал бы. Это страховка.

    ✓ Rego-правила протестированы. Безопасно использовать в CI.

  4. 04

    conftest для CI-friendly вывода

    opa eval гибкий, но в CI хочется human-friendly формат с pass/fail. conftest, обёртка с выводом, exit-code'ом, severity.

    Сначала, структура папки policies/ под conftest:

    bash
    # conftest по умолчанию читает namespace "main", переименуем package
    sed -i 's/^package terraform.tags$/package main/' policies/tags.rego
    sed -i 's/^package terraform.tags$/package main/' policies/tags_test.rego
    conftest test plan.json --policy policies/
    echo "exit: $?"

    Должно показать FAIL с двумя сообщениями и exit 1.

    Альтернатива, оставить namespace и использовать --all-namespaces или --namespace terraform.tags. Это вопрос вкуса; CI один раз решает.

    ✓ conftest показывает то же, что opa eval, но в CI-friendly формате.

    То же самое на 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

    Исправь HCL, gate проходит

    Добавим теги:

    bash
    sed -i '/"missing_tags"/,/^}$/ s|tags = {|tags = {\n    CostCenter  = "data-team"\n    Environment = "dev"|' main.tf
    cat main.tf
    terraform plan -no-color -out=plan.tfplan
    terraform show -json plan.tfplan > plan.json
    conftest test plan.json --policy policies/
    echo "exit: $?"

    Теперь exit 0, gate чистый. PR merge'абельный.

    ✓ Policy gate проходит. В CI это последний этап перед apply-job.

    Cross-resource policy

    Простой пример был, все S3 с тегами. Что OPA умеет лучше других:

    rego
    # «Если в plan'е создаётся Lambda, она должна быть в одной
    # VPC с RDS, на которую ссылается через DB_HOST env»
    deny contains msg if {
        some lambda in input.resource_changes
        lambda.type == "aws_lambda_function"
        lambda.change.actions[_] == "create"
        db_host := lambda.change.after.environment[0].variables.DB_HOST
        db_address := substr(db_host, 0, indexof(db_host, "."))
        some db in input.resource_changes
        db.type == "aws_db_instance"
        db.change.after.address == db_address
        # subnets
        lambda_subnets := lambda.change.after.vpc_config[0].subnet_ids
        db_subnet_group := db.change.after.db_subnet_group_name
        count(lambda_subnets) > 0
        db_subnet_group != ""
        msg := sprintf(
            "Lambda %q is in VPC subnet %v but reads RDS %q in subnet group %q, check VPC alignment",
            [lambda.address, lambda_subnets, db.address, db_subnet_group],
        )
    }

    Checkov такое не напишет. terraform-compliance, тоже. Это OPA-территория.

    Подробнее, tf-policy-as-code.

    • → OPA + Rego целиком
    • → BDD-вариант, terraform-compliance

Что ты узнал

terraform plan -out=plan.tfplan && terraform show -json plan.tfplan > plan.json. Полученный JSON прогоняешь через opa eval или conftest test. Правила в Rego используют input.resource_changes. deny, список сообщений; непустой = fail.

команды

  • opa version && opa eval --help | head -5OPA в образе уже стоит.
  • opa eval --data policies/ --input plan.json 'data.terraform.deny'что вернёт rule.
  • terraform show -json plan.tfplan > plan.jsonформат, который OPA понимает.

концепции

  • · input, это структура plan.json
  • · deny, convention; правила соглашаются на это имя
  • · Rego тесты, рядом с правилами, файл *_test.rego, запуск opa test

← предыдущий

Remote state в S3 (на LocalStack)

следующий →

OpenTofu, matrix-CI рядом с Terraform

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