文章目录
- 1. 方案介绍
- 2. 架构图
- 3. 操作演示
本实验将介绍如何利用 Amazon Lambda@Edge,在 Amazon CloudFront 自定义错误页面 上展示每个由 Amazon WAF 返回的“403 Forbidden”错误的 Request ID。通过这个唯一的 WAF Request ID,网站运维工程师能够快速查询相应的 WAF 日志,找到误杀的原因。随后,可以配置 Scope-down 来修复误杀问题。
1. 方案介绍
CloudFront 自定义错误页面是由 CloudFront(而不是 Client)发起的,它的 Request ID 与 Client 原始请求的 Request ID 相同。因此,我们使用 Lambda@Edge 捕获自定义错误页面的请求,从请求事件中读取 Request ID,插入到预先定义好的 HTML 代码中,直接将这个 HTML 作为 Response body 返回给 CloudFront。
2. 架构图
3. 操作演示
- 为 HTTP 状态码 403 创建 CloudFront 自定义错误页面。
- Client 看到的 403 错误页面的 Request ID 都应该是唯一的,所以需要设置“Error caching minimum TTL”为“0”,即不缓存。
- 为了避免错误页面的 URI 受到 DDoS 攻击,产生不必要的 Lambda@Edge 费用,需要将这个 URI path 设置的尽量复杂。
- 为 CloudFront 自定义错误页面的 URI path 单独创建一个 Cache Behavior
我们目的是只允许 CloudFront 向自定义错误页面发起的请求才能触发 Lambda@Edge,因此,需要为自定义错误页面的 URI path 单独创建一个 Cache behavior,单独关联 Lambda@Edge 函数。这个 Behavior 所配置的缓存策略必须是“Managed-CachingDisabled”。任何一个 Maximum TTL>0 的缓存策略都会使得 CloudFront 向自定义错误页面发起的请求无法触发 Viewer request Lambda@Edge 函数,而只能触发 Origin request Lambda@Edge 函数。
- 创建 Lambda@Edge 函数,并添加 CloudFront viewer request trigger。 复制以下 Python 代码,创建一个 Runtime 为 Python3.X,Architecture 为 X86_64 的 Lambda 函数。
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
}
为这个 Lambda 函数添加一个 Trigger。Resource 类型为“CloudFront”,Event type 类型为“viewer-request”,Path pattern 为 CloudFront 自定义错误页面的 URI path。
- 创建 WAF WebACL 并关联 CloudFront distribution 最后,我们创建一个 WAF WebACL,配置一个 WAF 规则,匹配 URI path /waf-id 来产生 Block 的动作。
- CloudFront 自定义错误页面展示 WAF Request ID 的效果 浏览器访问 https://d123.cloudfront.net/waf-id,触发了 WAF block 动作,成功显示如图 6 所示的自定义错误页面(截图中的几个 JS 脚本是 Chrome 浏览器的插件所产生的,与本次测试无关)。
使用 Amazon CloudWatch Log Insight 查询 Request I`D
如果 WAF 日志保存在 CloudWatch log group,可以使用下面的 CloudWatch log insight 查询语句检索 Request ID:
fields @message, httpRequest.requestId as requestId
| filter requestId = "tMzyyrTJhk5XiBbowY2v-WY5m-PGluVYKggI6KIJhlTHBlqpDEGQOQ==" # 替换成需要检索的Request ID.
| display @message
也可以使用 like 方法进行模糊查询:
fields @message, httpRequest.requestId as requestId
| filter requestId like "tMzyy" # 替换成需要检索的 Request ID
| display @message
CloudWatch log insight- 检索结果的部分截图。点击左边的黑色三角形符号,即可展开完整的日志,可以查看WAF的action和规则。