透過 SSH tunnel 連到中繼
中繼可以開啟一條通往你的伺服器的 SSH 連線,並請該伺服器把一個連接埠轉送回中繼。之後你透過伺服器的位址連到中繼,家裡沒有任何東西對網際網路監聽。中繼會自己啟動、監看並重新啟動 ssh 處理程序。
如果你已經有一台具備公開位址且可以 SSH 登入的伺服器,就選擇這種方式。如果你沒有伺服器,內建的 Cloudflare Tunnel 不需要伺服器。
你需要什麼 #
- 中繼主機的
PATH上有ssh程式(或用command指定另一個,見下方)。 - 伺服器上有一個使用金鑰且不會出現任何提示就能成功的 SSH 登入。中繼以批次模式執行
ssh,所以無法輸入密碼或金鑰的密語。有密語的金鑰只有在 SSH agent 持有它時才能使用。請先在中繼主機上測試:ssh relay@relay.必須不詢問任何東西就能登入。example. com - 伺服器上有一個可供 tunnel 使用的空閒連接埠(
remote)。Port - 伺服器上有能以 HTTPS 提供該連接埠的東西:網頁伺服器或反向 proxy,例如 Caddy 或 nginx。tunnel 傳送的是純 HTTP,所以一旦連接埠可以連到,若沒有它,你的 token 和對話就會以未加密的方式穿越網際網路。見在反向 proxy 後面執行中繼。
中繼不需要任何特別的 sshd 設定。remote 為 loopback 時,它要求轉送時不指定綁定位址,所以由伺服器自己的 Gateway 設定決定連接埠在哪裡開啟。在預設值 no 下,就是只在伺服器本身開啟,而反向 proxy 是唯一面向網際網路的東西。設為 public 時,中繼會要求所有介面(*:),而這項要求只有在伺服器的 sshd 允許時才會生效(Gateway 設為 clientspecified 或 yes)。
設定 #
在 config. 中加入一個 ssh 區塊(見檔案在哪裡),然後用 oar restart 重新啟動中繼。
"sshTunnel": {
"mode": "managed",
"required": false,
"user": "relay",
"host": "relay.example.com",
"remotePort": 4444,
"remoteBind": "loopback",
"identityFile": "~/.ssh/id_ed25519"
}
在伺服器上,把 HTTPS 送到 tunnel 的連接埠。使用 Caddy 時:
relay.example.com {
reverse_proxy localhost:4444
}
在瀏覽器中開啟 https://,並用你的存取 token 登入。中繼的啟動橫幅會印出一行 Remote:,而 oar url 會印出一行 Tunnel URL: 並附上 QR 碼。兩者都是 https:// 加上 host 的值,不含連接埠,也不會檢查 tunnel 是否已連線,所以只有在你的 proxy 於標準連接埠提供 HTTPS 時才正確。
sshTunnel 的 key #
所有 key 都在 ssh 區塊中。這張表與 config.json 一致,那裡有相同的清單。
| Key | 類型 | 預設值 | 作用 |
|---|---|---|---|
mode |
"disabled" 或 "managed" |
"disabled" |
managed 會讓中繼啟動並重新啟動 tunnel。 |
user |
字串 | 無 | 伺服器上的 SSH 使用者。受管理模式下為必填。 |
host |
字串 | 無 | 伺服器的主機名稱或位址。受管理模式下為必填。 |
remote |
數字 | 無 | 在伺服器上開啟的連接埠,1 到 65535。受管理模式下為必填。 |
remote |
"loopback" 或 "public" |
"loopback" |
loopback 要求連接埠時不指定綁定位址(由伺服器的 Gateway 決定,見你需要什麼);public 要求所有介面。其他任何值都視為 loopback。與 localhost 無關。 |
identity |
字串 | 無 | 私密金鑰檔案;~ 會被展開。沒有它時,ssh 會使用你的 agent 和它的預設金鑰。 |
command |
字串 | ssh |
SSH 程式,也用於伺服器上的清理。名稱會在 PATH 上查找;含有斜線的路徑照原樣使用,相對路徑則從中繼的程式資料夾算起。 |
required |
布林值 | false |
tunnel 未連線期間,中繼不會從佇列中取出訊息。見必要的 tunnel。 |
auto |
布林值 | true |
遠端連接埠已被占用時,在伺服器上執行清理並再試一次。只有 false 會關閉它。 |
reclaim |
布林值 | false |
作為清理的一部分,也結束你自己在伺服器上閒置的 SSH session。 |
remote |
字串 | 內建 | 你自己的清理指令,在伺服器上取代內建的清理執行。 |
舊的寫法 "enabled": true 仍然有效,等同於 "mode": "managed",但只在 mode 沒有設為 disabled 或 managed 時才算數。
中繼會把伺服器的 remote 轉送到它自己在 127. 上的連接埠(除非你變更過 port,否則是 3333)。設為 public 時,轉送以 *: 要求,否則以 remote 要求。
以下設定是固定的,沒有對應的 key:中繼以 Server、Server、Batch、Strict 和 Exit 執行 ssh -N。在三次間隔 30 秒的檢查都沒有回應後,ssh 就會放棄這條連線。
如果缺少必要的值(受管理模式下的 user、host 或有效的 remote),中繼仍照常啟動,在記錄中寫明缺少哪一個,並讓 tunnel 保持關閉。
運作方式 #
啟動。 中繼開始監聽後就會啟動 tunnel。當 ssh 已執行 1.2 秒且伺服器沒有拒絕轉送時,它就把 tunnel 視為已連線。記錄接著會顯示 Tunnel up to user@host remote port N。
重新連線。 ssh 結束時,中繼會在一段逐漸變長的等待後再次啟動它:5、10、20、40 秒,然後是 60 秒,之後一直維持 60 秒。持續超過 30 秒的連線中斷後,計數會從頭開始。停止中繼會停止 tunnel 和重試。
主機金鑰。 中繼主機從未見過的伺服器,會在第一次連線時被接受並記住。之後,如果主機金鑰變了,ssh 會拒絕連線,直到你從 known_ 中移除舊的項目。
必要的 tunnel #
設定 required: true 時,佇列從啟動起就保持暫停,直到 tunnel 連線為止,之後每次斷線時也會再次暫停。你傳送的訊息會在佇列中等待,並在 tunnel 連線後執行。如果沒有 tunnel 中繼對你就沒有用處,就使用這個設定。因為缺少值而無法啟動的 tunnel 也會讓佇列保持暫停。
遠端連接埠已被占用時 #
如果伺服器表示無法綁定該連接埠,通常的原因是先前某個 tunnel 留下的 session,而伺服器還沒發現它已經失效。除非 auto 為 false,否則中繼會第二次連到伺服器,並在那裡執行清理指令。
這些選項會在你的伺服器上執行指令
清理以你的 SSH 登入身分執行,而且會終止處理程序。它會結束該使用者在遠端連接埠上監聽的任何處理程序,不論那是不是殘留的 tunnel。設定 reclaim 時,它還會結束該使用者每一個閒置的 SSH session,其中包括同一個登入在其他連接埠上的其他 ssh -N -R tunnel。remote 會執行你寫的任何內容。在讓它們保持開啟之前,請先了解每個步驟會做什麼。
內建的清理會依序執行以下步驟:
- 檢查連接埠是否正在使用(使用
ss,否則讀取核心的監聽 socket 清單)。如果沒有,就停止並回報成功。 - 找出在該連接埠上監聽的處理程序(使用
lsof,否則使用fuser,看哪一個存在),向它們傳送終止訊號(kill),然後等待一秒。你的登入無權傳送訊號的處理程序會留下來。如果連接埠現在已空出,就回報成功。 - 只有在
reclaim時,而且只有在連接埠仍在使用時:向你的使用者所有沒有子處理程序的Stale Ssh Sessions: true sshdsession 傳送終止訊號,然後等待一秒。這些就是ssh -N -R留下的 session。執行 shell、編輯器的遠端伺服器或任何其他程式的 session 都有子處理程序,因此會被略過,執行清理本身的 session 也一樣。 - 如果連接埠仍在使用,清理會以代碼 3 結束,中繼會在記錄中寫下
is still held。
設定了 remote 時,會執行你的指令來取代以上所有步驟,而 reclaim 不會有任何作用。中繼以 sh -lc '<command>' 的形式,透過使用相同金鑰的第二個 ssh 登入,以批次模式執行它。連接埠空出時它必須以 0 結束,仍被占用時則以其他代碼結束。
清理成功空出連接埠後,中繼會在一秒後重試,最多連續三次。清理失敗後,或 auto 關閉時,它會使用上面所說的一般等待時間。
查看 tunnel 的狀態 #
SSH tunnel 不會顯示在 Cloudflare tunnel 顯示的那些地方:oar status 和 oar doctor 只回報 Cloudflare tunnel,而狀態點也不會因為 SSH tunnel 變成琥珀色(見狀態點)。請改看以下地方:
- 中繼的記錄。 每一行都以
[ssh-開頭。它會顯示中繼執行的tunnel] ssh指令列、ssh印出的內容、Tunnel up to ...、Reconnecting in Ns (attempt N)...以及回收連接埠的各個步驟。記錄在哪裡取決於中繼的執行方式;見檔案與資料夾。 - 啟動橫幅,設定了受管理的 tunnel 時,會有一行
Remote:和一行Tunnel: mode=(或loopback public)。它們顯示的是設定,而不是 tunnel 是否已連線。oar url會以Tunnel URL:印出相同的位址。 /,其中有一個api/ status ssh物件,包含Tunnel connected、connected、Since reconnect、Attempts last、Error blocking、required、mode、valid、host、remote和Port remote。請帶著你的 token 取得它。Bind Mode
last 在 ssh 結束後是 exit:<code>,在伺服器拒絕該連接埠後是 remote-,在區塊不完整時是 invalid- 或缺少值的訊息,tunnel 連線期間則為空。
安全性 #
tunnel 一旦連線,中繼就能從網際網路連到,而存取 token 是陌生人和你的 agent 之間唯一的屏障,而這些 agent 會在你的機器上執行指令。所以:
- 讓
remote維持Bind loopback,並在伺服器上用 HTTPS 擋在前面。設為public時,任何能連到伺服器 tunnel 連接埠的人,都能透過純 HTTP 連到中繼。 - 存取 token 是唯一的一道鎖。見存取 token 與誰能連到中繼和 OAR 會暴露什麼。
- 對中繼內部 session worker 路徑的請求,如果帶有 Cloudflare 的標記標頭
cf-,或你在ray tunnel中列出的標頭,中繼會拒絕。一般的反向 proxy 不會加上其中任何一個,所以如果你的 proxy 會加上某個你希望被擋下的標頭,請把它列進去。見 config.json。Marker Headers
疑難排解 #
先查看記錄中的 [ssh- 行。以 stderr: 開頭的行是 ssh 自己印出的內容。
| 你看到的情況 | 代表什麼 | 處理方式 |
|---|---|---|
ssh(host 也一樣) |
缺少必要的值。tunnel 保持關閉。 | 設定它,然後執行 oar restart。 |
ssh |
缺少連接埠,或它不是 1 到 65535 之間的數字。 | 修正它,然後重新啟動。 |
Tunnel mode disabled; running direct relay only. |
mode 不是 managed。 |
設定 "mode": "managed"。 |
stderr: ... Permission denied (publickey) |
伺服器不接受你的金鑰。批次模式表示不會出現密碼提示。 | 設定 identity,並在中繼主機上手動測試同樣的 ssh user@host。 |
一再出現 Process exited (code= |
ssh 無法連線或登入。 |
手動執行記錄中的 ssh 指令,並閱讀它的錯誤訊息。 |
stderr: ... remote port forwarding failed for listen port N,接著是 Attempting remote reclaim ... |
伺服器上的連接埠正在使用中。 | 等待回收完成。如果結果是 is still held,請自己空出該連接埠,或改選另一個 remote。 |
Remote reclaim finished: listen port N is still held (code= |
清理無法空出該連接埠。 | 在伺服器上找出占用者,試試 reclaim,或設定 remote。 |
stderr: ... Host key verification failed 或 REMOTE HOST IDENTIFICATION HAS CHANGED |
伺服器的金鑰與已儲存的不同。 | 如果這個變更在預期之中,請從 known_ 中移除舊的項目。如果不是,就不要連線。 |
一再出現 Reconnecting in 60s (attempt N),而 tunnel 從未顯示 Tunnel up |
每次連線都失敗。 | 閱讀每一次之前的 stderr: 行。 |
tunnel 顯示 Tunnel up,但該位址沒有回應 |
中繼這一端沒問題,伺服器那一端有問題。 | 檢查反向 proxy 是否在執行並指向 remote,以及 remote 是否符合 proxy 連到它的方式。 |
| tunnel 連線、中斷、再重新連線,不斷循環 | 伺服器或網路上的某個東西結束了這條連線。 | 記錄會顯示結束碼和 ssh 的訊息。請查看伺服器的 sshd 記錄。 |
| 訊息一直留在佇列中 | required 為 true,而 tunnel 未連線。 |
修好 tunnel,或把 required 設為 false。 |