本实验将介绍如何利用 Amazon Lambda@Edge,在 Amazon CloudFront 自定义错误页面 上展示每个由 Amazon WAF 返回的“403 Forbidden”错误的 Request ID。通过这个唯一的 WAF Request ID,网站运维工程师能够快速查询相应的 WAF 日志,找到误杀的原因。随后,可以配置 Scope-down 来修复误杀问题。
CloudFront 自定义错误页面是由 CloudFront(而不是 Client)发起的,它的 Request ID 与 Client 原始请求的 Request ID 相同。因此,我们使用 Lambda@Edge 捕获自定义错误页面的请求,从请求事件中读取 Request ID,插入到预先定义好的 HTML 代码中,直接将这个 HTML 作为 Response body 返回给 CloudFront。
我们目的是只允许 CloudFront 向自定义错误页面发起的请求才能触发 Lambda@Edge,因此,需要为自定义错误页面的 URI path 单独创建一个 Cache behavior,单独关联 Lambda@Edge 函数。这个 Behavior 所配置的缓存策略必须是“Managed-CachingDisabled”。任何一个 Maximum TTL>0 的缓存策略都会使得 CloudFront 向自定义错误页面发起的请求无法触发 Viewer request Lambda@Edge 函数,而只能触发 Origin request Lambda@Edge 函数。
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。
使用 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和规则。