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

5.0 KiB
Raw Permalink 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: SSHlocalhost: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).

Общий паттерн «жив, но шумит» 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-контейнера и логи туннеля