民生银行 OpenStack 安全加固探索与实践

阅读数:8647 2019 年 4 月 30 日 08:00

民生银行OpenStack安全加固探索与实践

1 前言

在构建企业私有云时,除了平台的功能性和稳定性,安全性也是非常重要的因素,尤其对于银行业,数据中心以及监管部门对平台的安全性要求更高。

OpenStack 是 IaaS 的开源实现,经过几年的发展,OpenStack 的功能越来越完善,运行也越来越稳定,目前已经成为企业构建私有 IaaS 云的主流选择之一。

民生银行从 2016 年就开始研究和使用 OpenStack 了,不仅积累了大量的 OpenStack 云平台开发和运维经验,还针对 OpenStack 平台的安全性进行了探索与研究,对社区 OpenStack 进行了大量的安全加固优化,本文接下来将详细分享我们针对开源 OpenStack 的安全加固优化方案。

2 配置文件明文密码加密

2.1 为什么明文密码需要加密

密码是非常重要的敏感数据,一旦密码被泄露,系统就有可能被非授权人员利用导致信息泄露、篡改,因此密码的安全性保障是企业的重中之重工作。避免在服务器上保存文本明文密码是防止密码泄露的有效手段之一,对于银行业来说,也是监管部门的硬性要求之一。

目前我们已基于开源 OpenStack 构建了多套 IaaS 云平台,社区 OpenStack 配置文件使用的都是明文密码存储,存在巨大的安全隐患,社区针对这个问题也有讨论,参考社区邮件列表 [1]。不过至今社区还没有现成的配置文件密码加密方案,但已经在尝试使用 Secrets Management 管理密码,如 Castellan,详细文档可参考社区关于 secrets-management 的讨论 [2],不过该方案离完全实现可能还需要一段时间。

然而由于我们线上系统的安全要求,我们对配置文件密码加密具有更迫切的需求,不得不在社区方案实现前完成 OpenStack 密码安全加固,对明文密码进行整改,对配置文件包含的所有敏感数据进行加密处理。

2.2 OpenStack 明文加密思路

对 OpenStack 配置文件进行加密的工具很多,但要 OpenStack 支持密文,修改源码不可避免。为了降低代码变更造成的风险,我们确立的三大原则是:

  • 尽量少的修改原有代码;
  • 对原有系统尽量少的侵入;
  • 避免交叉模块代码修改。

好在 OpenStack 具有良好的松耦合设计理念,虽然线上 OpenStack 环境涉及 Keystone、Glance、Nova、Cinder、Neutron、Heat 等多个项目,不同的项目关联不同的配置文件以及不同的配置项,但所有的配置文件读取都是通过 Oslo.config 库读取的。

关于 Oslo 库,OpenStack 不同项目中存在很多相同的功能,比如连接数据库、连接消息队列、线程池管理、配置读取等,因此早期 OpenStack 开发者经常从一个项目的代码拷贝到另一个项目去,导致 OpenStack 项目存在大量的重复代码。为了解决这个问题,OpenStack 社区从 Bexar 版本开始决定剥离这些公共功能组件形成共享公共库,不过进展一直很缓慢,直到 Grizzly 版本指派 PTL 专门负责公共库项目,并正式采用 Oslo 这个项目名称,从此吸引大量的开发者加入 Oslo 项目的开发以及 OpenStack 项目代码 Oslo 化改造,Oslo 成为了承载 OpenStack 核心组件的基石,更多关于 OpenStack Oslo 可参考官方文档 [3]。

而 Oslo.config 就是 Oslo 中负责 OpenStack 配置管理的子模块,包括配置项的声明、校验、解析等,因此只要我们解决了 Oslo.config 读取密文问题,也就解决了 OpenStack 所有项目的配置加密问题,完全不需要涉及 OpenStack 其他项目的代码修改,从而大大减少了代码的侵入面。

2.3 哪些配置项需要加密

接下来我们需要解决的问题是要区分哪些配置项是敏感数据。我们分析了 OpenStack 的配置文件,发现虽然 OpenStack 的项目众多,配置文件分散,但涉及的敏感数据基本可以分为如下三类:

  • Keystone 认证密码。主要用于 OpenStack 各个组件内部认证使用的账号密码,如 keystone_authtoken 配置组的 admin_password 配置项。
  • 数据库密码。OpenStack 组件连接数据的密码,如 database 配置组的 connection 配置项。
  • 消息队列连接密码。OpenStack 组件连接消息队列 RabbitMQ 的密码,如 rabbit_password 或者 transport_url 配置项。

