2025, Dec 31 00:08
Кастомный 403 для AWS WAF и CloudFront через Lambda@Edge: почему Viewer Response не срабатывает и рабочий вариант на Origin Request
Разбираем, почему при блокировке AWS WAF CloudFront не вызывает Viewer Response, и как отдать кастомный 403 HTML через Lambda@Edge на Origin Request — решение
Когда вы комбинируете AWS WAF с CloudFront и пытаетесь настроить видимый пользователю ответ через Lambda@Edge, легко захотеть подключиться к событию Viewer Response и переписать тело при 403. На практике это не сработает так, как вы рассчитываете. Если вы видите стандартный 403 от WAF, а ваша функция Lambda@Edge не запускается, вы упираетесь в то, как CloudFront вызывает события для ответов с ошибками.
Постановка задачи
Цель простая: когда запрос блокируется, показать небольшую HTML‑страницу с IP клиента. Функция привязана к Viewer Response и проверяет статус ответа, чтобы отдать собственный HTML.
def edge_entry(evt, ctx):
ip_addr = evt['Records'][0]['cf']['request']['clientIp']
resp_status = evt['Records'][0]['cf']['response']['status']
resp_headers = evt['Records'][0]['cf']['response']['headers']
passthrough = evt['Records'][0]['cf']['response']
html_doc = f"""
<!DOCTYPE html>
<html>
<head><title>Access Denied</title></head>
<body>
<h1>Access Denied</h1>
<p>Your IP address is: {ip_addr}</p>
</body>
</html>
"""
if resp_status == '403':
crafted = {
'status': '403',
'statusDescription': 'Forbidden',
'headers': resp_headers,
'body': html_doc,
'bodyEncoding': 'text',
}
return crafted
else:
return passthrough
Почему это не работает
Событие Viewer Response не срабатывает на ответах с кодами > 4xx. Поэтому ваша функция не запускается, когда WAF блокирует запрос. Более того, если вы не настроили собственный ответ, платформа вернет стандартную страницу блокировки. Как сказано:
WAF вернет клиенту стандартный ответ о блокировке, если ни в самом WAF, ни в защищаемом ресурсе (в данном случае CloudFront) не настроен пользовательский ответ.
Смена типа при проверке статуса (строка против числа) не поможет; это не ошибка сравнения, дело в том, что событие вообще не генерируется для ответов о блокировке.
Что делать вместо этого
Если вам нужна динамическая страница для заблокированных запросов, стройте решение вокруг обработки ошибок CloudFront и события Origin Request. Настройте в CloudFront пользовательский ответ для HTTP 403, создайте behavior для этого пути с отключенным кэшированием и привяжите вашу функцию Lambda@Edge к событию Origin Request для данного behavior. В таком потоке функция не получит ответ от origin для анализа, поэтому она должна всегда сама возвращать динамический 403.
Рабочий подход: всегда отдавайте 403 на Origin Request
Ниже функция, которая собирает HTML и возвращает 403 каждый раз, когда вызывается в рамках этого behavior. IP по‑прежнему читается из запроса, так что вы можете персонализировать страницу блокировки, не завися от ответа origin.
def edge_origin_request(ev, cx):
requester_ip = ev['Records'][0]['cf']['request']['clientIp']
content = f"""
<!DOCTYPE html>
<html>
<head><title>Access Denied</title></head>
<body>
<h1>Access Denied</h1>
<p>Your IP address is: {requester_ip}</p>
</body>
</html>
"""
return {
'status': '403',
'statusDescription': 'Forbidden',
'headers': {},
'body': content,
'bodyEncoding': 'text',
}
Почему это важно
Понимание того, какие события CloudFront генерирует в ошибочных сценариях, экономит время и избавляет от слепой отладки. Viewer Response — не то место, где стоит менять тела ответов для заблокированных запросов, когда такие ответы до этой стадии вообще не доходят. Используйте пользовательский обработчик ошибок CloudFront и перенесите логику в Origin Request — так вы сохраняете контроль, а AWS WAF продолжает применять блокировку.
Выводы
Если WAF возвращает 403 до того, как сработает Viewer Response, функция не запустится, даже будучи правильно привязанной. Настройте в CloudFront пользовательский ответ 403, направьте его через behavior с отключенным кэшированием и обработайте рендеринг в Lambda@Edge на Origin Request, которая всегда отдает страницу 403. Это сохраняет применение политики на периферии и дает предсказуемый, динамический результат для заблокированного трафика.