【问题标题】:Websocket Autobahn Python client: how to connect to server using server and client certificates?Websocket Autobahn Python客户端:如何使用服务器和客户端证书连接到服务器?
【发布时间】:2019-04-22 19:11:31
【问题描述】:

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

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

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

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

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

  • 如何告诉 websocket 因素向服务器提供客户端证书?
  • 如何告诉 websocket 检查服务器的证书以检测 MITM 攻击?

【问题讨论】:

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


    【解决方案1】:

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

    首先,安装 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 名称(或至少是标签)时才采用这种策略。

    唉,运行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 演示服务器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-13
      • 2015-10-20
      • 1970-01-01
      • 2022-06-27
      • 1970-01-01
      • 2016-07-06
      相关资源
      最近更新 更多