Caputchin
Прокси-барьер

Рецепты обратного прокси и контракт callback

Открыть как Markdown

Каждому обратному прокси нужны те же две части: проверка авторизатора, которая спрашивает 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>.

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-app

Caddy

Используй 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 (куда отправить посетителя). Добавь этот один маршрут на свой источник (или как отдельный location на прокси). Он должен:

  1. Читать cpt_gate и to из тела POST, никогда из URL (пропуск в URL утекает в логи и рефереры).
  2. Подтвердить, что to на твоём собственном источнике, и отклонить всё остальное, чтобы callback нельзя было превратить в open redirect.
  3. Поставить cookie с атрибутами ниже.
  4. Перенаправить (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, чтобы пропуск никогда не ехал попутчиком в заголовке реферера.

Разобранный пример: Authelia

Портал входа Authelia это скомпилированное одностраничное приложение без места, чтобы встроить виджет, и без хука плагина, так что барьер это способ его защитить. Authelia уже сидит за обратным прокси (так работает её собственный ForwardAuth), и этот прокси и есть место для барьера. Саму Authelia оставляют полностью без изменений.

Поставь барьер на vhost портала (например auth.example.com):

  • Закрой HTML портала и эндпоинт входа первого фактора (/api/firstfactor).
  • Исключи собственный verify-эндпоинт ForwardAuth Authelia (/api/verify), который прокси вызывает на каждый защищённый upstream-запрос. Закрыть его сломало бы каждое защищённое приложение за Authelia.
  • Исключи health-check и статические ассеты, которые нужны самой странице испытания.
  • Добавь маршрут /__cpt/callback как выше.

Барьер держит ботов подальше от портала; встроенная Regulation Authelia (лимит повторов и бан) по-прежнему ограничивает повторы учётных данных для того, кто прошёл, так что два слоя складываются. Барьер защищает только интерактивный портал (UI входа и согласия); он не закрывает, и не должен, неинтерактивные эндпоинты токенов.

См. также

На этой странице