63 lines
5.0 KiB
Markdown
63 lines
5.0 KiB
Markdown
# 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:
|
||
```bash
|
||
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`), который отвечает:
|
||
```bash
|
||
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`).
|
||
|
||
## Общий паттерн «жив, но шумит» 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-контейнера и логи туннеля
|