Files
obsidian-vault/personal/tech/cloudflare-tunnel-ssh-published-route.md
T

6.8 KiB
Executable File
Raw Blame History

Cloudflare Tunnel: SSH через Published Application Route

Обновлено: 2026-09-04 (итог отладки захода на Kraken) Рабочий case с полными командами: family/how-to/kraken-access.md

Генерализуемые выводы про то, как сделать SSH через Cloudflare Tunnel (published application route), чтобы вход работал ssh user@host — через cloudflared на клиенте.

Ключевое правило: hostname должен быть 1-уровневым subdomain (БЕЗ точки до домена)

Симптом: двухуровневый subdomain вида ssh.kraken.qentra.top (ssh.kraken + точка + qentra.top) НЕ работает как SSH/TLS через Cloudflare.

Проверка (решающий факт) — на edge НЕТ TLS-терминации для 2-уровневого hostname:

openssl s_client -connect ssh.kraken.qentra.top:443 -servername ssh.kraken.qentra.top
# → SSL alert number 40 (handshake failure), "no peer certificate available"
curl -s https://ssh.kraken.qentra.top   # ssl=1 handshake failure

В отличие от 1-уровневого hostname (kraken.qentra.top), который отвечает:

openssl s_client -connect kraken.qentra.top:443 ...  # CN=qentra.top, verify ok

Почему: Universal SSL Cloudflare-зоны покрывает только *.domain (1 уровень wildcard) и сам domain. Он НЕ покрывает *.sub.domain (2 уровень). Для 2-уровневых нужен Advanced Certificate (SAN на полный двухуровневый wildcard) — обычно проще взять одиночный subdomain.

⚠️ ПИТФОЛ: Cloudflare-точка в hostname (a.b.domain) = 2 уровня по SSL, точка НЕ безобидна. Рабочая замена — один уровень с дефисом: ssh-kraken.qentra.top (покрывается *.qentra.top).

Правильная схема SSH через Tunnel (по доке Cloudflare)

На сервере/туннеле (Zero Trust):

  • Tunnels → туннель → Routes → Add route → Published application
  • Service: SSH → localhost:22 (или <ip>:22)
  • hostname использовать одиночный subdomain (ssh-kraken.qentra.top, не ssh.kraken...)

В Zero Trust Access:

  • Access → Applications → Add self-hosted (тип SSH, browser rendering off) на тот же hostname, с политикой (Allow / Service Token).
  • SSH-route без Access Application не даёт клиенту пройти даже до авторизации: cloudflared access login <ssh-host> → failed to get app info: ... tls: handshake failure.

На клиенте (cloudflared установлен): ~/.ssh/config:

Host ssh-kraken.qentra.top
  HostName ssh-kraken.qentra.top
  User kraken
  ProxyCommand cloudflared access ssh --hostname %h

Первый раз — авторизация: cloudflared access login ssh-kraken.qentra.top (откроет SSO/браузер). Дальше ssh kraken@ssh-kraken.qentra.top.

Client-side подводные камни

  • cloudflared access login <ssh-hostname> нельзя проверить по HEAD https://<ssh-host> — SSH-kind hostname НЕ терминирует HTTP/TLS, поэтому failed to get app info: tls: handshake failure — ожидаемо, если на маршруте нет Access App.
  • ~/.cloudflared/*-org-token = not-available означает, что cloudflared НЕ авторизован в Access org — разовый cloudflared access login обязателен.
  • Сам ssh к hostname:22 напрямую (без cloudflared в ProxyCommand) к проксированному DNS не работает — hostname за Cloudflare-Anycast, в туннель трафик попадает только через cloudflared-клиент.
  • network_mode: host (container cloudflared) — сервис напрямую видит локальные порты (sshd на 0.0.0.0:22).

HTTP-сервис через существующий туннель (без рестарта cloudflared)

Туннель на origin — один процесс на все маршруты. Добавление нового HTTP-маршрута не требует ни правки токена, ни рестарта cloudflared: маршруты живут на стороне Cloudflare и подхватываются работающим туннелем.

Схема (Zero Trust):

  • Networks → Tunnels → туннель → Routes → Add route → Published application
  • Service: HTTP → localhost:<порт> (например localhost:8790)
  • hostname — одиночный subdomain (agents-bridge.qentra.top), см. правило выше

Обязательно: Access Application на тот же hostname (Access → Applications → Add self-hosted, тип HTTP). Без него сервис получается публичным без авторизации — для служебных мостов это недопустимо.

Живой пример на Mac (проверено 2026-09-26):

Факт Значение
Бинарь /opt/homebrew/bin/cloudflared (2026.3.0)
Процесс cloudflared tunnel --protocol quic run --token …
Автозапуск LaunchAgent com.cloudflare.cloudflared.plist
Токены ~/.cloudflared/*-token (0600), отдельный файл на каждый hostname
Маршрут на Mac ssh-kraken.qentra.top → ssh://localhost:22

Проверка, что туннель поднят: наличие процесса + запись в ~/.cloudflared/. Проверка нового маршрута: curl извне на <hostname>/healthz.


Общий паттерн «жив, но шумит» cloudflared на origin без IPv6

Если у origin нет глобального IPv6 (роутер не выдаёт), cloudflared пытается поднять QUIC и по IPv6 → в логах постоянные ERR ... write udp [::]->198.41.x.x:7844: sendmsg: network is unreachable, соединения падают/retry. Туннель продолжает работать по IPv4-QUIC. Возможный фикс — заставить cloudflared использовать только IPv4.

Связанные заметки

  • family/how-to/kraken-access.md — рабочий живой пример (hostname ssh-kraken.qentra.top, ingress, команды)
  • family/how-to/kraken-portainer-access.md — деплой cloudflared-контейнера и логи туннеля