OpenSSH/Cookbook/隧道
在隧道或埠轉發中,本地埠連線到遠端主機的埠,反之亦然。因此,對一臺機器上埠的連線實際上是對另一臺機器上埠的連線。
ssh(1) 的選項 -f (轉到後臺)、-N (不執行遠端程式) 和 -T (停用偽終端分配) 對於僅用於建立隧道的連線很有用。
在常規埠轉發中,對本地埠的連線將轉發到遠端機器上的埠。這是一種保護不安全協議或使遠端服務看起來像本地服務的方法。這裡我們分兩步轉發了 VNC。
$ ssh -L 5901:localhost:5901 -l fred desktop.example.org
這樣,在本地機器上對轉發埠的連線實際上將連線到遠端機器。
可以同時指定多個隧道。隧道可以是任何型別,不僅僅是常規轉發。有關反向隧道的更多資訊,請參見下面的下一節。有關動態轉發的更多資訊,請參見 代理和跳躍主機 部分。
$ ssh -L 5901:localhost:5901 \
-L 5432:localhost:5432 \
-l fred desktop.example.org
如果連線僅用於建立隧道,則可以指示它不執行任何遠端程式 (-N),使其成為非互動式會話,並將其置於後臺 (-f)。
$ ssh -fN -L 3128:localhost:3128 -l fred server.example.org
請注意,即使 authorized_keys 使用 command="..." 選項強制執行程式,-N 也會起作用。因此,使用 -N 的連線將保持開啟狀態,而不是執行程式然後退出。
上面的三個連線可以儲存在 SSH 客戶端的配置檔案 ~/.ssh/config 中,甚至可以設定快捷方式。
Host desktop desktop.example.org
HostName desktop.example.org
User fred
LocalForward 5901 localhost:5901
Host postgres
HostName desktop.example.org
User fred
LocalForward 5901 localhost:5901
LocalForward 5432 localhost:5432
Host server server.example.org
HostName server.example.org
User fred
ExitOnForwardFailure no
LocalForward 3128 localhost:3128
Host *
ExitOnForwardFailure yes
使用這些設定,連線到 desktop、desktop.example.org、postgres、server 或 server.example.org 時,會自動新增列出的隧道。最後面的萬用字元配置適用於上述所有尚未將 ExitOnForwardFailure 設定為 'no' 的主機,如果無法建立隧道,客戶端將拒絕連線。將使用任何給定配置指令的第一個獲得的值,但檔案的內容可以透過命令列傳遞的執行時選項覆蓋。
隧道可以透過一箇中間主機到達第二個主機,後者不需要在公開可訪問的網路上。但是,第二個遠端機器上的目標埠必須在與第一個主機相同的網路上可訪問。這裡,192.168.0.101 和 bastion.example.org 必須在同一個網路上,並且 bastion.example.org 必須直接可訪問執行 ssh(1) 的客戶端機器。因此,192.168.2.101 上的埠 80 必須對機器 bastion.example.org 可用。
$ ssh -fN -L 1880:192.168.0.101:80 -l fred bastion.example.org
因此,一旦隧道建立,要透過主機 bastion.example.org 連線到 192.168.2.101 上的埠 80,請連線到 localhost 上的埠 1880。這種方式適用於一兩個主機。也可以透過不同的方法將多個主機連結在一起。
這裡,想法是將一組使用者的許可權限制為透過躍點主機所需的最低限度,但仍然能夠將埠轉發到其他機器。如果帳戶被充分鎖定,那麼堡壘只能用於轉發,而不能用於 shell 訪問、指令碼甚至 SFTP。以下是在 sshd_config(5) 中堡壘主機上的設定可以防止 shell 訪問或 SFTP,但仍然允許埠轉發。
Match Group tunnelers
ForceCommand /bin/false
PasswordAuthentication no
ChrootDirectory %h
PermitTTY no
X11Forwarding no
AllowTcpForwarding yes
PermitTunnel no
Banner none
請注意,由於 sshd_config(5) 中的 ChrootDirectory 配置指令,它們的主目錄(但目錄中的檔案除外)必須由 root 擁有,並且只能由 root 寫入。另外,由於 PasswordAuthentication 配置指令,如果尚未配置備用位置,則必須在 ~/.ssh/authorized_keys 中的主目錄中設定金鑰。
$ ssh -N -L 9980:localhost:80 -J fred@bastion.example.org fred@192.168.79.124
這樣,客戶端上的埠 9980 將透過 bastion.example.org 指向 192.168.79.124 上的埠 80。
在堡壘必須從內部主機建立反向隧道才能到達它的情況下,可以使用相同的方法,但前提是從內部主機到堡壘首先建立反向隧道。
有關透過中間計算機的更多資訊,請參見 Cookbook 中有關 代理和跳躍主機 的部分。
當使用客戶端的 -f 選項將隧道連線傳送到後臺執行時,目前還沒有自動方法來查詢傳送到後臺的任務的程序 ID (PID)。後臺程序通常用於埠轉發或反向埠轉發。以下是一個埠轉發的示例,也稱為隧道。連線建立後,客戶端會離開,將隧道保留在後臺,將本地主機的埠 2194 連線到遠端系統本地主機的埠 194。
$ ssh -Nf -L 2194:localhost:194 fred@203.0.113.214
特殊變數 $! 仍然為空,即使 $? 報告操作成功或失敗。原因是 shell 的作業控制沒有將客戶端置於後臺。相反,客戶端在一段時間內在前景中執行,然後正常退出,在透過 fork 在後臺執行不同的程序之前。原始客戶端的程序 ID 會消失,因為該客戶端已經消失。
查詢程序 ID 通常至少需要兩個步驟。一些用於追溯識別程序的方法涉及嘗試 ps(1) 並在輸出中查詢該帳戶的所有程序,但這沒有必要。如果後臺 SSH 客戶端是最新的客戶端,那麼可以使用 pgrep(1),否則需要將輸出用逗號分隔,並透過 xargs(1) 或程序替換饋送到 ps(1) 中。
$ ps uw | less
$ pgrep -n -x ssh
$ pgrep -d, -x ssh | xargs ps -p
$ ps -p $(pgrep -d, -x ssh)
如果 -d 選項不受支援,則可能需要對上述方法進行一些調整,具體取決於作業系統。
$ pgrep -x ssh | xargs -n 1 ps -o user,pid,ppid,args -p | awk 'NR==1 || $3==1'
USER PID PPID COMMAND
fred 97778 1 ssh -fN -L 8008:localhost:80 fred@203.0.113.8
fred 14026 1 ssh -fN -L 8183:localhost:80 fred@203.0.113.183
fred 79522 1 ssh -fN -L 8228:localhost:80 fred@203.0.113.228
fred 49773 1 ssh -fN -L 8205:localhost:80 203.0.113.205
無論哪種方式,請注意,所有在後臺執行的連線的父程序 ID (PPID) 都是 1,即作業系統的程序控制初始化系統。
意識到這一缺點,可以使用 ControlMaster 和 ControlPath 配置指令採用主動方法,以便留下一個可讀的套接字以獲取後臺任務的程序 ID。
$ ssh -M -S ~/.ssh/sockets/pid.%C -fN -L 5901:localhost:5901 fred@203.0.113.122
$ ssh -S ~/.ssh/sockets/pid.%C -O check 203.0.113.122
-M 選項使客戶端進入使用 -S 選項指定的套接字進行多路複用的主模式。然後,在 -f 將客戶端分叉到後臺後,控制命令 check 將使用套接字檢查主程序是否正在執行,並報告程序 ID。
最好將套接字放在其他帳戶不可讀或不可寫的隔離目錄中。除了複雜性之外,一個明顯的缺點是,套接字可以重複使用用於其他連線。有關風險和額外用途的更多資訊,請參見 Cookbook 中有關 多路複用 的部分。
反向隧道與普通隧道方向相反,即與 SSH 會話發起方向相反。反向隧道也被稱為遠端轉發,它指的是 SSH 會話從本地主機發起到遠端主機,同時遠端主機上的埠被轉發到本地主機上的埠。使用反向隧道有兩個步驟:第一步,使用 SSH 啟用遠端轉發從端點 A 連線到端點 B;第二步,將其他系統連線到端點 B 上的指定埠,然後將該埠轉發到端點 A。因此,雖然系統 A 發起了到系統 B 的 SSH 連線,但到 B 上指定埠的連線將透過反向隧道傳送到 A。一旦建立了 SSH 連線,就可以在端點 B 上像使用普通隧道一樣使用反向隧道,即使是端點 A 發起的 SSH 連線。
遠端轉發是一種方法,可以用來將 SSH 透過 SSH 轉發,以訪問無法直接訪問的系統,例如始終開啟的位於家庭路由器後面的 SBC。首先,從無法訪問的系統開啟到另一個可以訪問的系統的 SSH 會話,該系統包含一個指定的反向隧道。在這個例子中,雖然 SSH 連線是從本地系統(端點 A)到遠端系統(端點 B),但該連線包含從該遠端系統(端點 B)上的埠 2022 到本地系統上的埠 22 的反向隧道。
$ ssh -fN -R 2022:localhost:22 -l fred server.example.org
最後,使用上面的例子,在遠端機器 server.example.org 上建立到埠 2022 上的反向隧道連線。因此,即使連線建立在 server.example.org 上的 locahost 上的埠 2022,資料包最終也會透過承載反向隧道的 SSH 初始連線系統上的埠 22 傳輸。
$ ssh -p 2022 -l fred localhost
因此,這個例子允許透過 SSH 訪問原本無法訪問的系統。SSH 連接出去,建立一個反向隧道,然後第二個系統可以隨時透過 SSH 連線到第一個系統,只要隧道一直存在。如果使用金鑰和迴圈來生成帶有遠端轉發的 SSH 連線,那麼反向隧道可以自動維護。
下一個例子展示瞭如何透過第二個系統 server.example.org 透過 SSH 從無法直接訪問的系統訪問 VNC。從執行 VNC 伺服器的系統開始,將第一個 VNC 顯示的埠反向轉發到 server.example.org 上的第三個 VNC 顯示。
$ ssh -fNT -R 5903:localhost:5901 -l fred server.example.org
然後,在系統 server.example.org 上,使用者可以連線到該系統 localhost 地址上的第三個 VNC 顯示,並透過隧道連線到源系統。
$ xvncviewer :3
這也是一個例子,說明轉發埠不需要相同。
遠端轉發可以包含在 SSH 客戶端的配置檔案中,使用 RemoteForward 指令。有關詳細資訊,請參閱下一節。
反向隧道的常見用例是,當您需要訪問位於 NAT 或防火牆(或兩者)後面的系統或服務時,而 SSH 連線被阻止,但您可以直接訪問防火牆外部的第二個系統,該系統可以接受傳入連線。在這種情況下,很容易從防火牆後面的內部系統建立到外部第二個系統的反向隧道。一旦 SSH 連線建立了反向隧道,就可以從外部連線到內部系統,其他系統可以連線到遠端系統上的轉發埠。外部的遠端系統充當中繼伺服器,將連線轉發到內部的啟動系統。
使用客戶端配置檔案配置遠端轉發
[edit | edit source]RemoteForward 客戶端配置指令可以在 ssh_config(5) 中使用,以從另一個系統建立反向隧道。ForkAfterAuthentication 將為 -f 和 SessionType 將為 -N,作為執行時引數,當然,在本例中是可選的,本例與上面小節中的第一個例子相同。
Host server server.example.org
User fred
HostName server.example.org
ForkAfterAuthentication yes
SessionType none
RemoteForward 2022 localhost:22
有了這些設定,在另一個系統上,只需輸入 ssh server 連線到 server 即可建立反向隧道。然後,在 server.example.org 上,連線到 localhost 上的埠 2022 將會傳回原始系統上的埠 22。
Host server server.example.org
User fred
HostName server.example.org
ForkAfterAuthentication yes
SessionType none
RequestTTY no
RemoteForward 5903 localhost:5901
同樣,這複製了上面小節中的第二個例子,只是在客戶端配置檔案中使用,而不是使用執行時引數。有關更多資訊,請參閱關於使用 客戶端配置檔案 的章節。
在已建立的連線中新增或刪除隧道
[edit | edit source]可以使用轉義序列在現有連線中新增或刪除隧道、反向隧道和 SOCKS 代理。預設的跳脫字元是波浪號 (~),完整選項範圍在 ssh(1) 的手冊頁中描述。轉義序列只有在作為一行中的第一個字元輸入並後跟回車時才有效。當向現有連線新增或刪除隧道時,將使用 ~C,即命令列。
要在活動的 SSH 會話中新增隧道,請使用轉義序列在 SSH 中開啟命令列,然後輸入隧道的引數。
~C L 2022:localhost:22
從活動的 SSH 會話中刪除隧道幾乎相同。我們使用 -KL、-KR 和 -KD 而不是 -L、-R 或 -D,再加上埠號。使用轉義序列在 SSH 中開啟命令列,然後輸入刪除隧道的引數。
~C KL2022
在多路複用連線中新增或刪除隧道
[edit | edit source]在多路複用時,有一個額外的轉發選項。多個 SSH 連線可以多路複用到單個 TCP 連線上。可以將控制命令傳遞給主程序,以向主程序新增或刪除埠轉發。
首先建立一個主連線,並分配一個套接字路徑。
$ ssh -S '/home/fred/.ssh/%h:%p' -M server.example.org
然後,使用套接字路徑,可以新增埠轉發。
$ ssh -O forward -L 2022:localhost:22 -S '/home/fred/.ssh/%h:%p' fred@server.example.org
從 OpenSSH 6.0 開始,可以使用控制命令取消特定的埠轉發。
$ ssh -S "/home/fred/.ssh/%h:%p" -O cancel -L 2022:localhost:22 fred@server.example.org
有關多路複用的更多資訊,請參閱 Cookbook 部分中的 多路複用。
將隧道限制到特定埠
[edit | edit source]預設情況下,埠轉發將允許轉發到任何埠(如果允許)。限制使用者可以在轉發中使用的埠的方法是在伺服器端應用 PermitOpen 選項,無論是在伺服器的配置中,還是在使用者的公鑰中的 authorized_keys 中。例如,在 sshd_config(5) 中使用此設定,所有使用者只能將埠轉發到伺服器上的埠 7654,如果他們嘗試轉發
PermitOpen localhost:7654
如果多個埠用空格隔開,則可以在同一行上指定多個埠。
PermitOpen localhost:7654 localhost:3306
如果客戶端嘗試轉發到不允許的埠,將會出現一條警告訊息,其中包含文字“open failed: administratively prohibited: open failed”,但連線會像往常一樣繼續。但是,即使客戶端在配置中設定了 ExitOnForwardFailure,連線仍然會成功,儘管有警告訊息。
但是,如果可以使用 shell 訪問,則可以執行其他埠轉發器,因此,如果沒有進一步的限制,PermitOpen 更多的是提醒或建議,而不是限制。但對於很多情況來說,這樣的提醒可能就足夠了。
對於反向隧道,可以使用 PermitListen 選項。它確定遠端系統上哪些埠可用。因此,以下示例允許 ssh -R 2022:localhost:xxxx,其中 xxxx 可以是反向隧道來源處任何可用的埠號,但只有隧道遠端的 2022 埠可用。
PermitListen localhost:2022
PermitOpen 或 PermitListen 選項可以作為一或多個 Match 塊的一部分使用,如果轉發選項需要根據使用者、組、客戶端地址或網路、伺服器地址或網路以及 sshd(8) 使用的監聽埠的各種組合而變化。如果使用 Match 條件來選擇性地應用埠轉發的規則,也可以透過將 PermitTTY 設定為 no 來阻止帳戶獲得互動式 shell。這將阻止在伺服器上分配偽終端 (PTY),從而阻止 shell 訪問,但允許其他程式仍然執行,除非另外指定了適當的強制命令。
Match Group mariadbusers
PermitOpen localhost:3306
PermitTTY no
ForceCommand /usr/bin/true
有了 sshd_config(5) 中的這個節,只能透過新增 -N 選項來連線,以避免執行遠端命令。
$ ssh -N -L 3306:localhost:3306 server.example.org
-N 選項可以單獨使用,也可以與 -f 選項一起使用,該選項在建立連線後將客戶端降至後臺。
如果伺服器配置中的 Match 塊中沒有 ForceCommand 選項,如果被阻止獲取 PTY 的客戶端嘗試獲取 PTY,客戶端上會顯示警告“PTY allocation request failed on channel n”,其中 n 是通道號,但連線會正常成功,沒有遠端 shell,埠仍然會轉發。包括 shell 在內的各種程式仍然可以由客戶端指定,只是它們不會獲得 PTY。因此,要真正阻止除轉發之外對系統的訪問,需要使用強制命令。工具 true(1) 非常適合這個用途。請注意,true(1) 可能在不同系統上的不同位置。
僅使用金鑰限制埠轉發請求
[edit | edit source]以下 authorized_keys 行展示了 PermitOpen 選項前置到金鑰中,以便使用該特定金鑰連線的使用者只能轉發到埠 8765
permitopen="localhost:8765" ssh-ed25519 AAAAC3NzaC1lZDI1NT...
如果用逗號分隔,則可以將多個 PermitOpen 選項應用於同一個公鑰,因此一個金鑰可以允許多個埠。
預設情況下,允許 shell 訪問。使用 shell 訪問,仍然可以執行其他埠轉發器。如果與使用 command 選項的適當強制命令相結合,金鑰內部的 no-pty 選項可以幫助建立僅允許轉發而不允許互動式 shell 的金鑰。以下是在 authorized_keys 中列出的此類金鑰的示例。
no-pty,permitopen="localhost:9876",command="/usr/bin/true" ssh-ed25519 AAAAC3NzaC1lZDI1NT...
no-pty 選項會阻止互動式 shell。客戶端仍然可以連線到遠端伺服器並允許轉發,但會返回一個錯誤資訊,其中包含“PTY 分配請求在通道 n 上失敗”的訊息。但如前文所述,仍然有很多方法可以繞過它,新增 command 選項會阻礙它們。
這種方法很難鎖定。如果帳戶以任何方式(直接或間接)對 authorized_keys 檔案具有寫入許可權,那麼使用者就可以新增新金鑰或用更寬鬆的選項覆蓋現有金鑰。為了防止這種情況,伺服器必須被配置為在使用者無法訪問的位置查詢該檔案。有關此方面的詳細資訊,請參見關於 公鑰認證 的部分。在許多情況下,使用 sshd_config(5) 在上一節中描述的方法可能更實用。