admin 发表于 2022-1-17 09:55:59

关于OpenStack中虚拟机VNC访问安全问题

关于OpenStack中虚拟机VNC访问安全问题
前几天收到甲方给出的文件信息,线上OpenStack环境的物理机VNC端口能够自由访问,要求整改。
随机抽查了两台宿主机的VNC端口,确实很多业务的同学使用noVNC后没有退出终端的习惯,往往都是用完了就直接关闭窗口。不得不说这样隐患很大啊,首先不说通过外部方式规避风险,如果内网里面有一些script kiddie随时都能将我们线上的虚拟机VNC端口扫出来干些坏事。我这里也用过nmap测试了下开发环境的网络端口,如下:
# nmap 10.161.53.1
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:46 CST
Nmap scan report for compute10 (10.161.53.1)
Host is up (0.000080s latency).
Not shown: 989 closed ports
PORT   STATE SERVICE
22/tcp   openssh
5900/tcp openvnc
5901/tcp openvnc-1
5902/tcp openvnc-2
5903/tcp openvnc-3
5904/tcp openunknown
5906/tcp openunknown
5907/tcp openunknown
5910/tcp opencm
5911/tcp opencpdlc
8022/tcp openoa-system
MAC Address: D4:5D:64:08:45:02 (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 1.65 seconds
# nmap 10.161.53.2
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:46 CST
Nmap scan report for compute11 (10.161.53.2)
Host is up (0.000070s latency).
Not shown: 989 closed ports
PORT   STATE SERVICE
22/tcp   openssh
5900/tcp openvnc
5901/tcp openvnc-1
5902/tcp openvnc-2
5903/tcp openvnc-3
5904/tcp openunknown
5906/tcp openunknown
5907/tcp openunknown
5911/tcp opencpdlc
5915/tcp openunknown
8022/tcp openoa-system
MAC Address: D4:5D:64:07:B3:DA (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 1.67 seconds
# nmap 10.161.53.3
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:46 CST
Nmap scan report for compute01 (10.161.53.3)
Host is up (0.000076s latency).
Not shown: 992 closed ports
PORT   STATE SERVICE
22/tcp   openssh
5900/tcp openvnc
5901/tcp openvnc-1
5902/tcp openvnc-2
5903/tcp openvnc-3
5904/tcp openunknown
5906/tcp openunknown
8022/tcp openoa-system
MAC Address: D4:5D:64:08:45:5E (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 1.68 seconds
# nmap 10.161.53.4
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:46 CST
Nmap scan report for compute02 (10.161.53.4)
Host is up (0.000081s latency).
Not shown: 988 closed ports
PORT   STATE SERVICE
22/tcp   openssh
5900/tcp openvnc
5901/tcp openvnc-1
5902/tcp openvnc-2
5903/tcp openvnc-3
5904/tcp openunknown
5906/tcp openunknown
5907/tcp openunknown
5910/tcp opencm
5911/tcp opencpdlc
5915/tcp openunknown
8022/tcp openoa-system
MAC Address: D4:5D:64:08:45:D6 (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 1.67 seconds
# nmap 10.161.53.5
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:47 CST
Nmap scan report for compute03 (10.161.53.5)
Host is up (0.000082s latency).
Not shown: 993 closed ports
PORT   STATE SERVICE
22/tcp   openssh
5900/tcp openvnc
5901/tcp openvnc-1
5902/tcp openvnc-2
5903/tcp openvnc-3
5904/tcp openunknown
8022/tcp openoa-system
MAC Address: D4:5D:64:08:44:DE (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 1.67 seconds
# nmap 10.161.53.6
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:47 CST
Nmap scan report for controller1 (10.161.53.6)
Host is up (0.000014s latency).
Not shown: 992 closed ports
PORT   STATE    SERVICE
22/tcp   open   ssh
80/tcp   open   http
1984/tcp open   bigbrother
3306/tcp open   mysql
4000/tcp filtered remoteanything
4567/tcp open   tram
5000/tcp open   upnp
5001/tcp open   commplex-link
Nmap done: 1 IP address (1 host up) scanned in 2.75 seconds
# nmap 10.161.53.7
Starting Nmap 6.40 ( http://nmap.org ) at 2022-01-17 09:47 CST
Nmap scan report for controller2 (10.161.53.7)
Host is up (0.000078s latency).
Not shown: 993 closed ports
PORT   STATE SERVICE
22/tcp   openssh
80/tcp   openhttp
1984/tcp openbigbrother
3306/tcp openmysql
4567/tcp opentram
5000/tcp openupnp
5001/tcp opencommplex-link
MAC Address: D4:5D:64:08:45:0A (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 1.55 seconds

结果太恐怖了吧,如果有业务同学在使用noVNC之后没有退出终端,那么另一个人如果知道了宿主机的IP和端口是完全可以登录这台虚拟机的,直接操作虚机,后果不堪设想:

解决
知道了问题,那就有对应的解决方案!
目前我们暂时只想到两个方法来解决这个问题,其他方法还有待大牛给予指导:

方案一
通过firewalld限制INPUT表对5900:6000的访问规则
方案二
添加密码访问VNC

操作
firewalld
我们知道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://contorller: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服务能够访问就行,有了这个就好办了。
操作firewalld
在所有计算节点firewalld的INPUT表中添加如下规则:

检查防火墙是否启动:
systemctl status firewalld.service
启动防火墙:
systemctl startfirewalld.service


开机启动防火墙:
systemctl enable firewalld.service
添加规则:
firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="10.161.53.6" port protocol="tcp" port="5900-6000" accept"
   firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="10.161.53.100" port protocol="tcp" port="5900-6000" accept"
firewall-cmd --reload
firewall-cmd --list-all
删除规则:
firewall-cmd --permanent --remove-rich-rule="rule family="ipv4" source address="10.161.53.6" port protocol="tcp" port="5900-6000" accept"

firewall-cmd --reload
firewall-cmd --list-all


添加防火墙规则:
firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="10.161.53.31/27" port protocol="tcp" port="5900-6000" accept"

firewall-cmd --reload

通过测试上面开启的firewalld会导致业务无法访问。
这里改变方式使用iptables的规则吧:

iptables -A INPUT -s 10.161.53.31/27 -p tcp -m multiport --dports 5900:5999 -m comment --comment "ACCEPT VNC Port only by Controller Node" -j ACCEPT
iptables -L
iptables -A INPUT -p tcp -m multiport --dports 5900:5999 -j REJECT --reject-with icmp-port-unreachable



admin 发表于 2022-1-17 10:59:40

4.解决方案

①从交换和防火墙的ACL控制IP访问
②修改各计算节点 nova.conf中 vncserver_listen 配置为内网 IP ,保证新建虚机没问题
③现有以及之后新建的的虚拟机,修改 libvirt.xml 中的 vnc 的监听端口,保证虚机重启后不会向公网开放端口
④修改IPtables配置规则,屏蔽端口访问,把除内网以外网段的5900~5999端口给封禁
⑤vnc增加访问密码

下面针对方法④和方法⑤进行详细的说明。
④配置IPtables
根据OpenStack VNC Proxy 流程分析中的,第16步, nova-novncproxy是通过连接host:vncport的方式提供vnc访问服务。即,计算节点的VNC端口只需要允许让nova-novncporxy服务能够访问就行。
在所有计算节点IPTABLES的INPUT表中添加如下规则:

admin 发表于 2022-1-17 14:49:23

①从交换和防火墙的ACL控制IP访问
②修改各计算节点 nova.conf中 vncserver_listen 配置为内网 IP ,保证新建虚机没问题
③现有以及之后新建的的虚拟机,修改 libvirt.xml 中的 vnc 的监听端口,保证虚机重启后不会向公网开放端口
④修改IPtables配置规则,屏蔽端口访问,把除内网以外网段的5900~5999端口给封禁
⑤vnc增加访问密码
下面针对方法④和方法⑤进行详细的说明。
④配置IPtables
根据OpenStack VNC Proxy 流程分析中的,第16步, nova-novncproxy是通过连接host:vncport的方式提供vnc访问服务。即,计算节点的VNC端口只需要允许让nova-novncporxy服务能够访问就行。

在所有计算节点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的端口。

$ nmap 10.161.53.1 5900
⑤VNC添加访问密码
对应的配置文件为:virt/libvirt/config.py

libvirtd在<graphics>域里面是支持配置VNC的访问密码

...
<graphics type='vnc' port='-1' autoport='yes' listen='192.168.23.59' passwd='YOUR-PASSWORD-HERE' keymap='en-us'/>
...
,那么Nova在创建虚拟机配置的方法中也可以找到对应graphics的代码,我这里修改得很简单,直接在返回的dev列表里面添加个passwd的value,而value就是VNC的访问密码。

class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):

    def __init__(self, **kwargs):
         super(LibvirtConfigGuestGraphics, self).__init__(root_name="graphics",
                                                          **kwargs)

         self.type = "vnc"
         self.autoport = True
         self.keymap = None
         self.listen = None

   def format_dom(self):
         dev = super(LibvirtConfigGuestGraphics, self).format_dom()

         dev.set("type", self.type)
         if self.autoport:
             dev.set("autoport", "yes")
         else:
             dev.set("autoport", "no")
         if self.keymap:
             dev.set("keymap", self.keymap)
         if self.listen:
             dev.set("listen", self.listen)
#       dev.set("passwd", "123456")
         return dev
其中dev.set("passwd", "123456")是新加入的一行,如果不需要vnc访问输入密码,直接注释掉即可。
下面是一次解决过程,因开发环境使用的是容器化部署,文件路径比较长。
解决过程
查找文件

root@controller1:~# find /var/lib/docker/aufs/diff-name config.py | grep nova
/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/config.py
/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt/config.py
/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/common/config.py
进入配置文件所在路径

root@controller01:~# cd /var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt/
root@controller01:/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt# ls
blockinfo.py   compat.py   config.py          config.pyc   designer.pycdriver.pyc   firewall.pycguest.pychost.pyc         imagebackend.pycimagecache.pyc__init__.pyc         instancejobtracker.pycmigration.pycutils.py   vif.py   volume
blockinfo.pyccompat.pycconfig.py.bak.oridesigner.pydriver.py   firewall.pyguest.py      host.py    imagebackend.pyimagecache.py   __init__.py   instancejobtracker.pymigration.py            storage      utils.pycvif.pyc
修改配置文件

root@controller1:/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt# vim config.py
在这里插入图片描述
修改后,重启nova-compute服务,即在下次创建虚拟机的时候生效,其结果如下:
在这里插入图片描述
输入密码virt/libvirt/config.py配置中新增的密码123456即可正进入虚拟机。

admin 发表于 2022-1-17 17:05:37

4.解决方案
①从交换和防火墙的ACL控制IP访问
②修改各计算节点 nova.conf中 vncserver_listen 配置为内网 IP ,保证新建虚机没问题
③现有以及之后新建的的虚拟机,修改 libvirt.xml 中的 vnc 的监听端口,保证虚机重启后不会向公网开放端口
④修改IPtables配置规则,屏蔽端口访问,把除内网以外网段的5900~5999端口给封禁
⑤vnc增加访问密码
下面针对方法④和方法⑤进行详细的说明。
④配置IPtables
根据OpenStack VNC Proxy 流程分析中的,第16步, nova-novncproxy是通过连接host:vncport的方式提供vnc访问服务。即,计算节点的VNC端口只需要允许让nova-novncporxy服务能够访问就行。

在所有计算节点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的端口。

$ nmap 192.168.23.12
⑤VNC添加访问密码
对应的配置文件为:virt/libvirt/config.py

libvirtd在<graphics>域里面是支持配置VNC的访问密码

...
<graphics type='vnc' port='-1' autoport='yes' listen='192.168.23.59' passwd='YOUR-PASSWORD-HERE' keymap='en-us'/>
...
,那么Nova在创建虚拟机配置的方法中也可以找到对应graphics的代码,我这里修改得很简单,直接在返回的dev列表里面添加个passwd的value,而value就是VNC的访问密码。

1482 class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):
1483
1484   def __init__(self, **kwargs):
1485         super(LibvirtConfigGuestGraphics, self).__init__(root_name="graphics",
1486                                                          **kwargs)
1487
1488         self.type = "vnc"
1489         self.autoport = True
1490         self.keymap = None
1491         self.listen = None
1492
1493   def format_dom(self):
1494         dev = super(LibvirtConfigGuestGraphics, self).format_dom()
1495
1496         dev.set("type", self.type)
1497         if self.autoport:
1498             dev.set("autoport", "yes")
1499         else:
1500             dev.set("autoport", "no")
1501         if self.keymap:
1502             dev.set("keymap", self.keymap)
1503         if self.listen:
1504             dev.set("listen", self.listen)
1505 #       dev.set("passwd", "123456")
1506         return dev
其中dev.set("passwd", "123456")是新加入的一行,如果不需要vnc访问输入密码,直接注释掉即可。
下面是一次解决过程,因开发环境使用的是容器化部署,文件路径比较长。
解决过程
查找文件

root@controller1:~# find /var/lib/docker/aufs/diff-name config.py | grep nova
/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/config.py
/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt/config.py
/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/common/config.py
进入配置文件所在路径

root@controller01:~# cd /var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt/
root@controller01:/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt# ls
blockinfo.py   compat.py   config.py          config.pyc   designer.pycdriver.pyc   firewall.pycguest.pychost.pyc         imagebackend.pycimagecache.pyc__init__.pyc         instancejobtracker.pycmigration.pycutils.py   vif.py   volume
blockinfo.pyccompat.pycconfig.py.bak.oridesigner.pydriver.py   firewall.pyguest.py      host.py    imagebackend.pyimagecache.py   __init__.py   instancejobtracker.pymigration.py            storage      utils.pycvif.pyc
修改配置文件

root@controller1:/var/lib/docker/aufs/diff/pr0XDEZwLDflwwzUPc0mNVYwf6b3wJ4wxEwxNBRlmKMD7qRurdlBck41J8hAkjd3/usr/lib/python2.7/dist-packages/nova/virt/libvirt# vim config.py
页: [1]
查看完整版本: 关于OpenStack中虚拟机VNC访问安全问题