Clarify CF Tunnel SSH is power-user only — client install required, not zero-install

This commit is contained in:
jester 2026-03-26 20:58:53 +00:00
parent 9ae64dab91
commit 8f73f60ed9

View File

@ -2,70 +2,65 @@
## Overview
Cloudflare Tunnel provides SSH access to dev containers for developers using
local VS Code or terminal. No VPN required. Uses the same hostname as the
browser IDE.
CF Tunnel SSH provides local VS Code / terminal access to dev containers for
developers who want to use their own tooling instead of the browser IDE.
**This is a power-user feature, not zero-install.** It requires `cloudflared`
installed on the developer's local machine. The browser IDE remains the
zero-install option for all developers.
---
## How It Works — Two Sides
## Install Requirement Clarification
This requires `cloudflared` on **both** the server (bastion) and the client
(developer machine). They do completely different things:
`cloudflared` is required on **both** sides — but they do different things:
**Bastion (server side):**
`cloudflared` runs as a persistent service maintaining an outbound tunnel to
Cloudflare's edge. This is already set up.
| Side | Role |
|------|------|
| Bastion VM | `cloudflared` runs as a persistent service maintaining the outbound tunnel to Cloudflare |
| Developer's local machine | `cloudflared` acts as an SSH ProxyCommand — routes SSH through Cloudflare instead of directly to the IP |
**Developer machine (client side):**
`cloudflared` is a lightweight binary used as an SSH ProxyCommand. It
intercepts the SSH connection and routes it through Cloudflare instead of
going directly to the IP. Without this, SSH bypasses Cloudflare entirely,
hits the raw IP on port 22, and gets rejected.
The dev container (LXC) only needs `sshd` running. It does not need cloudflared.
The two sides meet at Cloudflare — no inbound ports needed on either:
```
Developer machine
cloudflared (ProxyCommand) → Cloudflare edge ← cloudflared (tunnel)
Bastion VM
Dev container
```
**Critical:** If SSH connects directly to the IP instead of through
cloudflared, you will see:
**Without the ProxyCommand on the client, SSH goes directly to the raw IP and
gets rejected.** You will see:
```
kex_exchange_identification: Connection closed by remote host
```
This means the ProxyCommand is not being invoked — SSH is bypassing
Cloudflare entirely.
This means cloudflared is not being invoked — SSH is bypassing Cloudflare entirely.
---
## Architecture
```
Developer laptop
↓ ssh dev-6070.zerolaghub.dev (via cloudflared ProxyCommand)
Developer laptop (cloudflared as ProxyCommand)
↓ ssh dev-6070.zerolaghub.dev
Cloudflare edge
↓ CF Tunnel (persistent, runs on bastion)
↓ CF Tunnel (persistent service on bastion)
Bastion VM (private IP, no public exposure)
↓ SSH proxy jump
Dev container (10.100.x.x)
Dev container (10.100.x.x — just needs sshd)
```
HTTPS and SSH share the same hostname. Cloudflare routes them separately:
- HTTPS → Traefik → API → container (browser IDE)
- SSH → CF Tunnel → bastion → container
---
## Zero-Install vs Power-User Access
| Access Method | Install Required | Use Case |
|--------------|-----------------|----------|
| Browser IDE (`dev-<vmid>.zerolaghub.dev`) | Nothing | All developers — zero friction |
| CF Tunnel SSH | `cloudflared` on local machine | Power users wanting local VS Code or terminal |
The browser IDE is the primary access method. CF Tunnel SSH is optional and
for developers who specifically want local tool integration.
---
## Current State
- ✅ CF Tunnel created and connected to bastion VM
- ✅ Cloudflare Zero Trust free plan active (covers up to 50 users)
- ✅ Cloudflare Zero Trust free plan active (up to 50 users)
- ⏳ Tunnel SSH hostname mapping not yet configured in Zero Trust dashboard
- ⏳ Bastion SSH proxy jump config not yet done
- ⏳ Dev container SSH server not yet verified
@ -75,10 +70,40 @@ HTTPS and SSH share the same hostname. Cloudflare routes them separately:
## Remaining Steps
### 1. Install cloudflared on developer machines
### 1. Configure tunnel ingress in Cloudflare Zero Trust dashboard
This is required on every developer's machine — not optional.
Zero Trust → Networks → Tunnels → your tunnel → Configure:
Add a public hostname:
- Subdomain: `*` (wildcard)
- Domain: `zerolaghub.dev`
- Service type: `SSH`
- URL: `localhost:22`
### 2. Configure bastion SSH for proxy jump
Edit `/etc/ssh/sshd_config` on the bastion:
```
AllowTcpForwarding yes
PermitOpen 10.100.0.0/24:22
```
Restart: `systemctl restart sshd`
### 3. Ensure dev containers have SSH running
Add to agent dev provisioning pipeline:
```bash
apt-get install -y openssh-server
systemctl enable ssh
systemctl start ssh
```
### 4. Developer one-time setup (power users only)
Install cloudflared on their local machine:
```bash
# macOS
brew install cloudflare/cloudflare/cloudflared
@ -87,107 +112,52 @@ brew install cloudflare/cloudflare/cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
-o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared
# Windows
# Download from https://github.com/cloudflare/cloudflared/releases
# Termux (Android)
pkg install cloudflared
# Windows — download from GitHub releases
# Termux — pkg install cloudflared
```
### 2. Developer SSH config (one-time)
Developer adds this to `~/.ssh/config`:
Add to `~/.ssh/config`:
```
Host *.zerolaghub.dev
ProxyCommand cloudflared access ssh --hostname %h
User dev
```
This is what makes `ssh dev-6070.zerolaghub.dev` route through Cloudflare
instead of going directly to the IP. Without this config, SSH will fail.
Then: `ssh dev-6070.zerolaghub.dev`
To confirm it's working, run with `-v` and look for:
Confirm it's working by looking for in `-v` output:
```
Executing proxy command: cloudflared access ssh --hostname dev-6070.zerolaghub.dev
```
### 3. Configure tunnel ingress in Cloudflare Zero Trust dashboard
In Zero Trust → Networks → Tunnels → your tunnel → Configure:
Add a public hostname:
- Subdomain: `*` (wildcard) or specific `dev-ssh`
- Domain: `zerolaghub.dev`
- Service type: `SSH`
- URL: `localhost:22`
### 4. Configure bastion SSH for proxy jump
On the bastion, edit `/etc/ssh/sshd_config`:
```
AllowTcpForwarding yes
PermitOpen 10.100.0.0/24:22
```
Restart SSH: `systemctl restart sshd`
### 5. Ensure dev containers have SSH running
Each dev container needs openssh-server installed. Add to agent dev
provisioning pipeline:
```bash
apt-get install -y openssh-server
systemctl enable ssh
systemctl start ssh
```
### 6. VS Code Remote SSH
After SSH config is in place, developer opens VS Code → Remote Explorer →
Add new host → `ssh dev-6070.zerolaghub.dev`. VS Code handles the rest.
---
## Security Notes
- Bastion has no public IP — all SSH access goes through CF Tunnel only
- CF Tunnel is outbound-only from bastion to Cloudflare — no inbound ports open
- Zero Trust free tier: up to 50 users, core access control included
- `cloudflared` on the client is a lightweight binary, not a service
- code-server runs `--auth none` — SSH is enforced separately at OS level
---
## Portal Integration (Future)
Portal dev container page should show:
Show on the dev container page as an optional section:
```
SSH Access
──────────
1. Install cloudflared: https://developers.cloudflare.com/cloudflared/install
Local VS Code Access (Advanced)
────────────────────────────────
Requires a one-time install of cloudflared on your machine.
1. Install: https://developers.cloudflare.com/cloudflared/install
2. Add to ~/.ssh/config:
Host *.zerolaghub.dev
ProxyCommand cloudflared access ssh --hostname %h
User dev
Host *.zerolaghub.dev
ProxyCommand cloudflared access ssh --hostname %h
User dev
3. Connect:
ssh dev-6070.zerolaghub.dev
3. Connect: ssh dev-6070.zerolaghub.dev
Or open VS Code Remote Explorer and add this host.
```
One-time setup. Works for all dev containers after that.
---
## Troubleshooting
| Issue | Likely Cause |
|-------|--------------|
|-------|-------------|
| `kex_exchange_identification: Connection closed` | ProxyCommand not in SSH config — SSH going direct to IP |
| `cloudflared: command not found` | cloudflared not installed on client machine |
| `Connection refused` | SSH not running in container or bastion jump not configured |