这些配置项虽然分散在各个项目的不同配置文件,但所有敏感配置项都是相同并且所有敏感配置项可枚举,因此我们可以建立一个敏感配置项字典集,把所有敏感的配置项放在这个字典集中。读取配置时,如果配置项在这个字典中,则先解密再返回,否则无需解密直接返回。未来如果有新的敏感配置项引入,只需要修改字典文件即可,无需再修改代码,符合软件工程中的开放封闭设计原则。

Oslo.config 读取配置项的模块为 oslo_config.cfg.ConfigOpts 的 _get() 方法,因此我们只需要修改该方法,嵌入加密配置项解密代码即可:

复制代码
def _get(self, name, group=None, namespace=None):
# ... 省略其它代码
try:
if namespace is not None:
raise KeyError
return self.__cache[key] # 配置项没有缓存
except KeyError:
value = self._do_get(name, group, namespace)
if key in self._encrypted_opts:
value = self.decrypt_value(value) # 解密
self.__cache[key] = value # 加入缓存
return value

2.4 如何加密

解决了在哪里加密以及哪些配置项需要加密的问题,最后需要解决的问题就是如何加密,即加密算法的选择。我们选择了 AES 加密算法,该算法是对称密钥加密中最流行的算法之一,AES 加密在当前计算机计算能力下暴力破解的可能几乎为 0,符合加密强度要求。

由于 AES 加密后是一串二进制,为了能够以文本字符的形式保存到配置文件,我们把密文编码为 base64。

OpenStack 在解密时需要读取加密时的密钥,密钥如何安全保存又是一个问题,一旦密钥泄露,密码还是可能被攻破。解决这个问题的最根本的办法是压根不保存密钥。我们在加密和 OpenStack 解密过程中只需要使用一套相同的自定义规则生成密钥,密钥不需要保存在本地,OpenStack 组件启动时动态自动生成即可。

综上,我们实现的加密过程如下:

  1. 基于自定义规则生成密钥 K;
  2. 输入明文 T;
  3. 对输入的 T 以及生成的 K 进行 AES 加密,生成密文 D;
  4. 对密文 D 转化为 base64 编码 B;
  5. 输出 B。

如上过程通过外部脚本执行。

OpenStack 组件启动时读取配置解密过程如下:

  1. 基于自定义规则生成密钥 K;
  2. 读取配置项 C;如果配置项 C 的 key 是敏感配置项,执行 3,否则跳到 5;
  3. 对配置项的 value 进行 base64 解码,转化为密文 D;
  4. 使用生成的 K,对密文 D 进行解密,生成明文 T,value = T;
  5. 输出明文 value。

我们使用了 Python 的 PyCrypto 库实现 AES 加解密,其中解密的部分代码实现如下:

复制代码
def decrypt_value(self, enc):
enc = base64.b64decode(enc) # 解码 base64
iv = enc[:AES.block_size] # 使用密文前缀作为随机初始化向量
cipher = AES.new(self.key, AES.MODE_CBC, iv)
dec = cipher.decrypt(enc[AES.block_size:])
return self._unpad(dec).decode('utf-8')
@staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]

代码补丁开发完毕后,我们在测试环境下对补丁进行了充分验证后完成上线,目前平台已经顺利完成明文密码整改并稳定运行。

3 计算节点 VNC 加密

3.1 OpenStack 虚拟机 VNC 简介

虚拟机的 VNC 是非常重要的功能,类似于物理服务器的带外 console,能够不依赖于虚拟机操作系统的网络进行远程访问与控制。当虚拟机操作系统出现故障或者网络不通时,往往需要通过 VNC 进行远程连接修复。

OpenStack 原生支持 Web VNC 功能,用户可通过 Nova API 获取虚拟机的 VNC 链接,VNC 链接会带上一个授权的临时 Token。用户访问 Web VNC 时其实访问的是 Nova 的 nova-novncproxy 服务,nova-novncproxy 会首先检查 Token 是否有效,如果有效则会转发到对应虚拟机所在计算节点监听的 VNC 地址,否则连接将会被强制阻断。

