odmin@opslog: ~/blog
← cd ..
odmin@opslog:~/blog$ cat terrafrom.md

Политики Terraform: Sentinel и OPA на практике

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

В этой статье под политиками понимается Policy as Code — проверка инфраструктурных изменений. IAM policies и политики S3 управляют доступом в AWS и решают другую задачу.

Для чего это нужно: пример из работы

Представим, что нужно создать виртуальную машину для тестового сайта. Для этой задачи команда разрешила использовать небольшие EC2 типов t3.micro и t3.small. Разработчик копирует конфигурацию из другого проекта и оставляет в ней более крупный тип машины:

hcl
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "m5.4xlarge"

  tags = {
    Owner = "platform"
  }
}

С точки зрения Terraform такой тип машины может быть допустимым. Проверка terraform validate не обнаружит нарушение внутренних правил команды, а успешный terraform plan покажет создание ресурса. Без дополнительной проверки ошибку придётся заметить при просмотре плана вручную.

Добавим политику: для тестового окружения разрешены только t3.micro и t3.small. Теперь перед применением изменений проверка обнаружит m5.4xlarge и остановит процесс. Разработчик исправит значение:

hcl
instance_type = "t3.micro"

После исправления нужно сформировать новый план и проверить его заново. Если остальные правила выполнены, изменения можно применять. Так политика помогает избежать лишних расходов из-за случайно скопированного параметра. Пример Sentinel ниже реализует именно такое ограничение; назначать его следует workspace тестового окружения.

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

Кто проверяет правила

Terraform сам по себе не знает, какие типы машин разрешены в вашей команде. Чтобы проверить такое правило, нужна отдельная программа — движок политик. Ей передают сведения об инфраструктуре и написанные нами условия. Программа проверяет условия и возвращает результат: правило выполнено или найдено нарушение.

В статье рассмотрим два инструмента:

  • Sentinel — инструмент HashiCorp для написания и выполнения политик. Правила пишутся на языке Sentinel. В нашем примере их автоматически запускает платформа HCP Terraform или Terraform Enterprise.
  • OPA (Open Policy Agent) — отдельная программа с открытым исходным кодом для проверки правил. Правила пишутся на языке Rego. В нашем примере мы сами запустим OPA командой в терминале и передадим ей Terraform-план.

Для начала достаточно выбрать один вариант. Если Terraform запускается локально или в собственном CI, пример с OPA можно использовать без платформы HashiCorp. Если команда работает через HCP Terraform или Terraform Enterprise, рассмотренный пример Sentinel показывает проверку внутри этой платформы.

Нужно ли устанавливать движки отдельно

Ни Sentinel, ни OPA не входят в стандартную поставку Terraform CLI. После установки команды terraform команды opa и sentinel сами не появятся. Это отдельные инструменты, которые устанавливаются отдельно.

ИнструментМожет ли обычный пользователь начать пользоваться
OPAДа. Можно скачать программу, написать правило и проверить JSON-план на своём компьютере без аккаунта HCP Terraform
Sentinel CLIДа, для разработки и локальной проверки правил. Для примера с tfplan/v2 нужно также подготовить данные импорта, например mocks — тестовые данные, имитирующие план
Автоматическая проверка Sentinel в HCP Terraform / Terraform EnterpriseTerraform запускается через эту систему, а она сама вызывает Sentinel после plan и блокирует apply при нарушении обязательной политики. Нужны доступ к системе и права на настройку политик

Что означает автоматическая проверка в HCP Terraform или Terraform Enterprise? В этом варианте запуском Terraform управляет отдельная система. HCP Terraform — облачный сервис HashiCorp. Terraform Enterprise организация устанавливает на своих серверах. Пользователь отправляет конфигурацию и запускает выполнение через эту систему, например из интерфейса или через подключённый Git-репозиторий.

Администратор подключает к системе правила Sentinel и указывает, для каких рабочих пространств их выполнять. Дальше система сама организует последовательность:

text
Пользователь отправил изменения
    ↓
Система выполнила terraform plan
    ↓
Система запустила Sentinel и передала ему данные плана
    ↓
Обязательная политика нарушена → система не разрешает apply
Политики пройдены → можно продолжить запуск с учётом согласований

То есть Sentinel здесь является частью управляемого процесса запуска Terraform. Пользователю не требуется вручную вызывать команду sentinel для каждой проверки. Если же выполнять terraform apply напрямую на своём компьютере, эта проверка сама по себе не запустится. Чтобы обязательные правила нельзя было обойти, команда должна ограничить прямой доступ к изменению инфраструктуры и выполнять изменения через настроенную систему.

