erebe / wstunnel

Tunnel all your traffic over Websocket or HTTP2 - Bypass firewalls/DPI - Static binary available
BSD 3-Clause "New" or "Revised" License
4.36k stars 372 forks source link

Valid upgrade path detection requires dual / #346

Closed Ongy closed 2 months ago

Ongy commented 2 months ago

Describe the bug

When we setup a reverse proxy with a subpath and remove the prefix on the server side, the wstunnel server portion gets confused because it get's the path /events, which is not valid.

After looking at the code a bit, I found https://github.com/erebe/wstunnel/blob/18db0a73c0e09846fd9cdd7ccad2f07121f5ce2e/src/tunnel/transport/websocket.rs#L238 which compositos the path //events when there is no prefix set.

This allowed the implementation, to rely on having at least 2 slashes in the path, even if they are only 1 path separator. https://github.com/erebe/wstunnel/blob/18db0a73c0e09846fd9cdd7ccad2f07121f5ce2e/src/tunnel/server/utils.rs#L71 checks for a slash past the first byte.

To Reproduce

Run wstunnel server side behind a reverse proxy that strips the path. E.g. we run different versions (a 9.x and a 10.x for different clients) and provide -P /wstunnel and -P /wstunnel-10 to the respective clients. The result is //wstunnel/events on the server. The reverse proxy rewrites this to /events by dropping /wstunnel, as path segment.

Expected behavior

The server component should allow clients that reach it with /events. Since it is equivalent to //events as path.

Your wstunnel setup

Paste your logs of wstunnel, started with --log-lvl=DEBUG, and with the command line used

Desktop (please complete the following information):

Additional context Getting it started with the debug flags is a bit hard in our setup, but I hope pointing to the code helps here. I can likely provide a PR to fix this myself soon™

I think the client should retain the doubling behaviour, to not break old systems, but the server should be a bit more permissive/not require an empty path segment, but allow path normalization along the way.

erebe commented 2 months ago

Hello,

By default, if the user does not specify anything, the path prefix is v1 so /v1/events.

  -P, --http-upgrade-path-prefix <HTTP_UPGRADE_PATH_PREFIX>
          Use a specific prefix that will show up in the http path during the upgrade request.
          Useful if you need to route requests server side but don't have vhosts
          When using mTLS this option overrides the default behavior of using the common name of the
          client's certificate. This will likely result in the wstunnel server rejecting the connection.

          [env: WSTUNNEL_HTTP_UPGRADE_PATH_PREFIX=]
------->  [default: v1]     <----------

So you only need your reverse proxy to rewrite the url from //events to /whatever/events to solve your issue. There just must be always a path before the /events.

Out of curiosity, what kind of service are you providing to your clients that you are in need of wstunnel ?

Ongy commented 2 months ago

Ahh, I tried to find it in code and thought it was empty. Ok, that explains part of it.

Yea, we currently have <identifier>/aqualine as prefix, and use that identifier as hint for authentication. Aqualine being an internal codename for the feature. I.e. I consider the entire prefix in the "domain" of the reverse proxy.

This leads to a request on /project/aqualine/events that gets rewritten to /events. The workaround is to set the prefix with this in mind, so it ends on a /, which leads to /project/aqualine//events => //events which is odd.

Your pointer to the default being v1 makes sense. But I prefer my mental model over yours :o

We are using it to tunnel grpc(http2) connections through middleboxes we don't control, which do not support anything newer than http1.

Ongy commented 2 months ago

My mental model being, I think the entire specified prefix should be possible to strip.

Though judging from other features, it's partially intended to be used in tandem with the prefix filter on the frontend to use it as symmetric secret.

yea, if you don't like the proposed change, I can just do a different rewrite on the server.

erebe commented 2 months ago

indeed it is supposed to work as a shared secret between client and server, so it is mandatory when restriction is on.

I will go with letting you change the rewrite rule of your reverse-proxy 😉

erebe commented 2 months ago

In anycase, thank you for trying to debug your issue and understand the codebase 🙏