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等編程語言。
  • 劉文靜:任職民生銀行雲技術管理中心,目前主要致力於民生銀行雲管平臺建設和運維工作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章