前言
在前面的章節中我們記錄了 LoadBalancer、Listener、Pool、Member 等等 Octavia 核心資源對象的創建流程,本篇我們在此之上繼續討論處於 LB Management Network 上的 Amphorae 虛擬機是如何與處於 OpenStack Management Network 上的 Octavia Worker 進行安全通信的。
為什么 Octavia 需要自建 CA 證書?
首先我們提出一個問題:為什么 Octavia 需要自建 CA 而不使用 OpenStack 的通用認證體系?
答案是:For production use the ca issuing the client certificate and the ca issuing the server certificate need to be different so a hacker can’t just use the server certificate from a compromised amphora to control all the others.
簡而言之,Octavia 自建 CA 證書主要有兩個必要:
- amphora-agent 沒有加入 OpenStack 鑒權體系,需要證書來保證通訊安全
- 防止惡意用戶利用 amphora 作為 “肉雞” 攻擊 OpenStack 的內部網絡
基於自建 CA 實現的 SSL 通信
Octavia 提供了自動化腳本通過 OpenSSL 指令來創建 CA 中心並自簽發 CA 根證書。執行下述指令即可完成:
$ source /opt/rocky/octavia/bin/create_certificates.sh /etc/octavia/certs/ /opt/rocky/octavia/etc/certificates/openssl.cnf
NOTE:自簽發即自己擔保自己,用自己的私鑰對自己的 CSR 進行簽發。只有頂級認證角色才會自簽發,所以也稱為根證書,本質是簽發服務器證書的公鑰。
所謂 CA,在操作系統上的載體只是一個文件目錄(Directory),包含了各類型秘鑰的證書。CA 在信任系統中充當第三方信托機構的角色,提供證書簽發和管理服務,可以有效解決非對稱加密系統中常見的中間人攻擊問題。更多關於 CA 中心為內容可以參考《使用 OpenSSL 自建 CA 並簽發證書》,這里不再贅述。
Octavia 自建的 CA 中心:
$ ll /etc/octavia/certs/
total 44
-rw-r--r-- 1 stack stack 1294 Oct 26 12:51 ca_01.pem
-rw-r--r-- 1 stack stack 989 Oct 26 12:51 client.csr
-rw-r--r-- 1 stack stack 1708 Oct 26 12:51 client.key
-rw-r--r-- 1 stack stack 4405 Oct 26 12:51 client-.pem
-rw-r--r-- 1 stack stack 6113 Oct 26 12:51 client.pem
-rw-r--r-- 1 stack stack 71 Oct 26 12:51 index.txt
-rw-r--r-- 1 stack stack 21 Oct 26 12:51 index.txt.attr
-rw-r--r-- 1 stack stack 0 Oct 26 12:51 index.txt.old
drwxr-xr-x 2 stack stack 20 Oct 26 12:51 newcerts
drwx------ 2 stack stack 23 Oct 26 12:51 private
-rw-r--r-- 1 stack stack 3 Oct 26 12:51 serial
-rw-r--r-- 1 stack stack 3 Oct 26 12:51 serial.old
- newcerts dir:存放 CA 簽署(頒發)過的數字證書
- private dir:存放 CA 的私鑰
- serial file:存放證書序列號(e.g. 01),每新建一張證書,序列號會自動加 1
- index.txt file:存放證書信息
- ca_01.pem PEM file:CA 證書文件
- client.csr file:Server CSR 證書簽名請求文件
- client.key file:Server 私鑰文件
- client-.pem:PEM 編碼的 Server 證書文件
- client.pem:結合了 client-.pem 和 client.key 的文件
列舉 Octavia 與 CA 認證相關的配置項:
- 應用於 Create Amphora Flow 中的 TASK:GenerateServerPEMTask,生成 Amphora 私鑰並簽發 Amphora 證書。
[certificates]
ca_private_key_passphrase = foobar
ca_private_key = /etc/octavia/certs/private/cakey.pem
ca_certificate = /etc/octavia/certs/ca_01.pem
- 應用於 Octavia Worker 的 AmphoraAPIClient,拿着 CA 根證書(是 Amphora 證書的公鑰,可以解開 Amphora 證書得到 Amphora 的公鑰)和 Amphora 證書向 amphora-agent 發起 SSL 通信。
[haproxy_amphora]
server_ca = /etc/octavia/certs/ca_01.pem
client_cert = /etc/octavia/certs/client.pem
- 應用於 Task:CertComputeCreate,指定 CA 根證書的路徑
[controller_worker]
client_ca = /etc/octavia/certs/ca_01.pem
Amphora Agent 啟動加載證書
首先看為 Amphorae 生成證書的代碼實現:
# /opt/rocky/octavia/octavia/controller/worker/tasks/cert_task.py
class GenerateServerPEMTask(BaseCertTask):
"""Create the server certs for the agent comm Use the amphora_id for the CN """
def execute(self, amphora_id):
cert = self.cert_generator.generate_cert_key_pair(
cn=amphora_id,
validity=CERT_VALIDITY)
return cert.certificate + cert.private_key
Octavia Certificates 功能模塊提供了 local_cert_generator(default)
和 anchor_cert_generator
兩種證書生成器,通過配置項 [certificates] cert_generator
選用。
# /opt/rocky/octavia/octavia/certificates/generator/local.py
@classmethod
def generate_cert_key_pair(cls, cn, validity, bit_length=2048,
passphrase=None, **kwargs):
pk = cls._generate_private_key(bit_length, passphrase)
csr = cls._generate_csr(cn, pk, passphrase)
cert = cls.sign_cert(csr, validity, **kwargs)
cert_object = local_common.LocalCert(
certificate=cert,
private_key=pk,
private_key_passphrase=passphrase
)
return cert_object
上述 LocalCertGenerator.generate_cert_key_pair
Method 的語義是:
- 生成 Amphora 私鑰
- 生成 Amphora 證書簽名請求(CSR)
- 向 CA 中心申請簽署 Amphora證書
屬於常規的證書創建流程,與 create_certificates.sh 腳本的區別在於,Octavia Certificates 應用了 cryptography python 庫而非 OpenSSL 來實現。
TASK:GenerateServerPEMTask 最終 return 了 Amphora 私鑰和證書,然后實現 TASK:CertComputeCreate 將這些文件注入到 Amphora 虛擬機。登錄 Amphora 即可查看這些文件,路徑記錄在配置文件中:
# /etc/octavia/amphora-agent.conf
[amphora_agent]
# Octavia Worker 的證書
agent_server_ca = /etc/octavia/certs/client_ca.pem
# Amphora 的私鑰和證書
agent_server_cert = /etc/octavia/certs/server.pem
Gunicorn HTTP Server 啟動時就會將證書文件加載, 加載證書的 options 如下:
options = {
'bind': bind_ip_port,
'workers': 1,
'timeout': CONF.amphora_agent.agent_request_read_timeout,
'certfile': CONF.amphora_agent.agent_server_cert,
'ca_certs': CONF.amphora_agent.agent_server_ca,
'cert_reqs': True,
'preload_app': True,
'accesslog': '/var/log/amphora-agent.log',
'errorlog': '/var/log/amphora-agent.log',
'loglevel': 'debug',
}
AmphoraAPIClient 發送證書請求
class AmphoraAPIClient(object):
def __init__(self):
super(AmphoraAPIClient, self).__init__()
...
self.session = requests.Session()
self.session.cert = CONF.haproxy_amphora.client_cert
self.ssl_adapter = CustomHostNameCheckingAdapter()
self.session.mount('https://', self.ssl_adapter)
...
def request(self, method, amp, path='/', timeout_dict=None, **kwargs):
...
LOG.debug("request url %s", path)
_request = getattr(self.session, method.lower())
_url = self._base_url(amp.lb_network_ip) + path
LOG.debug("request url %s", _url)
reqargs = {
'verify': CONF.haproxy_amphora.server_ca,
'url': _url,
'timeout': (req_conn_timeout, req_read_timeout), }
reqargs.update(kwargs)
headers = reqargs.setdefault('headers', {})
...
上述代碼是 requests 庫啟用 HTTPS 請求的常規實現:
self.session.cert
:上傳 Octavia Worker 私鑰和證書,用於 Amphora 發起的 SSL 通信reqargs = {'verify': CONF.haproxy_amphora.server_ca, ...}
:攜帶 Amphora 證書,向 Amphora 發起 SSL 通信
小結
梳理 Octavia 建立 SSL 通信的步驟:
- 創建 Amphora 的過程中 Octavia Worker 會首先生成 Amphora 的私鑰,並且向 CA 中心申請簽發 Amphora 證書(內含 Amphora 公鑰),此時 Amphora 的私鑰、證書都會准備好
- Octavia Worker 通過 Config Driver 將 Amphora 的私鑰、Amphora 的證書作為 user data 注入到 Amphora 虛擬機。
- Amphora 虛擬機上運行的 amphora-agent Web Server 啟動時 Flask app 就會加載 Amphora 的私鑰和證書,並啟用 HTTPS 通訊協議。
- Octavia Worker 的 AmphoraAPIClient 首次想 amphora-agent 發送請求時,首先會下載 Amphora 證書,然后與自己手上的 CA 根證書解密出 Amphora 的公鑰,然后再與 Amphora 的私鑰進行匹配。
- 若匹配成功,則建立 SSL 安全通信。
NOTE: 同樣的 Amphora 如果希望向 Octavia Worker 自動發起建立 SSL 通信,那么 Amphora 就需要拿着 Octavia Worker 的證書進行訪問。所以 Octavia 的證書也同樣會被注入到 Amphora。
最后
本篇是《OpenStack Rocky Octavia 實現與分析》系列的第四篇,主要討論 Amphora 是如何與 Octavia Worker 建立雙向的 SSL 安全通信的問題。實話說,Octavia 在解決這個問題的時候並不那么清晰明了,從命名到代碼的實現都彌漫着混亂的氛圍,需要細細梳理才得以清晰。並且 Octavia 和 Amphora 能夠健康通信又是 LBaaS 功能正常運作的基礎,所以還是非常有必要掌握這一問題的。