關于OpenStack中虛擬機VNC訪問安全問題

背景

這兩天收到安全部門同事的郵件,說線上OpenStack環境的物理機VNC端口能夠自由訪問,要求整改。我這隨機抽查了兩臺宿主機的VNC端口,確實很多業務的同學使用noVNC后沒有退出終端的習慣,往往都是用完了就直接關閉窗口。不得不說這樣隱患很大啊,首先不說通過外部方式規避風險,如果內網里面有一些script kiddie隨時都能將我們線上的虛擬機VNC端口掃出來干些壞事。我這里也用過nmap測試了下開發環境的網絡端口,如下:

$ nmap 192.168.68.0/24

Nmap scan report for kvm54.com (192.168.68.54)
Host is up (0.00017s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE
111/tcp  open  rpcbind
5900/tcp open  vnc
5901/tcp open  vnc-1
6789/tcp open  ibm-db2-admin

Nmap scan report for kvm55.com (192.168.68.55)
Host is up (0.00020s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE
111/tcp  open  rpcbind
5900/tcp open  vnc
5901/tcp open  vnc-1
5902/tcp open  vnc-2

Nmap scan report for kvm56.com (192.168.68.56)
Host is up (0.00023s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE
5900/tcp open  vnc
5901/tcp open  vnc-1
5902/tcp open  vnc-2

Nmap scan report for kvm57.com (192.168.68.57)
Host is up (0.00018s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE
5900/tcp open  vnc
5901/tcp open  vnc-1
5902/tcp open  vnc-2
6789/tcp open  ibm-db2-admin

這個結果很恐怖啊,如果有業務同學在使用noVNC之后沒有退出終端,那么另一個人如果知道了宿主機的IP和端口是完全可以登錄這臺虛擬機的。

解決

知道了問題,那就有對應的解決方案!

我這里目前暫時只想到兩個方法來解決這個問題,也希望有大牛可以指點。

  • 方案一
    通過IPTABLES限制INPUT表對5900:5999的訪問規則

  • 方案二
    添加密碼訪問VNC

操作

IPTABLES

我們知道OpenStack通過VNC Proxy將管理網和業務網隔離開來,以便我們可以使用管理網絡的6080端口訪問虛擬機VNC,同時提供Token用于驗證訪問的合法性。一個VNC Proxy在OpenStack里的處理流程如下:

VNC Porxy處理流程

1. 一個用戶試圖從瀏覽器里面打開連接到虛擬機的VNC Client
2. 瀏覽器向nova-api發送請求,要求返回訪問vnc的url
3. nova-api調用nova-compute的get vnc console方法,要求返回連接VNC的信息
4.nova-compute調用libvirt的get vnc console函數
5.libvirt會通過解析虛擬機運行的/etc/libvirt/qemu/instance-0000000c.xml文件來獲得VNC Server的信息
6.libvirt將host, port等信息以json格式返回給nova-compute
7.nova-compute會隨機生成一個UUID作為Token
8.nova-compute將libvirt返回的信息以及配置文件中的信息綜合成connect_info返回給nova-api
9.nova-api會調用nova-consoleauth的authorize_console函數
10.nova-consoleauth會將instance –> token, token –> connect_info的信息cache起來
11.nova-api將connect_info中的access url信息返回給瀏覽器:http://172.24.1.1:6080/vnc_auto.html?token=7efaee3f-eada-4731-a87c-e173cbd25e98&title=helloworld%289169fdb2-5b74-46b1-9803-60d2926bd97c%29
12.瀏覽器會試圖打開這個鏈接
13.這個鏈接會將請求發送給nova-novncproxy
14.nova-novncproxy調用nova-consoleauth的check_token函數
15.nova-consoleauth驗證了這個token,將這個instance對應的connect_info返回給nova-novncproxy
16.nova-novncproxy通過connect_info中的host, port等信息,連接compute節點上的VNC Server,從而開始了proxy的工作

這里重要的就是第16步, nova-novncproxy是通過連接host:vncport的方式提供vnc訪問服務。
那么也就是說,計算節點的VNC端口只需要讓nova-novncporxy服務能夠訪問就行,有了這個就好辦了。

操作IPTABLES

在所有計算節點IPTABLES的INPUT表中添加如下規則:

$ iptables -A INPUT -s {{ CONTROLLER_NODE_IP }}/32 -p tcp -m multiport --dports 5900:5999 -m comment --comment "ACCEPT VNC Port only by Controller Node" -j ACCEPT

$ iptables -A INPUT -p tcp -m multiport --dports 5900:5999 -j REJECT --reject-with icmp-port-unreachable

原理很簡單,就是只接受控制節點訪問本機的5900到5999端口,其他的連接一律拒絕。
當下次再用nmap掃描局域網的宿主機端口時,便不能看到VNC的端口。

$ nc -vz 192.168.68.58 5900
nc: connect to 192.168.68.58 port 5900 (tcp) failed: Connection refused

VNC添加訪問密碼

另外我們知道Libvirtd在<graphics>域里面是支持配置VNC的訪問密碼的。

...
 <graphics type='vnc' port='-1' autoport='yes' listen='192.168.68.57' passwd='YOUR-PASSWORD-HERE' keymap='en-us'/>
...

那么Nova在創建虛擬機配置的方法中也可以找到對應graphics的代碼,我這里修改得很簡單,直接在返回的dev列表里面添加個passwd的value,而value就是VNC的訪問密碼。

virt/libvirt/config.py

1361 class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):
1362 
1363     def __init__(self, **kwargs):
1364         super(LibvirtConfigGuestGraphics, self).__init__(root_name="graphics",
1365                                                          **kwargs)
1366 
1367         self.type = "vnc"
1368         self.autoport = True
1369         self.keymap = None
1370         self.listen = None
1371 
1372     def format_dom(self):
1373         dev = super(LibvirtConfigGuestGraphics, self).format_dom()
1374 
1375         dev.set("type", self.type)
1376         if self.autoport:
1377             dev.set("autoport", "yes")
1378         else:
1379             dev.set("autoport", "no")
1380         if self.keymap:
1381             dev.set("keymap", self.keymap)
1382         if self.listen:
1383             dev.set("listen", self.listen)
1384         dev.set("passwd", "magine1989")
1385 
1386         return dev

修改過后重啟nova-compute服務,即在下次創建虛擬機的時候生效,其結果如下:

noVNC

關于兩種方案的思考

關于上面兩種方案,很顯然第一種方案很簡單,而且高效更安全。唯一一點不好的就是訪問計算節點的VNC端口只能通過控制節點做ssh代理,有時候在界面上使用noVNC訪問虛擬機,實時性沒那么好。至于第二種方案,在做公有云的場景下并不適用,不可能讓用戶在訪問VNC的過程中再加一層密碼。密碼的靈活性先不談,單是用戶體驗上就不太友好。如果在私有云或者內網環境下,VNC的訪問密碼還是可以在一定程度上隔離掉腳本小子的騷擾,又不至于影響VNC端口不能訪問的尷尬,不管怎樣方案二還是具有一定的侵入性。但是我相信Nova的開發沒將VNC訪問密碼作為用戶安全的輔助手段還是有道理的。畢竟有了第一種比較完美解決方案,再加個不太靈活的密碼就太畫蛇添足了。


參考:

http://libvirt.org/formatdomain.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容