Я користуюся Cloudflare вже багато років.

Як і багато інженерів інфраструктури, я чудово знав, що відбувається, коли вмикаєш помаранчеву хмару: ваш зворотний проксі-сервер (reverse proxy) більше не спілкується безпосередньо з клієнтами. Замість цього кожне вхідне з'єднання відображається як таке, що надходить з одного з межових серверів (edge servers) Cloudflare.

Через це я свідомо тримав проксіювання Cloudflare вимкненим для кількох внутрішніх сервісів. Компроміс був очевидним:

  • Зберегти реальний IP клієнта та підтримувати безпеку на основі IP;
  • Або ввімкнути проксі Cloudflare та отримати його захист, але втратити видимість реального клієнта.

Ці обмеження виникали не через нерозуміння. Це був звичайний технічний борг, на закриття якого просто не вистачало часу.

Нещодавно я вирішив повернутися до цієї проблеми — не просто щоб відновити вихідний IP клієнта, а щоб повністю відновити всю модель безпеки, побудовану навколо нього.

Суть проблеми

Cloudflare змінює межу довіри (trust boundary) вашої інфраструктури. На мережевому рівні ваш сервер більше не спілкується безпосередньо з клієнтом. Він спілкується з Cloudflare.

                Before

Client
   │
   ▼
HAProxy
   │
   ▼
Backend

HAProxy trusts src


──────────────────────────────────────────────


                After

Client
   │
   ▼
Cloudflare
   │
   ▼
HAProxy
   │
   ▼
Backend

HAProxy must trust: CF-Connecting-IP
ONLY if the connection originated from Cloudflare.

Ця відмінність впливає на кожен компонент, який покладається на IP-адресу клієнта. Без розуміння цієї архітектурної зміни легко неправильно налаштувати зворотні проксі-сервери або помилково припустити, що механізми безпеки працюють належним чином, коли насправді це не так.

Що змінюється, коли увімкнено Cloudflare Proxy?

Без Cloudflare:

Клієнт (203.0.113.42)
      │
      ▼
HAProxy (бачить: 203.0.113.42)

З увімкненим Cloudflare Proxy:

Клієнт (203.0.113.42)
      │
      ▼
Cloudflare
      │
      ▼
HAProxy (тепер бачить: 104.x.x.x)

Тепер HAProxy бачить IP-адресу 104.x.x.x, що належить Cloudflare. З точки зору HAProxy, кожен відвідувач фактично став Cloudflare.

Приховані наслідки

На перший погляд, усе працює. Сайти завантажуються. Додатки відповідають. TLS-сертифікати термінуються коректно. Але кожен механізм, який залежить від IP клієнта, тихо стає неефективним або повністю ламається.

IP Whitelisting (Білі списки)

Правила на кшталт:

acl my_whitelist src -f /etc/haproxy/whitelist.lst

більше не перевіряють адресу клієнта. Вони перевіряють Cloudflare. Ваш білий список більше не захищає вашу інфраструктуру належним чином.

Rate Limiting (Обмеження запитів)

Замість підрахунку запитів від кожного клієнта, HAProxy починає рахувати запити від межових вузлів Cloudflare. Через це кілька абсолютно різних відвідувачів можуть спільно вичерпати загальний ліміт запитів просто тому, що вони проходять через одну й ту саму точку присутності Cloudflare.

Stick Tables

Stick Tables стають набагато менш корисними, оскільки кожна відстежувана адреса належить Cloudflare, а не реальному клієнту.

Логування

До Cloudflare:

{
  "client": "203.0.113.42",
  "status": 401
}

Після Cloudflare:

{
  "client": "104.x.x.x",
  "status": 401
}

Пошук та усунення несправностей стає значно складнішим, оскільки реальний клієнт просто зникає з логів.

Відновлення оригінального IP клієнта

На щастя, Cloudflare вже передає оригінальну адресу клієнта в заголовку CF-Connecting-IP.

Проте важливо не просто відновити її, а зробити це виключно для довірених з'єднань від Cloudflare:

acl from_cloudflare src -f /etc/haproxy/cloudflare.lst

