
Инструмент для линтинга шаблонов CloudFormation
Инструмент cfn-nag ищет в шаблонах CloudFormation паттерны, которые могут указывать на небезопасную инфраструктуру. Если говорить в общем, он проверяет:
Дополнительную информацию об инструменте можно найти в этом посте в блоге Stelligent:
Поиск проблем безопасности на ранних этапах разработки шаблона CloudFormation с помощью "cfn-nag"
Если установлен Ruby >= 2.5.x, установка сводится к следующему:
gem install cfn-nag
На MacOS или Linux можно также установить через brew:
brew install ruby brew-gem
brew gem install cfn-nag
Чтобы запускать cfn_nag как действие в CodePipeline, можно развернуть его через AWS Serverless Application Repository.
Для запуска:
cfn_nag_scan --input-path <путь к cloudformation json>
Путь может указывать на каталог или конкретный шаблон. Если это каталог, будут обработаны все файлы .json, .template, .yml и .yaml, включая рекурсивный обход подкаталогов.
Формат вывода по умолчанию — произвольный текст, но вывод в json можно выбрать с помощью флага --output-format json.
При необходимости флаг --debug выведет информацию о внутренних процессах загрузки правил.
Запустите с --help, чтобы получить полный список поддерживаемых параметров.
Чтобы увидеть список всех правил, которые cfn-nag поддерживает в настоящее время, существует утилита командной строки, выводящая их в stdout:
cfn_nag_rules
Для удобства предоставлен Dockerfile. Он опубликован на DockerHub как stelligent/cfn_nag.
https://hub.docker.com/r/stelligent/cfn_nag
Вы также можете собрать его локально.
docker build -t stelligent/cfn_nag .
Вы можете смонтировать локальный каталог с шаблонами в Docker-контейнер, а затем вызвать cfn_nag внутри контейнера. В этом примере используются тестовые шаблоны, применяемые при модульном тестировании cfn_nag:
$ docker run -v `pwd`/spec/test_templates:/templates -t stelligent/cfn_nag /templates/json/efs/filesystem_with_encryption.json
{
"failure_count": 0,
"violations": [
]
}
$ docker run -v `pwd`/spec/test_templates:/templates -t stelligent/cfn_nag /templates/json/efs/filesystem_with_no_encryption.json
{
"failure_count": 1,
"violations": [
{
"id": "F27",
"type": "FAIL",
"message": "EFS FileSystem should have encryption enabled",
"logical_resource_ids": [
"filesystem"
]
}
]
}
cfn_nag_scan можно запускать как часть GitHub Workflow для проверки кода в конвейерах непрерывной интеграции.
В файле GitHub Workflow создайте шаг, использующий cfn_nag Action:
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests
Дополнительная информация о GitHub Action доступна здесь.
cfn-nag поддерживает понятие "профиль" — по сути, это список разрешённых правил для применения. Профиль — это текстовый файл, в котором каждая строка должна содержать идентификатор правила. Если профиль указан через аргумент командной строки --profile-path, cfn-nag будет возвращать нарушения ТОЛЬКО по этим конкретным правилам.
Мотивация создания "профиля" в том, что разные разработчики могут заботиться о разных правилах. Например, "инфраструктурный разработчик" может обращать внимание на правила IAM, а "прикладной разработчик" может вообще не иметь возможности создавать ресурсы IAM и поэтому не интересоваться этими правилами.
Вот пример профиля:
F1
F2
F27
W3
W5
Список запрета — это, по сути, противоположность профиля: перечень правил, которые НИКОГДА не применяются. Если он указан через аргумент командной строки --deny-list-path, cfn-nag НИКОГДА не будет возвращать нарушения по этим конкретным правилам, перечисленным в файле.
Если правило указано в обоих списках, список запрета имеет приоритет над профилем, и правило не применяется.
Формат следующий. Единственные два значимых поля — RulesToSuppress и id для каждого элемента. Поле reason не интерпретируется cfn-nag, но рекомендуется указывать обоснование и документально фиксировать, почему правило никогда не должно применяться.
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
Если вы хотите подавить какое-либо правило, к соответствующему ресурсу можно добавить ключ Metadata для cfn_nag, чтобы указать cfn_nag не выдавать ошибку или предупреждение по этому правилу.
Например, если вы настраиваете публичный ELB, открытый для входящих подключений из интернета, с ресурсами следующего вида:
public_alb.yaml
# Partial template
PublicAlbSecurityGroup:
Properties:
GroupDescription: 'Security group for a public Application Load Balancer'
VpcId:
Ref: vpc
Type: AWS::EC2::SecurityGroup
PublicAlbSecurityGroupHttpIngress:
Properties:
CidrIp: 0.0.0.0/0
FromPort: 80
GroupId:
Ref: PublicAlbSecurityGroup
IpProtocol: tcp
ToPort: 80
Type: AWS::EC2::SecurityGroupIngress
cfn_nag выдаст предупреждения следующего вида:
$ cfn_nag_scan -i public_alb.yaml
------------------------------------------------------------
public_alb.yaml
------------------------------------------------------------------------------------------------------------------------
| WARN W9
|
| Resources: ["PublicAlbSecurityGroup"]
|
| Security Groups found with ingress cidr that is not /32
------------------------------------------------------------
| WARN W2
|
| Resources: ["PublicAlbSecurityGroup"]
|
| Security Groups found with cidr open to world on ingress. This should never be true on instance. Permissible on ELB
Failures count: 0
Warnings count: 2
Добавив метаданные, эти предупреждения можно подавить:
public_alb_with_suppression.yaml
# Partial template
PublicAlbSecurityGroup:
Properties:
GroupDescription: 'Security group for a public Application Load Balancer'
VpcId:
Ref: vpc
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W9
reason: "This is a public facing ELB and ingress from the internet should be permitted."
- id: W2
reason: "This is a public facing ELB and ingress from the internet should be permitted."
PublicAlbSecurityGroupHttpIngress:
Properties:
CidrIp: 0.0.0.0/0
FromPort: 80
GroupId:
Ref: PublicAlbSecurityGroup
IpProtocol: tcp
ToPort: 80
Type: AWS::EC2::SecurityGroupIngress
$ cfn_nag_scan -i public_alb_with_suppression.yaml
------------------------------------------------------------
public_alb_with_supression.yaml
------------------------------------------------------------
Failures count: 0
Warnings count: 0
Параметры шаблона CloudFormation могут создавать проблему для статического анализа, поскольку значения задаются в момент развёртывания. Другими словами, на момент статического анализа значения недоступны — статический анализ может видеть только тот "код", который находится перед ним. Поэтому правило входящего трафика группы безопасности с адресом 0.0.0.0/0 не будет помечено, если cidr параметризован, а 0.0.0.0/0 передаётся во время развёртывания.
Чтобы обеспечить возможность проверки значений параметров, пользователь может указать значения параметров в JSON-файле, передаваемом в командной строке как cfn_nag, так и cfn_nag_scan с помощью флага --parameter-values-path=<filename/uri>.
Формат JSON — это один ключ "Parameters", значением которого является словарь, где каждая пара ключ/значение соответствует параметрам:
{
"Parameters": {
"Cidr": "0.0.0.0/0"
}
}
Это передаст значение "0.0.0.0/0" следующему параметру:
Parameters:
Cidr:
Type: String
ВНИМАНИЕ: если в JSON есть лишние параметры, они молча игнорируются (чтобы cfn_nag_scan мог применять один и тот же JSON ко всем шаблонам).
Если JSON некорректен или не соответствует указанному выше формату, разбор завершится с фатальным нарушением.
До версии 0.5.55 вызовы Fn::FindInMap фактически игнорировались. Базовая модель оставляла их как есть, и поэтому они отображались для правил как Hash-значения. Например: { "Fn::FindInMap" => [map1, key1, key2]}
Начиная с версии 0.5.55, модель пытается вычислить значение для вызова FindInMap и передать это значение правилам. Этот расчёт поддерживает ключи, которые:
Если логика расчёта не может определить значение ключа, она возвращается к прежнему поведению — возвращает Hash для всего выражения.
Также до версии 0.5.55 вызовы AWS-псевдофункций фактически игнорировались. Базовая модель оставляла их как есть, и поэтому они отображались для правил как Hash-значения. Например: {"Ref"=>"AWS::Region"}. Часто сопоставления организуются по регионам, поэтому вычисление псевдофункций важно для лучшей поддержки оценки сопоставлений.
Начиная с версии 0.5.55, модель передаёт правилам следующие AWS-псевдофункции со значениями по умолчанию:
'AWS::URLSuffix' => 'amazonaws.com',
'AWS::Partition' => 'aws',
'AWS::NotificationARNs' => '',
'AWS::AccountId' => '111111111111',
'AWS::Region' => 'us-east-1',
'AWS::StackId' => 'arn:aws:cloudformation:us-east-1:111111111111:stack/stackname/51af3dc0-da77-11e4-872e-1234567db123',
'AWS::StackName' => 'stackname'
Кроме того, конечный пользователь может переопределить значение, передаваемое через традиционный механизм подстановки параметров. Например:
{
"Parameters": {
"AWS::Region": "eu-west-1"
}
}
Вплоть до версии 0.4.66 cfn_nag базовая модель не выполняла никакой обработки Fn::If внутри шаблона. Это означало, что если свойство имело условное значение, разбор Fn::If возлагался на правило. Учитывая, что Fn::If может встречаться практически где угодно, это создавало для разработчиков правил ситуацию "игры в догонялки". В лучшем случае логика правила могла игнорировать значения, являющиеся Hash, предполагая, что значение изначально не было Hash.
Чтобы решить эту проблему, поведением по умолчанию для cfn_nag теперь является подстановка Fn::If с результатом true. Это означает, что по умолчанию правила не проверяют ветви false на предмет нарушений безопасности.
Помимо подстановки Fn::If на уровне значений свойств, то же поведение применяется к Fn::If на верхнем уровне Properties. Например:
Resource1:
Type: Foo
Properties: !If
- IsNone
- Description: Up
- Description: DOwn
Будет выглядеть так же, как:
Resource1:
Type: Foo
Properties:
Description: Up
Чтобы предоставить некоторый контроль над этим поведением, пользователь может указать значения условий в JSON-файле, передаваемом в командной строке как cfn_nag, так и cfn_nag_scan с помощью флага --condition-values-path=<filename/uri>.
Формат JSON — словарь, где каждая пара ключ/значение соответствует условиям:
{
"Condition1": true,
"Condition2": false
}
Основа SPCM описана в блоге Thought Experiment Proposed Complexity Metric for IAM Policy Documents.
Начиная с версии 0.6.0 cfn_nag:
spcm_scan может сканировать каталог шаблонов CloudFormation (как cfn_nag_scan) и формировать отчёт с показателями SPCM
в формате JSON или HTMLcfn_nag_scan --rule-arguments spcm_threshold:100--rule-arguments.
Объекту Rule достаточно объявить attr_accessor, например attr_accessor :spcm_threshold, а cfn_nag
возьмёт на себя детали внедрения значений из --rule-argumentsРелиз 0.5.x включает серьёзные изменения в том, как пользовательские правила (могут) распространяться и загружаться. До этого релиза правила загружались из двух мест: каталога lib/cfn-nag/custom_rules внутри основного гема cfn_nag и каталога пользовательских правил, указанного в командной строке.
Есть два сценария использования, которые потребовали перепроектирования того, как и откуда загружаются пользовательские правила. Механизм загрузки правил был обобщён таким образом, что для обнаружения правил можно использовать репозитории пользовательских правил.
Множество "файлов правил", разбросанных по файловой системе, — это не очень хорошо с точки зрения традиционной разработки ПО.
У этих файлов нет версий и отслеживаемости, поэтому в 0.5.x вводится понятие "cfn_nag rule gem". Разработчик
может создавать пользовательские правила в составе отдельного гема, версионировать его и устанавливать... и эти правила будут использоваться из cfn_nag
до тех пор, пока метаданные гема включают cfn_nag_rules => true. Для гема с именем вида "cfn-nag-hipaa-rules" будут загружены все *.rb из
lib/cfn-nag-hipaa-rules. Любые пользовательские правила должны наследоваться от CfnNag::BaseRule из cfn-nag/base_rule (не cfn-nag/custom-rules/base). Если правило должно наследоваться от чего-то другого, определение метода cfn_nag_rule?, возвращающего true, также приведёт к его загрузке как правила.
Когда cfn_nag работает в AWS Lambda — там нет файловой системы (кроме /tmp) в традиционном смысле. Поэтому из Lambda доступны только основные правила. Для поддержки пользовательских правил cfn_nag умеет обнаруживать правила в корзине S3 вместо файловой системы.
Всё, что вы, вероятно, видели о разработке пользовательских правил на Ruby, остаётся в силе.
Чтобы обнаруживать правила в корзине S3, создайте файл s3.yml со следующим содержимым:
---
repo_class_name: S3BucketBasedRuleRepo
repo_arguments:
s3_bucket_name: cfn-nag-rules-my-enterprise
prefix: /rules
Чтобы применить файлы *Rule.rb из корзины cfn-nag-rules-my-enterprise с префиксом /rules (например, /rules/MyNewRule.rb), укажите этот файл в командной строке cfn_nag следующим образом:
cat my_cfn_template.yml | cfn_nag --rule-repository s3.yml
Если правила находятся более чем в одной корзине, создайте несколько файлов s3*.yml и укажите их в аргументе --rule-repository.
Если текущие AWS-учётные данные имеют разрешение на доступ к корзине cfn-nag-rules-enterprise, то будут найдены все правила
вида /rules/*Rule.rb. Если нужно использовать конкретный aws_profile, добавьте его как ключ в repo_arguments, например
aws_profile: my_aws_profile
Помимо файловой системы, установки гемов и S3, новая архитектура теоретически поддерживает разработку других "репозиториев правил" для загрузки правил из DynamoDb, реляционных баз данных или других веб-сервисов.
Чтобы создавать новые правила для собственного использования и/или вклада в сообщество, обратитесь к разделу Разработка пользовательских правил.
Скринкаст, демонстрирующий полный цикл разработки пользовательского правила по TDD, доступен здесь:
https://www.youtube.com/watch?v=JRZct0naFd4&t=1601s
Для запуска спецификаций убедитесь, что установлен Docker, а зависимости cfn_nag установлены через
gem install bundle
bundle install
Затем, чтобы запустить все спецификации, просто выполните rake test:all.
Для запуска сквозных тестов выполните rake test:e2e. Скрипт соберёт все гемы из Gemfile, локально соберёт и установит гем cfn_nag, установит зависимости для спецификаций, а затем выполнит тесты, помеченные 'end_to_end'. Он также загрузит примеры шаблонов, предоставленные Amazon, и запустит против них cfn_nag_scan, чтобы проверить, не вызывают ли известные корректные шаблоны исключения в cfn-nag.
Чтобы установить текущую git-ветку локально:
bundle install
scripts/deploy_local.sh
Существует полная среда удалённой разработки, созданная и настроенная со всеми предварительно сконфигурированными инструментами и параметрами для удобной разработки и создания правил. Вы можете включить её, используя функциональность удалённой разработки VS Code.
Folder contains a dev container configuration file. Reopen folder to develop in a container, нажмите кнопку Reopen in Container[Dev Container] cfn_nag DevelopmentДополнительная информация о настройке удалённой разработки в VS Code доступна здесь: Удалённая разработка в VS Code.
Чтобы сообщить об ошибке или запросить новую функцию, создайте issue в репозитории GitHub: https://github.com/stelligent/cfn_nag/issues/new