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

时间:2019-04-22 19:11:32

标签: python-3.x ssl websocket ssl-certificate autobahn

一个websocket客户端(使用Autobahn / Python和Twisted)需要连接到一个websocket服务器:该客户端需要向服务器出示其客户端证书,而客户端则需要检查服务器的证书。例如,在设置Kubernetes minikube安装期间创建了这些证书。特别是:

  • 服务器证书~/.minikube/ca.crt(根据我的理解,格式为X509)。
  • 具有密钥~/.minikube/client.crt的客户端证书~/.minikube/client.key

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

从高速公路的echo_tls/client.py示例可以理解,我可能需要使用ssl.ClientContextFactory()ssl指的是自动扭曲导入的pyopenssl软件包。

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

  • 如何告诉websocket因素将客户端证书提交给服务器?
  • 我如何告诉Websocket检查服务器的证书以检测MITM攻击?

1 个答案:

答案 0 :(得分:0)

经过反复试验,我现在到达下面的解决方案。为了帮助他人,我不仅会显示代码,还将提供参考设置以测试示例代码。

首先,安装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代码。它使用远程API作为反向代理,尝试通过minikube中的Kubernetes远程API连接到我们刚刚启动的wsserver。 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标头之前,还需要通知虚拟服务器需要提供其多个服务器证书中的哪个证书!

不幸的是,这现在是丑陋的领域-我很高兴在这里获得反馈!仅在某些minikube实例上使用urlparse(ccfg.host).hostname即可,而在其他实例上则不能。我还没有弄清楚为什么看似相似的实例的行为会有所不同。

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

A,运行python3 wssex.py上方的Python 3代码。如果脚本正确连接,那么您应该会看到类似于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演示服务器。