Services
Guacamole — remote desktop trên trình duyệt— browser remote desktop
Admin bấm một nút trên /vps/{id} là mở màn hình VPS ngay trong trình duyệt, có ghi audit. Trang này tách rõ phần đã chạy trong code, phần đã đổi so với bản thiết kế và phần vẫn chỉ là kế hoạch.
One click on /vps/{id} opens the VPS desktop in the admin's browser, with an audit log. This page separates what is implemented in code, what changed from the design, and what is still only planned.
GUACAMOLE_FEATURE_PLAN.md (ngày 03/06/2026, "Phase 1 in progress") mô tả kết nối RDP. Code hiện tại dùng VNC: migration 2026_06_04_000001_replace_rdp_with_vnc_on_vpses xoá các cột rdp_username / rdp_password / rdp_port và thêm vnc_port / vnc_password. Lý do: RDP gắn vào console session sẽ khoá desktop khi ngắt kết nối, làm hỏng GUI automation của CmdAgent (thêm WebRequest URL cho MT5 dùng SendInput, khi bị khoá thì phím rơi vào Winlogon). VNC chỉ phản chiếu framebuffer đang chạy nên không tạo hay khoá session.
GUACAMOLE_FEATURE_PLAN.md (dated 03/06/2026, "Phase 1 in progress") describes RDP. The code now uses VNC: migration 2026_06_04_000001_replace_rdp_with_vnc_on_vpses drops rdp_username / rdp_password / rdp_port and adds vnc_port / vnc_password. Reason: attaching RDP to the Windows console session locks the desktop on disconnect, which breaks CmdAgent's GUI automation (the MT5 WebRequest URL step uses SendInput, which lands on Winlogon while locked). VNC mirrors the live framebuffer without creating or locking a session.
Đã làm và kế hoạchImplemented vs planned
| Hạng mụcItem | Trạng tháiStatus | Ghi chúNotes |
|---|---|---|
Service guacd + guacamole trong composeguacd + guacamole compose services | IMPLEMENTED | Không có container nginx-guac như bản kế hoạch; TLS do reverse proxy trên host (NPM hoặc aaPanel) đảm nhận.No nginx-guac container as the plan had; TLS is terminated by the host reverse proxy (NPM or aaPanel). |
| Cầu xác thực JSON Auth (token ký + mã hoá, hạn 60 s)JSON Auth bridge (signed + encrypted token, 60 s expiry) | IMPLEMENTED | GuacamoleSessionController::open |
| Giao thức kết nốiConnection protocol | CHANGED | RDP → VNC (TightVNC do prepare-vm.ps1 cài, cổng 5900)RDP → VNC (TightVNC installed by prepare-vm.ps1, port 5900) |
Credentials trên bảng vpsesCredentials on vpses | CHANGED | vnc_password (mã hoá theo APP_KEY, sinh ngẫu nhiên 8 ký tự phía server) + vnc_port (mặc định 5900); host lấy từ lan_ip agent báo qua heartbeatvnc_password (encrypted with APP_KEY, 8 random chars generated server-side) + vnc_port (default 5900); the host is the lan_ip the agent reports in its heartbeat |
Bảng audit vps_remote_sessionsAudit table vps_remote_sessions | IMPLEMENTED | Ghi dòng audit trước khi dựng tokenThe audit row is written before the token is built |
Nút Connect trên /vps/{id} + trang /admin/vps/{id}/remote-sessionsConnect button on /vps/{id} + /admin/vps/{id}/remote-sessions page | IMPLEMENTED | Chỉ adminAdmin only |
| Dọn session treo mỗi giờHourly stale-session GC | IMPLEMENTED | fxconsole:close-stale-remote-sessions |
Setting guac_json_secret_key, guac_public_urlSettings guac_json_secret_key, guac_public_url | IMPLEMENTED | Hiện trên /admin/settingsShown on /admin/settings |
| Hướng dẫn reverse proxy (NPM, aaPanel)Reverse-proxy guide (NPM, aaPanel) | IMPLEMENTED | docker/nginx-guac/ |
| DNS + chứng chỉ cho subdomain Guacamole, smoke test end-to-endDNS + certificate for the Guacamole subdomain, end-to-end smoke test | UNVERIFIED | Là việc còn lại của Phase 1 trong bản kế hoạch; repo không cho biết đã xong chưaListed as remaining Phase 1 work in the plan; the repo doesn't show whether it's done |
Webhook ngắt kết nối (điền closed_at)Disconnect webhook (fills closed_at) | PLANNED | Phase 2 |
| Kiểm tra TCP trước khi mởPre-flight TCP probe | PLANNED | Phase 2 |
| Chế độ nhúng iframeInline iframe mode | PLANNED | Phase 2 |
| Ghi hình session, credentials riêng từng admin, MFA, dọn audit sau 1 nămSession recording, per-admin credentials, MFA, 1-year audit prune | PLANNED | Phase 3 |
Kiến trúc thực tếActual architecture
flowchart LR
ADM(["Trình duyệt admin"]) -- "HTTPS" --> FX["fxconsole /vps/{id}"]
FX -- "ghi audit" --> MY[("MySQL vps_remote_sessions")]
FX -- "trả URL ?data=token (60 s)" --> ADM
ADM -- "HTTPS + WebSocket (tab mới)" --> RP["Reverse proxy NPM / aaPanel (TLS)"]
RP -- "HTTP" --> GC["guacamole :8080 (host :8282)"]
GC -- "guac_internal :4822" --> GD["guacd"]
GD -- "VNC :5900 tới lan_ip qua tunnel WireGuard" --> VPS["VPS Windows: TightVNC + phiên AutoLogon"]flowchart LR
ADM(["Admin browser"]) -- "HTTPS" --> FX["fxconsole /vps/{id}"]
FX -- "write audit row" --> MY[("MySQL vps_remote_sessions")]
FX -- "return URL ?data=token (60 s)" --> ADM
ADM -- "HTTPS + WebSocket (new tab)" --> RP["Reverse proxy NPM / aaPanel (TLS)"]
RP -- "HTTP" --> GC["guacamole :8080 (host :8282)"]
GC -- "guac_internal :4822" --> GD["guacd"]
GD -- "VNC :5900 to lan_ip through the WireGuard tunnel" --> VPS["Windows VPS: TightVNC + AutoLogon session"]Guacamole chạy trên subdomain riêng (ví dụ guacamole.gamelau.uk) để container fxconsole không phải mang Java, cookie phiên của fxconsole không bao giờ tới Guacamole (chỉ token đi qua), và Guacamole có thể chuyển sang host khác mà không đổi fxconsole. guacd quay số tới lan_ip của VPS, nên host chạy Docker (OVH Dedicated Server 01) phải tới được LAN chứa VPS: server 01 là WireGuard client của CT WireGuard trên từng server VPS, mỗi server một tunnel (xem Hạ tầng máy chủ).
Guacamole runs on its own subdomain (for example guacamole.gamelau.uk) so the fxconsole container doesn't carry Java, fxconsole's session cookie never reaches Guacamole (only the token crosses), and Guacamole can move to another host without touching fxconsole. guacd dials the VPS lan_ip, so the Docker host (OVH Dedicated Server 01) must reach the LAN the VPS sits in: server 01 is a WireGuard client of the WireGuard CT on every VPS server, one tunnel per server (see Server infrastructure).
Luồng bấm ConnectThe Connect flow
- Admin bấm Connect trên
/vps/{id}; React gọiPOST /admin/vps/{id}/remote-session.The admin clicks Connect on/vps/{id}; React callsPOST /admin/vps/{id}/remote-session. - Server kiểm tra: phải là admin (403); đã cấu hình
guac_json_secret_keyvàguac_public_url(503); secret đúng 32 ký tự hex = 16 byte (503); VPS đã báolan_ip(422); cóvnc_passwordvà giải mã được (422).The server checks: admin only (403);guac_json_secret_keyandguac_public_urlconfigured (503); the secret is exactly 32 hex chars = 16 bytes (503); the VPS has reportedlan_ip(422);vnc_passwordexists and decrypts (422). - Ghi dòng
vps_remote_sessions(token_jtiUUID,vps_id,admin_id,opened_at, IP, user agent).Insert avps_remote_sessionsrow (token_jtiUUID,vps_id,admin_id,opened_at, IP, user agent). - Dựng JSON:
username=admin-{id},expires= bây giờ + 60 s, một connection đặt tên theo tên VPS vớiprotocol: vnc,hostname=lan_ip,port,password,color-depth: 24, clipboard hai chiều (disable-copy/paste: false).Build the JSON:username=admin-{id},expires= now + 60 s, one connection named after the VPS withprotocol: vnc,hostname=lan_ip,port,password,color-depth: 24, two-way clipboard (disable-copy/paste: false). - Ký + mã hoá theo đặc tả
guacamole-auth-jsonrồi trả{url, jti}vớiurl=<guac_public_url>/?data=<base64>.Sign + encrypt per theguacamole-auth-jsonspec, then return{url, jti}whereurl=<guac_public_url>/?data=<base64>. - Trình duyệt mở URL ở tab mới; Guacamole giải mã token, gọi guacd, vẽ desktop. Tải lại trang = bấm Connect lại = dòng audit mới.The browser opens the URL in a new tab; Guacamole decodes the token, dials guacd and renders the desktop. Reloading means a new Connect click and a new audit row.
signature = HMAC-SHA256(secret_bytes, json_string)
signed = signature . json_string (raw byte concat)
encrypted = AES-128-CBC(secret_bytes, IV=zeros, signed) (PKCS#7 padded)
token = base64(encrypted)
Clipboard VNC chỉ là văn bản thuần (đủ để dán login), không có kéo thả file như RDP; đây là đánh đổi đã chấp nhận để giữ GUI automation của agent chạy ổn.
VNC clipboard is plain text only (fine for pasting a login), without RDP's file drop; that trade-off was accepted to keep the agent's GUI automation working.
Service trong docker-composeCompose services
guacd:
image: guacamole/guacd:latest
container_name: fxconsole-guacd
networks: [guac_internal]
restart: unless-stopped
guacamole:
image: guacamole/guacamole:latest
container_name: fxconsole-guacamole
depends_on: [guacd]
environment:
GUACD_HOSTNAME: guacd
GUACD_PORT: "4822"
JSON_SECRET_KEY: ${GUAC_JSON_SECRET_KEY:-}
WEBAPP_CONTEXT: ROOT
networks: [guac_internal]
ports:
- "8282:8080"
volumes:
- guac_drive:/var/lib/guacamole/drive
restart: unless-stopped
- Chỉ
guacamoleđược gọiguacd, qua network riêngguac_internal;guacdkhông có volume và không map cổng ra host. - Chỉ cần đặt
JSON_SECRET_KEYlà bật extensionguacamole-auth-jsontrong image (Guacamole 1.5+); không cần DB của Guacamole. Giá trị phải trùngGUAC_JSON_SECRET_KEYtrong.envcủa fxconsole. WEBAPP_CONTEXT: ROOTđưa webapp về/thay vì/guacamole/, để proxy chuyển thẳng/→/mà không phải viết lại path hay cookie (nguồn gốc thường gặp của lỗi "kẹt ở login" hoặc WebSocket hỏng).- Volume
guac_drivelà chỗ tạm cho tính năng drive redirection của RDP; với VNC hiện tại controller không bật drive nên volume này nhiều khả năng không còn dùng. Nếu file tích tụ, dọn bằngdocker compose exec guacamole find /var/lib/guacamole/drive -mtime +7 -delete.
- Only
guacamolemay dialguacd, over the isolatedguac_internalnetwork;guacdhas no volume and no host port. - Setting
JSON_SECRET_KEYalone enables theguacamole-auth-jsonextension inside the image (Guacamole 1.5+); no Guacamole DB is needed. The value must equalGUAC_JSON_SECRET_KEYin fxconsole's.env. WEBAPP_CONTEXT: ROOTserves the webapp at/instead of/guacamole/, so the proxy forwards/→/with no path or cookie rewriting (the usual source of "stuck on login" or broken WebSocket bugs).- The
guac_drivevolume is scratch space for RDP drive redirection; the VNC controller doesn't enable a drive, so the volume is most likely unused now. If files pile up, prune withdocker compose exec guacamole find /var/lib/guacamole/drive -mtime +7 -delete.
Comment trong compose nói bind vào IP LAN để container Nginx Proxy Manager (compose khác) truy cập được, nhưng "8282:8080" thực tế publish trên tất cả interface. Webapp nói HTTP thuần; token ?data= hết hạn sau 60 s nhưng vẫn chứa mật khẩu VNC đã mã hoá. Nếu host có IP public hoặc LAN không tin cậy, chặn 8282/tcp bằng ufw / iptables trừ subnet bridge của NPM. Cách sạch hơn: cho NPM tham gia network guac_internal (Option 1A bên dưới) rồi đổi lại thành 127.0.0.1:8282:8080.
The compose comment says it binds to the LAN IP so the Nginx Proxy Manager container (another compose project) can reach it, but "8282:8080" actually publishes on every interface. The webapp speaks plain HTTP; the ?data= token expires after 60 s but still carries the encrypted VNC password. If the host has a public IP or an untrusted LAN, drop 8282/tcp with ufw / iptables except from NPM's bridge subnet. The cleaner fix: join NPM to guac_internal (Option 1A below) and change the mapping back to 127.0.0.1:8282:8080.
Cấu hình (chỉ tên key)Configuration (key names only)
| SettingSetting | Env | Hành viBehaviour |
|---|---|---|
guac_json_secret_key | GUAC_JSON_SECRET_KEY | Khoá AES 16 byte dạng hex (32 ký tự), tạo một lần bằng openssl rand -hex 16. Chỉ đọc trên /admin/settings để tránh lệch; nguồn gốc là env JSON_SECRET_KEY của container. Đổi: sửa phía container, rồi .env của fxconsole, rồi khởi động lại cả hai (docker compose up -d guacamole).16-byte AES key in hex (32 chars), generated once with openssl rand -hex 16. Read-only on /admin/settings to prevent drift; the container's JSON_SECRET_KEY env is the source of truth. To change: update the container side, then fxconsole's .env, then restart both (docker compose up -d guacamole). |
guac_public_url | GUAC_PUBLIC_URL | URL HTTPS gốc trình duyệt dùng để tới Guacamole, không có dấu / cuối. Trống = tắt nút Connect. Đọc trực tiếp mỗi lần bấm, không cần restart; giá trị trên /admin/settings thắng .env. Đổi domain: sửa field này, cấp lại chứng chỉ Let's Encrypt cho hostname mới, render lại docker/nginx-guac/guacamole.conf.template với ${GUAC_DOMAIN} mới.HTTPS base URL browsers use to reach Guacamole, with no trailing /. Empty disables the Connect button. Read live on every click, no restart; the /admin/settings value wins over .env. To rename the domain: update this field, re-issue the Let's Encrypt cert for the new hostname, and re-render docker/nginx-guac/guacamole.conf.template with the new ${GUAC_DOMAIN}. |
Phía VPS: prepare-vm.ps1 lấy mật khẩu VNC riêng của VPS qua GET /api/agent/broker-config rồi cài TightVNC thành Windows Service trên cổng 5900. Admin có thể xem mật khẩu trên /vps/{id} để dùng VNC client native khi ở trong VPN, hoặc tạo lại bằng Edit VPS. Chi tiết: VPS agent.
VPS side: prepare-vm.ps1 fetches the per-VPS VNC password from GET /api/agent/broker-config and installs TightVNC as a Windows Service on port 5900. Admins can reveal the password on /vps/{id} for a native VNC client inside the VPN, or regenerate it via Edit VPS. Details: VPS agent.
Reverse proxyReverse proxy
docker/nginx-guac/README.md vẫn viết container publish ở 127.0.0.1:8081. Compose hiện tại dùng 8282:8080. Thay 8081 bằng 8282 ở mọi chỗ bên dưới dùng cổng host.
docker/nginx-guac/README.md still says the container publishes on 127.0.0.1:8081. Compose now uses 8282:8080. Read 8282 wherever the steps below use the host port.
Bước 1: cho NPM tới được containerStep 1: let NPM reach the container
NPM chạy trong Docker không thấy 127.0.0.1 của host. Chọn một cách và giữ nguyên:
NPM in Docker cannot see the host's 127.0.0.1. Pick one fix and stick with it:
| Option | Cách làmHow | Forward host / port |
|---|---|---|
| 1A (sạch nhất)(cleanest) | Gắn container NPM vào network fxconsole_guac_internal (xem lệnh dưới)Attach the NPM container to fxconsole_guac_internal (commands below) | fxconsole-guacamole / 8080 |
| 1B | host.docker.internal (Docker Desktop; trên Linux thêm extra_hosts: "host.docker.internal:host-gateway" vào compose NPM)host.docker.internal (Docker Desktop; on Linux add extra_hosts: "host.docker.internal:host-gateway" to NPM's compose) | host.docker.internal / cổng hosthost port |
| 1C (cuối cùng)(last resort) | Bind vào IP docker bridge, ví dụ 172.17.0.1:<port>:8080, rồi docker compose up -d guacamoleBind to the docker bridge IP, e.g. 172.17.0.1:<port>:8080, then docker compose up -d guacamole | 172.17.0.1 / cổng hosthost port |
docker network ls | grep guac
# fxconsole_guac_internal bridge local
docker network connect fxconsole_guac_internal nginx-proxy-manager_app_1
Để bền vững, thêm vào compose của NPM:
To make it persistent, add to NPM's own compose:
services:
app:
networks:
- default
- fxconsole_guac_internal
networks:
fxconsole_guac_internal:
external: true
Không bind 0.0.0.0 nếu ufw / iptables chưa chặn cổng đó từ bên ngoài: cổng nói HTTP thuần và một token ?data= bị lộ cho phép mở desktop.
Do not bind 0.0.0.0 unless ufw / iptables drop that port from outside: it speaks plain HTTP and a leaked ?data= token opens the desktop.
Bước 2: tạo proxy host trong NPMStep 2: create the NPM proxy host
| Tab / trườngTab / field | Giá trịValue |
|---|---|
| Details · Domain Names | guacamole.yourdomain.com |
| Details · Scheme | http |
| Details · Forward Hostname / Port | theo bước 1from step 1 |
| Details · Cache Assets | OFF |
| Details · Block Common Exploits | OFF (bộ lọc ăn mất URL token ?data=)(its filter eats the ?data= token URL) |
| Details · Websockets Support | ON (tunnel Guacamole là WebSocket)(the Guacamole tunnel is WebSocket) |
| SSL | Request a new SSL Certificate (Let's Encrypt), Force SSL ON, HTTP/2 ON, HSTS ON, HSTS Subdomains OFF, email, đồng ý ToS. Cấp chứng chỉ mất 15–60 s, lỗi ngay nếu DNS chưa trỏ.Request a new SSL Certificate (Let's Encrypt), Force SSL ON, HTTP/2 ON, HSTS ON, HSTS Subdomains OFF, email, agree to ToS. Issuing takes 15–60 s and fails fast if DNS hasn't propagated. |
| Advanced | Dán toàn bộ docker/nginx-guac/npm-advanced.conf: proxy_read_timeout 7200s, proxy_send_timeout 7200s, proxy_buffering off, access_log off (giữ token ngoài log). Không đặt header ở đây: NPM ghi đè ở mức location.Paste all of docker/nginx-guac/npm-advanced.conf: proxy_read_timeout 7200s, proxy_send_timeout 7200s, proxy_buffering off, access_log off (keeps the token out of logs). Don't set headers here: NPM overrides them at location level. |
Bước 3: trỏ fxconsole tới URL mớiStep 3: point fxconsole at the new URL
Đặt GUAC_PUBLIC_URL=https://guacamole.yourdomain.com trong .env của fxconsole (hoặc sửa trên /admin/settings), và kiểm tra GUAC_JSON_SECRET_KEY=<secret> trùng với JSON_SECRET_KEY của container.
Set GUAC_PUBLIC_URL=https://guacamole.yourdomain.com in fxconsole's .env (or on /admin/settings), and check that GUAC_JSON_SECRET_KEY=<secret> matches the container's JSON_SECRET_KEY.
Bước 4: kiểm traStep 4: verify
Chạy theo thứ tự, dừng ở lỗi đầu tiên:
Run in order and stop at the first failure:
# 1. NPM reaches the upstream (inside the NPM container for Option 1A)
docker exec -it nginx-proxy-manager_app_1 sh
curl -I http://fxconsole-guacamole:8080
# Expect: HTTP/1.1 200 OK
# 2. End-to-end through NPM
curl -I https://guacamole.yourdomain.com
# Expect: 200 OK + strict-transport-security header
# 3. WebSocket upgrade
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGVzdA==" -H "Sec-WebSocket-Version: 13" \
-H "Origin: https://guacamole.yourdomain.com" \
https://guacamole.yourdomain.com/websocket-tunnel
# Expect: HTTP/1.1 101 Switching Protocols
Nếu (3) trả 200 hoặc 400 thay vì 101, công tắc Websockets chưa ăn: lưu lại proxy host rồi thử lại. Sau đó bấm Connect trên một VPS: tab mới mở https://guacamole.yourdomain.com/#/?data=… và desktop hiện sau một hai giây.
If (3) returns 200 or 400 instead of 101, the Websockets toggle didn't take effect: re-save the proxy host and retry. Then click Connect on a VPS: a new tab opens at https://guacamole.yourdomain.com/#/?data=… and the desktop appears within a second or two.
Không dùng NPM: aaPanel / nginx trên hostWithout NPM: aaPanel / host nginx
GUAC_DOMAIN=guacamole.yourdomain.com \
envsubst '${GUAC_DOMAIN}' \
< guacamole.conf.template \
> /www/server/panel/vhost/nginx/guacamole.conf
nginx -t && nginx -s reload
Rồi cấp chứng chỉ LE cho ${GUAC_DOMAIN} (UI aaPanel hoặc certbot --nginx -d guacamole.yourdomain.com) và reload nginx lần nữa. Dùng NPM thì không cần template này.
Then issue the LE cert for ${GUAC_DOMAIN} (aaPanel UI or certbot --nginx -d guacamole.yourdomain.com) and reload nginx once more. With NPM this template is unused.
Audit và dọn sessionAudit and session cleanup
Cột vps_remote_sessionsvps_remote_sessions column | Ý nghĩaMeaning |
|---|---|
vps_id, admin_id | FK tới VPS và user đã bấm ConnectFKs to the VPS and the user who clicked Connect |
token_jti | UUID duy nhất cho mỗi tokenUnique UUID per token |
opened_at / closed_at | closed_at hiện chỉ được điền bởi lệnh dọn (chưa có webhook ngắt kết nối)closed_at is currently filled only by the cleanup command (no disconnect webhook yet) |
client_ip, user_agent | IP và trình duyệt của admin lúc bấmAdmin IP and browser at click time |
Lệnh fxconsole:close-stale-remote-sessions chạy mỗi giờ, đóng các dòng closed_at còn NULL sau --hours (mặc định 24). Xem trước số dòng:
fxconsole:close-stale-remote-sessions runs hourly and closes rows still NULL at closed_at after --hours (default 24). Preview the count:
docker exec fxconsole-app php artisan fxconsole:close-stale-remote-sessions --dry-run --hours=24
Log của luồng Connect nằm ở channel deployment với các khoá guac.open.success, guac.open.bad_secret, guac.open.password_decrypt_failed, guac.close_stale.done.
Connect-flow logs go to the deployment channel with keys guac.open.success, guac.open.bad_secret, guac.open.password_decrypt_failed, guac.close_stale.done.
Xử lý sự cốTroubleshooting
| Triệu chứngSymptom | Nguyên nhân / cách sửaCause / fix |
|---|---|
503 Guacamole is not configured | Thiếu guac_json_secret_key hoặc guac_public_url.guac_json_secret_key or guac_public_url is empty. |
503 … expect 16-byte hex = 32 chars | Secret không đúng 32 ký tự hex.The secret is not exactly 32 hex chars. |
422 VPS has not reported its LAN IP yet | Chờ heartbeat kế tiếp của agent.Wait for the agent's next heartbeat. |
422 VNC password is not set | Chạy lại prepare-vm.ps1 trên VPS hoặc tạo lại mật khẩu qua Edit VPS.Re-run prepare-vm.ps1 on the VPS or rotate the password via Edit VPS. |
422 VNC password is unreadable | Ciphertext mồ côi sau khi APP_KEY đổi; tạo lại qua Edit VPS.Ciphertext orphaned by an APP_KEY rotation; rotate via Edit VPS. |
| Màn đen / "Disconnected" sau vài giâyBlack screen / "Disconnected" after a few seconds | WebSocket upgrade lỗi: bật Websockets Support trong NPM, lưu lại.WebSocket upgrade failing: turn on Websockets Support in NPM and re-save. |
| 502 Bad Gateway | NPM không tới được upstream: xem lại bước 1, kiểm tra docker ps có fxconsole-guacamole.NPM can't reach the upstream: re-check step 1 and that docker ps shows fxconsole-guacamole. |
| Kẹt ở "Connecting..."Stuck on "Connecting..." | Như 502: thường do chưa làm Option 1A mà Forward Hostname vẫn là 127.0.0.1. Nếu proxy ổn, từ server 01 ping lan_ip của VPS: không được thì xem tunnel WireGuard tới server chứa VPS đó (handshake, AllowedIPs có subnet của LAN); ping được thì kiểm tra TightVNC đang chạy trên cổng 5900.Same as 502: usually Option 1A wasn't applied and Forward Hostname is still 127.0.0.1. If the proxy is fine, ping the VPS lan_ip from server 01: if that fails, check the WireGuard tunnel to the server hosting that VPS (handshake, AllowedIPs includes the LAN subnet); if it answers, check TightVNC is running on port 5900. |
| Vòng lặp redirect loginLogin redirect loop | Lệch cookie path; compose đã đặt WEBAPP_CONTEXT=ROOT. Nếu bỏ nó, NPM cần proxy_cookie_path /guacamole/ /;.Cookie path mismatch; compose already sets WEBAPP_CONTEXT=ROOT. If you override it, NPM also needs proxy_cookie_path /guacamole/ /;. |
| Rớt session sau ~60 s không thao tácSession drops after ~60 s idle | Timeout upstream mặc định còn hiệu lực: dán lại npm-advanced.conf.Default upstream timeout still applies: re-paste npm-advanced.conf. |
Token ?data= xuất hiện trong access log?data= token visible in access logs | access_log off chưa áp: dán lại snippet Advanced.access_log off not applied: re-paste the Advanced snippet. |
| Cấp / gia hạn chứng chỉ LE lỗiLE cert issue / renewal fails | DNS chưa trỏ về host hoặc cổng 80 bị chặn: dig guacamole.yourdomain.com, curl -I http://guacamole.yourdomain.com/.well-known/ từ bên ngoài; xem trạng thái gia hạn ở NPM → SSL Certificates.DNS not pointing at the host or port 80 blocked: dig guacamole.yourdomain.com, curl -I http://guacamole.yourdomain.com/.well-known/ from outside; check renewal status in NPM → SSL Certificates. |
Ghi chú bảo mậtSecurity notes
- Token trong URL. JSON đã ký chứa mật khẩu VNC dạng rõ trước khi mã hoá AES-128-CBC bằng
GUAC_JSON_SECRET_KEY, và nó nằm trên URL nên có thể lọt vào lịch sử trình duyệt, log proxy, header Referer. Giảm thiểu đang có: hạn 60 s,access_log offtrên vhost Guacamole. Bản kế hoạch đề xuấtReferrer-Policy: no-referrer; nginx của fxconsole hiện gửistrict-origin-when-cross-origin(không gửi path/query sang origin khác). - Admin bị chiếm quyền. Session admin bị lộ vốn đã deploy / gỡ tài khoản MT5 trên cả fleet; remote desktop là tập cha nghiêm ngặt của quyền đó. Audit giúp điều tra; MFA ở Phase 3 chặn đường chiếm quyền.
- Lưu audit. Bảng tăng tuyến tính (khoảng 10 admin × 5 session/ngày); giữ vĩnh viễn ở Phase 1, thêm dọn sau 1 năm ở Phase 3 nếu phình.
- Độ trễ. Nếu guacd ở host khác mạng với VPS, thêm khoảng 30–50 ms; chấp nhận được cho việc admin, không liên quan tới giao dịch.
- Token in the URL. The signed JSON holds the VNC password in clear before AES-128-CBC encryption with
GUAC_JSON_SECRET_KEY, and it travels in a URL, so browser history, proxy logs and Referer headers could leak it. Mitigations in place: 60 s expiry,access_log offon the Guacamole vhost. The plan proposedReferrer-Policy: no-referrer; fxconsole's nginx currently sendsstrict-origin-when-cross-origin(no path/query to other origins). - Compromised admin. A leaked admin session can already deploy / remove MT5 accounts across the fleet; remote desktop is a strict superset. The audit log gives forensic visibility; MFA in Phase 3 closes the acquisition vector.
- Audit retention. The table grows linearly (about 10 admins × 5 sessions/day); keep forever in Phase 1, add a 1-year prune in Phase 3 if it bloats.
- Latency. If guacd sits on a different network from the VPSes it adds roughly 30–50 ms; fine for admin work, unrelated to trading.
Kế hoạch còn lạiRemaining roadmap
Phase 2 — PolishPhase 2 — Polish PLANNED
- Webhook ngắt kết nối: extension Java nhỏ trên Guacamole POST
closed_atvề fxconsole (hoặc đọc access log Tomcat). - Kiểm tra trước: khi bấm Connect, fxconsole-app thử TCP tới cổng của VPS và báo "VPS unreachable" thay vì để Guacamole quay 30 s.
- Chế độ nhúng: route
/vps/{id}/remotedùng iframe thay chowindow.open. Phase 1 bỏ qua vì clipboard API và fullscreen API hành xử khác nhau giữa các trình duyệt khi ở trong iframe khác origin.
- Disconnect webhook: a small Java extension on Guacamole POSTs
closed_atback to fxconsole (or scrape Tomcat access logs). - Pre-flight check: on Connect, fxconsole-app runs a TCP probe to the VPS port and shows "VPS unreachable" instead of letting Guacamole spin for 30 s.
- Inline mode: a
/vps/{id}/remoteroute that iframes Guacamole instead ofwindow.open. Skipped in Phase 1 because clipboard and fullscreen APIs behave differently across browsers inside a cross-origin iframe.
Phase 3 — Chuẩn compliancePhase 3 — Compliance-grade PLANNED
- Ghi hình session qua
recording-pathcủa guacd, định kỳ đẩy lên storage kiểu S3, trang replay/admin/vps/{id}/remote-sessions/{n}/replay. - Credentials riêng từng admin (bảng
admin_vps_credentials) để audit biết admin dùng tài khoản Windows nào. - Cổng MFA (TOTP / WebAuthn) trước khi cấp token.
- Session recording via guacd's
recording-path, periodic upload to S3-compatible storage, replay page/admin/vps/{id}/remote-sessions/{n}/replay. - Per-admin credentials (
admin_vps_credentialstable) so the audit shows which Windows account each admin used. - MFA gate (TOTP / WebAuthn) before issuing the token.
Câu hỏi còn mởOpen questions
- Ai cấp DNS và chứng chỉ cho subdomain Guacamole; có dùng chung tài khoản Cloudflare với fxconsole không?
- Nếu gỡ Guacamole sau này: giữ bảng audit và cột credentials trên
vpsescho giải pháp remote desktop khác.
- Who provisions DNS and the certificate for the Guacamole subdomain; same Cloudflare account as fxconsole?
- If Guacamole is retired later: keep the audit table and the credential columns on
vpsesfor any future remote-desktop solution.