因此,用户通过 OpenStack 平台访问虚拟机 VNC 是安全的,能够有效阻止非授权人员通过端口扫描非法访问 VNC。

然而,原生 OpenStack 的 Libvirt Driver 目前还没有实现 VNC 连接密码认证功能,意味着非法人员可以不需要任何认证直接连接计算节点绕过 OpenStack 访问虚拟机 VNC,利用 VNC 可发送电源指令或者 Ctrl+Alt+Delete 指令重启虚拟机并进入单用户模式,绕过操作系统 root 认证直接登录虚拟机,这显然存在巨大的安全隐患。

社区针对这个问题也有讨论,但一直没有实现,参考社区 bug #1450294[4]。

3.2 VNC 加密优化

针对如上 OpenStack 虚拟机没有配置 VNC 密码问题,我们对 OpenStack 进行了二次开发,增加了 password 参数配置 VNC 密码,核心代码如下:

复制代码
@staticmethod
def _guest_add_video_device(guest):
# ...
if CONF.vnc.enabled and guest.virt_type not in ('lxc', 'uml'):
graphics = vconfig.LibvirtConfigGuestGraphics()
graphics.type = "vnc"
if CONF.vnc.keymap:
graphics.keymap = CONF.vnc.keymap
if CONF.vnc.vnc_password:
graphics.password = CONF.vnc.vnc_password
graphics.listen = CONF.vnc.server_listen
guest.add_device(graphics)
add_video_driver = True
# ...
return add_video_driver

如上实现了新创建虚拟机添加 VNC 密码功能,但是对正在运行的虚拟机并无影响,如果要使 VNC 密码生效必须重启虚拟机。但由于我们线上环境已经有业务在运行,重启虚拟机意味着必须中断业务,这显然不能接受。虚拟机不重启如何让其重刷配置呢?我们自然想到了虚拟机热迁移办法,虚拟机从一个宿主机热迁移到另一个宿主机,理论上会重新生成虚拟机配置,而又几乎对业务无影响。

然而当我们在测试环境上验证时发现虚拟机在线迁移并不会更新配置,于是我们又分析了虚拟机在线迁移的流程,发现在源端更新 xml 配置文件时没有添加 VNC 密码,该功能代码位于 nova/virt/libvirt/migration.py 的 _update_graphics_xml( ) 方法:

复制代码
def _update_graphics_xml(xml_doc, migrate_data):
listen_addrs = graphics_listen_addrs(migrate_data)
# change over listen addresses
for dev in xml_doc.findall('./devices/graphics'):
gr_type = dev.get('type')
listen_tag = dev.find('listen')
if gr_type in ('vnc', 'spice'):
if listen_tag is not None:
listen_tag.set('address', listen_addrs[gr_type])
if dev.get('listen') is not None:
dev.set('listen', listen_addrs[gr_type])
return xml_doc

我们修改了该方法实现,增加了 VNC 密码的更新,经过验证,所有虚拟机通过在线迁移方法增加了 VNC 密码认证功能。

3.3 用户 VNC 连接

前面提到用户是通过 Nova 的 novncproxy 代理访问虚拟机 VNC 的,novncproxy 北向接收用户请求,南向连接计算节点的 VNC server,由于我们的 VNC server 增加了密码认证功能,因此 novncproxy 就无法直接连接 VNC server 了。

