亚马逊云科技 WAF 部署小指南(六)追踪 Amazon WAF Request ID,排查误杀原因

Amazon CloudFront
Amazon WAF – Web
Amazon API Gateway
0
0
众所周知,中国是全球制造业的巨大力量,许多中国企业通过 2B 电商平台网站进行商品销售和采购。在这些电商平台上,Web 应用防火墙(WAF)成为不可或缺的安全工具。然而,WAF 也可能导致误杀问题。一旦误杀发生,网站管理员需要尽快解决,以免企业客户无法正常下单,造成巨大的损失。而要解决误杀问题,首先需要有能够定位相关日志的线索。 本文将介绍如何利用 Amazon Lambda@Edge,在 [Amazon CloudFront 自定义错误页面](https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/GeneratingCustomErrorResponses.html?trk=cndc-detail)上展示每个由 Amazon WAF 返回的“403 Forbidden”错误的 Request ID。通过这个唯一的 WAF Request ID,网站运维工程师能够快速查询相应的 WAF 日志,找到误杀的原因。随后,可以配置 [Scope-down](https://docs.aws.amazon.com/zh_cn/waf/latest/developerguide/waf-rule-scope-down-statements.html?trk=cndc-detail) 来修复误杀问题。 ### **01 工作原理** CloudFront 的[请求事件](https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request?trk=cndc-detail)包含有 `requestId` 字段,每一个请求都有一个唯一的 [Request ID](https://repost.aws/zh-Hans/knowledge-center/cloudfront-latency-diagnosis-data?trk=cndc-detail) 作为标识。以下是 Lambda@Edge 请求事件的 `requestId` 数据结构。 ```js { "Records": [ { "cf": { "config": { "eventType": "viewer-request", "requestId": "4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==" }, } } ] } ``` 如图 1 所示,CloudFront 自定义错误页面是由 CloudFront(而不是 Client)发起的,它的 Request ID 与 Client 原始请求的 Request ID 相同。因此,我们使用 Lambda@Edge 捕获自定义错误页面的请求,从请求事件中读取 Request ID,插入到预先定义好的 HTML 代码中,直接将这个 HTML 作为 Response body 返回给 CloudFront。 ![image.png](https://dev-media.amazoncloud.cn/1b801064c30849358981589dd8ff1c1c_image.png "image.png") 图 1:CloudFront 自定义错误页面展示 WAF Request ID 的工作流程 ### **02 配置步骤** #### 1. 为 HTTP 状态码 403 创建 CloudFront 自定义错误页面 ![image.png](https://dev-media.amazoncloud.cn/0aaef7a4228842529181fce3a9626645_image.png "image.png") 图 2:为 HTTP 状态码 403 创建 CloudFront 自定义错误页面 按照图 2 所示的方法,为 HTTP 状态码 403 创建 CloudFront 自定义错误页面,并配置错误页面的缓存时间(TTL)和错误页面的 URI path。这个步骤需要注意: - Client 看到的 403 错误页面的 Request ID 都应该是唯一的,所以需要设置“Error caching minimum TTL”为“0”,即不缓存。 - 为了避免错误页面的 URI 受到 DDoS 攻击,产生不必要的 Lambda@Edge 费用,需要将这个 URI path 设置的尽量长一些,复杂一些。我们建议随机生成一个 Universally Unique ID(UUID)作为错误页面的 URI path,并且不要泄露这个 UUID。这个 URI path 所对应的网页并不需要真正存储在源站,因为 Lambda@Edge 会提前终结 CloudFront 的请求。 #### 2. 为 CloudFront 自定义错误页面的 URI path 单独创建一个 Cache Behavior 我们目的是只允许 CloudFront 向自定义错误页面发起的请求才能触发 Lambda@Edge,因此,需要为自定义错误页面的 URI path 单独创建一个[ Cache behavior](https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesCacheBehavior?trk=cndc-detail),单独关联 Lambda@Edge 函数。 ![image.png](https://dev-media.amazoncloud.cn/2114d4ac74c14cf09f3d9ecd50aa8432_image.png "image.png") 图 3:为 CloudFront 自定义错误页面的 URI path 单独创建一个 Cache Behavior 如图 3 所示,这个 Behavior 所配置的缓存策略必须是“Managed-CachingDisabled”。任何一个 Maximum TTL>0 的缓存策略都会使得 CloudFront 向自定义错误页面发起的请求无法触发 Viewer request Lambda@Edge 函数,而只能触发 Origin request Lambda@Edge 函数。 #### 3. 创建 Lambda@Edge 函数,并添加 CloudFront viewer request trigger 复制以下 Python 代码,创建一个 Runtime 为 Python3.X,Architecture 为 X86_64 的 Lambda 函数。您可以编辑 `CONTENT` 变量的值——它实际上就是一个 HTML 网页的代码。您可以根据需求调用合适的 CSS 文件,插入您想要的图片。 ```js CONTENT = ''' <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>WAF custom error page</title> <link rel="stylesheet" href="/css/style.css"/> </head> <body> <h1>403 Forbidden!</h1> <p>Your request was blocked.</p> <p>Request ID: <span id="requestId">{CF_RID}</span></p> <button onclick="copyToClipboard()">Copy Request ID</button> <div id="copyMessage"></div> <script> function copyToClipboard() { var requestId = document.getElementById("requestId").innerText; var tempTextArea = document.createElement("textarea"); tempTextArea.value = requestId; document.body.appendChild(tempTextArea); tempTextArea.select(); document.execCommand("copy"); document.body.removeChild(tempTextArea); document.getElementById("copyMessage").textContent = "Copied!"; } </script> </body> </html> ''' def lambda_handler(event, context): record = event['Records'][0]['cf'] request_id = record['config']['requestId'] response = CONTENT.replace('{CF_RID}', request_id) return { 'status': 403, 'statusDescription': 'Forbidden', 'headers': { 'content-type': [{ 'key': 'Content-Type', 'value': 'text/html' }] }, 'body': response } ``` 如图 4 所示,为这个 Lambda 函数添加一个 Trigger。Resource 类型为“CloudFront”,Event type 类型为“viewer-request”,Path pattern 为 CloudFront 自定义错误页面的 URI path。 ![image.png](https://dev-media.amazoncloud.cn/89637e97408442ed8cf26d43a9c7682b_image.png "image.png") 图 4:为 Lambda 函数添加 Trigger #### 4. 创建 WAF WebACL 并关联 CloudFront distribution 最后,我们创建一个 WAF WebACL,配置一个 WAF 规则,匹配 URI path `/waf-id` 来产生 Block 的动作。另外,我们还创建了一个限速规则匹配 URI path `/rate-based-rule` 并配置了 WAF 自定义响应。我们会在下文介绍这样做的目的。 ![image.png](https://dev-media.amazoncloud.cn/167aff10bb0349a0be2dfd5a0fd9a388_image.png "image.png") 图 5:配置 WAF WebACL 用于测试 Block 动作 ### **03 CloudFront 自定义错误页面展示 WAF Request ID 的效果** 浏览器访问 `https://d123.cloudfront.net/waf-id` ,触发了 WAF block 动作,成功显示如图 6 所示的自定义错误页面(截图中的几个 JS 脚本是 Chrome 浏览器的插件所产生的,与本次测试无关)。 ![image.png](https://dev-media.amazoncloud.cn/46262505906f4c04b2db110fa4cb3dbf_image.png "image.png") 图 6:自定义错误页面和 WAF unique request ID 测试结果 测试结果如下: - 自定义错误页面可以显示 CloudFront request ID,并且和 X-Amz-Cf-Id response header 的值相同 - 每一次请求都可以得到不同的 Request ID - 浏览器无法观察到 CloudFront 自定义错误页面的真实 URI path - 点击“Copy Request ID”按钮即可将 Request ID 复制到操作系统剪切板 ### **04 通过 Request ID 查询 WAF 日志的方法** 企业客户通常习惯于使用在线通讯工具直接与网站运营方联系。在遇到 WAF 误杀时,他们通常使用通讯工具向网站提供错误页面的截图。我们的错误页面提供一键复制 Request ID 的按钮,企业客户也可以很方便地使用通讯工具发送 Request ID 的文本。网站运维工程师在收到 Request ID 之后,即可在 WAF 日志监控系统上查询到对应的 WAF 拦截日志。具体的查询方法取决于监控 WAF 日志的方式。 #### 在 Centralized Logging with OpenSearch 监控平台上查询 Request ID 如果网站使用了 [Centralized Logging with OpenSearch 解决方案](https://aws.amazon.com/cn/solutions/implementations/centralized-logging-with-opensearch/?trk=cndc-detail)监控 WAF 日志,可以在仪表盘上添加“Request ID”作为过滤条件。 步骤如下: 1)点击仪表盘右上角的“Edit”按钮 ![image.png](https://dev-media.amazoncloud.cn/3e30d3f74700405894dd1ee80d4d9bc2_image.png "image.png") 2)点击“Filters”面板右上角的齿轮图标,再点击“Edit visualization”菜单 ![image.png](https://dev-media.amazoncloud.cn/75535df7be124a2289196cc6a7daf9a7_image.png "image.png") 3)“Add”一个 Options list,输入 Control Label 名称,选择 Index Pattern,Field 选择 `httpRequest.requestId.keyword` ![image.png](https://dev-media.amazoncloud.cn/9845a12b50604e2bae704062ce57a2d2_image.png "image.png") 4)点击网页右下角蓝色的“Update”按钮,更新 Visualization 5)点击网页右上角“Save”按钮,保存对仪表板所做的更改 Centralized Logging with OpenSearch 的仪表盘支持模糊查询。虽然 WAF Request ID 比较长,但只需要输入几个字符就可以找到完整的 Request ID,进而找到对应的 WAF 日志。 ![image.png](https://dev-media.amazoncloud.cn/2e3e5337a13b4a60a4677ebccdebe13f_image.png "image.png") 图 7:使用 Centralized Logging with OpenSearch 检索 Request ID #### 使用 Amazon CloudWatch Log Insight 查询 Request ID 如果 WAF 日志保存在 CloudWatch log group,可以使用下面的 CloudWatch log insight 查询语句检索 Request ID: ```js fields @message, httpRequest.requestId as requestId | filter requestId = "tMzyyrTJhk5XiBbowY2v-WY5m-PGluVYKggI6KIJhlTHBlqpDEGQOQ==" # 替换成需要检索的Request ID. | display @message ``` 也可以使用 `like` 方法进行模糊查询: ```js fields @message, httpRequest.requestId as requestId | filter requestId like "tMzyy" # 替换成需要检索的 Request ID | display @message ``` 下面的图 8 是 CloudWatch log insight- 检索结果的部分截图。点击左边的黑色三角形符号,即可展开完整的日志。 ![image.png](https://dev-media.amazoncloud.cn/30c5a44e7b564d6f82cc464df23dcaf6_image.png "image.png") 图 8:CloudWatch log insight 检索 Request ID 的部分截图 #### 使用 Amazon Athena 查询 Request ID 如果 WAF 日志保存在 [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) 桶,并且没有使用 OpenSearch 等工具创建仪表盘,则先创建 [WAF 日志表](https://docs.aws.amazon.com/zh_cn/athena/latest/ug/waf-logs.html?trk=cndc-detail),再使用下面的 SQL 语句检索 Request ID,并使用 like 方法进行模糊查询: ```js select from_unixtime(timestamp/1000) as datetime, * from "waf_log_db"."waf_request_id" where httprequest.requestid like '%mLNIV%' ``` 您需要将“waf_request_id”替换成您自己的数据表的名字,并将“mLNIV”替换成您希望检索的 Request ID(需要保留前后两个“%”符号)。下面的图 9 是 Athena 检索结果的部分截图,向右滚动页面即可显示所有日志字段。 ![image.png](https://dev-media.amazoncloud.cn/61c1109ed28a4755b2604462b68957c5_image.png "image.png") 图 9:Athena 检索 Request ID 的部分截图 #### 成本估算 假设每月 10 Billion WAF 请求,其中 0.1% 为 Blocked 请求,即 10 Million。Lambda@Edge 内存 128GB,保守估计平均每个请求 Duration 为 5ms。使用 [Amazon 价格计算器](https://calculator.aws/#/addService/Lambda?trk=cndc-detail)评估的每月 Lambda@Edge 含免费套餐的成本为 1.80 USD,不含免费套餐的成本为 2.10 USD。 另外,CloudFront 自定义页面所返回的 HTML 和 CSS 也会增加少量的数据流出(DTO)费用。本文所使用的 HTML 和 CSS,加上它们的 HTTP response headers,一共不到 2KB。10 Million 请求一共产生 20GB 的 DTO,没有超出免费套餐。如果不考虑免费套餐,每月产生大约 2 USD 的 CloudFront DTO 成本。如果不使用 CSS 美化自定义页面,成本可以更低。 ### **05 避免 DDoS 事件消耗 Lambda@Edge 成本** 上文“配置步骤 1”介绍了使用 UUID 作为 CloudFront 自定义错误页面的 URI path,避免错误页面的 URI 受到 DDoS 攻击,产生不必要的 Lambda@Edge 费用。另外,HTTP Flood 等 DDoS 攻击会触发 WAF 限速规则产生大量的 403 error。我们通常不需要关注限速规则的误杀,所以不需要使用 Lambda@Edge 函数为限速规则的 Block 动作展示 WAF Request ID。解决办法是使用 WAF 自定义响应覆盖 CloudFront 自定义错误页面。 按照图 10 的方法,为 WAF 规则的 Block 动作配置自定义响应。WAF 自定义响应的优先级高于 CloudFront 自定义错误页面,所以限速规则返回的 403 error 并不会触发 CloudFront 请求自定义错误页面,因此也不会触发 Lambda@Edge 函数。 ![image.png](https://dev-media.amazoncloud.cn/f84eb0b8a1aa4f5ebc75fb5d67006b62_image.png "image.png") 图 10:为 WAF 规则配置自定义响应 #### WAF 自定义响应的测试结果 我们在之前的“配置步骤 4”已经创建了一个限速规则,匹配 URI path `/rate-based-rule` 并配置了 WAF 自定义响应。测试效果如图 11 所示: ![image.png](https://dev-media.amazoncloud.cn/4042b4e7f3284f9e94aed92086502fbf_image.png "image.png") 图 11:WAF自定义响应测试结果 对于其他(几乎)不会产生误杀的规则,在错误页面展示 Request ID 并不必要,都可以按此方法为它们配置 WAF 自定义响应,避免执行 Lambda@Edge 函数。 ### **06 方案总结** 按照本文所介绍的方法,仅需支付少量的 Lambda@Edge 和 CloudFront DTO 费用,完成简单的配置操作,即可实现 WAF unique request ID 解决方案。通过唯一的 WAF request ID,网站管理员可以快速排查和解决误杀问题,缩短 WAF 规则的评估时间,改善用户体验。这个解决方案也适用于 Amazon ALB,[Amazon API Gateway](https://aws.amazon.com/cn/api-gateway/?trk=cndc-detail),以及其他所有能够作为 CloudFront 源站的 Web 应用或服务。只需要部署 CloudFront distribution 加速 ALB、API Gateway 或其他 Web 应用,并将 WAF WebACL 关联到 CloudFront distribution,即可支持 403 错误页面显示 WAF unique request ID。同时,CloudFront 还提供边缘加速,并降低 Amazon 源站的 DTO 成本。 ![开发者尾巴.gif](https://dev-media.amazoncloud.cn/22e638fcd3374997a6b83b0b6db6739f_%E5%BC%80%E5%8F%91%E8%80%85%E5%B0%BE%E5%B7%B4.gif "开发者尾巴.gif")
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