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



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

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

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

从高速公路的echo_tls/客户端.py示例中,我了解到我可能需要使用ssl.ClientContextFactory()。 这里的ssl是指扭曲自动导入的pyopenssl包。

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

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

经过一些反复试验,我现在得出了下面的解决方案。为了帮助其他人,我不仅要展示代码,还要展示一个参考设置来测试示例代码。

首先,安装 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代码。它尝试从minikube通过Kubernetes的远程API连接到我们刚刚启动的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演示服务器。

最新更新