diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 63797b97..a51fddcc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -60,7 +60,13 @@ jobs: - name: Test vncserver run: | - container_id=$(docker run -d -it -p 5901:5901 test vncserver -xstartup /opt/install/jupyter_remote_desktop_proxy/share/xstartup -verbose -fg -geometry 1680x1050 -SecurityTypes None -rfbport 5901) + # TigerVNC needs to be configured with -rfbport -1 to not open a TCP + # port, while TurboVNC doesn't support being passed -1 and won't open + # a TCP port anyhow. + rfbport_arg="-rfbport -1" + if [ "${{ matrix.vncserver }}" == "turbovnc" ]; then rfbport_arg=""; fi + + container_id=$(docker run -d -it test vncserver -xstartup /opt/install/jupyter_remote_desktop_proxy/share/xstartup -verbose -fg -geometry 1680x1050 -SecurityTypes None -rfbunixpath /tmp/vncserver.socket $rfbport_arg) sleep 1 echo "::group::Install netcat, a test dependency" @@ -70,9 +76,18 @@ jobs: ' echo "::endgroup::" - docker exec -it $container_id timeout --preserve-status 1 nc -v localhost 5901 2>&1 | tee -a /dev/stderr | \ + docker exec -it $container_id timeout --preserve-status 1 nc -vU /tmp/vncserver.socket 2>&1 | tee -a /dev/stderr | \ grep --quiet RFB && echo "Passed test" || { echo "Failed test" && TEST_OK=false; } + echo "::group::Security - Verify TCP ports wasn't opened" + ports=(5800 5801 5900 5901) + for port in "${ports[@]}" + do + docker exec -it $container_id timeout --preserve-status 1 nc -vz localhost $port | tee -a /dev/stderr | \ + grep --quiet succeeded && { echo "Failed security check - port $port open" && SECURITY_OK=false; } || echo "Passed security check - port $port not opened" + done + echo "::endgroup::" + echo "::group::vncserver logs" docker exec $container_id bash -c 'cat ~/.vnc/*.log' echo "::endgroup::" @@ -82,6 +97,10 @@ jobs: echo "Test failed!" exit 1 fi + if [ "$SECURITY_OK" == "false" ]; then + echo "Security check failed!" + exit 1 + fi - name: Install playwright run: | diff --git a/.gitignore b/.gitignore index d52eca5d..4bcc56b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Extra ignore patterns specific to this project # Installed JS libraries node_modules/ +package-lock.json # Built JS files jupyter_remote_desktop_proxy/static/dist diff --git a/jupyter_remote_desktop_proxy/setup_websockify.py b/jupyter_remote_desktop_proxy/setup_websockify.py index ddc27f13..e9a56a72 100644 --- a/jupyter_remote_desktop_proxy/setup_websockify.py +++ b/jupyter_remote_desktop_proxy/setup_websockify.py @@ -12,8 +12,27 @@ def setup_websockify(): "vncserver executable not found, please install a VNC server" ) + # TurboVNC and TigerVNC share the same origin and both use a Perl script + # as the executable vncserver. We can determine if vncserver is TigerVNC + # by searching tigervnc string in the Perl script. + # + # The content of the vncserver executable can differ depending on how + # TigerVNC and TurboVNC has been distributed. Below are files known to be + # read in some situations: + # + # - https://github.com/TigerVNC/tigervnc/blob/v1.13.1/unix/vncserver/vncserver.in + # - https://github.com/TurboVNC/turbovnc/blob/3.1.1/unix/vncserver.in + # + with open(vncserver) as vncserver_file: + vncserver_file_text = vncserver_file.read().casefold() + is_turbovnc = "turbovnc" in vncserver_file_text + # {unix_socket} is expanded by jupyter-server-proxy - vnc_args = [vncserver, '-rfbunixpath', '{unix_socket}'] + vnc_args = [vncserver, '-rfbunixpath', "{unix_socket}", "-rfbport", "-1"] + if is_turbovnc: + # turbovnc doesn't handle being passed -rfbport -1, but turbovnc also + # defaults to not opening a TCP port which is what we want to ensure + vnc_args = [vncserver, '-rfbunixpath', "{unix_socket}"] xstartup = os.getenv("JUPYTER_REMOTE_DESKTOP_PROXY_XSTARTUP") if not xstartup and not os.path.exists(os.path.expanduser('~/.vnc/xstartup')):