Для первого знакомства с самостоятельной проверкой плана воспользуемся OPA. Скачайте подходящий для своей системы исполняемый файл по официальной инструкции установки OPA и добавьте его каталог в PATH — список каталогов, в которых оболочка ищет команды. После установки проверьте:

bash
opa version

Теперь можно запускать команды из раздела с OPA ниже. Отдельный сервер OPA для этих примеров не нужен: программа прочитает локальные файлы и завершится после проверки. Хранение бинарного файла в своём пользовательском каталоге позволяет обойтись без установки в системные каталоги и прав администратора.

Sentinel CLI тоже можно установить отдельно. Он позволяет разрабатывать и тестировать правила локально, но его установка не добавляет проверку в команды terraform plan и terraform apply. Кроме того, источник tfplan/v2 в локальном тесте нужно обеспечить данными: одного Terraform-файла рядом с политикой недостаточно. Назначение CLI описано в документации Sentinel.

Где выполняется проверка

Рассмотрим, на каком этапе выполняется проверка политик:

text
terraform init → terraform validate → terraform plan
                                         ↓
                                  проверка политик
                                         ↓
                                  terraform apply

terraform validate проверяет корректность конфигурации, но не знает правил вашей организации. Политика анализирует план и решает, допустимо ли изменение. HCP Terraform — облачный сервис HashiCorp. Он предназначен для запуска Terraform и совместной работы с инфраструктурой. Terraform Enterprise — платформа, которую организация разворачивает у себя. Они могут автоматически запускать Sentinel и OPA между планированием и применением изменений.

CI — система автоматического выполнения команд проекта, например после отправки изменений в Git. В собственном CI мы можем добавить отдельную команду запуска OPA и остановить дальнейшее выполнение, если проверка не прошла.

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

Доступны ли политики без HCP Terraform

Terraform CLI — программа terraform, которую мы запускаем в терминале. При её использовании проверку политик можно организовать самостоятельно. Встроенного запуска policy sets Sentinel и OPA между plan и apply в CLI нет: такую интеграцию предоставляют HCP Terraform и Terraform Enterprise.

Рассмотрим различия:

ВариантКак выполняется проверка
Terraform CLI и OPAOPA запускается отдельно, локально или в CI, и проверяет JSON-план
HCP Terraform / Terraform EnterpriseПлатформа запускает назначенные workspace наборы политик
Terraform CLI и файл SentinelСамо наличие файла не запускает проверку и не блокирует apply

Таким образом, пример с OPA ниже можно использовать без HCP Terraform и Terraform Enterprise. OPA — отдельный открытый движок политик. Потребуется самостоятельно настроить запуск проверки и остановку pipeline при нарушениях. Пользователь, имеющий права на прямой terraform apply, сможет обойти такую проверку, если доступ к применению не ограничен процессом CI.

Под «опенсорсной версией» часто подразумевают бесплатный Terraform CLI. Для современных выпусков точнее говорить о Community Edition: HashiCorp перевела новые выпуски Terraform на Business Source License (BSL), поэтому бесплатность CLI и открытая лицензия — разные вопросы. Подробнее об изменении лицензии — в пояснении HashiCorp.

Интеграцию платформ с политиками описывает документация HashiCorp, а самостоятельную проверку JSON-плана — руководство OPA.

Sentinel: ограничиваем типы виртуальных машин

Начнём с варианта, в котором проверку запускает платформа HashiCorp. EC2 — сервис виртуальных машин AWS, а aws_instance — название Terraform-ресурса для такой машины. Рассмотрим пример: разрешим создание EC2 только с типами t3.micro и t3.small. Чтобы Sentinel получил доступ к плану Terraform, подключим tfplan/v2. Это предоставляемый платформой источник данных: через него правило читает ресурсы и их запланированные значения.

Сохраним правило в файл allowed-instance-types.sentinel:

sentinel
import "tfplan/v2" as tfplan

allowed_types = ["t3.micro", "t3.small"]

instances = filter tfplan.resource_changes as _, rc {
  rc.mode is "managed" and
  rc.type is "aws_instance" and
  rc.change.actions is not ["delete"]
}

main = rule {
  all instances as _, rc {
    (rc.change.after.instance_type else "") in allowed_types
  }
}

