Python中实现的Websockets(WSS)服务器的问题

rqcrx0a6  于 2023-06-23  发布在  Python
关注(0)|答案(1)|浏览(275)

我知道www.example.com上的基于浏览器的实现以及安全实现(wss),但我很难将两者混合起来(基于浏览器的WSS)。websockets.readthedocs.io as well as the secure implementation(wss) but I am having extreme difficulty getting a mixture of both working(Browser-based WSS).
目前,我正在两个不同的环境上测试套接字服务器,Windows 10 NT(最新)通过ws和CentOS 7.9通过wss在Cloudflare代理后面。Windows测试工作正常,但在浏览器中尝试CentOS 7实现时,套接字失败,并在我的websocket.onerror回调中抛出 * 由于未知原因立即关闭 *(客户端Websockets错误,eventPhase #2)。
我怀疑我的sslContext不正确,应该承认我没有太多开发级别的证书经验。CORS不允许使用与加载websocket的初始页面不匹配的证书来完成握手,因此我安装了Cloudflare源代码证书,并在CF Jmeter 板中启用了身份验证的源代码拉取。自从访问了初始化websocket的页面后,我已经确认了我的原始证书被正确地提供给浏览器,但我不确定我的websocket是否按照它应该的方式提供证书到CF边缘服务器,这导致了CF错误。
在阅读了CF论坛上的许多帖子之后,我留下了这样一个想法:只要源服务器上的事情是正确的,CF就可以很好地代理WSS。我正在尝试CF支持建议的端口8443上的WebSocket。
我怀疑我在sslContext上错误地使用了提供的Cloudflare源代码证书。参考下面的sslContextcert.crt是CA,cert. key是相应的密钥,这两个密钥都是我在生成CF源证书时收到的。这是正确的吗?如果不是,如何使用CF源证书来保护WebSocket?

    • 这里是相关的sslContext:**
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ssl_context.load_cert_chain("/var/www/mysite.org/cert/cert.crt", "/var/www/mysite.org/cert/cert.key")

怀疑这只是一个基于浏览器的问题,我在ReadTheDocs(客户端)上使用了一个基于浏览器的示例的修改版本来增强一个安全性较低的浏览器,果然,在正确使用Certifi创建sslContext [下面的第二个粘贴]之后,我遇到了异常:

Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "c:\Users\User\.vscode\extensions\ms-python.python-2021.5.829140558\pythonFiles\lib\python\debugpy\__main__.py", line 45, in <module>
    cli.main()
  File "c:\Users\User\.vscode\extensions\ms-python.python-2021.5.829140558\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 444, in main
    run()
  File "c:\Users\User\.vscode\extensions\ms-python.python-2021.5.829140558\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 285, in run_file
    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 268, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "c:\Users\User\Desktop\mysite\socket\client.py", line 23, in <module>
    asyncio.get_event_loop().run_until_complete(test())
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\asyncio\base_events.py", line 642, in run_until_complete
    return future.result()
  File "c:\Users\User\Desktop\mysite\socket\client.py", line 13, in test
    async with websockets.connect(
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\websockets\legacy\client.py", line 604, in __aenter__
    return await self
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\websockets\legacy\client.py", line 629, in __await_impl__
    await protocol.handshake(
  File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\websockets\legacy\client.py", line 388, in handshake
    raise InvalidStatusCode(status_code)
websockets.exceptions.InvalidStatusCode: server rejected WebSocket connection: HTTP 525
    • 以下是返回525响应码的测试客户端:**
import asyncio
import ssl
import websockets
import certifi
import logging

logging.basicConfig(filename="client.log", level=logging.DEBUG)
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_verify_locations(certifi.where())

async def test():
    uri = "wss://mysite.org:8443/"
    async with websockets.connect(
        uri, ssl=ssl_context
    ) as websocket:

        await websocket.send("test")
        print("> test")

        response = await websocket.recv()
        print(f"< {response}")

asyncio.get_event_loop().run_until_complete(test())
    • 服务端相关脚本如下:**
if 'nt' in name:

    ENUMS.PLATFORM = 0
    platform = DEBUG_WINDOWS_NT

    start_server = websockets.serve(
        server,
        platform.adapter,
        platform.port,
        max_size=9000000
        )

else:

    ENUMS.PLATFORM = 1
    platform = PRODUCTION_CENTOS_7_xx 
    ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ssl_context.load_cert_chain("/var/www/mysite.org/cert/cert.crt", "/var/www/mysite.org/cert/cert.key")

    start_server = websockets.serve(
        server,
        platform.adapter,#0.0.0.0
        platform.port,#8443
        max_size=9000000,
        ssl=ssl_context
        )

try:
    asyncio.get_event_loop().run_until_complete(start_server)
    print("Mysite Socket -> LISTENING ON %s:%s" % (platform.adapter, platform.port,))
    asyncio.get_event_loop().run_forever()
except Exception as e:
    print(e)
    • 这里是对应的客户端JS(如果有用):**
let sAddress;
    if (location.hostname.includes('mysite.org')){
        sAddress = `wss://${location.hostname}:8443/`
    }else{
        sAddress = `ws://${location.hostname}:5567/`
    }
    console.log(`URI: ${sAddress}`);
    methods = [
        {
            action: "detection",
            captcha: "",
            blob: undefined
        },
    ];
    this.websocket = new WebSocket(sAddress);
    this.websocket.onerror = function (event){
        console.log(event);
        switch (event.eventPhase) {
            case 2:
                alert("The connection is going through the closing handshake, or the close() method has been invoked.");
                break;
        
            default:
                alert("Unknown error.");
                break;
        }
        location.reload();
    }
    • 以下是我的服务器脚本在尝试连接后的日志输出(客户端和浏览器的结果相同):**
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:websockets.protocol:server - state = CONNECTING
DEBUG:websockets.protocol:server - state = CONNECTING
DEBUG:websockets.protocol:server - state = CONNECTING
DEBUG:websockets.protocol:server - state = CONNECTING
DEBUG:websockets.protocol:server - state = CONNECTING
DEBUG:websockets.protocol:server - state = CONNECTING
    • 请注意“state = CONNECTING”**事件,它们持续不断。因此,本质上是建立连接,客户端关闭,但CF继续向源服务器发送请求。

在看到这个之后,我决定捕获端口8443上的流量,这里是从客户端到套接字服务器的单个尝试的转储,在进行一次尝试之后,这种情况会以相同的顺序不断重复。在十六进制编辑器中检查转储时,我可以看到CF源证书位于长度为1662的数据包中。据我所知,握手实际上是通过代理发生的,但失败/被浏览器丢弃,这使得CF边缘服务器重试握手,可能期待一个关闭的头部或它没有从客户端获得的东西。无论哪种方式,看起来握手被浏览器/客户端以一种任意的方式丢弃。

    • 在浏览器/客户端尝试期间,端口8443上的流量的Tcpdump**
01:04:21.585671 IP CLOUDFLARE > MY_SERVER: Flags [.], ack 1, win 64, length 0
01:04:21.586293 IP CLOUDFLARE > MY_SERVER: Flags [P.], seq 1:227, ack 1, win 64, length 226
01:04:21.586321 IP MY_SERVER > CLOUDFLARE: Flags [.], ack 227, win 237, length 0
01:04:21.590265 IP MY_SERVER > CLOUDFLARE: Flags [P.], seq 1:1663, ack 227, win 237, length 1662
01:04:21.749807 IP CLOUDFLARE > MY_SERVER: Flags [.], ack 1461, win 67, length 0
01:04:21.749871 IP CLOUDFLARE > MY_SERVER: Flags [.], ack 1663, win 70, length 0
01:04:21.751082 IP CLOUDFLARE > MY_SERVER: Flags [P.], seq 227:1583, ack 1663, win 70, length 1356
01:04:21.751978 IP MY_SERVER > CLOUDFLARE: Flags [F.], seq 1663, ack 1583, win 258, length 0
01:04:21.911537 IP CLOUDFLARE > MY_SERVER: Flags [F.], seq 1583, ack 1664, win 70, length 0
01:04:21.911639 IP MY_SERVER > CLOUDFLARE: Flags [.], ack 1584, win 258, length 0

这就是奇怪的地方

    • 最后,我尝试绕过CF,直接用python客户端访问我的服务器:**

我在日志中收到以下异常,with the same DEBUG:websockets.protocol:server - state = CONNECTING消息:

Exception has occurred: SSLCertVerificationError
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)
  File "C:\Users\User\Desktop\mysite.org\socket\client.py", line 13, in test
    async with websockets.connect(
  File "C:\Users\User\Desktop\mysite.org\socket\client.py", line 23, in <module>
    asyncio.get_event_loop().run_until_complete(test())
    • 下面是从Python客户端尝试直接访问服务器的Tcpdump:**
07:10:18.135487 IP MYIP > MYSERVER: Flags [S], seq 4035557944, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
07:10:18.135641 IP MYSERVER > MYIP: Flags [S.], seq 4244322261, ack 4035557945, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
07:10:18.306419 IP MYIP > MYSERVER: Flags [.], ack 1, win 1026, length 0
07:10:18.312218 IP MYIP > MYSERVER: Flags [P.], seq 1:518, ack 1, win 1026, length 517
07:10:18.312315 IP MYSERVER > MYIP: Flags [.], ack 518, win 237, length 0
07:10:18.316156 IP MYSERVER > MYIP: Flags [P.], seq 1:1663, ack 518, win 237, length 1662
07:10:18.485326 IP MYIP > MYSERVER: Flags [.], ack 1663, win 1026, length 0
07:10:18.485386 IP MYIP > MYSERVER: Flags [F.], seq 518, ack 1663, win 1026, length 0
07:10:18.485978 IP MYSERVER > MYIP: Flags [F.], seq 1663, ack 519, win 237, length 0
07:10:18.657095 IP MYIP > MYSERVER: Flags [.], ack 1664, win 1026, length 0
    • TLDR:**
  • 我的sslContext是否正确?
  • 我使用的CF原产地证书是否正确?
  • 这是可能的吗?如果是的话,我做错了什么?

在这一点上,我被难倒了,有点不知所措,在一个好得不真实的框架(websockets <3)中,这是一个真正意想不到的减速。
如果需要进一步的信息,请告诉我,如果问题有点长/混乱,我道歉。
任何反馈或解决方案将不胜感激,因为我希望我们的游客有一个安全的经验,谢谢!🔐

sqyvllje

sqyvllje1#

我已经解决了这个问题!
我错误地使用了cert,并且不需要使用origin cert/authed origin pull来让Cloudflare代理客户端。
我的解决方案是创建一个主机名为*.mysite.org的自签名证书,CF代理它没有问题:)

创建自签名证书

openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem

将自签名证书正确呈现给CF边缘服务器

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ssl_context.load_cert_chain("cert.pem", "cert.pem")
    start_server = websockets.serve(
        server,
        platform.adapter,
        platform.port,
        max_size=9000000,
        ssl=ssl_context
        )

相关问题