我有一个使用Python 3.5&asyncio库实现的自定义TCP服务器。我还使用Lets Encrypt证书进行与服务器通信的SSL加密。让加密只发出有效期为90天的证书,很可能我的服务器不会在很长一段时间内重新启动。
我正在生成这样的SSLContext:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
ssl_context.load_cert_chain(certfile='path/to/cert', keyfile='path/to/key')
服务器是这样的:
server = asyncio.streams.start_server(self._accept_client, ip, port, loop=self.loop, ssl=ssl_context)
证书过期后,现有连接将继续运行,但新连接将(正确)失败。显然,SSLContext
或服务器本身会将证书和密钥文件保存在内存中,因为更新磁盘上的文件并不能解决问题。
我已经阅读了很多Python文档,但没有找到解决方案。我有一个想法是尝试定期调用ssl_context.load_cert_chain()
,希望这会更新证书和密钥。我无法查看load_cert_chain
函数的来源以确定它的行为,因为它显然是用C编写的,并且文档没有指定调用此函数的行为一次上下文已传递给服务器。
有没有办法在运行时更新SSLContext
中加载的证书和密钥文件,而无需完全停止并重新启动服务器?
答案 0 :(得分:1)
据我所知,asyncio.Server中没有用于在服务器运行时更新SSL证书的API,您有两个解决方案:
您可以使用SNI回调在SSL握手期间更新证书,但它仅适用于支持SNI的客户端。 这意味着对于支持此功能的每个客户端,将调用您选择的回调,并可能返回将用于与客户端建立连接的SSLContext对象。您可以进行此回调,以便在每次调用时读取证书/密钥文件,或者在需要时通过您选择的触发器重新加载。
要设置回调,请参阅:https://docs.python.org/3/library/ssl.html#ssl.SSLContext.set_servername_callback
另一个解决方案更难以正确处理asyncio API的当前状态:如果asyncio的内部实现发生变化,它可能会中断,而且我没有检查它如何与proactor循环一起工作。但是,它适用于任何类型的客户。
您可以使用新上下文创建新服务器,但使用第一台服务器的套接字:
# This will create a new server with the current socket, and setup what's required to use the new callback.
new_server = await asyncio.create_server(callback, None, None,
sock=old_server.sockets[0], ssl=new_context)
# Ensure that when cleaning the old_server, it doesn't try to close the socket we kept
old_server.sockets = []
old_server.close()
await old_server.wait_closed()
在create_server()期间的某个时刻,asyncio将开始使用新的ssl上下文回答新连接。请注意,一旦连接开始并传递给您的回调(_accept_client),它实际上独立于调用它的服务器,因此它将保持任何现有连接不变。
答案 1 :(得分:1)
我相当确定保留您使用的上下文的副本并调用方法来重新加载证书链将正常工作。至少在C openssl API级别,这是一个合理的事情,它看起来不像python做任何事情来打破它。