Рецепты обратного прокси и контракт callback
Каждому обратному прокси нужны те же две части: проверка авторизатора, которая спрашивает Caputchin, несёт ли запрос валидный пропуск, и callback на твоём источнике, который превращает решённый пропуск в cookie cpt_gate. Прохождение с nginx показывает форму от края до края; эта страница даёт другие прокси, точный контракт callback, и полный пример Authelia. Замени YOUR_SITE_KEY своим публичным ключом повсюду.
Эндпоинты везде одни и те же:
- Авторизатор:
GET https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY, с пересланным заголовкомCookieпосетителя.204значит разрешить,401значит испытание. - Испытание: перенаправь браузер на
https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<исходный URL>.
Авторизатор отвечает только 204 или 401. Всё остальное, включая таймаут, значит, что Caputchin недостижим, и что будет дальше, решает твой режим отказа: заблокировать запрос (fail closed, безопаснее для портала входа) или пропустить его (fail open). Задай его на ключе сайта, и сгенерированный сниппет совпадёт. Дай подзапросу авторизатора короткий таймаут, чтобы сбой не смог затормозить каждый запрос, пока он решает.
Traefik
Используй middleware forwardAuth для авторизатора. Traefik пересылает входящий заголовок Cookie по умолчанию. При 401 от middleware перенаправь браузер на испытание (middleware страницы ошибки, указывающий на маленький маршрут, который выдаёт 302, или обработай 401 на своём краю).
http:
middlewares:
cpt-gate:
forwardAuth:
address: "https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY"
# The 401 from forwardAuth is your signal to send the browser to:
# https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<original-url>
routers:
portal:
rule: "Host(`auth.example.com`)"
middlewares: ["cpt-gate"]
service: your-appCaddy
Используй forward_auth для авторизатора и handle_errors, чтобы превратить 401 в редирект. Это отправная точка; подстрой синтаксис директив под свою версию Caddy.
auth.example.com {
forward_auth https://verify.caputchin.com {
uri /v1/gate/authz?site=YOUR_SITE_KEY
copy_headers Cookie
}
handle_errors {
@challenge expression {http.error.status_code} == 401
redir @challenge https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return={scheme}://{host}{uri} 302
}
reverse_proxy your-app:8080
}Callback
Размещённая страница проверки завершается автоотправкой формы POST на https://<твой-источник>/__cpt/callback с двумя полями в теле: cpt_gate (пропуском) и to (куда отправить посетителя). Если ты сменил путь callback на ключе сайта, POST уходит туда. Добавь этот один маршрут на свой источник (или как отдельный location на прокси). Он должен:
- Читать
cpt_gateиtoиз тела POST, никогда из URL (пропуск в URL утекает в логи и рефереры). - Подтвердить, что
toна твоём собственном источнике, и отклонить всё остальное, чтобы callback нельзя было превратить в open redirect. - Поставить cookie с атрибутами ниже.
- Перенаправить (
303) наto.
// Any tiny handler on your origin works. Express shown; ~15 lines.
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
const pass = String(req.body.cpt_gate || "");
const to = String(req.body.to || "/");
const target = new URL(to, `https://${req.headers.host}`);
if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
res.setHeader(
"Set-Cookie",
`cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
);
res.redirect(303, target.pathname + target.search);
});Атрибуты cookie составляют всю модель безопасности. Поскольку пропуск не привязан к устройству, HttpOnly (блокирует кражу скриптом), Secure (только HTTPS), SameSite=Lax, и короткий Max-Age (в тон твоему TTL пропуска) это то, что его защищает. Ещё: не логируй значение cpt_gate в своих access-логах, и держи Referrer-Policy: no-referrer на callback, чтобы пропуск никогда не ехал попутчиком в заголовке реферера.
Режим отказа для каждого прокси
Caputchin никогда не видит решения о режиме отказа, так что оно никогда не появляется в твоей статистике: твой прокси принимает его локально, когда не может достучаться до авторизатора.
- nginx:
proxy_intercept_errors on;плюсerror_page 500 502 503 504 = @cpt_unreachable;, где этот именованный location делаетreturn 403(закрытый) илиreturn 204(открытый). - Traefik: middleware
forwardAuthпадает с ошибкой на недостижимом адресе; направь это на сервис, отдающий403(закрытый), или убери middleware с роутера за healthcheck (открытый). - Caddy: расширь
handle_errorsматчером на5xx, который делает либоrespond 403(закрытый), либоrespond 204(открытый).
Ограничение по путям для каждого прокси
Закрывать шлюзом только эти пути и Никогда не закрывать шлюзом эти пути формируют сгенерированную конфигурацию; принуждает к ним твой прокси.
- nginx: один
location ^~ <префикс>на каждый закрытый путь иauth_request off;внутри каждого исключённого. Шаблон по расширению вроде*.phpстановитсяlocation ~* \.php$. - Traefik: вешай middleware
cpt-gateтолько на те роутеры, чьи правила совпадают с закрытыми путями, и не ставь его на остальные. - Caddy: заверни
forward_authв матчер пути, например@gated path /admin/* /login, а затемforward_auth @gated ....
Никогда не закрывай подзапрос авторизатора и свой путь callback.
Разобранный пример: Authelia
Портал входа Authelia представляет собой скомпилированное одностраничное приложение без места, чтобы встроить виджет, и без хука плагина, так что шлюз и есть способ его защитить. Authelia уже сидит за обратным прокси (так работает её собственный ForwardAuth), и этот прокси и есть место для шлюза. Саму Authelia оставляют полностью без изменений.
Поставь шлюз на vhost портала (например auth.example.com):
- Закрой HTML портала и эндпоинт входа первого фактора (
/api/firstfactor). - Исключи собственный verify-эндпоинт ForwardAuth Authelia (
/api/verify), который прокси вызывает на каждый защищённый upstream-запрос. Закрыть его сломало бы каждое защищённое приложение за Authelia. - Исключи health-check и статические ассеты, которые нужны самой странице испытания.
- Добавь маршрут
/__cpt/callbackкак выше (или свой настроенный путь callback).
Эти четыре пункта и есть ровно то, что выражают настройки области путей: / и /api/firstfactor как закрытые пути, /api/verify и твой healthcheck как исключённые.
Шлюз держит ботов подальше от портала; встроенная Regulation Authelia (лимит повторов и бан) по-прежнему ограничивает повторы учётных данных для того, кто прошёл, так что два слоя складываются. Шлюз защищает только интерактивный портал (UI входа и согласия); он не закрывает, и не должен, неинтерактивные эндпоинты токенов.
См. также
- Настрой страничный шлюз на прокси: прохождение с nginx и раскатка с предпросмотром-сначала.
- Настрой испытание под себя: тип испытания, язык, тема, имя cookie, путь callback, область путей.
- Обзор: концепция и cookie пропуска.
- Статистика: подтверди подключение, наблюдая испытание против прямого пропуска.