Безопасность инфраструктуры как кода
Почему открытый 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 снижают площадь ошибки на корню.