9

Right now there's an application that allows people to connect to a Desktop app through the web by exposing an AngularJS web server powered by Atmosphere. The Desktop app exposes the current person's IP address so anyone with the address can connect to.

I'm trying to mask this IP by proxying it through my server (example.com). My server currently hosts a series of applications (Ruby on Rails + Elastic Search, Logstash, Kibana - ELK) and are proxied by an Nginx client.

I've worked out to successfully mask the IP addresses through Node HTTP Proxy (locally), and now I'm trying to make it work while using Nginx. The AngularJS app uses Websockets so I need to proxy both the HTTP and WS request.

See this diagram: enter image description here

I'm very close to figuring everything out. I've tested locally without Nginx and the IP addresses are getting masked correctly. I'm having a challenge to make Nginx redirect the HTTP and Websockets through the same location (See code).

From all the tutorials and Server Fault posts I've seen that Websockets usually point to a different location, and Nginx gracefully upgrades the connection.

I'm having a challenge right now, that I'm trying to proxy through the same location HTTP/2 and Websockets protocols. I've resorted to evil hacks such as using IF inside the location blocks (but they haven't worked).

Ideally I'd like to have Websockets point to a different location than the HTTP, and that would solve the problem. My current problem is that I don't have the source code to the AngularJS App in order for me to do so.

The Atmosphere server seems to detect the Websockets connection through query parameters (This is the URL that it connects to):

ws://the-user-ip/?X-Atmosphere-tracking-id=0&X-Atmosphere-Framework=2.3.2-javascript&X-Atmosphere-Transport=websocket&Content-Type=application/json&X-atmo-protocol=true.

Here's part of my current configuration from Nginx:

upstream ipmask_docker_app {
  server ipmask:5050;
}
server {

  server_name "~^\d+\.example\.co$";

  # listen 80;
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # HTTPS config omitted due to conciseness. 

 location / {
    # https://www.digitalocean.com/community/questions/error-too-many-redirect-on-nginx
        # proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
        # proxy_ignore_headers Set-Cookie;
        # proxy_hide_header Set-Cookie;
        # proxy_hide_header X-powered-by;
        # proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Host $http_host;
        proxy_pass http://ipmask_docker_app;

        proxy_http_version 1.1;

    # Enables Websockets
    # https://www.nginx.com/blog/websocket-nginx/
    # https://stackoverflow.com/a/46675414/1057052
    # Have the http_version 1.1 disabled. I want to know if it works

      # THIS IS EVIL:
      set $ws_header_upgrade  '';
      set $ws_value_upgrade  '';
      set $ws_header_connection ''; 

      proxy_set_header 'Debug Header' $query_string;

      if ($args ~* "X-Atmosphere-tracking-id") {
        set  $ws_header_upgrade Upgrade;
        set  $ws_value_upgrade $http_upgrade;
        set $ws_header_connection "Upgrade";
      }

      proxy_set_header $ws_header_upgrade  $ws_value_upgrade;
      proxy_set_header Connection $ws_header_connection;

    # limit_req zone=one;
    access_log /var/www/cprint/log/nginx.access.log;
    error_log /var/www/cprint/log/nginx.error.log;
  }
}

In the code above I can't seem to have proxy_set_header Host $http_host; and proxy_set_header Upgrade $http_upgrade in the same location block. That's why I tried matching unsuccessfully the query_string of X-Atmosphere-tracking-idand setting the headers to upgrade it in case that it matches it.

Otherwise, if I upgrade the connection I'm not able to see the Web Page loaded as it doesn't seem to proxy the HTTP protocol but the WS.

Is the only way to upgrade Websockets is to have it point to a different location? Or is there a way to upgrade both (HTTP and WS) by pointing them at the same place?

Thank you!

Jose A
  • 193
  • 1
  • 1
  • 5
  • 1
    You really should have a unique path for your websocket endpoint. – Michael Hampton May 04 '18 at 21:25
  • @MichaelHampton: I'll have to speak with the dev in charge of that portion. I think that's the best way as well. – Jose A May 04 '18 at 21:39
  • I ended up changing the URL to /ws – Jose A Jul 24 '18 at 17:43
  • Just to reiterate, I ended up solving the problem by changing the WebSocket's URL to point to a different location. After that, Nginx managed to distinguish between the HTTP protocal and the WS and successfully masked the address with HTTPS. – Jose A Jul 02 '19 at 19:06

1 Answers1

19

The easiest way I found was to jump to different locations based on the "Upgrade" header:

server {
  # ...

  location / {
    try_files /nonexistent @$http_upgrade;
  }

  location @websocket {
    # websocket related stuff
  }

  location @ {
    # web related stuff
  }
}
mkg20001
  • 308
  • 3
  • 7
  • 1
    You can use $http_upgrade directly on try_files, @websocket for your ws location and just @ for your http location, avoiding the need for a map. – MacroMan Mar 29 '19 at 15:02
  • Updated based on your suggestion. Thanks! – mkg20001 Apr 01 '19 at 19:58
  • That is a dirty little trick, nice! – Addo Solutions Jun 19 '20 at 13:36
  • 2
    @mkg20001, @MacroMan that's a great answer. Adding a few words to explain how it works would be _even better_. A little googling on `try_files` and "named locations" (`@` prefix) helped me figure out what was going on. Nice answer. – broc.seib Jan 14 '22 at 21:25
  • @broc.seib may I ask you to share the links to your findings? – Brian Cannard Feb 27 '22 at 19:55
  • `try_files`: http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files and https://serverfault.com/a/329970/148611 – broc.seib Feb 28 '22 at 20:11
  • "named locations": https://www.nginx.com/resources/wiki/community/faq/#what-does-this-thing-mean – broc.seib Feb 28 '22 at 20:12
  • In the above, `try_files /nonexistent @$http_upgrade` will fail on `/nonexistent` and move on to resolve `@$http_upgrade`. The var `$http_upgrade` will contain the exact string `websocket` if the connection is a websocket. So either `location @` or `location @websocket` will be chosen depending on whether the connection is a websocket or not. – broc.seib Feb 28 '22 at 20:16