cfn-nag 工具用于查找 CloudFormation 模板中可能表明基础设施不安全的模式。大致来说,它会查找:
关于该工具的更多背景,请参阅 Stelligent 博客上的这篇文章:
利用 "cfn-nag" 在 CloudFormation 模板开发流程早期发现安全问题
假设已安装 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 <path to cloudformation json>
该路径可以是目录或特定的模板。如果是目录,则将处理所有 .json, .template、.yml 和 .yaml 文件,包括递归到子目录中。
默认输出格式是自由格式文本,但也可以通过 --output-format json 标志选择 JSON 输出。
可选地,--debug 标志会转储规则加载的内部信息。
运行 --help 可查看所有受支持开关的完整列表。
要查看 cfn-nag 当前支持的所有规则列表,可以使用一个命令行工具将它们输出到 stdout:
cfn_nag_rules
为方便起见,提供了一个 Dockerfile。它已作为 stelligent/cfn_nag 发布在 DockerHub 上。
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
cfn-nag 支持 "profile(配置文件)" 的概念,它实际上是一个要应用的规则允许列表。该配置文件是一个文本文件,每行必须包含一个规则标识符。当通过 --profile-path 命令行参数指定时,cfn-nag 将只返回来自这些特定规则的违规。
创建 "profile" 的动机在于,不同的开发者可能关心不同的规则。例如,"infrastructure_developer" 可能关心 IAM 规则,而 "app_developer" 可能甚至无法创建 IAM 资源,因此不关心这些规则。
以下是一个 profile 示例:
F1
F2
F27
W3
W5
拒绝列表基本上与 profile 相反:它是一个从不应用的规则列表。当通过 --deny-list-path 命令行参数指定时,cfn-nag 将永远不会返回文件中指定的那些特定规则的违规。
如果某个规则同时在两者中指定,则拒绝列表优先于 profile,并且该规则不会被应用。
格式如下。唯一两个重要字段是 RulesToSuppress 和每个条目的 id。reason 不会被 cfn-nag 解释,但建议用来说明并记录为什么该规则永远不应被应用。
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
如果有想要抑制的规则,可以在受影响的资源上添加 cfn_nag 的 Metadata 键,告诉 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
通过添加 metadata,可以抑制这些警告:
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 模板参数可能会给静态分析带来问题,因为参数值是在部署时指定的。换句话说,静态分析完成时这些值并不可用——静态分析只能查看它面前的“代码”。因此,如果 cidr 被参数化,并且 0.0.0.0/0 在部署时传入,则安全组入站规则 0.0.0.0/0 不会被标记。
为了允许检查参数值,用户可以在通过命令行传给 cfn_nag 和 cfn_nag_scan 的 JSON 文件中指定参数值,使用 --parameter-values-path=<filename/uri> 标志。
JSON 的格式是一个单独的键 "Parameters",其值是一个字典,每个键/值对映射到 Parameters:
{
"Parameters": {
"Cidr": "0.0.0.0/0"
}
}
这将把 "0.0.0.0/0" 提供给以下 Parameter:
Parameters:
Cidr:
Type: String
请注意,如果 JSON 中有额外的参数,它们会被静默忽略(以允许 cfn_nag_scan 将同一个 JSON 应用于所有模板)。
如果 JSON 格式错误或不符合上述规范,解析将以 FATAL 违规失败。
在 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"
}
}
在 cfn_nag 0.4.66 版本之前,底层模型不会对模板中的 Fn::If 做任何处理。这意味着,如果属性具有条件值,则需要规则来解析 Fn::If。鉴于 Fn::If 几乎可以出现在任何地方,这给规则开发者带来了一种“打地鼠”式的困境。充其量,规则逻辑可以忽略那些为 Hash 的值——前提是该值原本就不是 Hash。
为了解决此问题,cfn_nag 现在的默认行为是用 true 分支的结果替换 Fn::If。这意味着默认情况下,规则不会检查 false 分支结果中的安全违规。
除了在属性值级别替换 Fn::If 之外,同样的行为也应用于 Properties 顶层的 Fn::If。例如:
Resource1:
Type: Foo
Properties: !If
- IsNone
- Description: Up
- Description: DOwn
看起来将与以下内容相同:
Resource1:
Type: Foo
Properties:
Description: Up
为了对上述行为提供一定控制,用户可以在通过命令行传给 cfn_nag 和 cfn_nag_scan 的 JSON 文件中指定条件值,使用 --condition-values-path=<filename/uri> 标志。
该 JSON 的格式是一个字典,每个键/值对映射到 Conditions:
{
"Condition1": true,
"Condition2": false
}
SPCM 的基础在博客文章 IAM 策略文档复杂度度量的思想实验 中有所描述。
从 cfn_nag 0.6.0 版本开始:
spcm_scan 可以扫描 CloudFormation 模板目录(类似 cfn_nag_scan),并生成包含 SPCM 指标的 JSON 或 HTML 格式报告cfn_nag_scan --rule-arguments spcm_threshold:100--rule-arguments 机制开发规则,以接受最终用户对设置的配置值。Rule 对象只需声明一个 attr_accessor,例如 attr_accessor :spcm_threshold,cfn_nag 将处理从 --rule-arguments 注入值的细节。0.5.x 版本在自定义规则的分发和加载方式上引入了一些重大变化。在此版本之前,规则有两个加载位置:核心 cfn_nag gem 中的 lib/cfn-nag/custom_rules 目录,以及命令行中指定的 custom-rule-directory。
有两个用例促使重新设计了自定义规则的加载方式和位置。规则加载机制已通用化,使得自定义规则仓库可用于发现规则。
从传统软件开发的角度来看,一堆“规则文件”散落在文件系统中并不理想。这些文件没有版本或可追溯性,因此 0.5.x 引入了 “cfn_nag rule gem” 的概念。开发者可以在一个独立的 gem 中开发自定义规则、为其设定版本并安装……只要 gem 元数据包含 cfn_nag_rules => true,cfn_nag 就会引用这些规则。对于类似 "cfn-nag-hipaa-rules" 的 gem,lib/cfn-nag-hipaa-rules 下的任何 *.rb 文件都会被加载。任何自定义规则都应继承自 cfn-nag/base_rule 中的 CfnNag::BaseRule(不是 cfn-nag/custom-rules/base)。如果规则必须继承自其他类,定义一个返回 true 的 cfn_nag_rule? 方法也会使其作为规则被加载。
当 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
要应用存储桶 cfn-nag-rules-my-enterprise 中前缀为 /rules(例如 /rules/MyNewRule.rb)的 *Rule.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。
除了文件系统、gem 安装和 S3 之外,新架构理论上还支持开发其他“规则仓库”,以从 DynamoDb、关系数据库或其他 Web 服务加载规则。
要为个人使用和/或社区贡献编写新规则,请参阅 自定义规则开发 了解详情。
这里有一段演示从零到一进行 TDD 自定义规则开发的录屏:
https://www.youtube.com/watch?v=JRZct0naFd4&t=1601s
要运行 specs,你需要确保已安装 Docker,并通过以下方式安装 cfn_nag 依赖:
gem install bundle
bundle install
然后,要运行所有 specs,只需运行 rake test:all。
要运行端到端测试,请运行 rake test:e2e。该脚本将捆绑 Gemfile 中的所有 gem,在本地构建并安装 cfn_nag gem,安装 spec 依赖项,然后执行标记为 '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 Remote Development。
要报告 bug 或请求功能,请通过 GitHub 仓库提交 issue:https://github.com/stelligent/cfn_nag/issues/new