http-request set-src hdr(CF-Connecting-IP) if from_cloudflare { hdr(CF-Connecting-IP) -m found }

Щойно параметр src відновлено, кожне існуюче правило, яке на нього спирається, знову починає працювати належним чином. Без жодних змін у ваших ACL ви відновлюєте:

  • IP Whitelisting
  • Rate Limiting
  • Stick Tables
  • Логування
  • GeoIP фільтрацію

Іноді один рядок конфігурації здатний повернути до життя цілу модель безпеки.

Чому Fail2ban працює інакше (Пастка 22)

На цьому етапі виникає спокуса припустити, що Fail2ban також почне працювати автоматично. Але це не так.

Саме тут стає важливим розуміння рівнів мережевого стеку. HAProxy відновлює IP клієнта на рівні HTTP (L7). Fail2ban, у свою чергу, блокує трафік за допомогою системного брандмауера (firewall, L3/L4). Брандмауер оперує мережевими пакетами, а не заголовками HTTP.

Кожне TCP-з'єднання, яке приходить на ваш сервер, фізично йде від Cloudflare. Оригінальний IP-адрес клієнта ніколи не з'являється в заголовках самих пакетів. Тому навіть якщо Fail2ban правильно витягне 203.0.113.42 з логів HAProxy, він виконає команду на кшталт:

iptables -A INPUT -s 203.0.113.42 -j DROP
          +--------------------------+
          |        Fail2ban          | (reads logs)
          +--------------------------+
                       │
                       ▼
    iptables -A INPUT -s 203.0.113.42 -j DROP
                       │
                       ▼
                Linux Firewall
                       ▲
                       │
             Incoming TCP packet
         src = 104.x.x.x (Cloudflare)

Це правило ніколи не спрацює для вхідних пакетів, оскільки ядро Linux бачить джерелом виключно адресу Cloudflare. Атакуючий продовжує безперешкодно надсилати запити через Cloudflare. Блокування існує в системі, але не має жодного ефекту.

Як змусити Fail2ban працювати

Є два практичних підходи до вирішення цієї проблеми:

  • Блокування на межі: Замість локальних правил брандмауера налаштуйте Fail2ban на виклик Cloudflare API (наприклад, через скрипт fail2ban-cloudflare-waf), щоб блокувати зловмисника на самому Cloudflare. Тоді шкідливий трафік взагалі не дійде до вашої інфраструктури.
  • Блокування всередині HAProxy: Оскільки HAProxy вже знає реальний IP клієнта, він може виконувати блокування на рівні додатку за допомогою stick tables, карт (maps) або динамічних ACL. У такій архітектурі саме HAProxy (а не локальний firewall) стає правильним інструментом для блокування.

Але не вірте заголовкам наосліп

Це найважливіша частина. Будь-хто може надіслати HTTP-заголовок під назвою CF-Connecting-IP.

Якщо ваш HAProxy доступний безпосередньо з Інтернету, і ви сліпо довіряєте цьому заголовку, зловмисник може підробити будь-каку адресу джерела. Правильний підхід — довіряти CF-Connecting-IP лише тоді, коли запит дійсно надходить з опублікованих діапазонів IP-адрес Cloudflare.

Ще краще — обмежити ваш системний брандмауер так, щоб тільки IP-адреси Cloudflare взагалі могли підключатися до HAProxy на порти 80/443.

Корисні посилання

Висновки

Увімкнення Cloudflare Proxy змінює межу довіри вашої інфраструктури. Це не просто косметична зміна в логах — це архітектурний зсув. Тепер кожен інструмент безпеки (від ACL та лімітів до SIEM та логів аудиту) має спиратися на перевірене джерело ідентифікації.

Кейс із Fail2ban добре ілюструє, що просте відновлення заголовків у проксі-сервері не вирішує проблему на нижчих рівнях мережевого стеку. Кожне рішення з безпеки потрібно оцінювати заново, виходячи з того, як саме воно визначає клієнта: на рівні HTTP-запитів чи на рівні TCP-пакетів.

Відновлення оригінального IP клієнта в HAProxy — це не просто зручний трюк для красивих звітів. Це відновлення єдиного джерела правди, без якого неможливо побудувати надійний захист.