Websocket Autobahn Python 客户端:如何使用服务器和客户端证书连接到服务器?

Websocket Autobahn Python client: how to connect to server using server and client certificates?

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

我已经检查过我可以成功使用这些证书+密钥来使用 curl.

发出 Kubernetes 远程 API 调用

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

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

经过反复试验,我现在找到了下面的解决方案。为了帮助其他人,我不仅会展示代码,还会展示用于测试示例代码的参考设置。

首先,安装minikube,然后启动一个minikube实例;我已经使用 minikube 1.0.0 进行了测试,然后 运行s 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 代码。它尝试从 minikube 通过 Kubernetes 的远程 API 连接到我们刚刚启动的 wsserver,使用远程 API 作为其反向代理。 minikube 设置通常使用客户端和服务器的相互 SSL/TLS 身份验证,因此这里是 "hard" 测试。请注意,还有其他方法,例如服务器证书和不记名令牌(而不是客户端证书)。

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 headers!

之前

不幸的是,现在这是一个丑陋的领域——我很乐意在这里得到反馈!简单地使用 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 演示服务器。