理想情況下的運(yùn)維,是不需要ops去ssh到服務(wù)器上檢查問(wèn)題(包括安全問(wèn)題)/日志等,這些是可以通過(guò)更好
監(jiān)控,如使用newrelic,或者更好的日志收集系統(tǒng),如splunk等去避免。不過(guò)現(xiàn)實(shí)不總是完美的,加上歷史遺留的原因,ops總是會(huì)ssh到堡壘機(jī)(bastion host),然后跳轉(zhuǎn)到目標(biāo)服務(wù)器去做操作。
于是,就有很多人(包括我)在堡壘機(jī)上生成key/pair, 而且private key很少加密(包括我),這個(gè)存在很嚴(yán)重的安全風(fēng)險(xiǎn)。
一個(gè)比較合理的方式是通過(guò)ssh proxy的方式去訪問(wèn)目標(biāo)服務(wù)器,這樣不需要把key暴露給bastion,比如:
~> ssh -L 3333:destination_host:22 user@bastion
然后再啟動(dòng)一個(gè)新的ssh進(jìn)程去通過(guò)proxy連接:
~> ssh -p 3333 user@0
每次這么操作略麻煩,可以通過(guò)在ssh配置文件簡(jiǎn)化:
Host bastion
HostName 192.168.1.1
HostKeyAlias bastion
LocalForward 9999 target:22
那么建立proxy就只是ssh user@bastion就可以了,然后同理去ssh -p 9999 user@0。
這么做的壞處在于~/.ssh/config配置可能會(huì)迅速膨脹,同時(shí),每次還是啟動(dòng)兩個(gè)進(jìn)程去完成這件事情,不開(kāi)心。
于是,我們的安全大神介紹一個(gè)更加簡(jiǎn)單的方法,在~/.ssh/config中,加入下面的內(nèi)容:
Host */*
ProxyCommand ssh $(dirname %h) -W $(basename %h):%
如此我就可以通過(guò)ssh user@bastion/target的方式直接ssh到遠(yuǎn)程主機(jī),ProxyCommand指令會(huì)
生成兩個(gè)進(jìn)程,后臺(tái)proxy進(jìn)程,前臺(tái)的進(jìn)程直接通過(guò)proxy連接到目標(biāo)主機(jī)。這樣從命令行窗口看來(lái)我只
是打開(kāi)了一個(gè)會(huì)話。同時(shí),你可以鏈接很多個(gè)主機(jī),如ssh user@bastion/targetA/targetB/targetC。
依次通過(guò)前一個(gè)主機(jī)建立的proxy連接到后面的主機(jī)上。
這個(gè)方法有一些局限:
- 不能在主機(jī)鏈上指定不同的端口;
- 不能對(duì)不同的主機(jī)使用不同的登錄用戶名;
- 不同鏈上建立的連接不能重用已經(jīng)建立的連接,這可能會(huì)導(dǎo)致連接的速度減緩;
- 其實(shí)還有個(gè)問(wèn)題就是不能很容易的從
targetC退出到targetB…… (我想的)
為了解決這些問(wèn)題,大神想出了終極解決方案:
Host */*
ControlMaster auto
ControlPath ~/.ssh/.sessions/%r@%h:%p
ProxyCommand /bin/sh -c 'mkdir -p -m700 ~/.ssh/.sessions/"%r@$(dirname %h)" && exec ssh -o "ControlMaster auto" -o "ControlPath ~/.ssh/.sessions/%r@$(dirname %h):%p" -o "ControlPersist 120s" -l %r -p %p $(dirname %h) -W $(basename %h):%p'
-
Host */*: 匹配ssh到A/B/X這樣的主機(jī)類型,然后遞歸的ssh到鏈中的主機(jī); -
ControlMaster auto: 這個(gè)指令的意思是指ssh應(yīng)當(dāng)復(fù)用已有的control channel連接遠(yuǎn)程主
機(jī),如果這樣的channel不存在,則重新創(chuàng)建,以便以后的鏈接復(fù)用; -
ControlPath ~/.ssh/.sessions/%r@%h:%p: 這個(gè)指令告訴ssh control channel socket
文件的位置。對(duì)于每個(gè)遠(yuǎn)程主機(jī),socket文件應(yīng)該是唯一的,如此我們可以重用已有連接并且跳過(guò)驗(yàn)證。所
以我們用%r(remote login name),%h(remote host name)和%p(端口)作為文件名的部分。
唯一的問(wèn)題是因?yàn)槁窂街械?code>/,這里會(huì)在%h被當(dāng)成一個(gè)目錄,但是ssh不會(huì)自動(dòng)創(chuàng)建目錄; -
ProxyCommand blah: 命令開(kāi)始時(shí)就先創(chuàng)建了所有必須的目錄。ControlPersist的意思是如果
control channel 2分鐘內(nèi)沒(méi)有活動(dòng)則停止ssh進(jìn)程。如果你有兩個(gè)會(huì)話bastion/HostA和
bastion/HostB,如果不配置ControlPersist,結(jié)束第一個(gè)進(jìn)程時(shí)第二進(jìn)程也會(huì)同時(shí)被干掉。
所以,當(dāng)你用上面的配置去ssh user@bastion/A/B/C時(shí):
- ssh 匹配到了
*/*模式 - ssh 嘗試重用
~/.ssh/.sessions/user@bastion/A/B/C:22的socket,如果成功則建立連接,
沒(méi)有則繼續(xù)執(zhí)行 - ssh執(zhí)行
ProxyCommand中的內(nèi)容, 創(chuàng)建目錄同時(shí)遞歸的ssh到最終的主機(jī)C - 然后ssh在主機(jī)C上進(jìn)行身份驗(yàn)證,成功則創(chuàng)建
~/.ssh/.sessions/user@bastion/A/B/C:22的
control channel socket文件,并且成為control channel的master - 顯示命令行提示符
你現(xiàn)在有沒(méi)有和我一樣暈,在和大神交流一番后,大神告訴我一個(gè)改進(jìn)版的配置:
Host */*
ControlMaster auto
ProxyCommand /usr/bin/ssh -o "ControlMaster auto" -o "ControlPath ~/.ssh/.sessions/%%C" -o "ControlPersist 120s" -l %r -p %p $(dirname %h) -W $(basename %h):%p
Host *
ControlPath ~/.ssh/.sessions/%C
這個(gè)配置要簡(jiǎn)單些,不過(guò)他假設(shè)你已經(jīng)創(chuàng)建了~/.ssh/.sessions目錄。
榮耀歸于Dmitry大神,雖然那個(gè)ssh keypair我還沒(méi)有刪除……。
原文