Zero Trust для self-hosted AI-сервера: Cloudflare Tunnel, Access и WebAuthn без единого открытого порта
Если вы держите AI-сервер дома или на bare-metal в дата-центре, классическая схема такая: открываете порты 22, 80, 443 на роутере, вешаете fail2ban и надеетесь, что китайские ботнеты вас не найдут. Спойлер: найдут. И попробуют подобрать пароль к SSH быстрее, чем вы допьёте кофе.
Я перевёл свой Bronevik AI-сервер на Zero Trust-архитектуру: ни одного открытого порта наружу, вся публичная поверхность — через Cloudflare Tunnel, аутентификация — через Cloudflare Access с WebAuthn/YubiKey. SSH — только по FIDO2-ключам. В этой статье — пошаговый разбор архитектуры, настройки и пары неочевидных граблей.
Почему «просто закрыть порты» недостаточно
Даже если вы заменили парольную аутентификацию на ключи, открытый 22-й порт — это всё ещё вектор атаки. Ботнеты сканируют весь IPv4 за 45 минут. SSH-демон принимает соединения — значит, есть поверхность для zero-day в OpenSSH. Fail2ban банит по IP, но IP ротируются быстрее, чем вы дописываете правила.
Альтернатива: Cloudflare Tunnel (бывший Argo Tunnel). Это не VPN, это reverse proxy на стероидах. Вместо того чтобы открывать порты наружу, вы запускаете легковесный демон cloudflared внутри сервера. Он инициирует исходящее соединение к Cloudflare, и весь входящий трафик идёт через этот зашифрованный тунель. Ваш внешний IP не нужен вообще.
Плюс Cloudflare Access добавляет слой аутентификации поверх: любой, кто стучится к вашему сервису, сначала попадает на страницу входа Cloudflare с MFA, и только после проверки доходит до вашего приложения.
Архитектура: что куда
Вот как выглядит схема на Bronevik AI-сервере:
Интернет
│
▼
Cloudflare Edge (любой запрос к hermes.vdsservers.org)
│
├── Cloudflare Access (проверка: кто ты?)
│ │
│ ├── Email + OTP (TOTP)
│ └── WebAuthn (YubiKey)
│
▼
Cloudflare Tunnel (cloudflared)
│
▼
Hermes Agent (localhost:9119)
Codex web shell (localhost:9022)
Claude Code web shell (localhost:9023)
Admin-панель (localhost:9090)
Главное: порты 22, 80, 443, 9119, 9022, 9023 — закрыты наружу полностью. Даже если вы просканируете внешний IP сервера, вы не увидите ни одного из этих сервисов. Cloudflare принял запрос, проверил пользователя, и только тогда докинул до локального порта внутри тунеля.
Шаг 1: Cloudflare Tunnel — ставим за 5 минут
Домен у вас уже должен быть на Cloudflare (NS-сервера). Если нет — сначала переведите домен.
# Установка cloudflared на Linux
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
# Аутентификация (откроет браузер или даст ссылку)
cloudflared tunnel login
# Создаём тунель
cloudflared tunnel create bronevik-tunnel
# Получаем tunnel ID + JSON-файл с credentials в ~/.cloudflared/
Теперь настраиваем маршрутизацию. Файл ~/.cloudflared/config.yml:
tunnel: <YOUR_TUNNEL_ID>
credentials-file: /root/.cloudflared/<YOUR_TUNNEL_ID>.json
ingress:
- hostname: hermes.vdsservers.org
service: http://localhost:9119
- hostname: shell.vdsservers.org
service: http://localhost:9022
- hostname: admin.vdsservers.org
service: http://localhost:9090
- service: http_status:404
DNS записи создаются автоматически при запуске тунеля (или вручную через cloudflared tunnel route dns). Запускаем:
cloudflared tunnel run bronevik-tunnel
На этом этапе сервисы доступны через Cloudflare, но без аутентификации — любой, кто знает hostname, попадёт на ваш Hermes Agent. Это опасно. Добавляем Access.
Шаг 2: Cloudflare Access — ставим гейт с MFA
Cloudflare Access — это Zero Trust-прокси, который вставляется между пользователем и вашим сервисом. Не доходит до тунеля, пока Access не проверит личность.
Настройка через Cloudflare Zero Trust Dashboard (dash.teams.cloudflare.com):
- Applications → Add application → Self-hosted
- Указываем
hermes.vdsservers.orgкак домен приложения - В Policies создаём правило: Allow для конкретного email (вашего)
- В Settings включаем сессию (например, на 24 часа — чтобы не вводить YubiKey каждый раз)
- Сохраняем
Повторяем для shell.vdsservers.org и admin.vdsservers.org.
Теперь при заходе на hermes.vdsservers.org пользователь видит страницу Cloudflare Access: «Введите email». На указанный email приходит одноразовый код (OTP). После ввода кода — доступ.
Шаг 3: WebAuthn/YubiKey — вместо OTP по email
OTP — хорошо, но YubiKey — лучше. WebAuthn (стандарт FIDO2) использует аппаратный ключ: физическое устройство, которое нужно вставить в USB-порт и прикоснуться к нему. Украсть такой фактор удалённо невозможно.
Настройка в Cloudflare Zero Trust:
- Settings → Authentication → Login methods
- Добавляем Security Key (WebAuthn)
- При следующем входе Cloudflare предложит зарегистрировать YubiKey
- Тыкаем пальцем в ключ — готово
Теперь процесс входа выглядит так:
- Заходим на
hermes.vdsservers.org - Вводим email
- Вместо OTP — запрос: «Insert your security key»
- Вставляем YubiKey, касаемся контакта
- Доступ открыт
Второй фактор — то, что у вас физически в кармане, а не код в почте, которую могли перехватить.
Моя грабля: Access + WebAuthn на тестовом поддомене
При настройке Access на тестовом поддомене (test.vdsservers.org) я получил редирект-петлю. Оказалось, Cloudflare Access требует HTTPS на endpoint’е, а мой локальный сервис слушал только HTTP. Решение: в config.yml указать originRequest: noTLSVerify: true для конкретного ingress-правила (только для тестового — в проде так не делайте).
Шаг 4: SSH через YubiKey (FIDO2 ed25519-sk)
Остался SSH. Вариантов два:
Вариант А (простой): SSH через Cloudflare Tunnel + браузерный терминал (Cloudflare Access for SSH). Вы заходите на ssh.vdsservers.org через браузер, проходите Access, и получаете web-терминал. Плюс: не нужно ставить клиент. Минус: латенси и отсутствие привычного workflow.
Вариант Б (мой): FIDO2 SSH-ключи. Генерируем ключ, привязанный к YubiKey:
ssh-keygen -t ed25519-sk -O resident -C "bronevik-yubikey"
Это создаёт ключевую пару, где приватная часть хранится на YubiKey и никогда не покидает устройство. При каждом подключении нужно физическое касание ключа.
# На сервере — добавить публичный ключ в authorized_keys как обычно
cat ~/.ssh/id_ed25519_sk.pub >> ~/.ssh/authorized_keys
Теперь SSH выглядит так:
ssh -i ~/.ssh/id_ed25519_sk user@server
# YubiKey мигает → касаемся → подключение
Никаких паролей. Физический ключ + касание = второй фактор. Потеряли YubiKey? Никто не подключится.
Важно: FIDO2 SSH-ключи (ed25519-sk) работают с OpenSSH 8.2+. На старых системах может не быть поддержки — проверьте ssh -V.
Производительность: есть ли оверхед?
Cloudflare Tunnel добавляет минимальную задержку: плюс 1-5 мс через глобальную сеть Cloudflare. Для AI-ворклоуда (Hermes Agent, Codex, Claude Code) это незаметно — HTTP-запросы и так занимают десятки-сотни миллисекунд.
Единственный нюанс — WebSocket-соединения (используются в web-шеллах Codex и Claude Code). Cloudflare поддерживает WebSocket через Tunnel, но максимальный таймаут на бесплатном плане — 100 секунд. Для интерактивной работы с агентом этого более чем достаточно: код-ревью, дебаг, генерация — редко укладываются в сессию длиннее пары минут.
Что делать, если Cloudflare лёг
Самый частый вопрос: «А если Cloudflare упадёт? Как я попаду на сервер?»
Ответ: fallback-доступ. У вас должен быть способ попасть на сервер, не зависящий от Cloudflare:
- Прямой IPMI/iDRAC/iLO — если сервер в дата-центре, у вас есть KVM-доступ
- Резервный WireGuard VPN на отдельном порту (например, 51820) — только для экстренного доступа, с тем же FIDO2-ключом
- SSH через Tailscale — работает поверх WireGuard, не требует открытых портов, использует NAT traversal
У меня: основной доступ через Cloudflare Tunnel + Access (это 99% времени), резервный — WireGuard VPN до сервера с тем же YubiKey. Cloudflare падал за 3 года дважды, оба раза на 20-30 минут. За это время я просто подождал — ничего критичного.
Сравнение с альтернативами
| Решение | Открытые порты | MFA | Бесплатно | Сложность |
|---|---|---|---|---|
| Прямой SSH (port 22) | 22 ❌ | Нет ❌ | Да ✅ | Низкая |
| WireGuard VPN | 51820 ❌ | Нет ❌ | Да ✅ | Средняя |
| Tailscale | Нет ✅ | Через IdP ✅ | До 3 users ✅ | Низкая |
| Cloudflare Tunnel + Access | Нет ✅ | WebAuthn/YubiKey ✅ | До 50 users ✅ | Низкая |
| Cloudflare Tunnel + Access + FIDO2 SSH | Нет ✅ | Аппаратный ключ ✅ | Да ✅ | Средняя |
Tailscale — отличная альтернатива, если вам нужен mesh-VPN между устройствами. Но если задача — выставить наружу конкретный web-сервис (Hermes Agent, админку, web-шелл) с аутентификацией, не открывая портов — Cloudflare Tunnel + Access выигрывает по соотношению простота/безопасность.
Реальный опыт: что пошло не так
За год использования этой схемы было три инцидента:
-
Access-политика заблокировала меня же. После изменения email в настройках Cloudflare я 40 минут не мог зайти на сервер — политика была привязана к старому email. Решение: менять email через Cloudflare Dashboard (доступен без Access), а не через настройки приложения.
-
YubiKey отказал после обновления прошивки. Ключ перестал определяться в Linux. Решение: иметь второй зарегистрированный YubiKey в Access-политике (и в
authorized_keys). Сейчас у меня два ключа: основной на связке, резервный в сейфе. -
Cloudflare Access редирект-петля. При первом запуске браузер бесконечно редиректил между Cloudflare и локальным сервисом. Проблема была в том, что Access ожидал HTTPS, а локальный nginx слушал HTTP. Решение: в
config.ymlдобавитьoriginRequest: httpHostHeader, либо настроить самоподписанный сертификат на локальном endpoint’е.
Итог: Zero Trust за 35 минут
Полная настройка заняла у меня 35 минут с нуля:
- 5 минут — установка cloudflared и создание тунеля
- 10 минут — настройка DNS и ingress-правил
- 10 минут — Cloudflare Access с WebAuthn/YubiKey
- 5 минут — генерация ed25519-sk ключа для SSH
- 5 минут — проверка всего
Результат: ни одного открытого порта на внешнем IP, полная изоляция self-hosted AI-инфраструктуры, аутентификация через аппаратный ключ. Спать стало спокойнее.
Если вы запускаете AI-сервер дома или на своём bare-metal — это минимальный уровень безопасности, который стоит настроить до того, как вы начнёте экспериментировать с агентами.
📬 Не пропускай новые статьи
Подпишись на Telegram-канал или RSS, чтобы получать уведомления о новых материалах