由于 VNC 使用了 RFB(Remote Frame Buffer)协议进行数据传输,我们对 RFB 协议进行了研究,通过重写 (overwrite)(nova/console/websocketproxy.py 的 do_proxy 方法,实现 VNC 密码的代填功能,从而实现用户能够沿用原有的方式通过 OpenStack 标准 API 访问虚拟机 VNC,该部分实现准备在下一篇文章中进行详细介绍。

4 OpenStack 平台加固措施

4.1 服务访问策略控制

OpenStack 依赖很多公共组件服务,如数据库、消息队列、缓存服务等,这些服务是 OpenStack 的内部服务,通常不允许外部直接访问,安全访问控制非常重要,否则可能被非法访问导致信息泄露,甚至通过 webshell 进行主机攻击。

以 Memcached 服务为例,OpenStack 利用 Memcached 存储了 Keystone 认证 Token、VNC 链接等缓存的敏感数据。

由于 Memcached 未对安全做更多设计,导致客户端连接 Memcached 服务后无需任何认证即可读取、修改服务器缓存内容。

复制代码
# 导出 Memcached 数据,192.168.0.0/24 为 OpenStack 管理网平面
memcached-tool 192.168.0.4:11211 dump

同时,由于 Memcached 中数据和正常用户访问变量一样会被后端代码处理,当处理代码存在缺陷时,将可能导致不同类型的安全问题,比如 SQL 注入。

为了规避如上安全风险,我们通过 iptables 对访问来源进行严格限制,只允许 OpenStack 控制节点访问,其他源一律阻断访问。

复制代码
iptables -A INPUT -s 192.168.0.1 -p tcp --dport 11211 -j ACCEPT
iptables -A INPUT -s 192.168.0.2 -p tcp --dport 11211 -j ACCEPT
iptables -A INPUT -s 192.168.0.3 -p tcp --dport 11211 -j ACCEPT
# ... 其他控制节点
iptables -A INPUT -p tcp --dport 11211 -j DROP

其他服务如 mysql、rabbitmq 等,也做了类似的操作,尽可能缩小服务开放范围,最小权限控制。

4.2 源码和配置文件安全

为了确保密码的安全性,除了对配置文件的密码加密,还需要对配置文件的权限进行严格控制,禁止非授权用户读取,我们线上的所有配置文件均设置了仅 root 用户可读权限。

另外由于 OpenStack 是基于 Python 解释性语言编写的,源码的安全性也非常重要,需要对代码的读写权限进行严格地安全管控,避免非法人员通过断点注入方式截取密码等数据,因此我们线上的源码也同样设置了仅 root 可读写权限。

4.3 API SSL 加密

OpenStack 提供了 internal、admin、public 三种类型的 endpoint,通常 OpenStack 内部组件间通信会使用 internal endpoint,比如 Nova 向 Glance 获取镜像信息,向 Neutron 获取网络信息等,内部 endpoint 通常不对外开放。用户访问 OpenStack API 时通常使用 public endpoint,因此 public endpoint 通常是对外开放的,必须对其进行严格安全防控。

我们对 public endpoint 进行了 SSL 加密,只允许通过 https 协议访问 OpenStack API,保证数据传输的安全性。

5 总结

本文首先介绍了我们私有云的构建情况,引入私有云安全的重要性,然后详细介绍了我们针对开源 OpenStack 的安全加固优化措施,包括配置文件信息加密、VNC 密码认证等,最后介绍了我们针对 OpenStack 平台的安全加固方案,如对 OpenStack 内部服务访问进行严格控制以及配置文件和源码的读写权限设置等。

参考资料

  1. OpenStack 社区讨论明文密码加密的邮件列表: http://lists.openstack.org/pipermail/openstack-dev/2016-April/093358.html
  2. “secrets-management: passwords-in-config-files”: https://docs.openstack.org/security-guide/secrets-management/secrets-management-use-cases.html
  3. Oslo 官方文档: https://docs.openstack.org/project-team-guide/oslo.html
  4. OpenStack 社区 vnc 无密码认证问题讨论: https://bugs.launchpad.net/nova/+bug/1450294

作者介绍

  • 付广平:任职民生银行云技术管理中心,负责云计算相关技术研究。毕业于北京邮电大学,从 2013 开始从事 OpenStack 相关工作,参与了 OpenStack Nova、Cinder、Oslo 等项目社区开发,知乎专栏《OpenStack》作者。对 Ceph、Docker 等技术也有一定的了解。
  • 崔增顺:任职民生银行云技术管理中心,目前致力于 IaaS 云平台工作,熟悉服务器 / 操作系统 / 存储等。
  • 蔡泽宇:任职民生银行云技术管理中心,毕业于北京邮电大学,目前致力于民生银行 IaaS 运维相关工作,熟悉 Python、Shell 等编程语言。
  • 刘文静:任职民生银行云技术管理中心,目前主要致力于民生银行云管平台建设和运维工作。

收藏

评论

微博

用户头像
发表评论

注册/登录 InfoQ 发表评论

最新评论

用户头像
Geek_61b861 2019 年 06 月 03 日 17:11 0 回复
通过vnc访问客户机时填写的客户机用户名密码是通过websocket协议传输,是否有被抓包截取的风险?
没有更多了