Безопасность инфраструктуры как кода

Почему открытый S3-бакет или Security Group с портом 22 для всего мира чаще рождаются в коде, а не в консоли — и как поймать их до деплоя.

Infrastructure as Code (IaC) — описание облачной инфраструктуры (сети, диски, права, кластеры) в виде версионируемых файлов: Terraform, CloudFormation, Kubernetes-манифесты. Ошибка в таком файле тиражируется на сотни ресурсов одним apply.

Когда инфраструктура задаётся кодом, безопасность сдвигается влево: дыру можно найти на ревью pull request, а не через полгода в отчёте аудитора. Но обратная сторона — мисконфигурация масштабируется. Один небрежный модуль, подключённый в десяти сервисах, открывает десять брешей сразу. Поэтому защитнику важно уметь читать IaC глазами атакующего и автоматически отбраковывать опасные шаблоны.

Зачем это знать защитнику

Подавляющее большинство облачных инцидентов — не «хитрый взлом», а мисконфигурация: публичный бакет с бэкапами, база данных, доступная из интернета, роль с правами *:*. Если инфраструктура в коде, у вас есть редкая возможность: проверить конфигурацию до того, как она станет реальностью. Сканер в CI — это ваш постоянный ревьюер, который не устаёт и знает сотни типовых ошибок.

Есть и второй эффект, неочевидный для новичка. Кодом фиксируется не только то, что вы создали, но и обоснование: каждый ресурс прошёл через pull request, у него есть автор, дата и обсуждение. Это меняет разбор инцидента: вместо «кто и когда открыл этот порт в консоли, и почему — никто не помнит» вы открываете историю git и видите коммит, где правило появилось. Безопасность из разовой настройки превращается в воспроизводимый процесс: тот же код даёт ту же конфигурацию в dev, stage и prod, и нельзя «втихаря» поправить прод мимо ревью. Поэтому защита IaC — это в равной мере про инструменты и про дисциплину: код единственного источника правды, запрет ручных правок, обязательный сканер на входе.

Типичные мисконфиги в Terraform

Разберём, как опасная настройка выглядит в коде. Вот Security Group, открывающая SSH всему интернету — классический способ отдать сервер под брутфорс:

# НЕБЕЗОПАСНО: SSH открыт для 0.0.0.0/0
resource "aws_security_group" "web" {
  name = "web-sg"
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]   # <-- весь мир
  }
}

Защищённый вариант сужает источник до офисной сети или вовсе убирает прямой SSH в пользу SSM/бастиона:

# Безопаснее: доступ только из доверенной подсети
ingress {
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = [var.office_cidr]   # напр. 203.0.113.0/24
}

Аналогичная история — хранилище объектов без шифрования и с публичным доступом. Защита: включённое шифрование по умолчанию, блокировка публичного доступа, версионирование. В коде это пара флагов, но именно их забывают. Коварство в том, что небезопасное значение часто и есть значение по умолчанию провайдера или модуля из интернета: разработчик не «выбирает» опасную настройку — он просто не переопределяет умолчание. Поэтому правило хорошего тона — явно задавать критичные для безопасности параметры, даже если кажется, что «и так нормально»: block_public_access = true, шифрование, приватные подсети. Явность здесь дороже краткости.

Опасные манифесты Kubernetes

В манифестах под легко выдать лишние привилегии. Контейнер с privileged: true фактически равен root на ноде, может монтировать хостовые устройства и при пробитии приложения открывает путь к побегу из контейнера. Рядом стоят hostNetwork, hostPID и монтирование hostPath к чувствительным директориям ноды — каждый такой флаг стирает границу между подом и хостом. Безопасный манифест, наоборот, явно «обрезает» возможности:

# НЕБЕЗОПАСНО: привилегированный контейнер
securityContext:
  privileged: true
  runAsUser: 0
# Безопаснее:
securityContext:
  privileged: false
  runAsNonRoot: true
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]

Сканирование IaC

Ручной ревью не масштабируется — нужны статические анализаторы. Они парсят файлы и сверяют их с базой правил безопасности. Популярные инструменты: Checkov, tfsec/Trivy, KICS, terrascan. Запускают их в своей среде и в CI как иллюстрацию метода:

