
CloudFormation 模板的 lint 检查工具
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 版本开始: