一个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
软件包。
但是,我不知道如何将证书传递给工厂?
答案 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演示服务器。