dockerwebsocketnginx-reverse-proxy

KASM Stand Alone Service inaccessble behind Nginx: Websocket error or invalid HTTP version


KASM has the Docker Images of the GUI services they use with their "Work Space". I am interested only in one of them: Desktop but i suppose they all function more ore less the same. I made this Docker Compose to try and spin it up:

services:
  kasmweb:
    image: kasmweb/desktop:1.15.0-rolling-weekly
    container_name: kasmweb
    ports:
        - 6901:6901
    stdin_open: true
    tty: true
    shm_size: '2gb'
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    devices:
      - /dev/dri:/dev/dri
    env_file: /dockerfiles/kasmweb.env
    networks:
      - public

networks:
  public:
    external: true

It does run with errors related to being in Stand Alone and not connected to KASM Workspace. One Environment variable they mention in the documentation is VNC_PW=password which in turn is used in Basic HTTP Authentication i assume. Values were not changed from default:

User : kasm_user
Password: password

Going to https://server-host:6901 will get you to the Desktop GUI in your browser and it will work smoothly:

HTTP Basic Authentication

Because I like to secure my services I disabled the ports so the service is accessed only through NPM and enable Websockets for the Proxy Host. You will get again to the HTTP Authentication but even with correct cridentials it will error out:

 2024-10-17 10:41:04,174 [INFO] websocket 8: got client connection from 172.19.0.15
 2024-10-17 10:41:04,186 [DEBUG] websocket 8: using SSL socket
 2024-10-17 10:41:04,195 [DEBUG] websocket 8: X-Forwarded-For ip '192.168.20.59'
 2024-10-17 10:41:04,195 [INFO] websocket 8: Authentication attempt failed, BasicAuth required, but client didn't send any
 2024-10-17 10:41:04,195 [INFO] websocket 8: 172.19.0.15 192.168.20.59 - "GET / HTTP/1.1" 401 158
 2024-10-17 10:41:04,195 [DEBUG] websocket 8: No connection after handshake
 2024-10-17 10:41:04,195 [DEBUG] websocket 8: handler exit

For some reason NPM is not forwarding the cridentials to the KASM Host. So i added this to NPM custom configurations:

location / {
    proxy_pass https://kasmweb:6901;
    proxy_set_header Authorization "Basic a2FzbV91c2VyOnBhc3N3b3Jk";
    proxy_pass_header Authorization;
}

The error on KASM container is as following:

 2024-10-17 13:44:45,623 [INFO] websocket 56: got client connection from 172.19.0.15
 2024-10-17 13:44:45,634 [DEBUG] websocket 56: using SSL socket
 2024-10-17 13:44:45,634 [DEBUG] websocket 56: X-Forwarded-For ip '192.168.20.59'
 2024-10-17 13:44:45,639 [DEBUG] websocket 56: BasicAuth matched
 2024-10-17 13:44:45,639 [DEBUG] websocket 56: Invalid WS request, maybe a HTTP one
 2024-10-17 13:44:45,639 [DEBUG] websocket 56: Requested file '/index.html'
 2024-10-17 13:44:45,640 [INFO] websocket 56: 172.19.0.15 192.168.20.59 kasm_user "GET /index.html HTTP/1.1" 200 24135
 2024-10-17 13:44:45,640 [DEBUG] websocket 56: No connection after handshake
 2024-10-17 13:44:45,640 [DEBUG] websocket 56: handler exit

So Authentication is working but Websockets still not working. Websockets function is enabled in NPM. Additionally I did try the following which gave the same error:

location / {
    proxy_pass https://kasmweb:6901;
    proxy_set_header Authorization "Basic a2FzbV91c2VyOnBhc3N3b3Jk";
    proxy_pass_header Authorization;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_http_version 1.1;
}

My Goal is to run Linux Desktop Web GUI with Authentik Reverse Proxy Authentication as sand box.

How can I resolve this WS error and access KASM service with NPM?


Solution

  • location / {
        proxy_pass https://kasmweb:6901;
        proxy_set_header Authorization "Basic a2FzbV91c2VyOnBhc3N3b3Jk";
        proxy_pass_header Authorization;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Scheme $scheme;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_set_header X-Forwarded-For    $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_http_version 1.1;
    }
    

    Thanks to this discussion, this worked for Nginx proxy Manager and it connects imediatly. Authentik changed their configurations to include websockets and that broke the whole thing! reverting back to older configuration style and including the headers from above fixed the connection:

    The issue with Authentik is with their new NGINX Websocket configurations. removing them and adding ones from the previus comment fixed it:

    # Increase buffer size for large headers
    # This is needed only if you get 'upstream sent too big header while reading response
    # header from upstream' error when trying to access an application protected by goauthentik
    proxy_buffers 8 16k;
    proxy_buffer_size 32k;
    
    # Make sure not to redirect traffic to a port 4443
    port_in_redirect off;
    
    location / {
        # Put your proxy_pass to your application here
        proxy_pass          $forward_scheme://$server:$port;
        # Set any other headers your application might need
        # proxy_set_header Host $host;
        # proxy_set_header ...
        #proxy_pass https://kasmweb:6901;
        proxy_set_header Authorization "Basic a2FzbV91c2VyOnBhc3N3b3Jk";
        proxy_pass_header Authorization;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Scheme $scheme;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_set_header X-Forwarded-For    $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_http_version 1.1;
        ##############################
        # authentik-specific config
        ##############################
        auth_request     /outpost.goauthentik.io/auth/nginx;
        error_page       401 = u/goauthentik_proxy_signin;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header       Set-Cookie $auth_cookie;
    
        # translate headers from the outposts back to the actual upstream
        auth_request_set $authentik_username $upstream_http_x_authentik_username;
        auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
        auth_request_set $authentik_email $upstream_http_x_authentik_email;
        auth_request_set $authentik_name $upstream_http_x_authentik_name;
        auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
    
        proxy_set_header X-authentik-username $authentik_username;
        proxy_set_header X-authentik-groups $authentik_groups;
        proxy_set_header X-authentik-email $authentik_email;
        proxy_set_header X-authentik-name $authentik_name;
        proxy_set_header X-authentik-uid $authentik_uid;
        #add_header X-calibre-web-user "CloudAdmin" always;
    }
    
    # all requests to /outpost.goauthentik.io must be accessible without authentication
    location /outpost.goauthentik.io {
        proxy_pass              https://authentik-server:9443/outpost.goauthentik.io;
        # ensure the host of this vserver matches your external URL you've configured
        # in authentik
        proxy_set_header        Host $host;
        proxy_set_header        X-Original-URL $scheme://$http_host$request_uri;
        add_header              Set-Cookie $auth_cookie;
        auth_request_set        $auth_cookie $upstream_http_set_cookie;
        proxy_pass_request_body off;
        proxy_set_header        Content-Length "";
    }
    
    # Special location for when the /auth endpoint returns a 401,
    # redirect to the /start URL which initiates SSO
    location u/goauthentik_proxy_signin {
        internal;
        add_header Set-Cookie $auth_cookie;
        return 302 /outpost.goauthentik.io/start?rd=$request_uri;
        # For domain level, use the below error_page to redirect to your authentik server with the full redirect path
        # return 302 https://authentik.company/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
    }