Здесь allowed_types содержит разрешённые типы машин, а instances — выбранные из плана ресурсы EC2. Правило main проверяет тип каждой машины, которая останется после применения, включая неизменённые ресурсы и замены. Чистое удаление пропускается. Если тип отсутствует или неизвестен, подстановка пустой строки приводит к отказу. Для неизвестного значения здесь намеренно выбрана строгая проверка.

Одного файла с условием недостаточно: платформе нужно указать, какое правило запускать и что делать при нарушении. Для этого создадим файл настроек sentinel.hcl:

hcl
policy "allowed-instance-types" {
  source = "./allowed-instance-types.sentinel"
  enforcement_level = "hard-mandatory"
}

Здесь source задаёт путь к файлу правила, а enforcement_level определяет реакцию на нарушение. Значение hard-mandatory означает обязательную проверку: при отказе применять план нельзя.

Теперь свяжем политику с примером тестовой машины. Policy set — набор политик, который платформа запускает вместе. Workspace — отдельное рабочее пространство платформы со своей Terraform-конфигурацией, состоянием и запусками. Мы назначим набор правил рабочему пространству тестового окружения. Terraform-конфигурация и политика могут храниться в разных репозиториях:

text
terraform-project/
  main.tf                         # aws_instance.web с instance_type

terraform-policies/
  sentinel.hcl                    # подключение правила и уровень блокировки
  allowed-instance-types.sentinel  # список разрешённых типов и проверка

Ограничение задаётся именно в allowed-instance-types.sentinel:

sentinel
allowed_types = ["t3.micro", "t3.small"]

А эта часть правила сравнивает instance_type машины из плана с разрешённым списком:

sentinel
(rc.change.after.instance_type else "") in allowed_types

rc.change.after содержит запланированные значения ресурса после применения. Если в main.tf указано instance_type = "m5.4xlarge", выражение вернёт false: такого значения в списке нет. Следовательно, правило main не пройдёт проверку. Политика ограничивает конкретные типы машин, а не вычисляет количество CPU или объём памяти. Чтобы разрешить другой тип, его нужно явно добавить в список.

Для автоматического запуска проверки в HCP Terraform или Terraform Enterprise:

  • Подключите репозиторий terraform-policies как policy set через настройки организации. Если файлы находятся в подкаталоге, укажите его как путь к политикам.
  • Назначьте policy set workspace тестового окружения, в котором выполняется Terraform-конфигурация из terraform-project.
  • Запустите новое выполнение Terraform (run) с формированием плана.

После plan платформа прочитает sentinel.hcl, выполнит allowed-instance-types.sentinel и получит результат правила main. Уровень hard-mandatory остановит переход к apply, если правило вернуло false.

text
main.tf: instance_type = "m5.4xlarge"
    ↓ terraform plan
Проверка: "m5.4xlarge" входит в ["t3.micro", "t3.small"]?
    ↓ false
Политика не пройдена → apply заблокирован

Изменим в main.tf тип машины на t3.micro и запустим новый run:

text
main.tf: instance_type = "t3.micro"
    ↓ terraform plan
Проверка: "t3.micro" входит в ["t3.micro", "t3.small"]?
    ↓ true
Политика пройдена → можно перейти к apply

Успех этой политики не отменяет остальные проверки и ручное согласование, если они настроены. Сама политика не меняет instance_type в коде и не подбирает подходящий размер: разработчик исправляет конфигурацию самостоятельно. При обычном локальном запуске Terraform CLI файлы Sentinel автоматически не выполняются.

Для классических Sentinel policy checks используются уровни:

  • advisory — сообщает о нарушении, но не блокирует выполнение.
  • soft-mandatory — блокирует выполнение с возможностью override пользователем с нужными правами.
  • hard-mandatory — блокирует выполнение без override.

Механизм policy evaluations может иначе представлять уровни в интерфейсе; при настройке сверяйтесь с документацией используемой платформы.

OPA: проверяем обязательный тег Owner

Теперь рассмотрим вариант без платформы HashiCorp. OPA — отдельная программа: она не создаёт виртуальные машины и не запускает Terraform. Мы передадим ей план и файл правил, а затем посмотрим результат.

План передаётся в формате JSON — текстовом представлении данных с полями и значениями. Правила для OPA пишутся на языке Rego. Учить весь язык для этого примера не требуется: разберём, какие данные читает правило и какое нарушение оно ищет. Попробуем проверить наличие тега Owner: у каждого управляемого aws_instance, который останется после применения, этот тег должен содержать непустое значение. Для примера воспользуемся OPA 1.x.

