首页 > 解决方案 > Websocket Autobahn Python客户端:如何使用服务器和客户端证书连接到服务器?

问题描述

websocket 客户端(使用 Autobahn/Python 和 Twisted)需要连接到 websocket 服务器:客户端需要向服务器出示其客户端证书,并且客户端需要检查服务器的证书。例如,这些证书是在安装 Kubernetes minikube 的过程中创建的。尤其是:

我已经检查过我可以成功地使用这些证书+密钥来发出 Kubernetes 远程 API 调用curl

从 Autobahn 的echo_tls/client.py示例中,我了解到我可能需要使用ssl.ClientContextFactory(). ssl这里指的pyopenssl是twisted自动导入的包。

但是,我不知道如何将证书传递给工厂?

标签: python-3.xsslwebsocketssl-certificateautobahn

解决方案


经过一些试验和错误,我现在得到了下面的这个解决方案。为了帮助其他人,我不仅会展示代码,还会提供一个参考设置来测试驱动示例代码。

首先,安装 minikube,然后启动 minikube 实例;我已经使用 minikube 1.0.0 进行了测试,然后运行 ​​Kubernetes 1.14,在撰写本文时它是最新的。然后启动一个简单的 websocket 服务器,它只显示发送给它的内容,并将您所做的任何输入发送回连接的 websocket 客户端。

minikube start
kubectl run wsserver --generator=run-pod/v1 --rm -i --tty \
  --image ubuntu:disco -- bash -c "\
    apt-get update && apt-get install -y wget && \
    wget https://github.com/vi/websocat/releases/download/v1.4.0/websocat_1.4.0_ssl1.1_amd64.deb && \
    dpkg -i webso*.deb && \
    websocat -vv -s 0.0.0.0:8000"

接下来是 Python 代码。它尝试通过 Kubernetes 的远程 API 从 minikube 连接到我们刚刚启动的 wsserver,使用远程 API 作为其反向代理。minikube 设置通常使用客户端和服务器的相互 SSL/TLS 身份验证,因此这里是一个“硬”测试。请注意,还有其他方法,例如服务器证书和不记名令牌(而不是客户端证书)。

import kubernetes.client.configuration
from urllib.parse import urlparse
from twisted.internet import reactor
from twisted.internet import ssl
from twisted.python import log
from autobahn.twisted.websocket import WebSocketClientFactory, WebSocketClientProtocol, connectWS
import sys

if __name__ == '__main__':
    log.startLogging(sys.stdout)

    class EchoClientProto(WebSocketClientProtocol):
        def onOpen(self):
            print('onOpen')
            self.sendMessage('testing...\n'.encode('utf8'))
        def onMessage(self, payload, isBinary):
            print('onMessage')
            if not isBinary:
                print('message %s' % payload.decode('utf8'))
        def onClose(self, wasClean, code, reason):
            print('onClose', wasClean, code, reason)
            print('stopping reactor...')
            reactor.stop()

    # Select the Kubernetes cluster context of the minikube instance,
    # and see what client and server certificates need to be used in
    # order to talk to the minikube's remote API instance...
    kubernetes.config.load_kube_config(context='minikube')
    ccfg = kubernetes.client.configuration.Configuration._default
    print('Kubernetes API server CA certificate at %s' % ccfg.ssl_ca_cert)
    with open(ccfg.ssl_ca_cert) as ca_cert:
        trust_root = ssl.Certificate.loadPEM(ca_cert.read())
    print('Kubernetes client key at %s' % ccfg.key_file)
    print('Kubernetes client certificate at %s' % ccfg.cert_file)
    with open(ccfg.key_file) as cl_key:
        with open(ccfg.cert_file) as cl_cert:
            client_cert = ssl.PrivateCertificate.loadPEM(cl_key.read() + cl_cert.read())

    # Now for the real meat: construct the secure websocket URL that connects
    # us with the example wsserver inside the minikube cluster, via the
    # remote API proxy verb.
    ws_url = 'wss://%s/api/v1/namespaces/default/pods/wsserver:8000/proxy/test' % urlparse(ccfg.host).netloc
    print('will contact: %s' % ws_url)
    factory = WebSocketClientFactory(ws_url)
    factory.protocol = EchoClientProto

    # We need to attach the client and server certificates to our websocket
    # factory so it can successfully connect to the remote API.
    context = ssl.optionsForClientTLS(
        trust_root.getSubject().commonName.decode('utf8'),
        trustRoot=trust_root,
        clientCertificate=client_cert
    )

    connectWS(factory, context)
    print('starting reactor...')
    reactor.run()
    print('reactor stopped.')

在使用附加客户端和服务器证书时,这里的棘手部分optionsForClientTLS是 Twisted/SSL 希望被告知我们将要与之交谈的服务器名称。这也需要通知虚拟服务器他们需要提供多个服务器证书中的哪一个——在出现任何 HTTP 标头之前!

不幸的是,现在这是一个丑陋的领域——我很高兴在这里得到反馈!简单地使用urlparse(ccfg.host).hostname适用于一些 minikube 实例,但不适用于其他实例。我还没有弄清楚为什么看似相似的实例表现不同。

我目前的解决方法是简单地使用服务器证书中主题的 CN(通用名称)。当远程 API 服务器的 URL 使用 IP 地址文字而不是 DNS 名称(或至少是标签)时,也许更稳健的方法可能是仅采用这种策略。

唉,运行上面的 Python 3 代码python3 wssex.py。如果脚本正确连接,那么您应该会看到类似于以下内容的日志消息2019-05-03 12:34:56+9600 [-] {"peer": "tcp4:192.168.99.100:8443", "headers": {"sec-websocket-accept": ...

此外,您之前启动的 websocket 服务器应该显示日志消息,例如[INFO websocat::net_peer] Incoming TCP connection from Some(V4(172.17.0.1:35222)),等等。

这证明了客户端脚本已经通过安全的 websocket 成功连接到 minikube 的远程 API,通过了身份验证和访问控制,并且现在连接到了 minikube 内的(不安全的)websocket 演示服务器。


推荐阅读