rust-server icon indicating copy to clipboard operation
rust-server copied to clipboard

RCON over secure websocket (wss)

Open Iyashi opened this issue 3 years ago • 4 comments

Is your feature request related to a problem? Please describe.

Currently the rcon web frontend has hardcoded ws:// protocol and thus does not support secure websockets (wss:// protocol)

I got it working and wanted to share my solution here so that it may help in finding a clean and permanent solution or at least help those who might encounter this problem as well.

The problematic line of code is in container /usr/share/nginx/html/js/rconService.js

  Service.Connect = function(addr, pass) {
    this.Socket = new WebSocket("ws://" + addr + "/" + pass); // <-- here
    this.Address = addr;

Describe the solution you'd like

I used a solution with a custom subdomain specificly for the websocket. (ws.rcon.rust.example.com) It should also be possible with rcon.rust.example.com:28016 if you fix a few things here. (Shouldn't be that much of a problem, but if it is, just ask and I'll provide a working example here)

Create sub domains

I used the following domains

  • rcon.rust.example.com for the web frontend
  • www.rcon.rust.example.com for the web frontend (CNAME => rcon.rust.example.com)
  • ws.rcon.rust.example.com for the websocket

Fix the rcon web client

  1. start Rust container and download /usr/share/nginx/html/js/rconService.js:
    docker-compose up -d
    # you may have to wait for startup before the file becomes available (since some stuff is downloaded at container startup)
    docker cp <container>:/usr/share/nginx/html/js/rconService.js rconService.js
    
  2. edit local rconService.js:
      Service.Connect = function(addr, pass) {
        addr = addr.replace(/^www./i, '').split(':')[0] // trimPrefix "www" and remove port
        var wsurl = "wss://ws." + addr + "/";
        console.log("Websocket url:", wsurl);
        this.Socket = new WebSocket(wsurl + pass);
        this.Address = addr;
    
  3. add rconService.js as volume mount in Rust's docker-compose.yml:
        volumes:
          - ./rconService.js:/usr/share/nginx/html/js/rconService.js
    

Full docker-compose setup

  1. Traefik docker network: docker network create --attachable traefik
  2. Full Traefik docker-compose.yml (with letsencrypt): NOTE: Change [email protected]!
    version: "3.3"
    
    networks:
      traefik:
        external: true
    
    services:
    
      traefik:
        image: traefik:v2.5
        command:
          - "--log.level=INFO"
          - "--providers.docker=true"
          - "--providers.docker.exposedbydefault=false"
          - "--entrypoints.web.address=:80"
          - "--entrypoints.websecure.address=:443"
          - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
          - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
          - "[email protected]"
          - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
        networks:
          - traefik
        ports:
          - 80:80
          - 443:443
        volumes:
          - ./letsencrypt:/letsencrypt
          - /var/run/docker.sock:/var/run/docker.sock:ro
    
  3. Full Rust docker-compose.yml: NOTE: Change example.com to your domain!
    version: "3.3"
    
    networks:
      default:
      traefik:
        external: true
    
    services:
      rust:
        image: didstopia/rust-server
        volumes:
          - ./rust_data:/steamcmd/rust
          - ./rconService.js:/usr/share/nginx/html/js/rconService.js # fixed secure websocket (wss://)
        networks:
          - default
          - traefik
        ports:
          - 0.0.0.0:28015:28015
          - 0.0.0.0:28015:28015/udp
          - 0.0.0.0:28016:28016
          #- 0.0.0.0:8080:8080 # portforwarding using traefik (see last labels)
        environment:
          # ... omitted ...
        labels:
          - traefik.enable=true
          - traefik.docker.network=traefik
          # rcon web (:8080 => rcon.rust.example.com or www.rcon.rust.example.com)
          - traefik.http.routers.game-rust-rcon.rule=Host(`rcon.rust.example.com`) || Host(`www.rcon.rust.example.com`)
          - traefik.http.routers.game-rust-rcon.entrypoints=websecure
          - traefik.http.routers.game-rust-rcon.tls.certresolver=letsencrypt
          - traefik.http.routers.game-rust-rcon.service=game-rust-rcon
          - traefik.http.services.game-rust-rcon.loadbalancer.server.port=8080
          # rcon websocket (:28016 => ws.rcon.rust.example.com)
          - traefik.http.routers.game-rust-rcon-ws.rule=Host(`ws.rcon.rust.example.com`)
          - traefik.http.routers.game-rust-rcon-ws.entrypoints=websecure
          - traefik.http.routers.game-rust-rcon-ws.tls.certresolver=letsencrypt
          - traefik.http.routers.game-rust-rcon-ws.service=game-rust-rcon-ws
          - traefik.http.services.game-rust-rcon-ws.loadbalancer.server.port=28016
    

Hope it helps

Iyashi avatar Nov 28 '21 01:11 Iyashi

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 07 '22 23:01 stale[bot]

Thanks for this. I originally played around with this as well, but never got it to work.

For now, I pushed an update that adds RUST_RCON_SECURE_WEBSOCKET as a new environment variable, defaulting to 0, but if you set it to 1 it will replace the insecure protocol (ws://) protocol with the secure one (wss://).

Hopefully this helps!

Dids avatar Feb 09 '22 12:02 Dids

Took me a while to get this working...

There was no situation where the secure websocket setting worked, in fact it broke accessing rcon locally. Just set it to 0 in docker-compose.yml for rust.

RUST_RCON_SECURE_WEBSOCKET=0

Next, I applied similar, but different Iyashi's edits to rconService.js

 // remove port - we want to go through the default ssl entrypoint in traefik (443).
addr = addr.split(':')[0]; 

// infer websocket protocol from current http/s protocol (if you're on https, you are forbidden from using ws:// anyway).
if (window.location.protocol === "https:")
  this.Socket = new WebSocket("wss://" + addr + "/" + pass);  
else
  this.Socket = new WebSocket("ws://" + addr + "/" + pass);  

// unchanged
this.Address = addr;

finally, in traefik:

http:
 services:
   rust-rcon-web:
     loadBalancer:
       servers:
         - url: http://192.168.2.98:28080

   rust-rcon-ws:
     loadBalancer:
       servers:
         - url: http://192.168.2.98:28016  # notice we're not pointing at https... traefik is ssl offloading.

 routers:
   rust-rcon-web-rtr:
     service: rust-rcon-web
     rule: Host(`rust.domain.com`)
     entryPoints:
       - https

   rust-rcon-ws-rtr:
     service: rust-rcon-ws
     rule: Host(`rust.domain.com`) && Headers(`X-Forwarded-Proto`, `wss`)  # adding a rule looking for the forwarded proto means we don't need a new subdomain for websocket.
     entryPoints:
       - https

I think the only change that should happen to this image is in rconService.js

Ideally, add my conditional logic for selecting web socket protocol.

In the docker config, there should be two settings for rcon port...

RUST_RCON_SERVER_PORT=28016 RUST_RCON_CLIENT_PORT=443 this could default to RUST_RCON_SERVER_PORT if not provided. Then, the client port needs to be what is used when calling `rconService.js Service.Connect()

Honestly, this setting should be deleted (it doesn't work, and adds confusion). RUST_RCON_SECURE_WEBSOCKET

I may try to get a PR together for you.

josh-sachs avatar Mar 19 '22 13:03 josh-sachs

I miserably failed to get the rcon Web to work with my nginx setup...

The main problem seems to be, that the websocket and the web port are different... If the websocket connection would be built on the web port, a simple reverse proxy could get things running without any problems.

My first approach to get the Web Interface onto the Web worked, however connecting to the Websocket failed because of "https and ws can't be mixed up".

Simply activating RUST_RCON_SECURE_WEBSOCKET also didn't help, since then the intern websocket call expects certificates and stuff. Simply changing ws to wss doesn't work with the same reason why changing http to https without providing the necessary certs wont work.

If anyone managed to get the rcon stuff to work with nginx, please help. Until then, I'm pretty much forced to use rcon in my local network only.

Hyp3rSoniX avatar Apr 09 '22 06:04 Hyp3rSoniX

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 10 '23 06:04 stale[bot]