Перед началом установите Terraform и OPA, настройте доступ к AWS и подготовьте Terraform-конфигурацию. Команды ниже выполняются из каталога этой конфигурации.

Сначала сохраняем план и преобразуем его в JSON:

bash
terraform init
terraform validate
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json

Здесь -out=tfplan сохраняет план в файл, а terraform show -json записывает его представление в tfplan.json. Именно этот JSON прочитает OPA.

Создадим каталог policy и сохраним правило в файл policy/terraform.rego:

rego
package terraform

import rego.v1

deny contains msg if {
    some rc in input.resource_changes
    rc.mode == "managed"
    rc.type == "aws_instance"
    rc.change.after != null

    tags := object.get(rc.change.after, "tags", {})
    owner := object.get(tags, "Owner", "")
    not valid_owner(owner)

    msg := sprintf("%s: требуется непустой тег Owner", [rc.address])
}

valid_owner(owner) if {
    is_string(owner)
    trim_space(owner) != ""
}

В данном примере deny собирает сообщения о нарушениях. Функция valid_owner проверяет, что значение тега является строкой и после удаления пробелов по краям не остаётся пустым.

Правило использует tags, то есть требует явно заданный тег ресурса. Если организация допускает теги из default_tags AWS provider, проверять нужно tags_all, учитывая неизвестные значения и поведение закреплённой версии провайдера.

При неизвестном Owner значение отсутствует в after или представлено как null: такой план будет отклонён. Подробную информацию о неизвестных атрибутах Terraform хранит в after_unknown; сложные политики должны обрабатывать её явно.

Выполним проверку и посмотрим результат:

bash
opa eval --format pretty \
  --data policy/terraform.rego \
  --input tfplan.json \
  'data.terraform.deny'

В этой команде --data указывает файл правил, --input — файл проверяемого плана, а data.terraform.deny запрашивает собранные правилом сообщения. --format pretty делает вывод удобнее для чтения.

Если у ресурса aws_instance.web отсутствует тег Owner, получим следующий результат:

json
[
  "aws_instance.web: требуется непустой тег Owner"
]

Как видим, в сообщении указаны адрес ресурса и причина отказа. Добавьте непустой тег Owner и сформируйте план заново: если других нарушений нет, результатом проверки будет пустой список [].

Обычный opa eval может завершиться успешно даже при непустом списке нарушений. Для блокировки CI проверяем булево выражение с --fail:

bash
set -euo pipefail

terraform init -input=false
terraform validate
terraform plan -input=false -out=tfplan
terraform show -json tfplan > tfplan.json

opa eval --fail --format pretty \
  --data policy/terraform.rego \
  --input tfplan.json \
  'count(data.terraform.deny) == 0'

terraform apply -input=false tfplan

Этот Bash-сценарий предполагает установленные Terraform и OPA, настроенный backend и учётные данные провайдера. При нарушении выражение не даёт успешного результата, OPA возвращает ненулевой код, а оболочка останавливается до apply. В реальном pipeline между проверкой и применением можно добавить ручное согласование.

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

Какие сценарии проверить

Для правила Owner нужны как минимум такие входные планы:

СценарийОжидаемый результат
Создание EC2 с Owner=platformРазрешить
Создание EC2 без OwnerЗапретить
Owner содержит только пробелыЗапретить
Owner неизвестен до applyЗапретить
Обновление EC2 удаляет OwnerЗапретить
Чистое удаление EC2Разрешить
EC2 находится во вложенном модулеПроверить по тем же правилам

resource_changes содержит адреса ресурсов из модулей, поэтому дополнительный обход дерева модулей в этих примерах не нужен. Правила проверяют и существующие EC2 без изменений: старый ресурс без тега тоже может заблокировать запуск. Если требуется проверять только создание и обновление, добавьте фильтр по change.actions.

Как внедрять политики

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

  • Храните политики в Git и проверяйте изменения через review.
  • Закрепляйте версии Terraform, провайдеров и движка политик.
  • Добавляйте положительные и отрицательные примеры планов к каждому правилу.
  • Защитите CI и репозиторий политик от обхода проверок.
  • Оформляйте исключения с владельцем, причиной и сроком действия.

JSON-план может содержать чувствительные значения в открытом виде. Ограничивайте доступ к нему, не публикуйте его в логах и задайте срок хранения артефактов. Проверка плана также не заменяет ограничения доступа в облаке: изменения вне Terraform требуют отдельного контроля.

Документация