Amazon Firewall Manager (FMS)是一项安全管理服务,可让您在 Amazon Organizations 中跨账户和应用程序集中配置和管理防火墙规则。在创建新应用程序时,您可以借助 Firewall Manager 实施一套通用的安全规则,更轻松地让新应用程序和资源从一开始就达到合规要求。在多account的organization环境中,Amazon Firewall Manager可以大幅简化WAF的配置工作,集中管理大量Web ACL。
本文将介绍一个FMS的隐藏功能,我们知道Amazon WAF是支持配置请求被阻挡时返回给客户的 HTTP 状态代码和响应正文。这些代码可用于将用户重定向到应用程序的不同部分,或者根据用户被 WAF 阻挡的原因向其提供具体的响应代码。此外,您还可以使用自定义响应功能包含原因正文,以向用户返回自定义错误消息。自定义响应功能很强大实用,但是实际使用中会遇到以下两个问题:
1. 自定义响应只能在用户自建的rule group中设置,而对于AWS managed rule group的拦截响应内容,是无法直接修改的;
2. 当WAF规则和规则组很多的时候,只能在每个rule group中单独设置自定义响应内容,rule group之间无法共享这一设置;
那么是否可以通过FMS集中设置管理自定义HTTP状态代码和响应正文呢?在FMS的Web Console上无论是是创建还是修改Policy似乎都没有这一选项。
![image.png](https://dev-media.amazoncloud.cn/9640f0a3dfe14a9188e29c7844dad5a7_image.png "image.png")
关于FMS的自定义响应,并没有详细的文档描述如何设置,只有一个简短的提示:https://aws.amazon.com/cn/about-aws/whats-new/2022/09/aws-firewall-manager-support-aws-waf-custom-requests-responses/
那么,如何开启这一功能呢?其实,我们可以使用Terraform或CloudFormation来通过FMS集中管理Web ACL的自定义HTTP 状态代码和响应正文,从而使用FMS这一强大的“隐藏功能”。
以下是经过精简的FMS Policy配置Terraform代码,只保留了自定义响应相关的部分,读者可以根据自己的需要进行扩展。
```
# 创建 Amazon Firewall Manager (FMS) 策略的 Terraform 资源
resource "aws_fms_policy" "myfmspolicy" {
# 策略名称
name = "MY-FMS-POLICY"
# 在一定时间内未使用的 Amazon Firewall Manager 管理的资源将被删除
delete_unused_fm_managed_resources = true
# 是否启用自动修复
remediation_enabled = true
# 策略应用的资源类型
resource_type_list = [
"AWS::ElasticLoadBalancingV2::LoadBalancer",
"AWS::ApiGateway::Stage"
]
# 与策略关联的服务配置数据
security_service_policy_data {
# 安全服务的类型,此处使用 WAFV2
type = "WAFV2"
# 注意,这里是配置的关键,应用了terraform的模板功能,以方便在不同环境和策略间共享配置,从模板文件中获取
managed_service_data = templatefile("security_service_policy_data_default.tftpl", {
post_block_rule_group_arn = aws_wafv2_rule_group.postblockrulegroup.arn
})
}
}
```
security_service_policy_data.tftpl的内容如下:
```
# 使用 jsonencode 函数将 HCL 代码转换为 JSON 格式
\${jsonencode({
# 安全服务的类型
type = "WAFV2",
# 自定义请求处理 (未设置)
customRequestHandling = null,
# 这里就是自定义响应配置关键属性
customResponse = {
# 自定义响应体配置
customResponseBodies = {
"DefaultResponse" = {
# 自定义响应体内容(HTML 格式,显示警告信息)
responseBody = "<!DOCTYPE html><html lang=\\"en\\"><head><title>WAF Alert</title></head><body><div class=\\"container\\"><h1>ALERT</h1><h2>Your request has been denied by our Web Application Firewall (WAF)</h2><p text-align=\\"left\\">Any attempts of malicious access are strictly prohibited.</p><h1>警告</h1><h2>您的请求已被我们的Web应用程序防火墙(WAF)拒绝</h2><p text-align=\\"left\\">严禁任何恶意访问。</p></div></body></html>",
# 响应体类型为 HTML
responseBodyType = "TEXT_HTML",
},
},
# 使用自定义响应
customResponseBodyKey = "DefaultResponse",
enableCustomResponse = true,
# 响应头(未设置)
responseHeaders = null,
# 自定义响应的 HTTP 响应代码
responseCode = 403,
},
# 规则组处理配置
postProcessRuleGroups = [
# 第一个规则组:
{
ruleGroupType = "ManagedRuleGroup",
ruleGroupArn = null,
sampledRequestsEnabled = true,
excludeRules = [],
managedRuleGroupIdentifier = {
version = null,
vendorName = "AWS",
versionEnabled = null,
managedRuleGroupName = "AWSManagedRulesCommonRuleSet",
},
overrideAction = {
type = "NONE",
},
ruleActionOverrides = [
# 用于覆盖规则动作的设置,这里将所有规则的Action都替换为Count, 而不是Block
{
name = "BadBots_Header",
actionToUse = {
count = {},
},
}
# 其他规则略
],
},
# 其他规则设置省略,下面是根据之前规则标签进行实际拦截的规则组:
{
ruleGroupArn = "\${post_block_rule_group_arn}",
ruleGroupType = "RuleGroup",
excludeRules = [],
overrideAction = {
type = "NONE",
},
managedRuleGroupIdentifier = null,
sampledRequestsEnabled = null,
},
],
# 未触发任何规则时的默认动作
defaultAction = {
type = "ALLOW",
},
})}
```
以下对上述代码做进一步的说明。除了定义自定义响应的属性以外,post_block_rule_group是另一个配置的关键。以上配置的执行逻辑如下图所示:
![image.png](https://dev-media.amazoncloud.cn/00130eda6c544594a1c47f24fc242a6e_image.png "image.png")
符合条件的Web请求不会在AWS Managed Rule Goup直接进行拦截,而只是打上对应的label,而实际的拦截操作是在自定义的post_block_rule_group中进行的,通过这种特征匹配和拦截分开的设计,即可让AWS Managed Rule Goup的拦截也可以使用自定义的HTTP状态码和响应内容。
post_block_rule_group的内容如下:
```
# 创建 AWS WAFv2 规则组资源
resource "aws_wafv2_rule_group" "postblockrulegroup" {
# 规则组容量
capacity = "150"
# 规则组描述
description = "Block requests with specified labels."
# 规则组名称
name = "POST-Block-rg"
# 规则组作用范围(此处设置为区域级别)
scope = "REGIONAL"
# 自定义响应体配置
custom_response_body {
# content 设置为来自 "custom_response.tftpl" 模板文件的响应内容
content = templatefile("custom_response.tftpl", {})
# 内容类型设置为 HTML
content_type = "TEXT_HTML"
# 自定义响应密钥
key = "DefaultResponse"
}
# 定义真正的拦截规则,注意这条规则要放置在所有规则的最后
rule {
# 动作设置为阻止请求,并返回自定义响应
action {
block {
custom_response {
# 使用 "DefaultResponse" 作为自定义响应密钥
custom_response_body_key = "DefaultResponse"
# 自定义响应 HTTP 状态码
response_code = 403
}
}
}
# 规则名称
name = "block-restricted-extensions"
# 规则优先级(数值越低,优先级越高)
priority = 1
# 规则匹配条件
statement {
# 或条件匹配
or_statement {
statement {
# 按标签匹配规则
label_match_statement {
# 标签键
key = "awswaf:managed:aws:core-rule-set:BadBots_Header"
# 标签搜索范围
scope = "LABEL"
}
}
# 其他规则的匹配条件,已省略
}
}
# 可见性配置
visibility_config {
# 不启用 CloudWatch Metrics
cloudwatch_metrics_enabled = false
# 可见性配置的指标名称
metric_name = "PostBlock"
# 不启用采样请求
sampled_requests_enabled = false
}
}
# 规则组可见性配置
visibility_config {
# 不启用 CloudWatch Metrics
cloudwatch_metrics_enabled = false
# 可见性配置的指标名称
metric_name = "RuleGroupMetrics"
# 不启用采样请求
sampled_requests_enabled = false
}
}
```
最后,让我们来看下这套Terraform apply到环境中的效果。创建FMS Policy后,FMS会根据Policy在指定的accounts中创建Web ACL并挂载到指定的ALB或API Gateway上。当我们基于FMS管理的Web ACL上增加自己的规则的时候,如果规则的Action设置为Block,并且Enable 'Custom response',则可以看到通过FMS集中配置的定制化响应内容
![image.png](https://dev-media.amazoncloud.cn/68081ca4dd5147c3bfa790d8508d6b6a_image.png "image.png")
![image.png](https://dev-media.amazoncloud.cn/3099652e874c43d6a03e8bac0188ef6d_image.png "image.png")
这样,就可以在不同Account的Web ACL和不同的规则间,共享同一套通过FMS管理的自定义响应内容,而不用一个一个单独设置了。
总结一下,目前自定义的HTTP状态码和响应内容只能通过API、Terraform或CloudFormation模板在Amazon Firewall Manager中使用,这一功能简化了在大规模环境下批量设置WAF定制化响应内容的工作,不过FMS设置的自定义响应目前只有自定义规则可以引用,AWS Managed Rule Group和FMS管理的规则,仍需要单独进行设置。使用基础架构即代码(IaC)的方式来配置FMS,是充分发挥FMS功能特性的最佳方式。