通過SSH實(shí)現(xiàn)TCP/IP隧道(端口轉(zhuǎn)發(fā)):使用OpenSSH可能的8種場(chǎng)景
對(duì)于 Secure Shell (SSH) 這樣的網(wǎng)絡(luò)協(xié)議來說,其主要職責(zé)就是在終端模式下訪問一個(gè)遠(yuǎn)程系統(tǒng)。因?yàn)?SSH 協(xié)議對(duì)傳輸數(shù)據(jù)進(jìn)行了加密,所以通過它在遠(yuǎn)端系統(tǒng)執(zhí)行命令是安全的。此外,我們還可以在這種加密后的連接上通過創(chuàng)建隧道(端口轉(zhuǎn)發(fā))的方式,來實(shí)現(xiàn)兩個(gè)不同終端間的互聯(lián)。憑借這種方式,只要我們能通過 SSH 創(chuàng)建連接,就可以繞開防火墻或者端口禁用的限制。
這個(gè)話題在網(wǎng)絡(luò)領(lǐng)域有大量的應(yīng)用和討論:
- Wikipedia: SSH Tunneling
- O’Reilly: Using SSH Tunneling
- Ssh.com: Tunneling Explained
- Ssh.com: Port Forwarding
- SecurityFocus: SSH Port Forwarding
- Red Hat Magazine: SSH Port Forwarding
我們?cè)诮酉聛淼膬?nèi)容中并不討論端口轉(zhuǎn)發(fā)的細(xì)節(jié),而是準(zhǔn)備介紹一個(gè)如何使用 OpenSSH 來完成 TCP 端口轉(zhuǎn)發(fā)的速查表,其中包含了八種常見的場(chǎng)景。有些 SSH 客戶端,比如 PuTTY,也允許通過界面配置的方式來實(shí)現(xiàn)端口轉(zhuǎn)發(fā)。而我們著重關(guān)注的是通過 OpenSSH 來實(shí)現(xiàn)的的方式。
在下面的例子當(dāng)中,我們假設(shè)環(huán)境中的網(wǎng)絡(luò)劃分為外部網(wǎng)絡(luò)(network1)和內(nèi)部網(wǎng)絡(luò)(network2)兩部分,并且這兩個(gè)網(wǎng)絡(luò)之間,只能在 externo1 與 interno1 之間通過 SSH 連接的方式來互相訪問。外部網(wǎng)絡(luò)的節(jié)點(diǎn)之間和內(nèi)部網(wǎng)絡(luò)的節(jié)點(diǎn)之間是完全聯(lián)通的。
SSH tunnels: no tunnel
場(chǎng)景 1
在 externo1 節(jié)點(diǎn)訪問由 interno1 節(jié)點(diǎn)提供的 TCP 服務(wù)(本地端口轉(zhuǎn)發(fā) / 綁定地址 = localhost / 主機(jī) = localhost )
externo1 節(jié)點(diǎn)可以通過 OpenSSH 連接到 interno1 節(jié)點(diǎn),之后我們想通過其訪問運(yùn)行在 5900 端口上的 VNC 服務(wù)。
SSH Tunnels: Scenario 1
我們可以通過下面的命令來實(shí)現(xiàn):
- externo1 $ ssh -L 7900:localhost:5900 user@interno1
現(xiàn)在,我們可以在 externo1 節(jié)點(diǎn)上確認(rèn)下 7900 端口是否處于監(jiān)聽狀態(tài)中:
- externo1 $ netstat -ltn
- Active Internet connections (only servers)
- Proto Recv-Q Send-Q Local Address Foreign Address State
- ...
- Tcp 0 0 127.0.0.1:7900 0.0.0.0:* LISTEN
- ...
我們只需要在 externo1 節(jié)點(diǎn)上執(zhí)行如下命令即可訪問 internal 節(jié)點(diǎn)的 VNC 服務(wù):
- externo1 $ vncviewer localhost::7900
注意:在 vncviewer 的 man 手冊(cè)中并未提及這種修改端口號(hào)的方式。在 About VNCViewer configuration of the output TCP port 中可以看到。這也是 the TightVNC vncviewer 所介紹的的。
場(chǎng)景 2
在 externo2 節(jié)點(diǎn)上訪問由 interno1 節(jié)點(diǎn)提供的 TCP 服務(wù)(本地端口轉(zhuǎn)發(fā) / 綁定地址 = 0.0.0.0 / 主機(jī) = localhost)
這次的場(chǎng)景跟方案 1 的場(chǎng)景的類似,但是我們這次想從 externo2 節(jié)點(diǎn)來連接到 interno1 上的 VNC 服務(wù):
SSH Tunnels: Scenario 2
正確的命令如下:
- externo1 $ ssh -L 0.0.0.0:7900:localhost:5900 user@interno1
看起來跟方案 1 中的命令類似,但是讓我們看看 netstat 命令的輸出上的區(qū)別。7900 端口被綁定到了本地(127.0.0.1),所以只有本地進(jìn)程可以訪問。這次我們將端口關(guān)聯(lián)到了 0.0.0.0,所以系統(tǒng)允許任何 IP 地址的機(jī)器訪問 7900 這個(gè)端口。
- externo1 $ netstat -ltn
- Active Internet connections (only servers)
- Proto Recv-Q Send-Q Local Address Foreign Address State
- ...
- Tcp 0 0 0.0.0.0:7900 0.0.0.0:* LISTEN
- ...
所以現(xiàn)在在 externo2 節(jié)點(diǎn)上,我們可以執(zhí)行:
- externo2 $ vncviewer externo1::7900
來連接到 interno1 節(jié)點(diǎn)上的 VNC 服務(wù)。
除了將 IP 指定為 0.0.0.0 之外,我們還可以使用參數(shù) -g(允許遠(yuǎn)程機(jī)器使用本地端口轉(zhuǎn)發(fā)),完整命令如下:
- externo1 $ ssh -g -L 7900:localhost:5900 user@interno1
這條命令與前面的命令能實(shí)現(xiàn)相同效果:
- externo1 $ ssh -L 0.0.0.0:7900:localhost:5900 user@interno1
換句話說,如果我們想限制只能連接到系統(tǒng)上的某個(gè) IP,可以像下面這樣定義:
- externo1 $ ssh -L 192.168.24.80:7900:localhost:5900 user@interno1
- externo1 $ netstat -ltn
- Active Internet connections (only servers)
- Proto Recv-Q Send-Q Local Address Foreign Address State
- ...
- Tcp 0 0 192.168.24.80:7900 0.0.0.0:* LISTEN
- ...
場(chǎng)景 3
在 interno1 上訪問由 externo1 提供的 TCP 服務(wù)(遠(yuǎn)程端口轉(zhuǎn)發(fā) / 綁定地址 = localhost / 主機(jī) = localhost)
在場(chǎng)景 1 中 SSH 服務(wù)器與 TCP 服務(wù)(VNC)提供者在同一個(gè)節(jié)點(diǎn)上。現(xiàn)在我們想在 SSH 客戶端所在的節(jié)點(diǎn)上,提供一個(gè) TCP 服務(wù)(VNC)供 SSH 服務(wù)端來訪問:
SSH Tunnels: Scenario 3
將方案 1 中的命令參數(shù)由 -L 替換為 -R。
完整命令如下:
- externo1 $ ssh -R 7900:localhost:5900 user@interno1
然后我們就能看到 interno1 節(jié)點(diǎn)上對(duì) 7900 端口正在監(jiān)聽:
- interno1 $ netstat -lnt
- Active Internet connections (only servers)
- Proto Recv-Q Send-Q Local Address Foreign Address State
- ...
- Tcp 0 0 127.0.0.1:7900 0.0.0.0:* LISTEN
- ...
現(xiàn)在在 interno1 節(jié)點(diǎn)上,我們可以使用如下命令來訪問 externo1 上的 VNC 服務(wù):
- interno1 $ vncviewer localhost::7900
場(chǎng)景 4
interno2 使用 externo1 上提供的 TCP 服務(wù)(遠(yuǎn)端端口轉(zhuǎn)發(fā) / 綁定地址 = 0.0.0.0 / 主機(jī) = localhost)
與場(chǎng)景 3 類似,但是現(xiàn)在我們嘗試指定允許訪問轉(zhuǎn)發(fā)端口的 IP(就像場(chǎng)景 2 中做的一樣)為 0.0.0.0,這樣其他節(jié)點(diǎn)也可以訪問 VNC 服務(wù):
SSH Tunnels: Scenario 4
正確的命令是:
- externo1 $ ssh -R 0.0.0.0:7900:localhost:5900 user@interno1
但是這里有個(gè)重點(diǎn)需要了解,出于安全的原因,如果我們直接執(zhí)行該命令的話可能不會(huì)生效,因?yàn)槲覀冃枰薷?SSH 服務(wù)端的一個(gè)參數(shù)值 GatewayPorts,它的默認(rèn)值是:no。
GatewayPorts
該參數(shù)指定了遠(yuǎn)程主機(jī)是否允許客戶端訪問轉(zhuǎn)發(fā)端口。默認(rèn)情況下,sshd(8) 只允許本機(jī)進(jìn)程訪問轉(zhuǎn)發(fā)端口。這是為了阻止其他主機(jī)連接到該轉(zhuǎn)發(fā)端口。GatewayPorts 參數(shù)可用于讓 sshd 允許遠(yuǎn)程轉(zhuǎn)發(fā)端口綁定到非回環(huán)地址上,從而可以讓遠(yuǎn)程主機(jī)訪問。當(dāng)參數(shù)值設(shè)置為 “no” 的時(shí)候只有本機(jī)可以訪問轉(zhuǎn)發(fā)端口;“yes” 則表示允許遠(yuǎn)程轉(zhuǎn)發(fā)端口綁定到通配地址上;或者設(shè)置為 “clientspecified” 則表示由客戶端來選擇哪些主機(jī)地址允許訪問轉(zhuǎn)發(fā)端口。默認(rèn)值是 “no”。
如果我們沒有修改服務(wù)器配置的權(quán)限,我們將不能使用該方案來進(jìn)行端口轉(zhuǎn)發(fā)。這是因?yàn)槿绻麤]有其他的限制,用戶可以開啟一個(gè)端口(> 1024)來監(jiān)聽來自外部的請(qǐng)求并轉(zhuǎn)發(fā)到 localhost:7900。
參照這個(gè)案例:netcat ( Debian # 310431: sshd_config should warn about the GatewayPorts workaround. )
所以我們修改 /etc/ssh/sshd_config,添加如下內(nèi)容:
- GatewayPorts clientspecified
然后,我們使用如下命令來重載修改后的配置文件(在 Debian 和 Ubuntu 上)。
- sudo /etc/init.d/ssh reload
我們確認(rèn)一下現(xiàn)在 interno1 節(jié)點(diǎn)上存在 7900 端口的監(jiān)聽程序,監(jiān)聽來自不同 IP 的請(qǐng)求:
- interno1 $ netstat -ltn
- Active Internet connections (only servers)
- Proto Recv-Q Send-Q Local Address Foreign Address State
- ...
- Tcp 0 0 0.0.0.0:7900 0.0.0.0:* LISTEN
- ...
然后我們就可以在 interno2 節(jié)點(diǎn)上使用 VNC 服務(wù)了:
- interno2 $ internal vncviewer1::7900
場(chǎng)景 5
在 externo1 上使用由 interno2 提供的 TCP 服務(wù)(本地端口轉(zhuǎn)發(fā) / 綁定地址 localhost / 主機(jī) = interno2 )
SSH Tunnels: Scenario 5
在這種場(chǎng)景下我們使用如下命令:
- externo1 $ ssh -L 7900:interno2:5900 user@interno1
然后我們就能在 externo1 節(jié)點(diǎn)上,通過執(zhí)行如下命令來使用 VNC 服務(wù)了:
- externo1 $ vncviewer localhost::7900
場(chǎng)景 6
在 interno1 上使用由 externo2 提供的 TCP 服務(wù)(遠(yuǎn)程端口轉(zhuǎn)發(fā) / 綁定地址 = localhost / host = externo2)
SSH Tunnels: Scenario 6
在這種場(chǎng)景下,我們使用如下命令:
- externo1 $ ssh -R 7900:externo2:5900 user@interno1
然后我們可以在 interno1 上通過執(zhí)行如下命令來訪問 VNC 服務(wù):
- interno1 $ vncviewer localhost::7900
場(chǎng)景7
在 externo2 上使用由 interno2 提供的 TCP 服務(wù)(本地端口轉(zhuǎn)發(fā) / 綁定地址 = 0.0.0.0 / 主機(jī) = interno2)
SSH Tunnels: Scenario 7
本場(chǎng)景下,我們使用如下命令:
- externo1 $ ssh -L 0.0.0.0:7900:interno2:5900 user@interno1
或者:
- externo1 $ ssh -g -L 7900:interno2:5900 user@interno1
然后我們就可以在 externo2 上執(zhí)行如下命令來訪問 vnc 服務(wù):
- externo2 $ vncviewer externo1::7900
場(chǎng)景 8
在 interno2 上使用由 externo2 提供的 TCP 服務(wù)(遠(yuǎn)程端口轉(zhuǎn)發(fā) / 綁定地址 = 0.0.0.0 / 主機(jī) = externo2)
SSH Tunnels: Scenario 8
本場(chǎng)景下我們使用如下命令:
- externo1 $ ssh -R 0.0.0.0:7900:externo2:5900 user@interno1
SSH 服務(wù)器需要配置為:
- GatewayPorts clientspecified
就像我們?cè)趫?chǎng)景 4 中講過的那樣。
然后我們可以在 interno2 節(jié)點(diǎn)上執(zhí)行如下命令來訪問 VNC 服務(wù):
- interno2 $ internal vncviewer1::7900
如果我們需要一次性的創(chuàng)建多個(gè)隧道,使用配置文件的方式替代一個(gè)可能很長(zhǎng)的命令是一個(gè)更好的選擇。假設(shè)我們只能通過 SSH 的方式訪問某個(gè)特定網(wǎng)絡(luò),同時(shí)又需要?jiǎng)?chuàng)建多個(gè)隧道來訪問該網(wǎng)絡(luò)內(nèi)不同服務(wù)器上的服務(wù),比如 VNC 或者 遠(yuǎn)程桌面。此時(shí)只需要?jiǎng)?chuàng)建一個(gè)如下的配置文件 $HOME/redirects 即可(在 SOCKS 服務(wù)器 上)。
- # SOCKS server
- DynamicForward 1080
- # SSH redirects
- LocalForward 2221 serverlinux1: 22
- LocalForward 2222 serverlinux2: 22
- LocalForward 2223 172.16.23.45:22
- LocalForward 2224 172.16.23.48:22
- # RDP redirects for Windows systems
- LocalForward 3391 serverwindows1: 3389
- LocalForward 3392 serverwindows2: 3389
- # VNC redirects for systems with "vncserver"
- LocalForward 5902 serverlinux1: 5901
- LocalForward 5903 172.16.23.45:5901
然后我們只需要執(zhí)行如下命令:
- externo1 $ ssh -F $HOME/redirects user@interno1