From 389d318e417cb494df1493ed5b65b671405cbde1 Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:45:32 -0700 Subject: [PATCH 1/9] Update WebSocket URL construction in Dockerfile Mixed Content Fix --- labs/remotebrowser/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile index 6924e04..7f3fd26 100644 --- a/labs/remotebrowser/Dockerfile +++ b/labs/remotebrowser/Dockerfile @@ -166,7 +166,8 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML function connect() { strip(); msg('Connecting...', true); - const url = 'ws://' + location.hostname + ':' + (location.port||'6080') + '/websockify'; + const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; + const url = proto + '//' + location.hostname + ':' + (location.port||'6080') + '/websockify'; rfb = new RFB(scrn, url, { credentials: {password:''} }); rfb.scaleViewport = true; rfb.resizeSession = true; From fd7f3209ce8db1c98f9b5d29fe1994b9f32bbef0 Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:56:59 -0700 Subject: [PATCH 2/9] Fix URL construction for WebSocket connection --- labs/remotebrowser/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile index 7f3fd26..667255a 100644 --- a/labs/remotebrowser/Dockerfile +++ b/labs/remotebrowser/Dockerfile @@ -167,7 +167,7 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML strip(); msg('Connecting...', true); const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; - const url = proto + '//' + location.hostname + ':' + (location.port||'6080') + '/websockify'; + const url = proto + '//' + location.hostname + ':' + (location.port||'6080'); rfb = new RFB(scrn, url, { credentials: {password:''} }); rfb.scaleViewport = true; rfb.resizeSession = true; From 3716ed30ff3d0d4bdaa1aba9076bf2fdc2c97672 Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:18:51 -0700 Subject: [PATCH 3/9] Create README.md --- labs/remotebrowser_TLS/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 labs/remotebrowser_TLS/README.md diff --git a/labs/remotebrowser_TLS/README.md b/labs/remotebrowser_TLS/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/labs/remotebrowser_TLS/README.md @@ -0,0 +1 @@ + From d20d1e414c263ea089b13816739b90285160a88c Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:19:56 -0700 Subject: [PATCH 4/9] Add files via upload --- labs/remotebrowser_TLS/Dockerfile | 616 ++++++++++++++++++++++ labs/remotebrowser_TLS/README.md | 2 +- labs/remotebrowser_TLS/docker-compose.yml | 58 ++ 3 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 labs/remotebrowser_TLS/Dockerfile create mode 100644 labs/remotebrowser_TLS/docker-compose.yml diff --git a/labs/remotebrowser_TLS/Dockerfile b/labs/remotebrowser_TLS/Dockerfile new file mode 100644 index 0000000..ee8c77a --- /dev/null +++ b/labs/remotebrowser_TLS/Dockerfile @@ -0,0 +1,616 @@ +# ============================================================================= +# TLSDebug + Headless Chrome + noVNC — single self-contained Dockerfile +# ============================================================================= +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive \ + DISPLAY=:99 \ + VNC_PORT=5900 \ + NOVNC_PORT=6080 \ + NOVNC_HTTPS_PORT=6443 \ + PROXY_PORT=8080 \ + CHROME_REMOTE_DEBUGGING_PORT=9222 \ + PROXY_HOST=127.0.0.1 \ + NOVNC_ENABLE_HTTPS=false + +# --------------------------------------------------------------------------- +# 1. Base packages +# --------------------------------------------------------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + golang-go \ + git \ + xvfb \ + x11vnc \ + openbox \ + xterm \ + python3 \ + python3-pip \ + python3-websockify \ + novnc \ + wget \ + gnupg \ + ca-certificates \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libcups2 \ + libdbus-1-3 \ + libgdk-pixbuf2.0-0 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libnss3-tools \ + libx11-xcb1 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxkbcommon0 \ + libxrandr2 \ + libxshmfence1 \ + xdg-utils \ + openssl \ + curl \ + netcat-openbsd \ + procps \ + psmisc \ + x11-utils \ + unclutter \ + && rm -rf /var/lib/apt/lists/* + +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# 2. Install browser +# amd64 -> Google Chrome stable (official Google .deb) +# arm64 -> Chromium from Debian bookworm (native deb, no snap) +# +# Base image is debian:bookworm-slim which has real chromium packages +# on all architectures — no snap wrappers. +# --------------------------------------------------------------------------- +RUN ARCH=$(dpkg --print-architecture) && \ + if [ "${ARCH}" = "amd64" ]; then \ + echo "[browser] amd64: installing Google Chrome ..." && \ + wget -q -O /tmp/chrome.deb \ + "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \ + apt-get update && \ + apt-get install -y --no-install-recommends /tmp/chrome.deb && \ + rm /tmp/chrome.deb && \ + rm -rf /var/lib/apt/lists/* && \ + ln -sf /usr/bin/google-chrome /usr/local/bin/browser && \ + echo "[browser] installed: $(google-chrome --version)"; \ + elif [ "${ARCH}" = "arm64" ]; then \ + echo "[browser] arm64: installing Chromium from Debian bookworm ..." && \ + apt-get update && \ + apt-get install -y --no-install-recommends chromium && \ + rm -rf /var/lib/apt/lists/* && \ + ln -sf /usr/bin/chromium /usr/local/bin/browser && \ + echo "[browser] installed: $(chromium --version)"; \ + else \ + echo "[browser] ERROR: unsupported arch ${ARCH}" && exit 1; \ + fi + +# --------------------------------------------------------------------------- +# 3. Build TLSDebug from source +# AllTrafficModule is already active by default and logs every request, +# response, headers and body — no source patching needed. +# We set log flags via the LOG_FLAGS env var at runtime instead. +# --------------------------------------------------------------------------- +WORKDIR /build +RUN git clone --depth 1 https://github.com/secdev02/TLSDebug.git . \ + && CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/local/bin/tlsproxy tlsproxy.go \ + && chmod +x /usr/local/bin/tlsproxy \ + && rm -rf /build + +# 4. Locate noVNC web root and write kiosk index.html +# Kiosk mode: auto-connects, scales to fill browser window, hides toolbar, +# no password prompt, no connection dialog — just the desktop. +# --------------------------------------------------------------------------- +RUN find /usr -name "vnc.html" 2>/dev/null | head -1 | xargs -I{} dirname {} > /opt/novnc_path \ + && NOVNC_WEB=$(cat /opt/novnc_path) \ + && echo "noVNC web root: ${NOVNC_WEB}" \ + && ls -la "${NOVNC_WEB}" + +RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML' + + + + + TLSDebug + + + +
+
Connecting...
+ + + +HTML + +# Also overwrite vnc.html so any direct /vnc.html request shows our kiosk too +RUN NOVNC_WEB=$(cat /opt/novnc_path) && cp "${NOVNC_WEB}/index.html" "${NOVNC_WEB}/vnc.html" + +# --------------------------------------------------------------------------- +# 5. Working directories +# --------------------------------------------------------------------------- +RUN mkdir -p /opt/tlsdebug /opt/chrome-profile /var/log/tlsdebug /root/.config + +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# 4b. Openbox config — fullscreen enforcement + black desktop, no decorations +# --------------------------------------------------------------------------- +RUN mkdir -p /root/.config/openbox + +# rc.xml — force every window fullscreen, no decorations, black desktop +RUN cat > /root/.config/openbox/rc.xml << 'RCXML' + + + + Clearlooks + + no + no + + 1 + + + yes + yes + no + yes + + + +RCXML + +# autostart — black root window, hide idle cursor +RUN cat > /root/.config/openbox/autostart << 'OBSTART' +xsetroot -solid black +unclutter -idle 0 -root -noevents & +OBSTART + +# --------------------------------------------------------------------------- +# 5b. Log viewer — real-time web UI on port 4040 +# --------------------------------------------------------------------------- +RUN echo 'aW1wb3J0IGh0dHAuc2VydmVyLCBvcywganNvbgpmcm9tIGh0dHAuc2VydmVyIGltcG9ydCBCYXNlSFRUUFJlcXVlc3RIYW5kbGVyLCBIVFRQU2VydmVyCmZyb20gdXJsbGliLnBhcnNlIGltcG9ydCB1cmxwYXJzZSwgcGFyc2VfcXMKCkxPR19ESVIgPSBvcy5lbnZpcm9uLmdldCgiTE9HX0RJUiIsICIvdmFyL2xvZy90bHNkZWJ1ZyIpClBPUlQgICAgPSA0MDQwCgpMT0dfRklMRVMgPSBbInRsc3Byb3h5LmxvZyIsICJjYXB0dXJlZF90b2tlbnMuanNvbiIsICJjaHJvbWUubG9nIiwKICAgICAgICAgICAgICJ4MTF2bmMubG9nIiwgIm5vdm5jLmxvZyIsICJ4dmZiLmxvZyIsICJvcGVuYm94LmxvZyJdCgpkZWYgYnVpbGRfcGFnZSgpOgogICAgZmlsZXNfanMgPSBqc29uLmR1bXBzKExPR19GSUxFUykKICAgIHJldHVybiBmIiIiPCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEiPgo8dGl0bGU+VExTRGVidWcgTG9nIFZpZXdlcjwvdGl0bGU+CjxzdHlsZT4KICA6cm9vdHt7LS1iZzojMGQxMTE3Oy0tcGFuZWw6IzE2MWIyMjstLWJvcmRlcjojMzAzNjNkOy0tYWNjZW50OiM1OGE2ZmY7CiAgICAgICAgLS1ncmVlbjojM2ZiOTUwOy0tdGV4dDojYzlkMWQ5Oy0tbXV0ZWQ6IzhiOTQ5ZX19CiAgKnt7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH19CiAgYm9keXt7YmFja2dyb3VuZDp2YXIoLS1iZyk7Y29sb3I6dmFyKC0tdGV4dCk7Zm9udC1mYW1pbHk6J1NGIE1vbm8nLE1vbmFjbyxtb25vc3BhY2U7CiAgICAgICBmb250LXNpemU6MTNweDtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDB2aH19CiAgaGVhZGVye3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpOwogICAgICAgICAgcGFkZGluZzoxMnB4IDIwcHg7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtnYXA6MTZweDtmbGV4LXNocmluazowfX0KICBoZWFkZXIgaDF7e2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0OjYwMDtjb2xvcjp2YXIoLS1hY2NlbnQpfX0KICAuYmFkZ2V7e2JhY2tncm91bmQ6dmFyKC0tZ3JlZW4pO2NvbG9yOiMwMDA7Zm9udC1zaXplOjEwcHg7Zm9udC13ZWlnaHQ6NzAwOwogICAgICAgICAgcGFkZGluZzoycHggOHB4O2JvcmRlci1yYWRpdXM6MTBweH19CiAgI3N0YXR1c2Jhcnt7Zm9udC1zaXplOjExcHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdpbi1sZWZ0OmF1dG99fQogIC50YWJze3tkaXNwbGF5OmZsZXg7Z2FwOjRweDtvdmVyZmxvdy14OmF1dG87YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7CiAgICAgICAgIHBhZGRpbmc6OHB4IDE2cHggMDtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjB9fQogIC50YWJ7e3BhZGRpbmc6NnB4IDE0cHg7Ym9yZGVyLXJhZGl1czo2cHggNnB4IDAgMDtjdXJzb3I6cG9pbnRlcjtjb2xvcjp2YXIoLS1tdXRlZCk7CiAgICAgICAgYm9yZGVyOjFweCBzb2xpZCB0cmFuc3BhcmVudDtib3JkZXItYm90dG9tOm5vbmU7Zm9udC1zaXplOjEycHg7d2hpdGUtc3BhY2U6bm93cmFwfX0KICAudGFiOmhvdmVye3tjb2xvcjp2YXIoLS10ZXh0KTtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA1KX19CiAgLnRhYi5hY3RpdmV7e2NvbG9yOnZhcigtLWFjY2VudCk7YmFja2dyb3VuZDp2YXIoLS1iZyk7Ym9yZGVyLWNvbG9yOnZhcigtLWJvcmRlcil9fQogIC50b29sYmFye3tkaXNwbGF5OmZsZXg7Z2FwOjhweDtwYWRkaW5nOjhweCAxNnB4O2JhY2tncm91bmQ6dmFyKC0tYmcpOwogICAgICAgICAgICBib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjA7YWxpZ24taXRlbXM6Y2VudGVyfX0KICAudG9vbGJhciBpbnB1dHt7ZmxleDoxO2JhY2tncm91bmQ6dmFyKC0tcGFuZWwpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tYm9yZGVyKTsKICAgICAgICAgICAgICAgICAgY29sb3I6dmFyKC0tdGV4dCk7cGFkZGluZzo1cHggMTBweDtib3JkZXItcmFkaXVzOjZweDtmb250LWZhbWlseTppbmhlcml0fX0KICAudG9vbGJhciBsYWJlbHt7Y29sb3I6dmFyKC0tbXV0ZWQpO2ZvbnQtc2l6ZToxMnB4O2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjVweH19CiAgLnRvb2xiYXIgYnV0dG9ue3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWJvcmRlcik7Y29sb3I6dmFyKC0tdGV4dCk7CiAgICAgICAgICAgICAgICAgICBwYWRkaW5nOjVweCAxMnB4O2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjpwb2ludGVyO2ZvbnQtZmFtaWx5OmluaGVyaXR9fQogIC50b29sYmFyIGJ1dHRvbjpob3Zlcnt7Ym9yZGVyLWNvbG9yOnZhcigtLWFjY2VudCk7Y29sb3I6dmFyKC0tYWNjZW50KX19CiAgI2xvZ3ZpZXd7e2ZsZXg6MTtvdmVyZmxvdy15OmF1dG87cGFkZGluZzoxMnB4IDE2cHg7d2hpdGUtc3BhY2U6cHJlLXdyYXA7CiAgICAgICAgICAgIHdvcmQtYnJlYWs6YnJlYWstYWxsO2xpbmUtaGVpZ2h0OjEuNn19CiAgLmxue3twYWRkaW5nOjFweCAwfX0gLmxuOmhvdmVye3tiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA0KX19CiAgLm1hdGNoe3tiYWNrZ3JvdW5kOnJnYmEoODgsMTY2LDI1NSwuMTIpfX0gLmhse3tiYWNrZ3JvdW5kOiNlM2IzNDE0NDtib3JkZXItcmFkaXVzOjJweH19Cjwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CjxoZWFkZXI+CiAgPGgxPiYjeDFGNTBEOyBUTFNEZWJ1ZyBMb2cgVmlld2VyPC9oMT4KICA8c3BhbiBjbGFzcz0iYmFkZ2UiPkxJVkU8L3NwYW4+CiAgPHNwYW4gaWQ9InN0YXR1c2JhciI+Q29ubmVjdGluZy4uLjwvc3Bhbj4KPC9oZWFkZXI+CjxkaXYgY2xhc3M9InRhYnMiIGlkPSJ0YWJzIj48L2Rpdj4KPGRpdiBjbGFzcz0idG9vbGJhciI+CiAgPGlucHV0IGlkPSJmaWx0ZXIiIHBsYWNlaG9sZGVyPSJGaWx0ZXIgbGluZXMuLi4iIG9uaW5wdXQ9ImFwcGx5RmlsdGVyKCkiPgogIDxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIGlkPSJhdXRvIiBjaGVja2VkPiBBdXRvLXNjcm9sbDwvbGFiZWw+CiAgPGJ1dHRvbiBvbmNsaWNrPSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWU9Jyc7YXBwbHlGaWx0ZXIoKSI+Q2xlYXI8L2J1dHRvbj4KICA8YnV0dG9uIG9uY2xpY2s9ImxvY2F0aW9uLmhyZWY9Jy9hcGkvZG93bmxvYWQ/ZmlsZT0nK2N1ciI+RG93bmxvYWQ8L2J1dHRvbj4KPC9kaXY+CjxkaXYgaWQ9ImxvZ3ZpZXciPjwvZGl2Pgo8c2NyaXB0Pgpjb25zdCBMT0dTPXtmaWxlc19qc307CmxldCBjdXI9TE9HU1swXSxvZmY9MCxyYXc9W107CmZ1bmN0aW9uIGluaXQoKXt7CiAgY29uc3QgdD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndGFicycpOwogIExPR1MuZm9yRWFjaChmPT57ewogICAgY29uc3QgZD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgIGQuY2xhc3NOYW1lPSd0YWInKyhmPT09Y3VyPycgYWN0aXZlJzonJyk7CiAgICBkLnRleHRDb250ZW50PWY7ZC5vbmNsaWNrPSgpPT5zdyhmKTt0LmFwcGVuZENoaWxkKGQpOwogIH19KTsKICBwb2xsKCk7c2V0SW50ZXJ2YWwocG9sbCwxNTAwKTsKfX0KZnVuY3Rpb24gc3coZil7e2N1cj1mO29mZj0wO3Jhdz1bXTsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudGFiJykuZm9yRWFjaCh0PT50LmNsYXNzTmFtZT0ndGFiJysodC50ZXh0Q29udGVudD09PWY/JyBhY3RpdmUnOicnKSk7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvZ3ZpZXcnKS5pbm5lckhUTUw9Jyc7cG9sbCgpO319CmFzeW5jIGZ1bmN0aW9uIHBvbGwoKXt7CiAgdHJ5e3sKICAgIGNvbnN0IHI9YXdhaXQgZmV0Y2goJy9hcGkvbG9nP2ZpbGU9JytlbmNvZGVVUklDb21wb25lbnQoY3VyKSsnJm9mZnNldD0nK29mZik7CiAgICBpZighci5vaylyZXR1cm47CiAgICBjb25zdCBkPWF3YWl0IHIuanNvbigpO29mZj1kLnNpemU7CiAgICBpZihkLmxpbmVzJiZkLmxpbmVzLmxlbmd0aCl7e3Jhdz1yYXcuY29uY2F0KGQubGluZXMpO2lmKHJhdy5sZW5ndGg+NTAwMClyYXc9cmF3LnNsaWNlKC01MDAwKTthcHBseUZpbHRlcigpO319CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3RhdHVzYmFyJykudGV4dENvbnRlbnQ9J1VwZGF0ZWQgJytuZXcgRGF0ZSgpLnRvTG9jYWxlVGltZVN0cmluZygpOwogIH19Y2F0Y2goZSl7e2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdGF0dXNiYXInKS50ZXh0Q29udGVudD0nRXJyb3I6ICcrZTt9fQp9fQpmdW5jdGlvbiBhcHBseUZpbHRlcigpe3sKICBjb25zdCBmdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWUudG9Mb3dlckNhc2UoKTsKICBjb25zdCBsdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9ndmlldycpOwogIGNvbnN0IHJvd3M9ZnY/cmF3LmZpbHRlcihsPT5sLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpKTpyYXc7CiAgbHYuaW5uZXJIVE1MPXJvd3MubWFwKGw9Pnt7CiAgICBjb25zdCBlPWwucmVwbGFjZSgvJi9nLCcmYW1wOycpLnJlcGxhY2UoLzwvZywnJmx0OycpLnJlcGxhY2UoLz4vZywnJmd0OycpOwogICAgY29uc3QgaD1mdj9lLnJlcGxhY2UobmV3IFJlZ0V4cChmdi5yZXBsYWNlKC9bLiorP14ke3t9fSgpfFtcXF1cXFxcXS9nLCdcXFxcJCYnKSwnZ2knKSxtPT4nPHNwYW4gY2xhc3M9ImhsIj4nK20rJzwvc3Bhbj4nKTplOwogICAgcmV0dXJuICc8ZGl2IGNsYXNzPSJsbicrKGZ2JiZsLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpPycgbWF0Y2gnOicnKSsnIj4nK2grJzwvZGl2Pic7CiAgfX0pLmpvaW4oJycpOwogIGlmKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhdXRvJykuY2hlY2tlZClsdi5zY3JvbGxUb3A9bHYuc2Nyb2xsSGVpZ2h0Owp9fQppbml0KCk7Cjwvc2NyaXB0Pgo8L2JvZHk+PC9odG1sPiIiIgoKUEFHRSA9IGJ1aWxkX3BhZ2UoKQoKY2xhc3MgSGFuZGxlcihCYXNlSFRUUFJlcXVlc3RIYW5kbGVyKToKICAgIGRlZiBsb2dfbWVzc2FnZShzZWxmLCBmbXQsICphcmdzKTogcGFzcwogICAgZGVmIGRvX0dFVChzZWxmKToKICAgICAgICBwID0gc2VsZi5wYXRoLnNwbGl0KCc/JylbMF0KICAgICAgICBpZiBwIGluICgnLycsICcvaW5kZXguaHRtbCcpOgogICAgICAgICAgICBib2R5ID0gUEFHRS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCd0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgnKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LUxlbmd0aCcsIHN0cihsZW4oYm9keSkpKQogICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoYm9keSkKICAgICAgICBlbGlmIHAgPT0gJy9hcGkvbG9nJzoKICAgICAgICAgICAgcSA9IHBhcnNlX3FzKHVybHBhcnNlKHNlbGYucGF0aCkucXVlcnkpCiAgICAgICAgICAgIGZuYW1lID0gb3MucGF0aC5iYXNlbmFtZShxLmdldCgnZmlsZScsWyd0bHNwcm94eS5sb2cnXSlbMF0pCiAgICAgICAgICAgIG9mZnNldCA9IGludChxLmdldCgnb2Zmc2V0JyxbJzAnXSlbMF0pCiAgICAgICAgICAgIGZwYXRoID0gb3MucGF0aC5qb2luKExPR19ESVIsIGZuYW1lKQogICAgICAgICAgICBsaW5lcyA9IFtdOyBzaXplID0gb2Zmc2V0CiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKGZwYXRoKToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihmcGF0aCwncmInKSBhcyBmOgogICAgICAgICAgICAgICAgICAgIGYuc2VlayhvZmZzZXQpOyBjaHVuayA9IGYucmVhZCgpCiAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IG9mZnNldCArIGxlbihjaHVuaykKICAgICAgICAgICAgICAgICAgICByYXdfdGV4dCA9IGNodW5rLmRlY29kZSgndXRmLTgnLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgICAgICAgICAgICAgICAgIGlmIGZuYW1lLmVuZHN3aXRoKCcuanNvbicpOgogICAgICAgICAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQganNvbiBhcyBfanNvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VkID0gX2pzb24ubG9hZHMob3BlbihmcGF0aCkucmVhZCgpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJldHR5ID0gX2pzb24uZHVtcHMocGFyc2VkLCBpbmRlbnQ9MikKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzICA9IHByZXR0eS5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgICA9IG9zLnBhdGguZ2V0c2l6ZShmcGF0aCkKICAgICAgICAgICAgICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzID0gcmF3X3RleHQuc3BsaXRsaW5lcygpCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgbGluZXMgPSByYXdfdGV4dC5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgYm9keSA9IGpzb24uZHVtcHMoeydsaW5lcyc6bGluZXMsJ3NpemUnOnNpemV9KS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCdhcHBsaWNhdGlvbi9qc29uJykKICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luJywnKicpCiAgICAgICAgICAgIHNlbGYuZW5kX2hlYWRlcnMoKTsgc2VsZi53ZmlsZS53cml0ZShib2R5KQogICAgICAgIGVsaWYgcCA9PSAnL2FwaS9kb3dubG9hZCc6CiAgICAgICAgICAgIHEgPSBwYXJzZV9xcyh1cmxwYXJzZShzZWxmLnBhdGgpLnF1ZXJ5KQogICAgICAgICAgICBmbmFtZSA9IG9zLnBhdGguYmFzZW5hbWUocS5nZXQoJ2ZpbGUnLFsndGxzcHJveHkubG9nJ10pWzBdKQogICAgICAgICAgICBmcGF0aCA9IG9zLnBhdGguam9pbihMT0dfRElSLCBmbmFtZSkKICAgICAgICAgICAgaWYgb3MucGF0aC5leGlzdHMoZnBhdGgpOgogICAgICAgICAgICAgICAgd2l0aCBvcGVuKGZwYXRoLCdyYicpIGFzIGY6IGRhdGEgPSBmLnJlYWQoKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX3Jlc3BvbnNlKDIwMCkKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoJ0NvbnRlbnQtVHlwZScsJ3RleHQvcGxhaW4nKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQ29udGVudC1EaXNwb3NpdGlvbicsJ2F0dGFjaG1lbnQ7IGZpbGVuYW1lPScrZm5hbWUpCiAgICAgICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoZGF0YSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9yZXNwb25zZSg0MDQpOyBzZWxmLmVuZF9oZWFkZXJzKCkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoNDA0KTsgc2VsZi5lbmRfaGVhZGVycygpCgpvcy5tYWtlZGlycyhMT0dfRElSLCBleGlzdF9vaz1UcnVlKQpwcmludChmIltsb2d2aWV3ZXJdIDAuMC4wLjA6e1BPUlR9ICBsb2dzPXtMT0dfRElSfSIpCkhUVFBTZXJ2ZXIoKCcwLjAuMC4wJywgUE9SVCksIEhhbmRsZXIpLnNlcnZlX2ZvcmV2ZXIoKQo=' | base64 -d > /opt/logviewer.py + +# --------------------------------------------------------------------------- +# 6. install-ca.sh +# Installs TLSDebug CA into: +# (a) Debian/OS system trust store +# (b) ~/.pki/nssdb — where Chromium/Chrome actually looks on Linux +# (c) Chrome profile NSS db — belt-and-suspenders +# --------------------------------------------------------------------------- +RUN cat > /opt/install-ca.sh << 'SCRIPT' +#!/bin/bash +set -e +CA_CERT="${1:-/opt/tlsdebug/proxy-ca.crt}" +CA_NAME="TLSDebug CA" +HOME_NSS="/root/.pki/nssdb" +PROFILE_NSS="/opt/chrome-profile/nssdb" + +log() { echo "[CA] $*"; } + +if [ ! -f "${CA_CERT}" ]; then + log "ERROR: cert not found at ${CA_CERT}"; exit 1 +fi + +log "=== Installing TLSDebug CA ===" +log "Subject : $(openssl x509 -noout -subject -in "${CA_CERT}" 2>/dev/null)" +log "Expires : $(openssl x509 -noout -enddate -in "${CA_CERT}" 2>/dev/null)" + +# ------------------------------------------------------------------ +# (a) OS / system trust store +# ------------------------------------------------------------------ +log "--- System trust store ---" +cp "${CA_CERT}" /usr/local/share/ca-certificates/tlsdebug.crt +update-ca-certificates --fresh +log "System store updated." + +# ------------------------------------------------------------------ +# (b) ~/.pki/nssdb (THIS is where Chromium on Linux actually looks) +# ------------------------------------------------------------------ +log "--- ~/.pki/nssdb (Chromium default location) ---" +mkdir -p "${HOME_NSS}" +if [ ! -f "${HOME_NSS}/cert9.db" ]; then + certutil -N -d "sql:${HOME_NSS}" --empty-password + log "Created new NSS db at ${HOME_NSS}" +fi +certutil -d "sql:${HOME_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true +certutil -d "sql:${HOME_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}" +log "Installed into ${HOME_NSS}" + +# Verify +if certutil -d "sql:${HOME_NSS}" -L -n "${CA_NAME}" 2>/dev/null | grep -q "CT"; then + log "OK: ${HOME_NSS} verification passed." +else + log "WARNING: cert not found in ${HOME_NSS} after install" +fi + +# ------------------------------------------------------------------ +# (c) Chrome profile NSS db (belt-and-suspenders) +# ------------------------------------------------------------------ +log "--- Chrome profile NSS db ---" +mkdir -p "${PROFILE_NSS}" +if [ ! -f "${PROFILE_NSS}/cert9.db" ]; then + certutil -N -d "sql:${PROFILE_NSS}" --empty-password + log "Created new NSS db at ${PROFILE_NSS}" +fi +certutil -d "sql:${PROFILE_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true +certutil -d "sql:${PROFILE_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}" +log "Installed into ${PROFILE_NSS}" + +log "=== CA installation complete ===" +SCRIPT +RUN chmod +x /opt/install-ca.sh + +# --------------------------------------------------------------------------- +# 6b. generate-novnc-cert.sh — Self-signed certificate for noVNC HTTPS +# --------------------------------------------------------------------------- +RUN cat > /opt/generate-novnc-cert.sh << 'SCRIPT' +#!/bin/bash +set -e + +CERT_DIR="${1:-/opt/novnc-certs}" +CERT_FILE="${CERT_DIR}/novnc.pem" + +log() { echo "[cert] $*"; } + +mkdir -p "${CERT_DIR}" + +if [ -f "${CERT_FILE}" ]; then + log "Certificate already exists: ${CERT_FILE}" + log "Expires: $(openssl x509 -noout -enddate -in "${CERT_FILE}" 2>/dev/null || echo 'unknown')" + exit 0 +fi + +log "Generating self-signed certificate for noVNC..." + +openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout "${CERT_FILE}" \ + -out "${CERT_FILE}" \ + -days 365 \ + -subj "/C=US/ST=State/L=City/O=TLSDebug/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:*.local,IP:127.0.0.1" \ + 2>/dev/null + +chmod 600 "${CERT_FILE}" + +log "Certificate generated: ${CERT_FILE}" +log "Subject: $(openssl x509 -noout -subject -in "${CERT_FILE}" 2>/dev/null)" +log "Expires: $(openssl x509 -noout -enddate -in "${CERT_FILE}" 2>/dev/null)" +SCRIPT +RUN chmod +x /opt/generate-novnc-cert.sh + +# 7. start-chrome.sh +# --------------------------------------------------------------------------- +RUN cat > /opt/start-chrome.sh << 'SCRIPT' +#!/bin/bash +export DISPLAY="${DISPLAY:-:99}" +export HOME=/root + +PROXY_HOST="${PROXY_HOST:-127.0.0.1}" +PROXY_PORT="${PROXY_PORT:-8080}" +REMOTE_DEBUG_PORT="${CHROME_REMOTE_DEBUGGING_PORT:-9222}" +START_URL="${START_URL:-https://www.google.com}" + +echo "[Browser] $(browser --version 2>/dev/null || echo unknown)" +echo "[Browser] DISPLAY=${DISPLAY} proxy=${PROXY_HOST}:${PROXY_PORT}" + +exec browser \ + --no-sandbox \ + --disable-dev-shm-usage \ + --disable-gpu \ + --disable-software-rasterizer \ + --use-gl=swiftshader \ + --use-angle=swiftshader \ + --enable-unsafe-swiftshader \ + --in-process-gpu \ + --disable-setuid-sandbox \ + --user-data-dir="/opt/chrome-profile" \ + --proxy-server="http://${PROXY_HOST}:${PROXY_PORT}" \ + --ignore-certificate-errors \ + --ignore-certificate-errors-spki-list \ + --test-type \ + --remote-debugging-port="${REMOTE_DEBUG_PORT}" \ + --remote-debugging-address=0.0.0.0 \ + --window-size=1920,1080 \ + --start-fullscreen \ + --kiosk \ + --disable-extensions \ + --no-first-run \ + --no-default-browser-check \ + --password-store=basic \ + --use-mock-keychain \ + --enable-logging=stderr \ + --v=1 \ + "${START_URL}" 2>&1 +SCRIPT +RUN chmod +x /opt/start-chrome.sh + + +# --------------------------------------------------------------------------- +# 8. entrypoint.sh +# --------------------------------------------------------------------------- +RUN cat > /entrypoint.sh << 'SCRIPT' +#!/bin/bash +set -e + +export HOME=/root +PROXY_PORT="${PROXY_PORT:-8080}" +VNC_PORT="${VNC_PORT:-5900}" +NOVNC_PORT="${NOVNC_PORT:-6080}" +DISPLAY="${DISPLAY:-:99}" +# LOG_DIR is bind-mounted to ./logs/ on the host. +# CERTDIR is set to the same path so tlsproxy writes captured_tokens.json, +# session files, and the CA cert directly into the host-visible logs folder. +LOG_DIR="/var/log/tlsdebug" +CERTDIR="${LOG_DIR}" +NOVNC_WEB=$(cat /opt/novnc_path 2>/dev/null || echo "/usr/share/novnc") + +mkdir -p "${LOG_DIR}" + +log() { echo "[$(date '+%H:%M:%S')] $*"; } +wait_port() { + local port=$1 label=$2 tries=${3:-30} + for i in $(seq 1 "${tries}"); do + if nc -z 127.0.0.1 "${port}" 2>/dev/null; then + log "[+] ${label} ready on port ${port}." + return 0 + fi + sleep 0.5 + done + log "[!] TIMEOUT waiting for ${label} on port ${port}." + return 1 +} + +log "============================================" +log " TLSDebug + Headless Chrome Container" +log " noVNC Web UI : http://localhost:${NOVNC_PORT}" +log " TLS Proxy : localhost:${PROXY_PORT}" +log " noVNC root : ${NOVNC_WEB}" +log "============================================" + +# ---- Diagnostics ----------------------------------------------------------- +log "[diag] novnc web root contents:" +ls -la "${NOVNC_WEB}" 2>&1 | head -20 || log " (none)" +log "[diag] websockify location: $(which websockify 2>/dev/null || echo NOT FOUND)" +log "[diag] x11vnc location: $(which x11vnc 2>/dev/null || echo NOT FOUND)" +log "[diag] browser symlink: $(which browser 2>/dev/null || echo NOT FOUND) -> $(readlink /usr/local/bin/browser 2>/dev/null || echo none)" +log "[diag] tlsproxy location: $(which tlsproxy 2>/dev/null || echo NOT FOUND)" + +# ---- 1. Xvfb --------------------------------------------------------------- +log "[*] Starting Xvfb on ${DISPLAY} ..." +rm -f /tmp/.X99-lock /tmp/.X11-unix/X99 2>/dev/null || true +Xvfb "${DISPLAY}" \ + -screen 0 1920x1080x24 \ + -ac \ + +extension GLX \ + +extension RANDR \ + +render \ + -noreset \ + >> "${LOG_DIR}/xvfb.log" 2>&1 & +XVFB_PID=$! + +log "[*] Waiting for Xvfb ..." +for i in $(seq 1 30); do + if DISPLAY="${DISPLAY}" xdpyinfo >/dev/null 2>&1; then + log "[+] Xvfb ready." + break + fi + sleep 0.5 +done +export DISPLAY="${DISPLAY}" + +# ---- 2. Openbox window manager --------------------------------------------- +log "[*] Starting openbox ..." +openbox --config-file /root/.config/openbox/rc.xml --display "${DISPLAY}" >> "${LOG_DIR}/openbox.log" 2>&1 & +sleep 1 + +# ---- 3. TLSDebug proxy ----------------------------------------------------- +log "[*] Starting TLSDebug proxy on port ${PROXY_PORT} ..." +cd "${CERTDIR}" +tlsproxy \ + -port "${PROXY_PORT}" \ + -certdir "${CERTDIR}" \ + -skip-install \ + 2>&1 | grep -v 'SSLV3_ALERT_CERTIFICATE_UNKNOWN' | tee -a "${LOG_DIR}/tlsproxy.log" & +PROXY_PID=$! + +log "[*] Waiting for CA certificate ..." +for i in $(seq 1 30); do + if [ -f "${CERTDIR}/proxy-ca.crt" ]; then + log "[+] CA certificate ready." + break + fi + sleep 1 +done +# Install CA before Chrome launches — must succeed +if [ -f "${CERTDIR}/proxy-ca.crt" ]; then + if /opt/install-ca.sh "${CERTDIR}/proxy-ca.crt"; then + log "[+] CA trusted by OS and Chrome NSS." + else + log "[!] CA install failed — Chrome may show cert errors." + fi +else + log "[!] proxy-ca.crt missing — Chrome may show cert errors." +fi + +# ---- 4. x11vnc ------------------------------------------------------------- +log "[*] Starting x11vnc on port ${VNC_PORT} ..." +x11vnc \ + -display "${DISPLAY}" \ + -forever \ + -shared \ + -nopw \ + -rfbport "${VNC_PORT}" \ + -noxdamage \ + -noxfixes \ + -noxcomposite \ + -cursor arrow \ + -loop \ + -repeat \ + -xkb \ + >> "${LOG_DIR}/x11vnc.log" 2>&1 & + +sleep 2 +wait_port "${VNC_PORT}" "x11vnc" 40 + +# ---- 5. noVNC -------------------------------------------------------------- +# Check for custom certificate or generate self-signed +CERT_DIR="/opt/novnc-certs" +CUSTOM_CERT="/certs/novnc.pem" +CERT_FILE="${CERT_DIR}/novnc.pem" + +if [ "${NOVNC_ENABLE_HTTPS}" = "true" ]; then + log "[*] HTTPS mode enabled" + + # Use custom cert if provided, otherwise generate self-signed + if [ -f "${CUSTOM_CERT}" ]; then + log "[*] Using custom certificate: ${CUSTOM_CERT}" + mkdir -p "${CERT_DIR}" + cp "${CUSTOM_CERT}" "${CERT_FILE}" + chmod 600 "${CERT_FILE}" + else + log "[*] No custom cert found, generating self-signed..." + /opt/generate-novnc-cert.sh "${CERT_DIR}" + fi + + NOVNC_PORT="${NOVNC_HTTPS_PORT:-6443}" + SSL_ARGS="--cert=${CERT_FILE} --ssl-only" + SCHEME="https" +else + log "[*] HTTP mode (set NOVNC_ENABLE_HTTPS=true for HTTPS)" + SSL_ARGS="" + SCHEME="http" +fi + +log "[*] Starting noVNC websockify on port ${NOVNC_PORT} (${SCHEME}) ..." +log "[*] web root : ${NOVNC_WEB}" +log "[*] target : 127.0.0.1:${VNC_PORT}" + +# note: SSL_ARGS may include --ssl-only; omit to allow fallbacks when certs are untrusted +tmp_args="${SSL_ARGS}" +# remove ssl-only if present to avoid 'non-SSL connection received but disallowed' errors +tmp_args=$(echo "${tmp_args}" | sed 's/--ssl-only//g') +websockify \ + --web "${NOVNC_WEB}" \ + --heartbeat 30 \ + ${tmp_args} \ + --log-file "${LOG_DIR}/novnc.log" \ + "${NOVNC_PORT}" \ + "127.0.0.1:${VNC_PORT}" & +NOVNC_PID=$! + +wait_port "${NOVNC_PORT}" "noVNC" 40 + +# Confirm websockify process +log "[diag] websockify process: $(pgrep -a websockify 2>/dev/null || echo NOT RUNNING)" +log "[diag] listening ports: $(ss -tlnp 2>/dev/null | grep -E '5900|6080|8080' || echo none)" + +# ---- 6. Chrome ------------------------------------------------------------- +# remove stale profile locks which can prevent Chrome from launching +rm -f /opt/chrome-profile/Singleton* /opt/chrome-profile/Default/Singleton* + +log "[*] Starting Chrome ..." +/opt/start-chrome.sh >> "${LOG_DIR}/chrome.log" 2>&1 & +CHROME_PID=$! + +# ---- 7. Log viewer on port 4040 -------------------------------------------- +log "[*] Starting log viewer on port 4040 ..." +python3 /opt/logviewer.py >> "${LOG_DIR}/logviewer.log" 2>&1 & +LOGVIEWER_PID=$! +wait_port 4040 "log viewer" 20 + +log "" +log "[+] All services started." +log "[+] Browser (noVNC) : ${SCHEME}://localhost:${NOVNC_PORT}" +log "[+] Log viewer : http://localhost:4040" +log "[+] TLS proxy : localhost:${PROXY_PORT}" +log "[+] Host logs dir : ${LOG_DIR} (mounted to ./logs/ on host)" +if [ "${NOVNC_ENABLE_HTTPS}" = "true" ]; then + log "[+] Certificate : ${CERT_FILE}" +fi +log "" + +cleanup() { + log "[*] Shutting down ..." + kill "${CHROME_PID}" "${NOVNC_PID}" "${PROXY_PID}" "${XVFB_PID}" "${LOGVIEWER_PID}" 2>/dev/null || true + exit 0 +} +trap cleanup SIGTERM SIGINT + +# Stream proxy traffic to docker logs so 'docker logs -f tlsdebug' shows live traffic +tail -f "${LOG_DIR}/tlsproxy.log" +SCRIPT +RUN chmod +x /entrypoint.sh + +# --------------------------------------------------------------------------- +# 9. Ports +# --------------------------------------------------------------------------- +EXPOSE 6080 6443 80 443 5900 8080 4040 9222 + +WORKDIR /opt/tlsdebug +ENTRYPOINT ["/entrypoint.sh"] diff --git a/labs/remotebrowser_TLS/README.md b/labs/remotebrowser_TLS/README.md index 8b13789..6ad042d 100644 --- a/labs/remotebrowser_TLS/README.md +++ b/labs/remotebrowser_TLS/README.md @@ -1 +1 @@ - +### Allow Testing of Browser In Browser diff --git a/labs/remotebrowser_TLS/docker-compose.yml b/labs/remotebrowser_TLS/docker-compose.yml new file mode 100644 index 0000000..05a300a --- /dev/null +++ b/labs/remotebrowser_TLS/docker-compose.yml @@ -0,0 +1,58 @@ +services: + tlsdebug: + build: + context: . + dockerfile: Dockerfile + image: tlsdebug:latest + container_name: tlsdebug + ports: + # noVNC web UI — HTTP on port 6080, HTTPS on port 6443 + - "6080:6080" # noVNC HTTP (default) + - "6443:6443" # noVNC HTTPS (when NOVNC_ENABLE_HTTPS=true) + # - "80:6080" # Optional: expose HTTP on port 80 + - "443:6443" # Optional: expose HTTPS on port 443 + # Raw VNC (optional — use any VNC client against localhost:5900) + - "5900:5900" + # TLSDebug intercepting proxy (change host port if 8080 is taken) + - "8888:8080" + # TLSDebug built-in HTTP log viewer + - "4040:4040" + # Chrome remote debugging (optional) + - "9222:9222" + volumes: + # All logs, CA cert, captured_tokens.json and session files + # are written here — readable on the host at ./logs/ + - ./logs:/var/log/tlsdebug + # Persist Chrome profile (bookmarks, saved state, etc.) + - chrome-profile:/opt/chrome-profile + # Optional: Mount custom noVNC certificate (PEM format) + # - ./certs/novnc.pem:/certs/novnc.pem:ro + environment: + PROXY_PORT: "8080" + VNC_PORT: "5900" + NOVNC_PORT: "6080" + NOVNC_HTTPS_PORT: "6443" + DISPLAY: ":99" + CHROME_REMOTE_DEBUGGING_PORT: "9222" + # Change START_URL to set the browser's home page + START_URL: "https://www.google.com" + # Enable HTTPS for noVNC (set to "true" to enable) + NOVNC_ENABLE_HTTPS: "true" + # When HTTPS is enabled: + # - Self-signed cert is auto-generated if no custom cert provided + # - Mount custom cert at /certs/novnc.pem to use your own + cap_add: + - SYS_ADMIN + security_opt: + - seccomp:unconfined + restart: unless-stopped + shm_size: "2gb" + healthcheck: + test: ["CMD", "sh", "-c", "[ \"${NOVNC_ENABLE_HTTPS}\" = \"true\" ] && curl -k -sf https://localhost:6443 || curl -sf http://localhost:6080"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + chrome-profile: \ No newline at end of file From 3390caf9e939f74a1a086daa71ad4f244716a4bf Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:10:03 -0600 Subject: [PATCH 5/9] Add files via upload --- labs/remotebrowser_TLS/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labs/remotebrowser_TLS/docker-compose.yml b/labs/remotebrowser_TLS/docker-compose.yml index 05a300a..6cebb0b 100644 --- a/labs/remotebrowser_TLS/docker-compose.yml +++ b/labs/remotebrowser_TLS/docker-compose.yml @@ -35,7 +35,7 @@ services: DISPLAY: ":99" CHROME_REMOTE_DEBUGGING_PORT: "9222" # Change START_URL to set the browser's home page - START_URL: "https://www.google.com" + START_URL: "https://microsoft.com" # Enable HTTPS for noVNC (set to "true" to enable) NOVNC_ENABLE_HTTPS: "true" # When HTTPS is enabled: From 6c4e10281b200f7770381d0ed978038e5795be44 Mon Sep 17 00:00:00 2001 From: Casey Smith Date: Sun, 8 Mar 2026 20:32:18 -0600 Subject: [PATCH 6/9] Update Dockerfile --- labs/remotebrowser_TLS/Dockerfile | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/labs/remotebrowser_TLS/Dockerfile b/labs/remotebrowser_TLS/Dockerfile index ee8c77a..158cbce 100644 --- a/labs/remotebrowser_TLS/Dockerfile +++ b/labs/remotebrowser_TLS/Dockerfile @@ -123,6 +123,14 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML overflow:hidden; background:#000; } #screen { position:fixed; top:0; left:0; width:100vw; height:100vh; background:#000; } + /* Force the injected noVNC canvas to fill the container exactly */ + #screen canvas, #noVNC_canvas { + display:block!important; + position:absolute!important; + top:0!important; left:0!important; + width:100%!important; height:100%!important; + border:none!important; outline:none!important; + } /* Nuke every noVNC chrome element by ID and class */ #noVNC_control_bar_anchor, #noVNC_control_bar, #noVNC_control_bar_handle, #noVNC_hint_anchor, @@ -174,15 +182,24 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML const url = proto + '//' + location.hostname + ':' + port; rfb = new RFB(scrn, url, { credentials: {password:''} }); rfb.scaleViewport = true; - rfb.resizeSession = false; + rfb.resizeSession = true; rfb.clipViewport = false; rfb.showDotCursor = false; rfb.background = '#000000'; - rfb.addEventListener('connect', () => { strip(); msg('TLSDebug Active'); }); + rfb.addEventListener('connect', () => { + strip(); + msg('TLSDebug Active'); + // Immediately sync session size to the actual viewport + rfb.resizeSession = true; + }); rfb.addEventListener('disconnect', () => { msg('Reconnecting...', true); setTimeout(connect, 3000); }); rfb.addEventListener('credentialsrequired', () => rfb.sendCredentials({password:''})); } + // Keep session size in sync whenever the browser window is resized + new ResizeObserver(() => { if (rfb) rfb.resizeSession = true; }) + .observe(document.documentElement); + connect(); From 934664e5dff8bd3e635540fc3d79cfbf7168496b Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:05:31 -0600 Subject: [PATCH 7/9] Revise README for TLSDebug Remote Browser Lab Updated README to provide detailed setup instructions and features for TLSDebug Remote Browser Lab. --- labs/remotebrowser/README.md | 66 +++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/labs/remotebrowser/README.md b/labs/remotebrowser/README.md index 6ad042d..3582fd2 100644 --- a/labs/remotebrowser/README.md +++ b/labs/remotebrowser/README.md @@ -1 +1,65 @@ -### Allow Testing of Browser In Browser +# TLSDebug Remote Browser Lab + +Browser-in-browser setup for testing HTTPS interception with TLSDebug. All-in-one Docker container with Chrome, noVNC, and the TLS intercepting proxy pre-configured. + +## Quick Start + +```bash +docker-compose up +``` + +Open your browser: +- **http://localhost:6080** - Chrome browser interface (proxy already configured) +- **http://localhost:4040** - Real-time traffic monitor + +Browse any HTTPS site in the remote Chrome - all traffic will be intercepted and logged. + +## What's Included + +- **Headless Chrome** - Full browser with TLS proxy pre-configured +- **noVNC** - Web-based VNC viewer (no VNC client needed) +- **TLSDebug Proxy** - Intercepts and logs all HTTPS traffic +- **Debug Monitor** - Port 4040 shows live traffic, tokens, and sessions +- **Auto CA Trust** - Proxy certificate automatically trusted in Chrome + +## Ports + +| Port | Service | Description | +|------|---------|-------------| +| 6080 | noVNC | Browser interface - start here | +| 4040 | Monitor | Real-time traffic viewer | +| 8888 | Proxy | TLS proxy (mapped from internal 8080) | +| 5900 | VNC | Raw VNC access (optional) | +| 9222 | DevTools | Chrome debugging protocol (optional) | + +## Debug Monitor (Port 4040) + +The monitor shows: +- **All HTTP requests/responses** with full headers +- **Session cookies** - Marked with `"session": true` +- **JWT tokens** - Automatically decoded with claims visible +- **OAuth tokens** - Access and refresh tokens +- **POST data** - Form parameters and JSON payloads + +## Captured Data + +All logs and tokens are saved to `./logs/` on your host: +- `proxy.log` - Full request/response logs +- `EditThisCookie_Sessions.json` - All captured cookies and sessions +- `captured_tokens.json` - JWT and OAuth tokens +- `proxy-ca.crt` - CA certificate (if you need to trust it elsewhere) + +## Configuration + +Edit `docker-compose.yml` to customize: +- `START_URL` - Set the browser's starting page +- Port mappings - Change if ports conflict +- `./logs` volume - Where captured data is saved + +## Use Cases + +- Debug OAuth/SAML flows +- Inspect JWT token contents +- Extract session cookies for testing +- Analyze API request/response patterns +- Troubleshoot TLS/HTTPS issues From c0029d5a01ccdab7fe517a1c2f83532dd15e15b9 Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:55:34 -0600 Subject: [PATCH 8/9] Update Dockerfile fix web socket - tls - false --- labs/remotebrowser/Dockerfile | 594 ++++------------------------------ 1 file changed, 58 insertions(+), 536 deletions(-) diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile index 667255a..a5afa4b 100644 --- a/labs/remotebrowser/Dockerfile +++ b/labs/remotebrowser/Dockerfile @@ -1,536 +1,58 @@ -# ============================================================================= -# TLSDebug + Headless Chrome + noVNC — single self-contained Dockerfile -# ============================================================================= -FROM debian:bookworm-slim - -ENV DEBIAN_FRONTEND=noninteractive \ - DISPLAY=:99 \ - VNC_PORT=5900 \ - NOVNC_PORT=6080 \ - PROXY_PORT=8080 \ - CHROME_REMOTE_DEBUGGING_PORT=9222 \ - PROXY_HOST=127.0.0.1 - -# --------------------------------------------------------------------------- -# 1. Base packages -# --------------------------------------------------------------------------- -RUN apt-get update && apt-get install -y --no-install-recommends \ - golang-go \ - git \ - xvfb \ - x11vnc \ - openbox \ - xterm \ - python3 \ - python3-pip \ - python3-websockify \ - novnc \ - wget \ - gnupg \ - ca-certificates \ - fonts-liberation \ - libasound2 \ - libatk-bridge2.0-0 \ - libatk1.0-0 \ - libcups2 \ - libdbus-1-3 \ - libgdk-pixbuf2.0-0 \ - libgtk-3-0 \ - libnspr4 \ - libnss3 \ - libnss3-tools \ - libx11-xcb1 \ - libxcomposite1 \ - libxdamage1 \ - libxfixes3 \ - libxkbcommon0 \ - libxrandr2 \ - libxshmfence1 \ - xdg-utils \ - openssl \ - curl \ - netcat-openbsd \ - procps \ - psmisc \ - x11-utils \ - unclutter \ - && rm -rf /var/lib/apt/lists/* - -# --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# 2. Install browser -# amd64 -> Google Chrome stable (official Google .deb) -# arm64 -> Chromium from Debian bookworm (native deb, no snap) -# -# Base image is debian:bookworm-slim which has real chromium packages -# on all architectures — no snap wrappers. -# --------------------------------------------------------------------------- -RUN ARCH=$(dpkg --print-architecture) && \ - if [ "${ARCH}" = "amd64" ]; then \ - echo "[browser] amd64: installing Google Chrome ..." && \ - wget -q -O /tmp/chrome.deb \ - "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \ - apt-get update && \ - apt-get install -y --no-install-recommends /tmp/chrome.deb && \ - rm /tmp/chrome.deb && \ - rm -rf /var/lib/apt/lists/* && \ - ln -sf /usr/bin/google-chrome /usr/local/bin/browser && \ - echo "[browser] installed: $(google-chrome --version)"; \ - elif [ "${ARCH}" = "arm64" ]; then \ - echo "[browser] arm64: installing Chromium from Debian bookworm ..." && \ - apt-get update && \ - apt-get install -y --no-install-recommends chromium && \ - rm -rf /var/lib/apt/lists/* && \ - ln -sf /usr/bin/chromium /usr/local/bin/browser && \ - echo "[browser] installed: $(chromium --version)"; \ - else \ - echo "[browser] ERROR: unsupported arch ${ARCH}" && exit 1; \ - fi - -# --------------------------------------------------------------------------- -# 3. Build TLSDebug from source -# AllTrafficModule is already active by default and logs every request, -# response, headers and body — no source patching needed. -# We set log flags via the LOG_FLAGS env var at runtime instead. -# --------------------------------------------------------------------------- -WORKDIR /build -RUN git clone --depth 1 https://github.com/secdev02/TLSDebug.git . \ - && CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/local/bin/tlsproxy tlsproxy.go \ - && chmod +x /usr/local/bin/tlsproxy \ - && rm -rf /build - -# 4. Locate noVNC web root and write kiosk index.html -# Kiosk mode: auto-connects, scales to fill browser window, hides toolbar, -# no password prompt, no connection dialog — just the desktop. -# --------------------------------------------------------------------------- -RUN find /usr -name "vnc.html" 2>/dev/null | head -1 | xargs -I{} dirname {} > /opt/novnc_path \ - && NOVNC_WEB=$(cat /opt/novnc_path) \ - && echo "noVNC web root: ${NOVNC_WEB}" \ - && ls -la "${NOVNC_WEB}" - -RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML' - - - - - TLSDebug - - - -
-
Connecting...
- - - -HTML - -# Also overwrite vnc.html so any direct /vnc.html request shows our kiosk too -RUN NOVNC_WEB=$(cat /opt/novnc_path) && cp "${NOVNC_WEB}/index.html" "${NOVNC_WEB}/vnc.html" - -# --------------------------------------------------------------------------- -# 5. Working directories -# --------------------------------------------------------------------------- -RUN mkdir -p /opt/tlsdebug /opt/chrome-profile /var/log/tlsdebug /root/.config - -# --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# 4b. Openbox config — fullscreen enforcement + black desktop, no decorations -# --------------------------------------------------------------------------- -RUN mkdir -p /root/.config/openbox - -# rc.xml — force every window fullscreen, no decorations, black desktop -RUN cat > /root/.config/openbox/rc.xml << 'RCXML' - - - - Clearlooks - - no - no - - 1 - - - yes - yes - no - yes - - - -RCXML - -# autostart — black root window, hide idle cursor -RUN cat > /root/.config/openbox/autostart << 'OBSTART' -xsetroot -solid black -unclutter -idle 0 -root -noevents & -OBSTART - -# --------------------------------------------------------------------------- -# 5b. Log viewer — real-time web UI on port 4040 -# --------------------------------------------------------------------------- -RUN echo 'aW1wb3J0IGh0dHAuc2VydmVyLCBvcywganNvbgpmcm9tIGh0dHAuc2VydmVyIGltcG9ydCBCYXNlSFRUUFJlcXVlc3RIYW5kbGVyLCBIVFRQU2VydmVyCmZyb20gdXJsbGliLnBhcnNlIGltcG9ydCB1cmxwYXJzZSwgcGFyc2VfcXMKCkxPR19ESVIgPSBvcy5lbnZpcm9uLmdldCgiTE9HX0RJUiIsICIvdmFyL2xvZy90bHNkZWJ1ZyIpClBPUlQgICAgPSA0MDQwCgpMT0dfRklMRVMgPSBbInRsc3Byb3h5LmxvZyIsICJjYXB0dXJlZF90b2tlbnMuanNvbiIsICJjaHJvbWUubG9nIiwKICAgICAgICAgICAgICJ4MTF2bmMubG9nIiwgIm5vdm5jLmxvZyIsICJ4dmZiLmxvZyIsICJvcGVuYm94LmxvZyJdCgpkZWYgYnVpbGRfcGFnZSgpOgogICAgZmlsZXNfanMgPSBqc29uLmR1bXBzKExPR19GSUxFUykKICAgIHJldHVybiBmIiIiPCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEiPgo8dGl0bGU+VExTRGVidWcgTG9nIFZpZXdlcjwvdGl0bGU+CjxzdHlsZT4KICA6cm9vdHt7LS1iZzojMGQxMTE3Oy0tcGFuZWw6IzE2MWIyMjstLWJvcmRlcjojMzAzNjNkOy0tYWNjZW50OiM1OGE2ZmY7CiAgICAgICAgLS1ncmVlbjojM2ZiOTUwOy0tdGV4dDojYzlkMWQ5Oy0tbXV0ZWQ6IzhiOTQ5ZX19CiAgKnt7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH19CiAgYm9keXt7YmFja2dyb3VuZDp2YXIoLS1iZyk7Y29sb3I6dmFyKC0tdGV4dCk7Zm9udC1mYW1pbHk6J1NGIE1vbm8nLE1vbmFjbyxtb25vc3BhY2U7CiAgICAgICBmb250LXNpemU6MTNweDtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDB2aH19CiAgaGVhZGVye3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpOwogICAgICAgICAgcGFkZGluZzoxMnB4IDIwcHg7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtnYXA6MTZweDtmbGV4LXNocmluazowfX0KICBoZWFkZXIgaDF7e2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0OjYwMDtjb2xvcjp2YXIoLS1hY2NlbnQpfX0KICAuYmFkZ2V7e2JhY2tncm91bmQ6dmFyKC0tZ3JlZW4pO2NvbG9yOiMwMDA7Zm9udC1zaXplOjEwcHg7Zm9udC13ZWlnaHQ6NzAwOwogICAgICAgICAgcGFkZGluZzoycHggOHB4O2JvcmRlci1yYWRpdXM6MTBweH19CiAgI3N0YXR1c2Jhcnt7Zm9udC1zaXplOjExcHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdpbi1sZWZ0OmF1dG99fQogIC50YWJze3tkaXNwbGF5OmZsZXg7Z2FwOjRweDtvdmVyZmxvdy14OmF1dG87YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7CiAgICAgICAgIHBhZGRpbmc6OHB4IDE2cHggMDtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjB9fQogIC50YWJ7e3BhZGRpbmc6NnB4IDE0cHg7Ym9yZGVyLXJhZGl1czo2cHggNnB4IDAgMDtjdXJzb3I6cG9pbnRlcjtjb2xvcjp2YXIoLS1tdXRlZCk7CiAgICAgICAgYm9yZGVyOjFweCBzb2xpZCB0cmFuc3BhcmVudDtib3JkZXItYm90dG9tOm5vbmU7Zm9udC1zaXplOjEycHg7d2hpdGUtc3BhY2U6bm93cmFwfX0KICAudGFiOmhvdmVye3tjb2xvcjp2YXIoLS10ZXh0KTtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA1KX19CiAgLnRhYi5hY3RpdmV7e2NvbG9yOnZhcigtLWFjY2VudCk7YmFja2dyb3VuZDp2YXIoLS1iZyk7Ym9yZGVyLWNvbG9yOnZhcigtLWJvcmRlcil9fQogIC50b29sYmFye3tkaXNwbGF5OmZsZXg7Z2FwOjhweDtwYWRkaW5nOjhweCAxNnB4O2JhY2tncm91bmQ6dmFyKC0tYmcpOwogICAgICAgICAgICBib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjA7YWxpZ24taXRlbXM6Y2VudGVyfX0KICAudG9vbGJhciBpbnB1dHt7ZmxleDoxO2JhY2tncm91bmQ6dmFyKC0tcGFuZWwpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tYm9yZGVyKTsKICAgICAgICAgICAgICAgICAgY29sb3I6dmFyKC0tdGV4dCk7cGFkZGluZzo1cHggMTBweDtib3JkZXItcmFkaXVzOjZweDtmb250LWZhbWlseTppbmhlcml0fX0KICAudG9vbGJhciBsYWJlbHt7Y29sb3I6dmFyKC0tbXV0ZWQpO2ZvbnQtc2l6ZToxMnB4O2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjVweH19CiAgLnRvb2xiYXIgYnV0dG9ue3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWJvcmRlcik7Y29sb3I6dmFyKC0tdGV4dCk7CiAgICAgICAgICAgICAgICAgICBwYWRkaW5nOjVweCAxMnB4O2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjpwb2ludGVyO2ZvbnQtZmFtaWx5OmluaGVyaXR9fQogIC50b29sYmFyIGJ1dHRvbjpob3Zlcnt7Ym9yZGVyLWNvbG9yOnZhcigtLWFjY2VudCk7Y29sb3I6dmFyKC0tYWNjZW50KX19CiAgI2xvZ3ZpZXd7e2ZsZXg6MTtvdmVyZmxvdy15OmF1dG87cGFkZGluZzoxMnB4IDE2cHg7d2hpdGUtc3BhY2U6cHJlLXdyYXA7CiAgICAgICAgICAgIHdvcmQtYnJlYWs6YnJlYWstYWxsO2xpbmUtaGVpZ2h0OjEuNn19CiAgLmxue3twYWRkaW5nOjFweCAwfX0gLmxuOmhvdmVye3tiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA0KX19CiAgLm1hdGNoe3tiYWNrZ3JvdW5kOnJnYmEoODgsMTY2LDI1NSwuMTIpfX0gLmhse3tiYWNrZ3JvdW5kOiNlM2IzNDE0NDtib3JkZXItcmFkaXVzOjJweH19Cjwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CjxoZWFkZXI+CiAgPGgxPiYjeDFGNTBEOyBUTFNEZWJ1ZyBMb2cgVmlld2VyPC9oMT4KICA8c3BhbiBjbGFzcz0iYmFkZ2UiPkxJVkU8L3NwYW4+CiAgPHNwYW4gaWQ9InN0YXR1c2JhciI+Q29ubmVjdGluZy4uLjwvc3Bhbj4KPC9oZWFkZXI+CjxkaXYgY2xhc3M9InRhYnMiIGlkPSJ0YWJzIj48L2Rpdj4KPGRpdiBjbGFzcz0idG9vbGJhciI+CiAgPGlucHV0IGlkPSJmaWx0ZXIiIHBsYWNlaG9sZGVyPSJGaWx0ZXIgbGluZXMuLi4iIG9uaW5wdXQ9ImFwcGx5RmlsdGVyKCkiPgogIDxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIGlkPSJhdXRvIiBjaGVja2VkPiBBdXRvLXNjcm9sbDwvbGFiZWw+CiAgPGJ1dHRvbiBvbmNsaWNrPSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWU9Jyc7YXBwbHlGaWx0ZXIoKSI+Q2xlYXI8L2J1dHRvbj4KICA8YnV0dG9uIG9uY2xpY2s9ImxvY2F0aW9uLmhyZWY9Jy9hcGkvZG93bmxvYWQ/ZmlsZT0nK2N1ciI+RG93bmxvYWQ8L2J1dHRvbj4KPC9kaXY+CjxkaXYgaWQ9ImxvZ3ZpZXciPjwvZGl2Pgo8c2NyaXB0Pgpjb25zdCBMT0dTPXtmaWxlc19qc307CmxldCBjdXI9TE9HU1swXSxvZmY9MCxyYXc9W107CmZ1bmN0aW9uIGluaXQoKXt7CiAgY29uc3QgdD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndGFicycpOwogIExPR1MuZm9yRWFjaChmPT57ewogICAgY29uc3QgZD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgIGQuY2xhc3NOYW1lPSd0YWInKyhmPT09Y3VyPycgYWN0aXZlJzonJyk7CiAgICBkLnRleHRDb250ZW50PWY7ZC5vbmNsaWNrPSgpPT5zdyhmKTt0LmFwcGVuZENoaWxkKGQpOwogIH19KTsKICBwb2xsKCk7c2V0SW50ZXJ2YWwocG9sbCwxNTAwKTsKfX0KZnVuY3Rpb24gc3coZil7e2N1cj1mO29mZj0wO3Jhdz1bXTsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudGFiJykuZm9yRWFjaCh0PT50LmNsYXNzTmFtZT0ndGFiJysodC50ZXh0Q29udGVudD09PWY/JyBhY3RpdmUnOicnKSk7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvZ3ZpZXcnKS5pbm5lckhUTUw9Jyc7cG9sbCgpO319CmFzeW5jIGZ1bmN0aW9uIHBvbGwoKXt7CiAgdHJ5e3sKICAgIGNvbnN0IHI9YXdhaXQgZmV0Y2goJy9hcGkvbG9nP2ZpbGU9JytlbmNvZGVVUklDb21wb25lbnQoY3VyKSsnJm9mZnNldD0nK29mZik7CiAgICBpZighci5vaylyZXR1cm47CiAgICBjb25zdCBkPWF3YWl0IHIuanNvbigpO29mZj1kLnNpemU7CiAgICBpZihkLmxpbmVzJiZkLmxpbmVzLmxlbmd0aCl7e3Jhdz1yYXcuY29uY2F0KGQubGluZXMpO2lmKHJhdy5sZW5ndGg+NTAwMClyYXc9cmF3LnNsaWNlKC01MDAwKTthcHBseUZpbHRlcigpO319CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3RhdHVzYmFyJykudGV4dENvbnRlbnQ9J1VwZGF0ZWQgJytuZXcgRGF0ZSgpLnRvTG9jYWxlVGltZVN0cmluZygpOwogIH19Y2F0Y2goZSl7e2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdGF0dXNiYXInKS50ZXh0Q29udGVudD0nRXJyb3I6ICcrZTt9fQp9fQpmdW5jdGlvbiBhcHBseUZpbHRlcigpe3sKICBjb25zdCBmdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWUudG9Mb3dlckNhc2UoKTsKICBjb25zdCBsdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9ndmlldycpOwogIGNvbnN0IHJvd3M9ZnY/cmF3LmZpbHRlcihsPT5sLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpKTpyYXc7CiAgbHYuaW5uZXJIVE1MPXJvd3MubWFwKGw9Pnt7CiAgICBjb25zdCBlPWwucmVwbGFjZSgvJi9nLCcmYW1wOycpLnJlcGxhY2UoLzwvZywnJmx0OycpLnJlcGxhY2UoLz4vZywnJmd0OycpOwogICAgY29uc3QgaD1mdj9lLnJlcGxhY2UobmV3IFJlZ0V4cChmdi5yZXBsYWNlKC9bLiorP14ke3t9fSgpfFtcXF1cXFxcXS9nLCdcXFxcJCYnKSwnZ2knKSxtPT4nPHNwYW4gY2xhc3M9ImhsIj4nK20rJzwvc3Bhbj4nKTplOwogICAgcmV0dXJuICc8ZGl2IGNsYXNzPSJsbicrKGZ2JiZsLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpPycgbWF0Y2gnOicnKSsnIj4nK2grJzwvZGl2Pic7CiAgfX0pLmpvaW4oJycpOwogIGlmKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhdXRvJykuY2hlY2tlZClsdi5zY3JvbGxUb3A9bHYuc2Nyb2xsSGVpZ2h0Owp9fQppbml0KCk7Cjwvc2NyaXB0Pgo8L2JvZHk+PC9odG1sPiIiIgoKUEFHRSA9IGJ1aWxkX3BhZ2UoKQoKY2xhc3MgSGFuZGxlcihCYXNlSFRUUFJlcXVlc3RIYW5kbGVyKToKICAgIGRlZiBsb2dfbWVzc2FnZShzZWxmLCBmbXQsICphcmdzKTogcGFzcwogICAgZGVmIGRvX0dFVChzZWxmKToKICAgICAgICBwID0gc2VsZi5wYXRoLnNwbGl0KCc/JylbMF0KICAgICAgICBpZiBwIGluICgnLycsICcvaW5kZXguaHRtbCcpOgogICAgICAgICAgICBib2R5ID0gUEFHRS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCd0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgnKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LUxlbmd0aCcsIHN0cihsZW4oYm9keSkpKQogICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoYm9keSkKICAgICAgICBlbGlmIHAgPT0gJy9hcGkvbG9nJzoKICAgICAgICAgICAgcSA9IHBhcnNlX3FzKHVybHBhcnNlKHNlbGYucGF0aCkucXVlcnkpCiAgICAgICAgICAgIGZuYW1lID0gb3MucGF0aC5iYXNlbmFtZShxLmdldCgnZmlsZScsWyd0bHNwcm94eS5sb2cnXSlbMF0pCiAgICAgICAgICAgIG9mZnNldCA9IGludChxLmdldCgnb2Zmc2V0JyxbJzAnXSlbMF0pCiAgICAgICAgICAgIGZwYXRoID0gb3MucGF0aC5qb2luKExPR19ESVIsIGZuYW1lKQogICAgICAgICAgICBsaW5lcyA9IFtdOyBzaXplID0gb2Zmc2V0CiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKGZwYXRoKToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihmcGF0aCwncmInKSBhcyBmOgogICAgICAgICAgICAgICAgICAgIGYuc2VlayhvZmZzZXQpOyBjaHVuayA9IGYucmVhZCgpCiAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IG9mZnNldCArIGxlbihjaHVuaykKICAgICAgICAgICAgICAgICAgICByYXdfdGV4dCA9IGNodW5rLmRlY29kZSgndXRmLTgnLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgICAgICAgICAgICAgICAgIGlmIGZuYW1lLmVuZHN3aXRoKCcuanNvbicpOgogICAgICAgICAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQganNvbiBhcyBfanNvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VkID0gX2pzb24ubG9hZHMob3BlbihmcGF0aCkucmVhZCgpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJldHR5ID0gX2pzb24uZHVtcHMocGFyc2VkLCBpbmRlbnQ9MikKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzICA9IHByZXR0eS5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgICA9IG9zLnBhdGguZ2V0c2l6ZShmcGF0aCkKICAgICAgICAgICAgICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzID0gcmF3X3RleHQuc3BsaXRsaW5lcygpCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgbGluZXMgPSByYXdfdGV4dC5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgYm9keSA9IGpzb24uZHVtcHMoeydsaW5lcyc6bGluZXMsJ3NpemUnOnNpemV9KS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCdhcHBsaWNhdGlvbi9qc29uJykKICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luJywnKicpCiAgICAgICAgICAgIHNlbGYuZW5kX2hlYWRlcnMoKTsgc2VsZi53ZmlsZS53cml0ZShib2R5KQogICAgICAgIGVsaWYgcCA9PSAnL2FwaS9kb3dubG9hZCc6CiAgICAgICAgICAgIHEgPSBwYXJzZV9xcyh1cmxwYXJzZShzZWxmLnBhdGgpLnF1ZXJ5KQogICAgICAgICAgICBmbmFtZSA9IG9zLnBhdGguYmFzZW5hbWUocS5nZXQoJ2ZpbGUnLFsndGxzcHJveHkubG9nJ10pWzBdKQogICAgICAgICAgICBmcGF0aCA9IG9zLnBhdGguam9pbihMT0dfRElSLCBmbmFtZSkKICAgICAgICAgICAgaWYgb3MucGF0aC5leGlzdHMoZnBhdGgpOgogICAgICAgICAgICAgICAgd2l0aCBvcGVuKGZwYXRoLCdyYicpIGFzIGY6IGRhdGEgPSBmLnJlYWQoKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX3Jlc3BvbnNlKDIwMCkKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoJ0NvbnRlbnQtVHlwZScsJ3RleHQvcGxhaW4nKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQ29udGVudC1EaXNwb3NpdGlvbicsJ2F0dGFjaG1lbnQ7IGZpbGVuYW1lPScrZm5hbWUpCiAgICAgICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoZGF0YSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9yZXNwb25zZSg0MDQpOyBzZWxmLmVuZF9oZWFkZXJzKCkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoNDA0KTsgc2VsZi5lbmRfaGVhZGVycygpCgpvcy5tYWtlZGlycyhMT0dfRElSLCBleGlzdF9vaz1UcnVlKQpwcmludChmIltsb2d2aWV3ZXJdIDAuMC4wLjA6e1BPUlR9ICBsb2dzPXtMT0dfRElSfSIpCkhUVFBTZXJ2ZXIoKCcwLjAuMC4wJywgUE9SVCksIEhhbmRsZXIpLnNlcnZlX2ZvcmV2ZXIoKQo=' | base64 -d > /opt/logviewer.py - -# --------------------------------------------------------------------------- -# 6. install-ca.sh -# Installs TLSDebug CA into: -# (a) Debian/OS system trust store -# (b) ~/.pki/nssdb — where Chromium/Chrome actually looks on Linux -# (c) Chrome profile NSS db — belt-and-suspenders -# --------------------------------------------------------------------------- -RUN cat > /opt/install-ca.sh << 'SCRIPT' -#!/bin/bash -set -e -CA_CERT="${1:-/opt/tlsdebug/proxy-ca.crt}" -CA_NAME="TLSDebug CA" -HOME_NSS="/root/.pki/nssdb" -PROFILE_NSS="/opt/chrome-profile/nssdb" - -log() { echo "[CA] $*"; } - -if [ ! -f "${CA_CERT}" ]; then - log "ERROR: cert not found at ${CA_CERT}"; exit 1 -fi - -log "=== Installing TLSDebug CA ===" -log "Subject : $(openssl x509 -noout -subject -in "${CA_CERT}" 2>/dev/null)" -log "Expires : $(openssl x509 -noout -enddate -in "${CA_CERT}" 2>/dev/null)" - -# ------------------------------------------------------------------ -# (a) OS / system trust store -# ------------------------------------------------------------------ -log "--- System trust store ---" -cp "${CA_CERT}" /usr/local/share/ca-certificates/tlsdebug.crt -update-ca-certificates --fresh -log "System store updated." - -# ------------------------------------------------------------------ -# (b) ~/.pki/nssdb (THIS is where Chromium on Linux actually looks) -# ------------------------------------------------------------------ -log "--- ~/.pki/nssdb (Chromium default location) ---" -mkdir -p "${HOME_NSS}" -if [ ! -f "${HOME_NSS}/cert9.db" ]; then - certutil -N -d "sql:${HOME_NSS}" --empty-password - log "Created new NSS db at ${HOME_NSS}" -fi -certutil -d "sql:${HOME_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true -certutil -d "sql:${HOME_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}" -log "Installed into ${HOME_NSS}" - -# Verify -if certutil -d "sql:${HOME_NSS}" -L -n "${CA_NAME}" 2>/dev/null | grep -q "CT"; then - log "OK: ${HOME_NSS} verification passed." -else - log "WARNING: cert not found in ${HOME_NSS} after install" -fi - -# ------------------------------------------------------------------ -# (c) Chrome profile NSS db (belt-and-suspenders) -# ------------------------------------------------------------------ -log "--- Chrome profile NSS db ---" -mkdir -p "${PROFILE_NSS}" -if [ ! -f "${PROFILE_NSS}/cert9.db" ]; then - certutil -N -d "sql:${PROFILE_NSS}" --empty-password - log "Created new NSS db at ${PROFILE_NSS}" -fi -certutil -d "sql:${PROFILE_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true -certutil -d "sql:${PROFILE_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}" -log "Installed into ${PROFILE_NSS}" - -log "=== CA installation complete ===" -SCRIPT -RUN chmod +x /opt/install-ca.sh - -# 7. start-chrome.sh -# --------------------------------------------------------------------------- -RUN cat > /opt/start-chrome.sh << 'SCRIPT' -#!/bin/bash -export DISPLAY="${DISPLAY:-:99}" -export HOME=/root - -PROXY_HOST="${PROXY_HOST:-127.0.0.1}" -PROXY_PORT="${PROXY_PORT:-8080}" -REMOTE_DEBUG_PORT="${CHROME_REMOTE_DEBUGGING_PORT:-9222}" -START_URL="${START_URL:-https://www.google.com}" - -echo "[Browser] $(browser --version 2>/dev/null || echo unknown)" -echo "[Browser] DISPLAY=${DISPLAY} proxy=${PROXY_HOST}:${PROXY_PORT}" - -exec browser \ - --no-sandbox \ - --disable-dev-shm-usage \ - --disable-gpu \ - --disable-software-rasterizer \ - --use-gl=swiftshader \ - --use-angle=swiftshader \ - --enable-unsafe-swiftshader \ - --in-process-gpu \ - --disable-setuid-sandbox \ - --user-data-dir="/opt/chrome-profile" \ - --proxy-server="http://${PROXY_HOST}:${PROXY_PORT}" \ - --ignore-certificate-errors \ - --ignore-certificate-errors-spki-list \ - --test-type \ - --remote-debugging-port="${REMOTE_DEBUG_PORT}" \ - --remote-debugging-address=0.0.0.0 \ - --window-size=1920,1080 \ - --start-fullscreen \ - --kiosk \ - --disable-extensions \ - --no-first-run \ - --no-default-browser-check \ - --password-store=basic \ - --use-mock-keychain \ - --enable-logging=stderr \ - --v=1 \ - "${START_URL}" 2>&1 -SCRIPT -RUN chmod +x /opt/start-chrome.sh - - -# --------------------------------------------------------------------------- -# 8. entrypoint.sh -# --------------------------------------------------------------------------- -RUN cat > /entrypoint.sh << 'SCRIPT' -#!/bin/bash -set -e - -export HOME=/root -PROXY_PORT="${PROXY_PORT:-8080}" -VNC_PORT="${VNC_PORT:-5900}" -NOVNC_PORT="${NOVNC_PORT:-6080}" -DISPLAY="${DISPLAY:-:99}" -# LOG_DIR is bind-mounted to ./logs/ on the host. -# CERTDIR is set to the same path so tlsproxy writes captured_tokens.json, -# session files, and the CA cert directly into the host-visible logs folder. -LOG_DIR="/var/log/tlsdebug" -CERTDIR="${LOG_DIR}" -NOVNC_WEB=$(cat /opt/novnc_path 2>/dev/null || echo "/usr/share/novnc") - -mkdir -p "${LOG_DIR}" - -log() { echo "[$(date '+%H:%M:%S')] $*"; } -wait_port() { - local port=$1 label=$2 tries=${3:-30} - for i in $(seq 1 "${tries}"); do - if nc -z 127.0.0.1 "${port}" 2>/dev/null; then - log "[+] ${label} ready on port ${port}." - return 0 - fi - sleep 0.5 - done - log "[!] TIMEOUT waiting for ${label} on port ${port}." - return 1 -} - -log "============================================" -log " TLSDebug + Headless Chrome Container" -log " noVNC Web UI : http://localhost:${NOVNC_PORT}" -log " TLS Proxy : localhost:${PROXY_PORT}" -log " noVNC root : ${NOVNC_WEB}" -log "============================================" - -# ---- Diagnostics ----------------------------------------------------------- -log "[diag] novnc web root contents:" -ls -la "${NOVNC_WEB}" 2>&1 | head -20 || log " (none)" -log "[diag] websockify location: $(which websockify 2>/dev/null || echo NOT FOUND)" -log "[diag] x11vnc location: $(which x11vnc 2>/dev/null || echo NOT FOUND)" -log "[diag] browser symlink: $(which browser 2>/dev/null || echo NOT FOUND) -> $(readlink /usr/local/bin/browser 2>/dev/null || echo none)" -log "[diag] tlsproxy location: $(which tlsproxy 2>/dev/null || echo NOT FOUND)" - -# ---- 1. Xvfb --------------------------------------------------------------- -log "[*] Starting Xvfb on ${DISPLAY} ..." -rm -f /tmp/.X99-lock /tmp/.X11-unix/X99 2>/dev/null || true -Xvfb "${DISPLAY}" \ - -screen 0 1920x1080x24 \ - -ac \ - +extension GLX \ - +extension RANDR \ - +render \ - -noreset \ - >> "${LOG_DIR}/xvfb.log" 2>&1 & -XVFB_PID=$! - -log "[*] Waiting for Xvfb ..." -for i in $(seq 1 30); do - if DISPLAY="${DISPLAY}" xdpyinfo >/dev/null 2>&1; then - log "[+] Xvfb ready." - break - fi - sleep 0.5 -done -export DISPLAY="${DISPLAY}" - -# ---- 2. Openbox window manager --------------------------------------------- -log "[*] Starting openbox ..." -openbox --config-file /root/.config/openbox/rc.xml --display "${DISPLAY}" >> "${LOG_DIR}/openbox.log" 2>&1 & -sleep 1 - -# ---- 3. TLSDebug proxy ----------------------------------------------------- -log "[*] Starting TLSDebug proxy on port ${PROXY_PORT} ..." -cd "${CERTDIR}" -tlsproxy \ - -port "${PROXY_PORT}" \ - -certdir "${CERTDIR}" \ - -skip-install \ - 2>&1 | tee -a "${LOG_DIR}/tlsproxy.log" & -PROXY_PID=$! - -log "[*] Waiting for CA certificate ..." -for i in $(seq 1 30); do - if [ -f "${CERTDIR}/proxy-ca.crt" ]; then - log "[+] CA certificate ready." - break - fi - sleep 1 -done -# Install CA before Chrome launches — must succeed -if [ -f "${CERTDIR}/proxy-ca.crt" ]; then - if /opt/install-ca.sh "${CERTDIR}/proxy-ca.crt"; then - log "[+] CA trusted by OS and Chrome NSS." - else - log "[!] CA install failed — Chrome may show cert errors." - fi -else - log "[!] proxy-ca.crt missing — Chrome may show cert errors." -fi - -# ---- 4. x11vnc ------------------------------------------------------------- -log "[*] Starting x11vnc on port ${VNC_PORT} ..." -x11vnc \ - -display "${DISPLAY}" \ - -forever \ - -shared \ - -nopw \ - -rfbport "${VNC_PORT}" \ - -o "${LOG_DIR}/x11vnc.log" \ - -bg \ - -noipv6 \ - -xkb \ - -noxrecord \ - -noxfixes \ - -noxdamage \ - -cursor arrow \ - -wait 5 \ - -defer 5 - -wait_port "${VNC_PORT}" "x11vnc" 40 - -# ---- 5. noVNC -------------------------------------------------------------- -log "[*] Starting noVNC websockify on port ${NOVNC_PORT} ..." -log "[*] web root : ${NOVNC_WEB}" -log "[*] target : 127.0.0.1:${VNC_PORT}" - -websockify \ - --web "${NOVNC_WEB}" \ - --heartbeat 30 \ - --log-file "${LOG_DIR}/novnc.log" \ - "${NOVNC_PORT}" \ - "127.0.0.1:${VNC_PORT}" & -NOVNC_PID=$! - -wait_port "${NOVNC_PORT}" "noVNC" 40 - -# Confirm websockify process -log "[diag] websockify process: $(pgrep -a websockify 2>/dev/null || echo NOT RUNNING)" -log "[diag] listening ports: $(ss -tlnp 2>/dev/null | grep -E '5900|6080|8080' || echo none)" - -# ---- 6. Chrome ------------------------------------------------------------- -log "[*] Starting Chrome ..." -/opt/start-chrome.sh >> "${LOG_DIR}/chrome.log" 2>&1 & -CHROME_PID=$! - -# ---- 7. Log viewer on port 4040 -------------------------------------------- -log "[*] Starting log viewer on port 4040 ..." -python3 /opt/logviewer.py >> "${LOG_DIR}/logviewer.log" 2>&1 & -LOGVIEWER_PID=$! -wait_port 4040 "log viewer" 20 - -log "" -log "[+] All services started." -log "[+] Browser (noVNC) : http://localhost:${NOVNC_PORT}" -log "[+] Log viewer : http://localhost:4040" -log "[+] TLS proxy : localhost:${PROXY_PORT}" -log "[+] Host logs dir : ${LOG_DIR} (mounted to ./logs/ on host)" -log "" - -cleanup() { - log "[*] Shutting down ..." - kill "${CHROME_PID}" "${NOVNC_PID}" "${PROXY_PID}" "${XVFB_PID}" "${LOGVIEWER_PID}" 2>/dev/null || true - exit 0 -} -trap cleanup SIGTERM SIGINT - -# Stream proxy traffic to docker logs so 'docker logs -f tlsdebug' shows live traffic -tail -f "${LOG_DIR}/tlsproxy.log" -SCRIPT -RUN chmod +x /entrypoint.sh - -# --------------------------------------------------------------------------- -# 9. Ports -# --------------------------------------------------------------------------- -EXPOSE 6080 5900 8080 4040 9222 - -WORKDIR /opt/tlsdebug -ENTRYPOINT ["/entrypoint.sh"] +services: + tlsdebug: + build: + context: . + dockerfile: Dockerfile + image: tlsdebug:latest + container_name: tlsdebug + ports: + # noVNC web UI — HTTP on port 6080, HTTPS on port 6443 + - "6080:6080" # noVNC HTTP (default) + - "6443:6443" # noVNC HTTPS (when NOVNC_ENABLE_HTTPS=true) + # - "80:6080" # Optional: expose HTTP on port 80 + - "443:6443" # Optional: expose HTTPS on port 443 + # Raw VNC (optional — use any VNC client against localhost:5900) + - "5900:5900" + # TLSDebug intercepting proxy (change host port if 8080 is taken) + - "8888:8080" + # TLSDebug built-in HTTP log viewer + - "4040:4040" + # Chrome remote debugging (optional) + - "9222:9222" + volumes: + # All logs, CA cert, captured_tokens.json and session files + # are written here — readable on the host at ./logs/ + - ./logs:/var/log/tlsdebug + # Persist Chrome profile (bookmarks, saved state, etc.) + - chrome-profile:/opt/chrome-profile + # Optional: Mount custom noVNC certificate (PEM format) + # - ./certs/novnc.pem:/certs/novnc.pem:ro + environment: + PROXY_PORT: "8080" + VNC_PORT: "5900" + NOVNC_PORT: "6080" + NOVNC_HTTPS_PORT: "6443" + DISPLAY: ":99" + CHROME_REMOTE_DEBUGGING_PORT: "9222" + # Change START_URL to set the browser's home page + START_URL: "https://google.com" + # Enable HTTPS for noVNC (set to "true" to enable) + NOVNC_ENABLE_HTTPS: "false" + # When HTTPS is enabled: + # - Self-signed cert is auto-generated if no custom cert provided + # - Mount custom cert at /certs/novnc.pem to use your own + cap_add: + - SYS_ADMIN + security_opt: + - seccomp:unconfined + restart: unless-stopped + shm_size: "2gb" + healthcheck: + test: ["CMD", "sh", "-c", "[ \"${NOVNC_ENABLE_HTTPS}\" = \"true\" ] && curl -k -sf https://localhost:6443 || curl -sf http://localhost:6080"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + chrome-profile: From 86d35dd755c3024a2c11bc3c4edd7f07b9ac03dd Mon Sep 17 00:00:00 2001 From: Casey <151179166+secdev02@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:56:28 -0600 Subject: [PATCH 9/9] fix - TLS No TLS - fix websockify --- labs/remotebrowser/Dockerfile | 691 +++++++++++++++++++++++++++++++--- 1 file changed, 633 insertions(+), 58 deletions(-) diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile index a5afa4b..158cbce 100644 --- a/labs/remotebrowser/Dockerfile +++ b/labs/remotebrowser/Dockerfile @@ -1,58 +1,633 @@ -services: - tlsdebug: - build: - context: . - dockerfile: Dockerfile - image: tlsdebug:latest - container_name: tlsdebug - ports: - # noVNC web UI — HTTP on port 6080, HTTPS on port 6443 - - "6080:6080" # noVNC HTTP (default) - - "6443:6443" # noVNC HTTPS (when NOVNC_ENABLE_HTTPS=true) - # - "80:6080" # Optional: expose HTTP on port 80 - - "443:6443" # Optional: expose HTTPS on port 443 - # Raw VNC (optional — use any VNC client against localhost:5900) - - "5900:5900" - # TLSDebug intercepting proxy (change host port if 8080 is taken) - - "8888:8080" - # TLSDebug built-in HTTP log viewer - - "4040:4040" - # Chrome remote debugging (optional) - - "9222:9222" - volumes: - # All logs, CA cert, captured_tokens.json and session files - # are written here — readable on the host at ./logs/ - - ./logs:/var/log/tlsdebug - # Persist Chrome profile (bookmarks, saved state, etc.) - - chrome-profile:/opt/chrome-profile - # Optional: Mount custom noVNC certificate (PEM format) - # - ./certs/novnc.pem:/certs/novnc.pem:ro - environment: - PROXY_PORT: "8080" - VNC_PORT: "5900" - NOVNC_PORT: "6080" - NOVNC_HTTPS_PORT: "6443" - DISPLAY: ":99" - CHROME_REMOTE_DEBUGGING_PORT: "9222" - # Change START_URL to set the browser's home page - START_URL: "https://google.com" - # Enable HTTPS for noVNC (set to "true" to enable) - NOVNC_ENABLE_HTTPS: "false" - # When HTTPS is enabled: - # - Self-signed cert is auto-generated if no custom cert provided - # - Mount custom cert at /certs/novnc.pem to use your own - cap_add: - - SYS_ADMIN - security_opt: - - seccomp:unconfined - restart: unless-stopped - shm_size: "2gb" - healthcheck: - test: ["CMD", "sh", "-c", "[ \"${NOVNC_ENABLE_HTTPS}\" = \"true\" ] && curl -k -sf https://localhost:6443 || curl -sf http://localhost:6080"] - interval: 15s - timeout: 5s - retries: 5 - start_period: 30s - -volumes: - chrome-profile: +# ============================================================================= +# TLSDebug + Headless Chrome + noVNC — single self-contained Dockerfile +# ============================================================================= +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive \ + DISPLAY=:99 \ + VNC_PORT=5900 \ + NOVNC_PORT=6080 \ + NOVNC_HTTPS_PORT=6443 \ + PROXY_PORT=8080 \ + CHROME_REMOTE_DEBUGGING_PORT=9222 \ + PROXY_HOST=127.0.0.1 \ + NOVNC_ENABLE_HTTPS=false + +# --------------------------------------------------------------------------- +# 1. Base packages +# --------------------------------------------------------------------------- +RUN apt-get update && apt-get install -y --no-install-recommends \ + golang-go \ + git \ + xvfb \ + x11vnc \ + openbox \ + xterm \ + python3 \ + python3-pip \ + python3-websockify \ + novnc \ + wget \ + gnupg \ + ca-certificates \ + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libcups2 \ + libdbus-1-3 \ + libgdk-pixbuf2.0-0 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libnss3-tools \ + libx11-xcb1 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxkbcommon0 \ + libxrandr2 \ + libxshmfence1 \ + xdg-utils \ + openssl \ + curl \ + netcat-openbsd \ + procps \ + psmisc \ + x11-utils \ + unclutter \ + && rm -rf /var/lib/apt/lists/* + +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# 2. Install browser +# amd64 -> Google Chrome stable (official Google .deb) +# arm64 -> Chromium from Debian bookworm (native deb, no snap) +# +# Base image is debian:bookworm-slim which has real chromium packages +# on all architectures — no snap wrappers. +# --------------------------------------------------------------------------- +RUN ARCH=$(dpkg --print-architecture) && \ + if [ "${ARCH}" = "amd64" ]; then \ + echo "[browser] amd64: installing Google Chrome ..." && \ + wget -q -O /tmp/chrome.deb \ + "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \ + apt-get update && \ + apt-get install -y --no-install-recommends /tmp/chrome.deb && \ + rm /tmp/chrome.deb && \ + rm -rf /var/lib/apt/lists/* && \ + ln -sf /usr/bin/google-chrome /usr/local/bin/browser && \ + echo "[browser] installed: $(google-chrome --version)"; \ + elif [ "${ARCH}" = "arm64" ]; then \ + echo "[browser] arm64: installing Chromium from Debian bookworm ..." && \ + apt-get update && \ + apt-get install -y --no-install-recommends chromium && \ + rm -rf /var/lib/apt/lists/* && \ + ln -sf /usr/bin/chromium /usr/local/bin/browser && \ + echo "[browser] installed: $(chromium --version)"; \ + else \ + echo "[browser] ERROR: unsupported arch ${ARCH}" && exit 1; \ + fi + +# --------------------------------------------------------------------------- +# 3. Build TLSDebug from source +# AllTrafficModule is already active by default and logs every request, +# response, headers and body — no source patching needed. +# We set log flags via the LOG_FLAGS env var at runtime instead. +# --------------------------------------------------------------------------- +WORKDIR /build +RUN git clone --depth 1 https://github.com/secdev02/TLSDebug.git . \ + && CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/local/bin/tlsproxy tlsproxy.go \ + && chmod +x /usr/local/bin/tlsproxy \ + && rm -rf /build + +# 4. Locate noVNC web root and write kiosk index.html +# Kiosk mode: auto-connects, scales to fill browser window, hides toolbar, +# no password prompt, no connection dialog — just the desktop. +# --------------------------------------------------------------------------- +RUN find /usr -name "vnc.html" 2>/dev/null | head -1 | xargs -I{} dirname {} > /opt/novnc_path \ + && NOVNC_WEB=$(cat /opt/novnc_path) \ + && echo "noVNC web root: ${NOVNC_WEB}" \ + && ls -la "${NOVNC_WEB}" + +RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML' + + + + + TLSDebug + + + +
+
Connecting...
+ + + +HTML + +# Also overwrite vnc.html so any direct /vnc.html request shows our kiosk too +RUN NOVNC_WEB=$(cat /opt/novnc_path) && cp "${NOVNC_WEB}/index.html" "${NOVNC_WEB}/vnc.html" + +# --------------------------------------------------------------------------- +# 5. Working directories +# --------------------------------------------------------------------------- +RUN mkdir -p /opt/tlsdebug /opt/chrome-profile /var/log/tlsdebug /root/.config + +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# 4b. Openbox config — fullscreen enforcement + black desktop, no decorations +# --------------------------------------------------------------------------- +RUN mkdir -p /root/.config/openbox + +# rc.xml — force every window fullscreen, no decorations, black desktop +RUN cat > /root/.config/openbox/rc.xml << 'RCXML' + + + + Clearlooks + + no + no + + 1 + + + yes + yes + no + yes + + + +RCXML + +# autostart — black root window, hide idle cursor +RUN cat > /root/.config/openbox/autostart << 'OBSTART' +xsetroot -solid black +unclutter -idle 0 -root -noevents & +OBSTART + +# --------------------------------------------------------------------------- +# 5b. Log viewer — real-time web UI on port 4040 +# --------------------------------------------------------------------------- +RUN echo 'aW1wb3J0IGh0dHAuc2VydmVyLCBvcywganNvbgpmcm9tIGh0dHAuc2VydmVyIGltcG9ydCBCYXNlSFRUUFJlcXVlc3RIYW5kbGVyLCBIVFRQU2VydmVyCmZyb20gdXJsbGliLnBhcnNlIGltcG9ydCB1cmxwYXJzZSwgcGFyc2VfcXMKCkxPR19ESVIgPSBvcy5lbnZpcm9uLmdldCgiTE9HX0RJUiIsICIvdmFyL2xvZy90bHNkZWJ1ZyIpClBPUlQgICAgPSA0MDQwCgpMT0dfRklMRVMgPSBbInRsc3Byb3h5LmxvZyIsICJjYXB0dXJlZF90b2tlbnMuanNvbiIsICJjaHJvbWUubG9nIiwKICAgICAgICAgICAgICJ4MTF2bmMubG9nIiwgIm5vdm5jLmxvZyIsICJ4dmZiLmxvZyIsICJvcGVuYm94LmxvZyJdCgpkZWYgYnVpbGRfcGFnZSgpOgogICAgZmlsZXNfanMgPSBqc29uLmR1bXBzKExPR19GSUxFUykKICAgIHJldHVybiBmIiIiPCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEiPgo8dGl0bGU+VExTRGVidWcgTG9nIFZpZXdlcjwvdGl0bGU+CjxzdHlsZT4KICA6cm9vdHt7LS1iZzojMGQxMTE3Oy0tcGFuZWw6IzE2MWIyMjstLWJvcmRlcjojMzAzNjNkOy0tYWNjZW50OiM1OGE2ZmY7CiAgICAgICAgLS1ncmVlbjojM2ZiOTUwOy0tdGV4dDojYzlkMWQ5Oy0tbXV0ZWQ6IzhiOTQ5ZX19CiAgKnt7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH19CiAgYm9keXt7YmFja2dyb3VuZDp2YXIoLS1iZyk7Y29sb3I6dmFyKC0tdGV4dCk7Zm9udC1mYW1pbHk6J1NGIE1vbm8nLE1vbmFjbyxtb25vc3BhY2U7CiAgICAgICBmb250LXNpemU6MTNweDtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDB2aH19CiAgaGVhZGVye3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpOwogICAgICAgICAgcGFkZGluZzoxMnB4IDIwcHg7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtnYXA6MTZweDtmbGV4LXNocmluazowfX0KICBoZWFkZXIgaDF7e2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0OjYwMDtjb2xvcjp2YXIoLS1hY2NlbnQpfX0KICAuYmFkZ2V7e2JhY2tncm91bmQ6dmFyKC0tZ3JlZW4pO2NvbG9yOiMwMDA7Zm9udC1zaXplOjEwcHg7Zm9udC13ZWlnaHQ6NzAwOwogICAgICAgICAgcGFkZGluZzoycHggOHB4O2JvcmRlci1yYWRpdXM6MTBweH19CiAgI3N0YXR1c2Jhcnt7Zm9udC1zaXplOjExcHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdpbi1sZWZ0OmF1dG99fQogIC50YWJze3tkaXNwbGF5OmZsZXg7Z2FwOjRweDtvdmVyZmxvdy14OmF1dG87YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7CiAgICAgICAgIHBhZGRpbmc6OHB4IDE2cHggMDtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjB9fQogIC50YWJ7e3BhZGRpbmc6NnB4IDE0cHg7Ym9yZGVyLXJhZGl1czo2cHggNnB4IDAgMDtjdXJzb3I6cG9pbnRlcjtjb2xvcjp2YXIoLS1tdXRlZCk7CiAgICAgICAgYm9yZGVyOjFweCBzb2xpZCB0cmFuc3BhcmVudDtib3JkZXItYm90dG9tOm5vbmU7Zm9udC1zaXplOjEycHg7d2hpdGUtc3BhY2U6bm93cmFwfX0KICAudGFiOmhvdmVye3tjb2xvcjp2YXIoLS10ZXh0KTtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA1KX19CiAgLnRhYi5hY3RpdmV7e2NvbG9yOnZhcigtLWFjY2VudCk7YmFja2dyb3VuZDp2YXIoLS1iZyk7Ym9yZGVyLWNvbG9yOnZhcigtLWJvcmRlcil9fQogIC50b29sYmFye3tkaXNwbGF5OmZsZXg7Z2FwOjhweDtwYWRkaW5nOjhweCAxNnB4O2JhY2tncm91bmQ6dmFyKC0tYmcpOwogICAgICAgICAgICBib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjA7YWxpZ24taXRlbXM6Y2VudGVyfX0KICAudG9vbGJhciBpbnB1dHt7ZmxleDoxO2JhY2tncm91bmQ6dmFyKC0tcGFuZWwpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tYm9yZGVyKTsKICAgICAgICAgICAgICAgICAgY29sb3I6dmFyKC0tdGV4dCk7cGFkZGluZzo1cHggMTBweDtib3JkZXItcmFkaXVzOjZweDtmb250LWZhbWlseTppbmhlcml0fX0KICAudG9vbGJhciBsYWJlbHt7Y29sb3I6dmFyKC0tbXV0ZWQpO2ZvbnQtc2l6ZToxMnB4O2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjVweH19CiAgLnRvb2xiYXIgYnV0dG9ue3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWJvcmRlcik7Y29sb3I6dmFyKC0tdGV4dCk7CiAgICAgICAgICAgICAgICAgICBwYWRkaW5nOjVweCAxMnB4O2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjpwb2ludGVyO2ZvbnQtZmFtaWx5OmluaGVyaXR9fQogIC50b29sYmFyIGJ1dHRvbjpob3Zlcnt7Ym9yZGVyLWNvbG9yOnZhcigtLWFjY2VudCk7Y29sb3I6dmFyKC0tYWNjZW50KX19CiAgI2xvZ3ZpZXd7e2ZsZXg6MTtvdmVyZmxvdy15OmF1dG87cGFkZGluZzoxMnB4IDE2cHg7d2hpdGUtc3BhY2U6cHJlLXdyYXA7CiAgICAgICAgICAgIHdvcmQtYnJlYWs6YnJlYWstYWxsO2xpbmUtaGVpZ2h0OjEuNn19CiAgLmxue3twYWRkaW5nOjFweCAwfX0gLmxuOmhvdmVye3tiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA0KX19CiAgLm1hdGNoe3tiYWNrZ3JvdW5kOnJnYmEoODgsMTY2LDI1NSwuMTIpfX0gLmhse3tiYWNrZ3JvdW5kOiNlM2IzNDE0NDtib3JkZXItcmFkaXVzOjJweH19Cjwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CjxoZWFkZXI+CiAgPGgxPiYjeDFGNTBEOyBUTFNEZWJ1ZyBMb2cgVmlld2VyPC9oMT4KICA8c3BhbiBjbGFzcz0iYmFkZ2UiPkxJVkU8L3NwYW4+CiAgPHNwYW4gaWQ9InN0YXR1c2JhciI+Q29ubmVjdGluZy4uLjwvc3Bhbj4KPC9oZWFkZXI+CjxkaXYgY2xhc3M9InRhYnMiIGlkPSJ0YWJzIj48L2Rpdj4KPGRpdiBjbGFzcz0idG9vbGJhciI+CiAgPGlucHV0IGlkPSJmaWx0ZXIiIHBsYWNlaG9sZGVyPSJGaWx0ZXIgbGluZXMuLi4iIG9uaW5wdXQ9ImFwcGx5RmlsdGVyKCkiPgogIDxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIGlkPSJhdXRvIiBjaGVja2VkPiBBdXRvLXNjcm9sbDwvbGFiZWw+CiAgPGJ1dHRvbiBvbmNsaWNrPSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWU9Jyc7YXBwbHlGaWx0ZXIoKSI+Q2xlYXI8L2J1dHRvbj4KICA8YnV0dG9uIG9uY2xpY2s9ImxvY2F0aW9uLmhyZWY9Jy9hcGkvZG93bmxvYWQ/ZmlsZT0nK2N1ciI+RG93bmxvYWQ8L2J1dHRvbj4KPC9kaXY+CjxkaXYgaWQ9ImxvZ3ZpZXciPjwvZGl2Pgo8c2NyaXB0Pgpjb25zdCBMT0dTPXtmaWxlc19qc307CmxldCBjdXI9TE9HU1swXSxvZmY9MCxyYXc9W107CmZ1bmN0aW9uIGluaXQoKXt7CiAgY29uc3QgdD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndGFicycpOwogIExPR1MuZm9yRWFjaChmPT57ewogICAgY29uc3QgZD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgIGQuY2xhc3NOYW1lPSd0YWInKyhmPT09Y3VyPycgYWN0aXZlJzonJyk7CiAgICBkLnRleHRDb250ZW50PWY7ZC5vbmNsaWNrPSgpPT5zdyhmKTt0LmFwcGVuZENoaWxkKGQpOwogIH19KTsKICBwb2xsKCk7c2V0SW50ZXJ2YWwocG9sbCwxNTAwKTsKfX0KZnVuY3Rpb24gc3coZil7e2N1cj1mO29mZj0wO3Jhdz1bXTsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudGFiJykuZm9yRWFjaCh0PT50LmNsYXNzTmFtZT0ndGFiJysodC50ZXh0Q29udGVudD09PWY/JyBhY3RpdmUnOicnKSk7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvZ3ZpZXcnKS5pbm5lckhUTUw9Jyc7cG9sbCgpO319CmFzeW5jIGZ1bmN0aW9uIHBvbGwoKXt7CiAgdHJ5e3sKICAgIGNvbnN0IHI9YXdhaXQgZmV0Y2goJy9hcGkvbG9nP2ZpbGU9JytlbmNvZGVVUklDb21wb25lbnQoY3VyKSsnJm9mZnNldD0nK29mZik7CiAgICBpZighci5vaylyZXR1cm47CiAgICBjb25zdCBkPWF3YWl0IHIuanNvbigpO29mZj1kLnNpemU7CiAgICBpZihkLmxpbmVzJiZkLmxpbmVzLmxlbmd0aCl7e3Jhdz1yYXcuY29uY2F0KGQubGluZXMpO2lmKHJhdy5sZW5ndGg+NTAwMClyYXc9cmF3LnNsaWNlKC01MDAwKTthcHBseUZpbHRlcigpO319CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3RhdHVzYmFyJykudGV4dENvbnRlbnQ9J1VwZGF0ZWQgJytuZXcgRGF0ZSgpLnRvTG9jYWxlVGltZVN0cmluZygpOwogIH19Y2F0Y2goZSl7e2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdGF0dXNiYXInKS50ZXh0Q29udGVudD0nRXJyb3I6ICcrZTt9fQp9fQpmdW5jdGlvbiBhcHBseUZpbHRlcigpe3sKICBjb25zdCBmdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWUudG9Mb3dlckNhc2UoKTsKICBjb25zdCBsdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9ndmlldycpOwogIGNvbnN0IHJvd3M9ZnY/cmF3LmZpbHRlcihsPT5sLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpKTpyYXc7CiAgbHYuaW5uZXJIVE1MPXJvd3MubWFwKGw9Pnt7CiAgICBjb25zdCBlPWwucmVwbGFjZSgvJi9nLCcmYW1wOycpLnJlcGxhY2UoLzwvZywnJmx0OycpLnJlcGxhY2UoLz4vZywnJmd0OycpOwogICAgY29uc3QgaD1mdj9lLnJlcGxhY2UobmV3IFJlZ0V4cChmdi5yZXBsYWNlKC9bLiorP14ke3t9fSgpfFtcXF1cXFxcXS9nLCdcXFxcJCYnKSwnZ2knKSxtPT4nPHNwYW4gY2xhc3M9ImhsIj4nK20rJzwvc3Bhbj4nKTplOwogICAgcmV0dXJuICc8ZGl2IGNsYXNzPSJsbicrKGZ2JiZsLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpPycgbWF0Y2gnOicnKSsnIj4nK2grJzwvZGl2Pic7CiAgfX0pLmpvaW4oJycpOwogIGlmKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhdXRvJykuY2hlY2tlZClsdi5zY3JvbGxUb3A9bHYuc2Nyb2xsSGVpZ2h0Owp9fQppbml0KCk7Cjwvc2NyaXB0Pgo8L2JvZHk+PC9odG1sPiIiIgoKUEFHRSA9IGJ1aWxkX3BhZ2UoKQoKY2xhc3MgSGFuZGxlcihCYXNlSFRUUFJlcXVlc3RIYW5kbGVyKToKICAgIGRlZiBsb2dfbWVzc2FnZShzZWxmLCBmbXQsICphcmdzKTogcGFzcwogICAgZGVmIGRvX0dFVChzZWxmKToKICAgICAgICBwID0gc2VsZi5wYXRoLnNwbGl0KCc/JylbMF0KICAgICAgICBpZiBwIGluICgnLycsICcvaW5kZXguaHRtbCcpOgogICAgICAgICAgICBib2R5ID0gUEFHRS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCd0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgnKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LUxlbmd0aCcsIHN0cihsZW4oYm9keSkpKQogICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoYm9keSkKICAgICAgICBlbGlmIHAgPT0gJy9hcGkvbG9nJzoKICAgICAgICAgICAgcSA9IHBhcnNlX3FzKHVybHBhcnNlKHNlbGYucGF0aCkucXVlcnkpCiAgICAgICAgICAgIGZuYW1lID0gb3MucGF0aC5iYXNlbmFtZShxLmdldCgnZmlsZScsWyd0bHNwcm94eS5sb2cnXSlbMF0pCiAgICAgICAgICAgIG9mZnNldCA9IGludChxLmdldCgnb2Zmc2V0JyxbJzAnXSlbMF0pCiAgICAgICAgICAgIGZwYXRoID0gb3MucGF0aC5qb2luKExPR19ESVIsIGZuYW1lKQogICAgICAgICAgICBsaW5lcyA9IFtdOyBzaXplID0gb2Zmc2V0CiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKGZwYXRoKToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihmcGF0aCwncmInKSBhcyBmOgogICAgICAgICAgICAgICAgICAgIGYuc2VlayhvZmZzZXQpOyBjaHVuayA9IGYucmVhZCgpCiAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IG9mZnNldCArIGxlbihjaHVuaykKICAgICAgICAgICAgICAgICAgICByYXdfdGV4dCA9IGNodW5rLmRlY29kZSgndXRmLTgnLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgICAgICAgICAgICAgICAgIGlmIGZuYW1lLmVuZHN3aXRoKCcuanNvbicpOgogICAgICAgICAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQganNvbiBhcyBfanNvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VkID0gX2pzb24ubG9hZHMob3BlbihmcGF0aCkucmVhZCgpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJldHR5ID0gX2pzb24uZHVtcHMocGFyc2VkLCBpbmRlbnQ9MikKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzICA9IHByZXR0eS5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgICA9IG9zLnBhdGguZ2V0c2l6ZShmcGF0aCkKICAgICAgICAgICAgICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzID0gcmF3X3RleHQuc3BsaXRsaW5lcygpCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgbGluZXMgPSByYXdfdGV4dC5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgYm9keSA9IGpzb24uZHVtcHMoeydsaW5lcyc6bGluZXMsJ3NpemUnOnNpemV9KS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCdhcHBsaWNhdGlvbi9qc29uJykKICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luJywnKicpCiAgICAgICAgICAgIHNlbGYuZW5kX2hlYWRlcnMoKTsgc2VsZi53ZmlsZS53cml0ZShib2R5KQogICAgICAgIGVsaWYgcCA9PSAnL2FwaS9kb3dubG9hZCc6CiAgICAgICAgICAgIHEgPSBwYXJzZV9xcyh1cmxwYXJzZShzZWxmLnBhdGgpLnF1ZXJ5KQogICAgICAgICAgICBmbmFtZSA9IG9zLnBhdGguYmFzZW5hbWUocS5nZXQoJ2ZpbGUnLFsndGxzcHJveHkubG9nJ10pWzBdKQogICAgICAgICAgICBmcGF0aCA9IG9zLnBhdGguam9pbihMT0dfRElSLCBmbmFtZSkKICAgICAgICAgICAgaWYgb3MucGF0aC5leGlzdHMoZnBhdGgpOgogICAgICAgICAgICAgICAgd2l0aCBvcGVuKGZwYXRoLCdyYicpIGFzIGY6IGRhdGEgPSBmLnJlYWQoKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX3Jlc3BvbnNlKDIwMCkKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoJ0NvbnRlbnQtVHlwZScsJ3RleHQvcGxhaW4nKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQ29udGVudC1EaXNwb3NpdGlvbicsJ2F0dGFjaG1lbnQ7IGZpbGVuYW1lPScrZm5hbWUpCiAgICAgICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoZGF0YSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9yZXNwb25zZSg0MDQpOyBzZWxmLmVuZF9oZWFkZXJzKCkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoNDA0KTsgc2VsZi5lbmRfaGVhZGVycygpCgpvcy5tYWtlZGlycyhMT0dfRElSLCBleGlzdF9vaz1UcnVlKQpwcmludChmIltsb2d2aWV3ZXJdIDAuMC4wLjA6e1BPUlR9ICBsb2dzPXtMT0dfRElSfSIpCkhUVFBTZXJ2ZXIoKCcwLjAuMC4wJywgUE9SVCksIEhhbmRsZXIpLnNlcnZlX2ZvcmV2ZXIoKQo=' | base64 -d > /opt/logviewer.py + +# --------------------------------------------------------------------------- +# 6. install-ca.sh +# Installs TLSDebug CA into: +# (a) Debian/OS system trust store +# (b) ~/.pki/nssdb — where Chromium/Chrome actually looks on Linux +# (c) Chrome profile NSS db — belt-and-suspenders +# --------------------------------------------------------------------------- +RUN cat > /opt/install-ca.sh << 'SCRIPT' +#!/bin/bash +set -e +CA_CERT="${1:-/opt/tlsdebug/proxy-ca.crt}" +CA_NAME="TLSDebug CA" +HOME_NSS="/root/.pki/nssdb" +PROFILE_NSS="/opt/chrome-profile/nssdb" + +log() { echo "[CA] $*"; } + +if [ ! -f "${CA_CERT}" ]; then + log "ERROR: cert not found at ${CA_CERT}"; exit 1 +fi + +log "=== Installing TLSDebug CA ===" +log "Subject : $(openssl x509 -noout -subject -in "${CA_CERT}" 2>/dev/null)" +log "Expires : $(openssl x509 -noout -enddate -in "${CA_CERT}" 2>/dev/null)" + +# ------------------------------------------------------------------ +# (a) OS / system trust store +# ------------------------------------------------------------------ +log "--- System trust store ---" +cp "${CA_CERT}" /usr/local/share/ca-certificates/tlsdebug.crt +update-ca-certificates --fresh +log "System store updated." + +# ------------------------------------------------------------------ +# (b) ~/.pki/nssdb (THIS is where Chromium on Linux actually looks) +# ------------------------------------------------------------------ +log "--- ~/.pki/nssdb (Chromium default location) ---" +mkdir -p "${HOME_NSS}" +if [ ! -f "${HOME_NSS}/cert9.db" ]; then + certutil -N -d "sql:${HOME_NSS}" --empty-password + log "Created new NSS db at ${HOME_NSS}" +fi +certutil -d "sql:${HOME_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true +certutil -d "sql:${HOME_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}" +log "Installed into ${HOME_NSS}" + +# Verify +if certutil -d "sql:${HOME_NSS}" -L -n "${CA_NAME}" 2>/dev/null | grep -q "CT"; then + log "OK: ${HOME_NSS} verification passed." +else + log "WARNING: cert not found in ${HOME_NSS} after install" +fi + +# ------------------------------------------------------------------ +# (c) Chrome profile NSS db (belt-and-suspenders) +# ------------------------------------------------------------------ +log "--- Chrome profile NSS db ---" +mkdir -p "${PROFILE_NSS}" +if [ ! -f "${PROFILE_NSS}/cert9.db" ]; then + certutil -N -d "sql:${PROFILE_NSS}" --empty-password + log "Created new NSS db at ${PROFILE_NSS}" +fi +certutil -d "sql:${PROFILE_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true +certutil -d "sql:${PROFILE_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}" +log "Installed into ${PROFILE_NSS}" + +log "=== CA installation complete ===" +SCRIPT +RUN chmod +x /opt/install-ca.sh + +# --------------------------------------------------------------------------- +# 6b. generate-novnc-cert.sh — Self-signed certificate for noVNC HTTPS +# --------------------------------------------------------------------------- +RUN cat > /opt/generate-novnc-cert.sh << 'SCRIPT' +#!/bin/bash +set -e + +CERT_DIR="${1:-/opt/novnc-certs}" +CERT_FILE="${CERT_DIR}/novnc.pem" + +log() { echo "[cert] $*"; } + +mkdir -p "${CERT_DIR}" + +if [ -f "${CERT_FILE}" ]; then + log "Certificate already exists: ${CERT_FILE}" + log "Expires: $(openssl x509 -noout -enddate -in "${CERT_FILE}" 2>/dev/null || echo 'unknown')" + exit 0 +fi + +log "Generating self-signed certificate for noVNC..." + +openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout "${CERT_FILE}" \ + -out "${CERT_FILE}" \ + -days 365 \ + -subj "/C=US/ST=State/L=City/O=TLSDebug/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:*.local,IP:127.0.0.1" \ + 2>/dev/null + +chmod 600 "${CERT_FILE}" + +log "Certificate generated: ${CERT_FILE}" +log "Subject: $(openssl x509 -noout -subject -in "${CERT_FILE}" 2>/dev/null)" +log "Expires: $(openssl x509 -noout -enddate -in "${CERT_FILE}" 2>/dev/null)" +SCRIPT +RUN chmod +x /opt/generate-novnc-cert.sh + +# 7. start-chrome.sh +# --------------------------------------------------------------------------- +RUN cat > /opt/start-chrome.sh << 'SCRIPT' +#!/bin/bash +export DISPLAY="${DISPLAY:-:99}" +export HOME=/root + +PROXY_HOST="${PROXY_HOST:-127.0.0.1}" +PROXY_PORT="${PROXY_PORT:-8080}" +REMOTE_DEBUG_PORT="${CHROME_REMOTE_DEBUGGING_PORT:-9222}" +START_URL="${START_URL:-https://www.google.com}" + +echo "[Browser] $(browser --version 2>/dev/null || echo unknown)" +echo "[Browser] DISPLAY=${DISPLAY} proxy=${PROXY_HOST}:${PROXY_PORT}" + +exec browser \ + --no-sandbox \ + --disable-dev-shm-usage \ + --disable-gpu \ + --disable-software-rasterizer \ + --use-gl=swiftshader \ + --use-angle=swiftshader \ + --enable-unsafe-swiftshader \ + --in-process-gpu \ + --disable-setuid-sandbox \ + --user-data-dir="/opt/chrome-profile" \ + --proxy-server="http://${PROXY_HOST}:${PROXY_PORT}" \ + --ignore-certificate-errors \ + --ignore-certificate-errors-spki-list \ + --test-type \ + --remote-debugging-port="${REMOTE_DEBUG_PORT}" \ + --remote-debugging-address=0.0.0.0 \ + --window-size=1920,1080 \ + --start-fullscreen \ + --kiosk \ + --disable-extensions \ + --no-first-run \ + --no-default-browser-check \ + --password-store=basic \ + --use-mock-keychain \ + --enable-logging=stderr \ + --v=1 \ + "${START_URL}" 2>&1 +SCRIPT +RUN chmod +x /opt/start-chrome.sh + + +# --------------------------------------------------------------------------- +# 8. entrypoint.sh +# --------------------------------------------------------------------------- +RUN cat > /entrypoint.sh << 'SCRIPT' +#!/bin/bash +set -e + +export HOME=/root +PROXY_PORT="${PROXY_PORT:-8080}" +VNC_PORT="${VNC_PORT:-5900}" +NOVNC_PORT="${NOVNC_PORT:-6080}" +DISPLAY="${DISPLAY:-:99}" +# LOG_DIR is bind-mounted to ./logs/ on the host. +# CERTDIR is set to the same path so tlsproxy writes captured_tokens.json, +# session files, and the CA cert directly into the host-visible logs folder. +LOG_DIR="/var/log/tlsdebug" +CERTDIR="${LOG_DIR}" +NOVNC_WEB=$(cat /opt/novnc_path 2>/dev/null || echo "/usr/share/novnc") + +mkdir -p "${LOG_DIR}" + +log() { echo "[$(date '+%H:%M:%S')] $*"; } +wait_port() { + local port=$1 label=$2 tries=${3:-30} + for i in $(seq 1 "${tries}"); do + if nc -z 127.0.0.1 "${port}" 2>/dev/null; then + log "[+] ${label} ready on port ${port}." + return 0 + fi + sleep 0.5 + done + log "[!] TIMEOUT waiting for ${label} on port ${port}." + return 1 +} + +log "============================================" +log " TLSDebug + Headless Chrome Container" +log " noVNC Web UI : http://localhost:${NOVNC_PORT}" +log " TLS Proxy : localhost:${PROXY_PORT}" +log " noVNC root : ${NOVNC_WEB}" +log "============================================" + +# ---- Diagnostics ----------------------------------------------------------- +log "[diag] novnc web root contents:" +ls -la "${NOVNC_WEB}" 2>&1 | head -20 || log " (none)" +log "[diag] websockify location: $(which websockify 2>/dev/null || echo NOT FOUND)" +log "[diag] x11vnc location: $(which x11vnc 2>/dev/null || echo NOT FOUND)" +log "[diag] browser symlink: $(which browser 2>/dev/null || echo NOT FOUND) -> $(readlink /usr/local/bin/browser 2>/dev/null || echo none)" +log "[diag] tlsproxy location: $(which tlsproxy 2>/dev/null || echo NOT FOUND)" + +# ---- 1. Xvfb --------------------------------------------------------------- +log "[*] Starting Xvfb on ${DISPLAY} ..." +rm -f /tmp/.X99-lock /tmp/.X11-unix/X99 2>/dev/null || true +Xvfb "${DISPLAY}" \ + -screen 0 1920x1080x24 \ + -ac \ + +extension GLX \ + +extension RANDR \ + +render \ + -noreset \ + >> "${LOG_DIR}/xvfb.log" 2>&1 & +XVFB_PID=$! + +log "[*] Waiting for Xvfb ..." +for i in $(seq 1 30); do + if DISPLAY="${DISPLAY}" xdpyinfo >/dev/null 2>&1; then + log "[+] Xvfb ready." + break + fi + sleep 0.5 +done +export DISPLAY="${DISPLAY}" + +# ---- 2. Openbox window manager --------------------------------------------- +log "[*] Starting openbox ..." +openbox --config-file /root/.config/openbox/rc.xml --display "${DISPLAY}" >> "${LOG_DIR}/openbox.log" 2>&1 & +sleep 1 + +# ---- 3. TLSDebug proxy ----------------------------------------------------- +log "[*] Starting TLSDebug proxy on port ${PROXY_PORT} ..." +cd "${CERTDIR}" +tlsproxy \ + -port "${PROXY_PORT}" \ + -certdir "${CERTDIR}" \ + -skip-install \ + 2>&1 | grep -v 'SSLV3_ALERT_CERTIFICATE_UNKNOWN' | tee -a "${LOG_DIR}/tlsproxy.log" & +PROXY_PID=$! + +log "[*] Waiting for CA certificate ..." +for i in $(seq 1 30); do + if [ -f "${CERTDIR}/proxy-ca.crt" ]; then + log "[+] CA certificate ready." + break + fi + sleep 1 +done +# Install CA before Chrome launches — must succeed +if [ -f "${CERTDIR}/proxy-ca.crt" ]; then + if /opt/install-ca.sh "${CERTDIR}/proxy-ca.crt"; then + log "[+] CA trusted by OS and Chrome NSS." + else + log "[!] CA install failed — Chrome may show cert errors." + fi +else + log "[!] proxy-ca.crt missing — Chrome may show cert errors." +fi + +# ---- 4. x11vnc ------------------------------------------------------------- +log "[*] Starting x11vnc on port ${VNC_PORT} ..." +x11vnc \ + -display "${DISPLAY}" \ + -forever \ + -shared \ + -nopw \ + -rfbport "${VNC_PORT}" \ + -noxdamage \ + -noxfixes \ + -noxcomposite \ + -cursor arrow \ + -loop \ + -repeat \ + -xkb \ + >> "${LOG_DIR}/x11vnc.log" 2>&1 & + +sleep 2 +wait_port "${VNC_PORT}" "x11vnc" 40 + +# ---- 5. noVNC -------------------------------------------------------------- +# Check for custom certificate or generate self-signed +CERT_DIR="/opt/novnc-certs" +CUSTOM_CERT="/certs/novnc.pem" +CERT_FILE="${CERT_DIR}/novnc.pem" + +if [ "${NOVNC_ENABLE_HTTPS}" = "true" ]; then + log "[*] HTTPS mode enabled" + + # Use custom cert if provided, otherwise generate self-signed + if [ -f "${CUSTOM_CERT}" ]; then + log "[*] Using custom certificate: ${CUSTOM_CERT}" + mkdir -p "${CERT_DIR}" + cp "${CUSTOM_CERT}" "${CERT_FILE}" + chmod 600 "${CERT_FILE}" + else + log "[*] No custom cert found, generating self-signed..." + /opt/generate-novnc-cert.sh "${CERT_DIR}" + fi + + NOVNC_PORT="${NOVNC_HTTPS_PORT:-6443}" + SSL_ARGS="--cert=${CERT_FILE} --ssl-only" + SCHEME="https" +else + log "[*] HTTP mode (set NOVNC_ENABLE_HTTPS=true for HTTPS)" + SSL_ARGS="" + SCHEME="http" +fi + +log "[*] Starting noVNC websockify on port ${NOVNC_PORT} (${SCHEME}) ..." +log "[*] web root : ${NOVNC_WEB}" +log "[*] target : 127.0.0.1:${VNC_PORT}" + +# note: SSL_ARGS may include --ssl-only; omit to allow fallbacks when certs are untrusted +tmp_args="${SSL_ARGS}" +# remove ssl-only if present to avoid 'non-SSL connection received but disallowed' errors +tmp_args=$(echo "${tmp_args}" | sed 's/--ssl-only//g') +websockify \ + --web "${NOVNC_WEB}" \ + --heartbeat 30 \ + ${tmp_args} \ + --log-file "${LOG_DIR}/novnc.log" \ + "${NOVNC_PORT}" \ + "127.0.0.1:${VNC_PORT}" & +NOVNC_PID=$! + +wait_port "${NOVNC_PORT}" "noVNC" 40 + +# Confirm websockify process +log "[diag] websockify process: $(pgrep -a websockify 2>/dev/null || echo NOT RUNNING)" +log "[diag] listening ports: $(ss -tlnp 2>/dev/null | grep -E '5900|6080|8080' || echo none)" + +# ---- 6. Chrome ------------------------------------------------------------- +# remove stale profile locks which can prevent Chrome from launching +rm -f /opt/chrome-profile/Singleton* /opt/chrome-profile/Default/Singleton* + +log "[*] Starting Chrome ..." +/opt/start-chrome.sh >> "${LOG_DIR}/chrome.log" 2>&1 & +CHROME_PID=$! + +# ---- 7. Log viewer on port 4040 -------------------------------------------- +log "[*] Starting log viewer on port 4040 ..." +python3 /opt/logviewer.py >> "${LOG_DIR}/logviewer.log" 2>&1 & +LOGVIEWER_PID=$! +wait_port 4040 "log viewer" 20 + +log "" +log "[+] All services started." +log "[+] Browser (noVNC) : ${SCHEME}://localhost:${NOVNC_PORT}" +log "[+] Log viewer : http://localhost:4040" +log "[+] TLS proxy : localhost:${PROXY_PORT}" +log "[+] Host logs dir : ${LOG_DIR} (mounted to ./logs/ on host)" +if [ "${NOVNC_ENABLE_HTTPS}" = "true" ]; then + log "[+] Certificate : ${CERT_FILE}" +fi +log "" + +cleanup() { + log "[*] Shutting down ..." + kill "${CHROME_PID}" "${NOVNC_PID}" "${PROXY_PID}" "${XVFB_PID}" "${LOGVIEWER_PID}" 2>/dev/null || true + exit 0 +} +trap cleanup SIGTERM SIGINT + +# Stream proxy traffic to docker logs so 'docker logs -f tlsdebug' shows live traffic +tail -f "${LOG_DIR}/tlsproxy.log" +SCRIPT +RUN chmod +x /entrypoint.sh + +# --------------------------------------------------------------------------- +# 9. Ports +# --------------------------------------------------------------------------- +EXPOSE 6080 6443 80 443 5900 8080 4040 9222 + +WORKDIR /opt/tlsdebug +ENTRYPOINT ["/entrypoint.sh"]