6.8 KiB
Executable File
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— рабочий живой пример (hostnamessh-kraken.qentra.top, ingress, команды)family/how-to/kraken-portainer-access.md— деплой cloudflared-контейнера и логи туннеля