为了账号安全,请及时绑定邮箱和手机立即绑定

Python Twisted:是否可以即时重新加载证书?

Python Twisted:是否可以即时重新加载证书?

翻阅古今 2022-05-19 15:54:13
我有一个简单的 Web 服务器,它通过 HTTPS 提供内容:sslContext = ssl.DefaultOpenSSLContextFactory(  '/home/user/certs/letsencrypt-privkey.pem',  '/home/user/certs/letsencrypt-fullchain.pem',)reactor.listenSSL(  port=https_server_port,  factory=website_factory,  contextFactory=sslContext,  interface=https_server_interface)do_print(bcolors.YELLOW + 'server.py | running https server on ' + https_server_interface + ':' + str(https_server_port) + bcolors.END)是否可以即时重新加载证书(例如通过调用https://example.com/server/reload-certificates之类的路径并让它执行一些代码)或者我需要做什么才能获得它完毕?我想避免重新启动 Python 进程。
查看完整描述

2 回答

?
Smart猫小萌

TA贡献1911条经验 获得超7个赞

有几种方法是可能的。Daniel F 的回答非常好,并展示了一种很好的通用技术来动态重新配置服务器。


这里还有一些技术更具体地适用于 Twisted 中的 TLS 支持。


DefaultOpenSSLContextFactory首先,您可以从实例中重新加载 OpenSSL“上下文”对象。当需要重新加载证书时,运行:


sslContext._context = None

sslContext.cacheContext()

该cacheContext调用将创建一个新的 OpenSSL 上下文,在此过程中重新读取证书文件。这确实有依赖私有接口 ( _context) 及其与非真正公共接口 ( cacheContext) 交互的缺点。


你也可以实现你自己的版本,DefaultOpenSSLContextFactory这样你就不必依赖这些东西了。 DefaultOpenSSLContextFactory并没有真正做太多。这是一个完全删除缓存行为的复制/粘贴/编辑:


class DefaultOpenSSLContextFactory(ContextFactory):

    """

    L{DefaultOpenSSLContextFactory} is a factory for server-side SSL context

    objects.  These objects define certain parameters related to SSL

    handshakes and the subsequent connection.

    """

    _context = None


    def __init__(self, privateKeyFileName, certificateFileName,

                 sslmethod=SSL.SSLv23_METHOD, _contextFactory=SSL.Context):

        """

        @param privateKeyFileName: Name of a file containing a private key

        @param certificateFileName: Name of a file containing a certificate

        @param sslmethod: The SSL method to use

        """

        self.privateKeyFileName = privateKeyFileName

        self.certificateFileName = certificateFileName

        self.sslmethod = sslmethod

        self._contextFactory = _contextFactory


    def getContext(self):

        """

        Return an SSL context.

        """

        ctx = self._contextFactory(self.sslmethod)

        # Disallow SSLv2!  It's insecure!  SSLv3 has been around since

        # 1996.  It's time to move on.

        ctx.set_options(SSL.OP_NO_SSLv2)

        ctx.use_certificate_file(self.certificateFileName)

        ctx.use_privatekey_file(self.privateKeyFileName)

当然,这会为每个可能不受欢迎的连接重新加载证书文件。您可以重新添加自己的缓存逻辑,并使用适合您的证书刷新系统的控制界面。这也有一个缺点,那DefaultOpenSSLContextFactory就是一开始就不是一个非常好的 SSL 上下文工厂。它不遵循 TLS 配置的当前最佳实践。


所以你可能真的想twisted.internet.ssl.CertificateOptions改用。这有一个类似的_context缓存,您可以清除它:


sslContext = CertificateOptions(...) # Or PrivateCertificate(...).options(...)

...

sslContext._context = None

它会在发现上下文时自动重新生成上下文,None因此至少您不必以cacheContext这种方式调用。但是,您再次依赖于私有接口。


另一种更类似于 Daniel F 建议的技术是为已经在监听的套接字提供一个新工厂。这避免了stopListening和之间的服务短暂中断listenSSL。这将是这样的:


from twisted.protocols.tls import TLSMemoryBIOFactory


# DefaultOpenSSLContextFactory or CertificateOptions or whatever

newContextFactory = ... 

tlsWebsiteFactory = TLSMemoryBIOFactory(

    newContextFactory,

    isClient=False,

    websiteFactory,

)


listeningPortFileno = sslPort.fileno()

websiteFactory.sslPort.stopReading()

websiteFactory.sslPort = reactor.adoptStreamPort(

    listeningPortFileno, 

    AF_INET, 

    tlsWebsiteFactory,

)

这基本上只是让反应堆停止为sslPort具有过时配置的旧设备提供服务,并告诉它开始为新工厂上该端口的底层套接字服务事件。在这种方法中,您必须下降到稍低级别的 TLS 接口,因为您不能采用“TLS 端口”,因为没有这样的东西。相反,您采用 TCP 端口并自己应用必要的 TLS 包装(这正是 listenSSL 在幕后为您所做的)。


请注意,这种方法比其他方法更受限制,因为并非所有反应器都提供filenooradoptStreamPort方法。如果您想在受支持的地方使用它并在其他地方优雅地降级,您可以测试各种对象提供的接口。


另请注意,因为TLSMemoryBIOFactory无论如何它总是在幕后工作,如果你有对它的引用,你也可以旋转它的私有接口:


tlsMemoryBIOFactory._connectionCreator = IOpenSSLServerConnectionCreator(

    newContextFactory,

)

它将开始将其用于新连接。但是,再次,私人...


查看完整回答
反对 回复 2022-05-19
?
BIG阳

TA贡献1859条经验 获得超6个赞

有可能的。


reactor.listenSSL返回一个twisted.internet.tcp.Port实例,您可以将其存储在可访问的位置,例如服务器的网站资源中,以便您以后可以访问它:


website_resource = Website()


website_factory = server.Site(website_resource)


website_resource.sslPort = reactor.listenSSL( # <---

  port=https_server_port,

  factory=website_factory,

  contextFactory=sslContext,

  interface=https_server_interface

)

然后稍后在您的 http 处理程序(render函数)中,您可以执行以下操作:


if request.path == b'/server/reload-certificates':

  request.setHeader("connection", "close")

  self.sslPort.connectionLost(reason=None)

  self.sslPort.stopListening()

  self.sslListen()

  return b'ok'

self.sslListen初始设置代码在哪里:


website_resource = Website()


website_factory = server.Site(website_resource)


def sslListen():

  sslContext = ssl.DefaultOpenSSLContextFactory(

    '/home/user/certs/letsencrypt-privkey.pem',

    '/home/user/certs/letsencrypt-fullchain.pem',

  )

  website_resource.sslPort = reactor.listenSSL(

    port=https_server_port,

    factory=website_factory,

    contextFactory=sslContext,

    interface=https_server_interface

  )


website_resource.sslListen = sslListen # <---

sslListen() # invoke once initially


# ...


reactor.run()

请注意,这request.setHeader("connection", "close")是可选的。它指示浏览器应该关闭连接,而不是在下一次获取服务器时重用它(HTTP/1.1 连接通常保持打开至少 30 秒以便重用)。


如果connection: close未发送标头,那么一切仍然有效,连接仍然有效且可用,但仍将使用旧证书,如果您只是重新加载证书以在更新后刷新它们,这应该没certbot问题他们。来自其他浏览器的新连接将立即开始使用新证书。


查看完整回答
反对 回复 2022-05-19
  • 2 回答
  • 0 关注
  • 158 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号