# Сканирование Terraform-кода в своём репозитории (учебная среда)
checkov -d ./infra

# Trivy умеет и IaC, и манифесты
trivy config ./k8s-manifests

Типичный фрагмент отчёта подсказывает и проблему, и ссылку на правило:

FAILED for resource: aws_security_group.web
  Check: Ensure no security groups allow ingress from 0.0.0.0:0 to port 22
  File: /infra/network.tf:5-12
  Severity: HIGH

Главное — встроить сканер в pipeline так, чтобы критичные находки блокировали merge. Тогда небезопасный шаблон физически не доедет до прода. У статического сканирования есть и предсказуемая болезнь — ложные срабатывания. Если их игнорировать, команда привыкает «прокликивать» отчёт, и реальные находки тонут в шуме. Лечится это не отключением сканера, а аккуратной работой с исключениями: осознанный skip на конкретное правило с комментарием-обоснованием в коде (#checkov:skip=CKV_AWS_..:причина), а не глобальное подавление. Так у каждого исключения есть автор и причина, и его видно на ревью.

Безопасные модули и политика как код

Чтобы разработчики не переизобретали безопасную конфигурацию каждый раз, заведите проверенные модули: общий Terraform-модуль «бакет» уже включает шифрование и блокировку публичного доступа, модуль «сеть» — приватные подсети. Это снижает площадь ошибки.

Следующий уровень — Policy as Code: правила организации, выраженные кодом. OPA/Conftest и Sentinel позволяют запретить, например, любой публичный бакет на уровне политики, а не доброй воли. Пример правила Rego (читаем, не исполняем):

deny[msg] {
  input.resource_type == "aws_s3_bucket"
  input.config.acl == "public-read"
  msg := "Публичные S3-бакеты запрещены политикой"
}

Как это работает под капотом

IaC-сканер строит из файлов граф ресурсов и прогоняет по нему набор предикатов: «есть ли ingress 0.0.0.0/0 на чувствительный порт», «выключено ли шифрование», «привязана ли роль с wildcard». Он не запускает apply — анализ статический, поэтому быстрый и безопасный. Policy as Code работает на том же принципе, но проверяет план развёртывания (terraform plan -out → JSON) против организационных правил. Так бизнес-требование «никаких публичных баз» превращается в детерминированный gate.

Как защититься

Практический набор мер:

  • Сканируй IaC в CI (Checkov/Trivy/tfsec) и блокируй merge на HIGH/CRITICAL.
  • Используй проверенные модули с безопасными значениями по умолчанию (шифрование, приватность, drop capabilities).
  • Вводи Policy as Code (OPA/Conftest, Sentinel) для запретов на уровне организации.
  • Включай secure-by-default: блокировка публичного доступа, шифрование, runAsNonRoot в манифестах.
  • Делай code review инфраструктуры и не давай править прод вручную в обход кода (избегай дрейфа).

Юридическое напоминание: сканировать и менять можно только свою инфраструктуру или стенд с письменного разрешения владельца. Несанкционированный доступ к чужим системам наказуем (УК РФ ст. 272).

Итоги

  • Большинство облачных инцидентов — мисконфиги; IaC даёт шанс поймать их до деплоя.
  • Опасные паттерны: открытый 0.0.0.0/0, публичные бакеты, privileged-контейнеры, wildcard-права.
  • Сканеры IaC и Policy as Code превращают правила безопасности в автоматический gate в CI.
  • Безопасные модули и secure-by-default снижают площадь ошибки на корню.
Проверьте себя
1. Почему мисконфигурация в IaC опаснее ручной ошибки в облачной консоли?
AПотому что IaC работает медленнее
BПотому что один небезопасный модуль/шаблон тиражируется на множество ресурсов одним apply
CПотому что Terraform нельзя сканировать
DПотому что консоль всегда безопаснее кода
2. Какой инструмент относится к статическому сканированию IaC на мисконфиги?
ACheckov
BWireshark
CPostman
DGrafana
3. Что делает Policy as Code (например OPA/Conftest) в pipeline?
AУскоряет terraform apply
BШифрует код Terraform
CПроверяет план развёртывания против организационных правил и блокирует запрещённые конфигурации
DЗаменяет систему контроля версий